1import numpy as np 2import pytest 3from numpy.testing import assert_array_equal 4from scipy.ndimage import correlate 5 6from skimage import draw 7from skimage._shared._warnings import expected_warnings 8from skimage._shared.testing import fetch 9from skimage.io import imread 10from skimage.morphology import medial_axis, skeletonize, thin 11from skimage.morphology._skeletonize import (_generate_thin_luts, 12 G123_LUT, G123P_LUT) 13 14 15class TestSkeletonize(): 16 def test_skeletonize_no_foreground(self): 17 im = np.zeros((5, 5)) 18 result = skeletonize(im) 19 assert_array_equal(result, np.zeros((5, 5))) 20 21 def test_skeletonize_wrong_dim1(self): 22 im = np.zeros((5)) 23 with pytest.raises(ValueError): 24 skeletonize(im) 25 26 def test_skeletonize_wrong_dim2(self): 27 im = np.zeros((5, 5, 5)) 28 with pytest.raises(ValueError): 29 skeletonize(im, method='zhang') 30 31 def test_skeletonize_not_binary(self): 32 im = np.zeros((5, 5)) 33 im[0, 0] = 1 34 im[0, 1] = 2 35 with pytest.raises(ValueError): 36 skeletonize(im) 37 38 def test_skeletonize_unexpected_value(self): 39 im = np.zeros((5, 5)) 40 im[0, 0] = 2 41 with pytest.raises(ValueError): 42 skeletonize(im) 43 44 def test_skeletonize_all_foreground(self): 45 im = np.ones((3, 4)) 46 skeletonize(im) 47 48 def test_skeletonize_single_point(self): 49 im = np.zeros((5, 5), np.uint8) 50 im[3, 3] = 1 51 result = skeletonize(im) 52 assert_array_equal(result, im) 53 54 def test_skeletonize_already_thinned(self): 55 im = np.zeros((5, 5), np.uint8) 56 im[3, 1:-1] = 1 57 im[2, -1] = 1 58 im[4, 0] = 1 59 result = skeletonize(im) 60 assert_array_equal(result, im) 61 62 def test_skeletonize_output(self): 63 im = imread(fetch("data/bw_text.png"), as_gray=True) 64 65 # make black the foreground 66 im = (im == 0) 67 result = skeletonize(im) 68 69 expected = np.load(fetch("data/bw_text_skeleton.npy")) 70 assert_array_equal(result, expected) 71 72 def test_skeletonize_num_neighbours(self): 73 # an empty image 74 image = np.zeros((300, 300)) 75 76 # foreground object 1 77 image[10:-10, 10:100] = 1 78 image[-100:-10, 10:-10] = 1 79 image[10:-10, -100:-10] = 1 80 81 # foreground object 2 82 rs, cs = draw.line(250, 150, 10, 280) 83 for i in range(10): 84 image[rs + i, cs] = 1 85 rs, cs = draw.line(10, 150, 250, 280) 86 for i in range(20): 87 image[rs + i, cs] = 1 88 89 # foreground object 3 90 ir, ic = np.indices(image.shape) 91 circle1 = (ic - 135)**2 + (ir - 150)**2 < 30**2 92 circle2 = (ic - 135)**2 + (ir - 150)**2 < 20**2 93 image[circle1] = 1 94 image[circle2] = 0 95 result = skeletonize(image) 96 97 # there should never be a 2x2 block of foreground pixels in a skeleton 98 mask = np.array([[1, 1], 99 [1, 1]], np.uint8) 100 blocks = correlate(result, mask, mode='constant') 101 assert not np.any(blocks == 4) 102 103 def test_lut_fix(self): 104 im = np.zeros((6, 6), np.uint8) 105 im[1, 2] = 1 106 im[2, 2] = 1 107 im[2, 3] = 1 108 im[3, 3] = 1 109 im[3, 4] = 1 110 im[4, 4] = 1 111 im[4, 5] = 1 112 result = skeletonize(im) 113 expected = np.array([[0, 0, 0, 0, 0, 0], 114 [0, 0, 1, 0, 0, 0], 115 [0, 0, 0, 1, 0, 0], 116 [0, 0, 0, 0, 1, 0], 117 [0, 0, 0, 0, 0, 1], 118 [0, 0, 0, 0, 0, 0]], dtype=np.uint8) 119 assert np.all(result == expected) 120 121 122class TestThin(): 123 @property 124 def input_image(self): 125 """image to test thinning with""" 126 ii = np.array([[0, 0, 0, 0, 0, 0, 0], 127 [0, 1, 1, 1, 1, 1, 0], 128 [0, 1, 0, 1, 1, 1, 0], 129 [0, 1, 1, 1, 1, 1, 0], 130 [0, 1, 1, 1, 1, 1, 0], 131 [0, 1, 1, 1, 1, 1, 0], 132 [0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8) 133 return ii 134 135 def test_zeros(self): 136 assert np.all(thin(np.zeros((10, 10))) == False) 137 138 def test_iter_1(self): 139 result = thin(self.input_image, 1).astype(np.uint8) 140 expected = np.array([[0, 0, 0, 0, 0, 0, 0], 141 [0, 0, 1, 0, 0, 0, 0], 142 [0, 1, 0, 1, 1, 0, 0], 143 [0, 0, 1, 1, 1, 0, 0], 144 [0, 0, 1, 1, 1, 0, 0], 145 [0, 0, 0, 0, 0, 0, 0], 146 [0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8) 147 assert_array_equal(result, expected) 148 149 def test_max_iter_kwarg_deprecation(self): 150 result1 = thin(self.input_image, max_num_iter=1).astype(np.uint8) 151 with expected_warnings(["`max_iter` is a deprecated argument name"]): 152 result2 = thin(self.input_image, max_iter=1).astype(np.uint8) 153 assert_array_equal(result1, result2) 154 155 def test_noiter(self): 156 result = thin(self.input_image).astype(np.uint8) 157 expected = np.array([[0, 0, 0, 0, 0, 0, 0], 158 [0, 0, 1, 0, 0, 0, 0], 159 [0, 1, 0, 1, 0, 0, 0], 160 [0, 0, 1, 0, 0, 0, 0], 161 [0, 0, 0, 0, 0, 0, 0], 162 [0, 0, 0, 0, 0, 0, 0], 163 [0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8) 164 assert_array_equal(result, expected) 165 166 def test_baddim(self): 167 for ii in [np.zeros((3)), np.zeros((3, 3, 3))]: 168 with pytest.raises(ValueError): 169 thin(ii) 170 171 def test_lut_generation(self): 172 g123, g123p = _generate_thin_luts() 173 174 assert_array_equal(g123, G123_LUT) 175 assert_array_equal(g123p, G123P_LUT) 176 177 178class TestMedialAxis(): 179 def test_00_00_zeros(self): 180 '''Test skeletonize on an array of all zeros''' 181 result = medial_axis(np.zeros((10, 10), bool)) 182 assert np.all(result == False) 183 184 def test_00_01_zeros_masked(self): 185 '''Test skeletonize on an array that is completely masked''' 186 result = medial_axis(np.zeros((10, 10), bool), 187 np.zeros((10, 10), bool)) 188 assert np.all(result == False) 189 190 def test_vertical_line(self): 191 '''Test a thick vertical line, issue #3861''' 192 img = np.zeros((9, 9)) 193 img[:, 2] = 1 194 img[:, 3] = 1 195 img[:, 4] = 1 196 197 expected = np.full(img.shape, False) 198 expected[:, 3] = True 199 200 result = medial_axis(img) 201 assert_array_equal(result, expected) 202 203 def test_01_01_rectangle(self): 204 '''Test skeletonize on a rectangle''' 205 image = np.zeros((9, 15), bool) 206 image[1:-1, 1:-1] = True 207 # 208 # The result should be four diagonals from the 209 # corners, meeting in a horizontal line 210 # 211 expected = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 212 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], 213 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 214 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], 215 [0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], 216 [0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], 217 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 218 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], 219 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 220 dtype=bool) 221 result = medial_axis(image) 222 assert np.all(result == expected) 223 result, distance = medial_axis(image, return_distance=True) 224 assert distance.max() == 4 225 226 def test_01_02_hole(self): 227 '''Test skeletonize on a rectangle with a hole in the middle''' 228 image = np.zeros((9, 15), bool) 229 image[1:-1, 1:-1] = True 230 image[4, 4:-4] = False 231 expected = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 232 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], 233 [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], 234 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 235 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 236 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], 237 [0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0], 238 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], 239 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 240 dtype=bool) 241 result = medial_axis(image) 242 assert np.all(result == expected) 243 244 def test_narrow_image(self): 245 """Test skeletonize on a 1-pixel thin strip""" 246 image = np.zeros((1, 5), bool) 247 image[:, 1:-1] = True 248 result = medial_axis(image) 249 assert np.all(result == image) 250