243 lines
8.6 KiB
Python
243 lines
8.6 KiB
Python
|
import numpy as np
|
||
|
from skimage.morphology import skeletonize, medial_axis, thin
|
||
|
from skimage.morphology._skeletonize import (_generate_thin_luts,
|
||
|
G123_LUT, G123P_LUT)
|
||
|
from skimage import draw
|
||
|
from scipy.ndimage import correlate
|
||
|
from skimage.io import imread
|
||
|
from skimage import data
|
||
|
|
||
|
from skimage._shared import testing
|
||
|
from skimage._shared.testing import assert_array_equal, fetch
|
||
|
|
||
|
|
||
|
class TestSkeletonize():
|
||
|
def test_skeletonize_no_foreground(self):
|
||
|
im = np.zeros((5, 5))
|
||
|
result = skeletonize(im)
|
||
|
assert_array_equal(result, np.zeros((5, 5)))
|
||
|
|
||
|
def test_skeletonize_wrong_dim1(self):
|
||
|
im = np.zeros((5))
|
||
|
with testing.raises(ValueError):
|
||
|
skeletonize(im)
|
||
|
|
||
|
def test_skeletonize_wrong_dim2(self):
|
||
|
im = np.zeros((5, 5, 5))
|
||
|
with testing.raises(ValueError):
|
||
|
skeletonize(im, method='zhang')
|
||
|
|
||
|
def test_skeletonize_not_binary(self):
|
||
|
im = np.zeros((5, 5))
|
||
|
im[0, 0] = 1
|
||
|
im[0, 1] = 2
|
||
|
with testing.raises(ValueError):
|
||
|
skeletonize(im)
|
||
|
|
||
|
def test_skeletonize_unexpected_value(self):
|
||
|
im = np.zeros((5, 5))
|
||
|
im[0, 0] = 2
|
||
|
with testing.raises(ValueError):
|
||
|
skeletonize(im)
|
||
|
|
||
|
def test_skeletonize_all_foreground(self):
|
||
|
im = np.ones((3, 4))
|
||
|
skeletonize(im)
|
||
|
|
||
|
def test_skeletonize_single_point(self):
|
||
|
im = np.zeros((5, 5), np.uint8)
|
||
|
im[3, 3] = 1
|
||
|
result = skeletonize(im)
|
||
|
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(im)
|
||
|
assert_array_equal(result, im)
|
||
|
|
||
|
def test_skeletonize_output(self):
|
||
|
im = imread(fetch("data/bw_text.png"), as_gray=True)
|
||
|
|
||
|
# make black the foreground
|
||
|
im = (im == 0)
|
||
|
result = skeletonize(im)
|
||
|
|
||
|
expected = np.load(fetch("data/bw_text_skeleton.npy"))
|
||
|
assert_array_equal(result, expected)
|
||
|
|
||
|
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.line(250, 150, 10, 280)
|
||
|
for i in range(10):
|
||
|
image[rs + i, cs] = 1
|
||
|
rs, cs = draw.line(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(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')
|
||
|
assert not np.any(blocks == 4)
|
||
|
|
||
|
def test_lut_fix(self):
|
||
|
im = np.zeros((6, 6), np.uint8)
|
||
|
im[1, 2] = 1
|
||
|
im[2, 2] = 1
|
||
|
im[2, 3] = 1
|
||
|
im[3, 3] = 1
|
||
|
im[3, 4] = 1
|
||
|
im[4, 4] = 1
|
||
|
im[4, 5] = 1
|
||
|
result = skeletonize(im)
|
||
|
expected = np.array([[0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 0, 1, 0, 0],
|
||
|
[0, 0, 0, 0, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 1],
|
||
|
[0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||
|
assert np.all(result == expected)
|
||
|
|
||
|
|
||
|
class TestThin():
|
||
|
@property
|
||
|
def input_image(self):
|
||
|
"""image to test thinning with"""
|
||
|
ii = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 1, 1, 1, 1, 1, 0],
|
||
|
[0, 1, 0, 1, 1, 1, 0],
|
||
|
[0, 1, 1, 1, 1, 1, 0],
|
||
|
[0, 1, 1, 1, 1, 1, 0],
|
||
|
[0, 1, 1, 1, 1, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||
|
return ii
|
||
|
|
||
|
def test_zeros(self):
|
||
|
assert np.all(thin(np.zeros((10, 10))) == False)
|
||
|
|
||
|
def test_iter_1(self):
|
||
|
result = thin(self.input_image, 1).astype(np.uint8)
|
||
|
expected = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 0, 0, 0],
|
||
|
[0, 1, 0, 1, 1, 0, 0],
|
||
|
[0, 0, 1, 1, 1, 0, 0],
|
||
|
[0, 0, 1, 1, 1, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||
|
assert_array_equal(result, expected)
|
||
|
|
||
|
def test_noiter(self):
|
||
|
result = thin(self.input_image).astype(np.uint8)
|
||
|
expected = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 0, 0, 0],
|
||
|
[0, 1, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||
|
assert_array_equal(result, expected)
|
||
|
|
||
|
def test_baddim(self):
|
||
|
for ii in [np.zeros((3)), np.zeros((3, 3, 3))]:
|
||
|
with testing.raises(ValueError):
|
||
|
thin(ii)
|
||
|
|
||
|
def test_lut_generation(self):
|
||
|
g123, g123p = _generate_thin_luts()
|
||
|
|
||
|
assert_array_equal(g123, G123_LUT)
|
||
|
assert_array_equal(g123p, G123P_LUT)
|
||
|
|
||
|
|
||
|
class TestMedialAxis():
|
||
|
def test_00_00_zeros(self):
|
||
|
'''Test skeletonize on an array of all zeros'''
|
||
|
result = medial_axis(np.zeros((10, 10), bool))
|
||
|
assert np.all(result == False)
|
||
|
|
||
|
def test_00_01_zeros_masked(self):
|
||
|
'''Test skeletonize on an array that is completely masked'''
|
||
|
result = medial_axis(np.zeros((10, 10), bool),
|
||
|
np.zeros((10, 10), bool))
|
||
|
assert np.all(result == False)
|
||
|
|
||
|
def test_vertical_line(self):
|
||
|
'''Test a thick vertical line, issue #3861'''
|
||
|
img = np.zeros((9, 9))
|
||
|
img[:, 2] = 1
|
||
|
img[:, 3] = 1
|
||
|
img[:, 4] = 1
|
||
|
|
||
|
expected = np.full(img.shape, False)
|
||
|
expected[:, 3] = True
|
||
|
|
||
|
result = medial_axis(img)
|
||
|
assert_array_equal(result, expected)
|
||
|
|
||
|
def test_01_01_rectangle(self):
|
||
|
'''Test skeletonize on a rectangle'''
|
||
|
image = np.zeros((9, 15), bool)
|
||
|
image[1:-1, 1:-1] = True
|
||
|
#
|
||
|
# The result should be four diagonals from the
|
||
|
# corners, meeting in a horizontal line
|
||
|
#
|
||
|
expected = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||
|
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||
|
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.bool_)
|
||
|
result = medial_axis(image)
|
||
|
assert np.all(result == expected)
|
||
|
result, distance = medial_axis(image, return_distance=True)
|
||
|
assert distance.max() == 4
|
||
|
|
||
|
def test_01_02_hole(self):
|
||
|
'''Test skeletonize on a rectangle with a hole in the middle'''
|
||
|
image = np.zeros((9, 15), bool)
|
||
|
image[1:-1, 1:-1] = True
|
||
|
image[4, 4:-4] = False
|
||
|
expected = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||
|
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||
|
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||
|
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||
|
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||
|
dtype=np.bool_)
|
||
|
result = medial_axis(image)
|
||
|
assert np.all(result == expected)
|
||
|
|
||
|
def test_narrow_image(self):
|
||
|
"""Test skeletonize on a 1-pixel thin strip"""
|
||
|
image = np.zeros((1, 5), bool)
|
||
|
image[:, 1:-1] = True
|
||
|
result = medial_axis(image)
|
||
|
assert np.all(result == image)
|