diff --git a/scikits/image/morphology/skeletonize.py b/scikits/image/morphology/skeletonize.py index c5588370..853aba3e 100644 --- a/scikits/image/morphology/skeletonize.py +++ b/scikits/image/morphology/skeletonize.py @@ -6,6 +6,7 @@ Original author: Neil Yager import numpy as np from scipy.ndimage import correlate +from .. import util def skeletonize(image): """ @@ -26,7 +27,8 @@ def skeletonize(image): image: ndarray (2D) A binary image containing the objects to be skeletonized. '1' - represents foreground, and '0' represents background. + represents foreground, and '0' represents background. It + also accepts arrays of boolean values where True is foreground. Notes ----- @@ -62,15 +64,24 @@ def skeletonize(image): 3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 2,3,1,3,0,0,1,3,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 2,3,0,1,0,0,0,1,0,0,0,0,0,0,0,0,3,3,0,1,0,0,0,0,2,2,0,0,2,0,0,0] + + # convert to unsigned int (this should work for boolean values) + skeleton = np.array(image).astype(np.uint8) + + # check some properties of the input image: + # - 2D + # - binary image with only 0's and 1's + if skeleton.ndim != 2: + raise ValueError('Skeletonize requires a 2D array') + for val in np.unique(skeleton): + if val not in [0, 1]: + raise ValueError('Invalid value in the image: %d'%(val)) - # initialize the skeleton to the original image - # TODO: how to handle data types - skeleton = image.copy().astype(np.int8) - - # create the mask that will assign a value based on neighbouring pixels + # create the mask that will assign a unique value based on the + # arrangement of neighbouring pixels mask = np.array([[ 1, 2, 4], [128, 0, 8], - [ 64, 32, 16]], np.int8) + [ 64, 32, 16]], np.uint8) pixelRemoved = True while pixelRemoved: diff --git a/scikits/image/morphology/tests/test_skeletonize.py b/scikits/image/morphology/tests/test_skeletonize.py new file mode 100644 index 00000000..5a6bacb9 --- /dev/null +++ b/scikits/image/morphology/tests/test_skeletonize.py @@ -0,0 +1,83 @@ +import unittest +import numpy as np +from scikits.image.morphology import skeletonize +import numpy.testing +from scikits.image.draw import draw +from scipy.ndimage import correlate + +class TestSkeletonize(unittest.TestCase): + def test_skeletonize_no_foreground(self): + im = np.zeros((5,5)) + result = skeletonize.skeletonize(im) + numpy.testing.assert_array_equal(result, np.zeros((5,5))) + + def test_skeletonize_wrong_dim1(self): + im = np.zeros((5)) + self.assertRaises(ValueError, skeletonize.skeletonize, im) + + def test_skeletonize_wrong_dim2(self): + im = np.zeros((5, 5, 5)) + self.assertRaises(ValueError, skeletonize.skeletonize, im) + + def test_skeletonize_not_binary(self): + im = np.zeros((5, 5)) + im[0, 0] = 1 + im[0, 1] = 2 + self.assertRaises(ValueError, skeletonize.skeletonize, im) + + def test_skeletonize_unexpected_value(self): + im = np.zeros((5, 5)) + im[0, 0] = 2 + self.assertRaises(ValueError, skeletonize.skeletonize, im) + + def test_skeletonize_all_foreground(self): + im = np.ones((3,4)) + result = skeletonize.skeletonize(im) + + def test_skeletonize_single_point(self): + im = np.zeros((5, 5), np.uint8) + im[3, 3] = 1 + result = skeletonize.skeletonize(im) + numpy.testing.assert_array_equal(result, im) + + def test_skeletonize_already_thinned(self): + im = np.zeros((5, 5), np.uint8) + im[3,1:-1] = 1 + im[2, -1] = 1 + im[4, 0] = 1 + result = skeletonize.skeletonize(im) + numpy.testing.assert_array_equal(result, im) + + def test_skeletonize_num_neighbours(self): + # an empty image + image = np.zeros((300, 300)) + + # foreground object 1 + image[10:-10, 10:100] = 1 + image[-100:-10, 10:-10] = 1 + image[10:-10, -100:-10] = 1 + + # foreground object 2 + rs, cs = draw.bresenham(250, 150, 10, 280) + for i in range(10): image[rs+i, cs] = 1 + rs, cs = draw.bresenham(10, 150, 250, 280) + for i in range(20): image[rs+i, cs] = 1 + + # foreground object 3 + ir, ic = np.indices(image.shape) + circle1 = (ic - 135)**2 + (ir - 150)**2 < 30**2 + circle2 = (ic - 135)**2 + (ir - 150)**2 < 20**2 + image[circle1] = 1 + image[circle2] = 0 + result = skeletonize.skeletonize(image) + + # there should never be a 2x2 block of foreground pixels + # in a skeleton + mask = np.array([[1, 1], + [1, 1]], np.uint8) + blocks = correlate(result, mask, mode='constant') + self.assertFalse(numpy.any(blocks == 4)) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file