mirror of
https://github.com/wassname/scikit-image.git
synced 2026-06-28 06:15:35 +08:00
Updates based on review comments
This commit is contained in:
@@ -78,3 +78,5 @@
|
||||
- Christoph Gohlke
|
||||
Windows packaging and Python 3 compatibility.
|
||||
|
||||
- Neil Yager
|
||||
Skeletonization.
|
||||
@@ -3,7 +3,17 @@
|
||||
Skeletonize
|
||||
===========
|
||||
|
||||
An example of thinning a binary image using skeletonize.
|
||||
Skeletonization reduces binary objects to 1 pixel wide representations. This
|
||||
can be useful for feature extraction, and/or representing an object's topology.
|
||||
|
||||
The algorithm works by making successive passes of the image. On each pass,
|
||||
border pixels are identified and removed on the condition that they do not
|
||||
break the connectivity of the corresponding object.
|
||||
|
||||
This module provides an example of calling the routine and displaying the
|
||||
results. The input is a 2D ndarray, with either boolean or integer elements.
|
||||
In the case of boolean, 'True' indicates foreground, and for integer arrays,
|
||||
the foreground is 1's.
|
||||
"""
|
||||
from scikits.image.morphology import skeletonize
|
||||
from scikits.image.draw import draw
|
||||
@@ -32,7 +42,7 @@ image[circle1] = 1
|
||||
image[circle2] = 0
|
||||
|
||||
# perform skeletonization
|
||||
skeleton = skeletonize.skeletonize(image)
|
||||
skeleton = skeletonize(image)
|
||||
|
||||
# display results
|
||||
plt.figure(figsize=(10,6))
|
||||
@@ -50,4 +60,4 @@ plt.title('skeleton', fontsize=20)
|
||||
plt.subplots_adjust(wspace=0.02, hspace=0.02, top=0.98,
|
||||
bottom=0.02, left=0.02, right=0.98)
|
||||
|
||||
plt.show()
|
||||
plt.show()
|
||||
|
||||
@@ -2,3 +2,4 @@ from grey import *
|
||||
from selem import *
|
||||
from .ccomp import label
|
||||
from watershed import watershed, is_local_maximum
|
||||
from skeletonize import skeletonize
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""skeletonize.py - Use an iterative thinning algorithm to find the
|
||||
skeletons of binary objects in an image.
|
||||
|
||||
Original author: Neil Yager
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
@@ -11,16 +9,16 @@ from .. import util
|
||||
def skeletonize(image):
|
||||
"""
|
||||
Return a single pixel wide skeleton of all connected components
|
||||
in a binary image.
|
||||
in a binary image.
|
||||
|
||||
The algorithm works by making successive passes of the image,
|
||||
removing pixels on object borders. This continues until no
|
||||
more pixels can be removed. The image is correlated with a
|
||||
mask that assigns each pixel a number in the range [0...255]
|
||||
corresponding to each possible pattern of its 8 neighbouring
|
||||
pixels. A look up table is then used to assign the pixels a
|
||||
value of 0, 1, 2 or 3, which are selectively removed during
|
||||
the iterations.
|
||||
removing pixels on object borders. This continues until no
|
||||
more pixels can be removed. The image is correlated with a
|
||||
mask that assigns each pixel a number in the range [0...255]
|
||||
corresponding to each possible pattern of its 8 neighbouring
|
||||
pixels. A look up table is then used to assign the pixels a
|
||||
value of 0, 1, 2 or 3, which are selectively removed during
|
||||
the iterations.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -34,9 +32,9 @@ def skeletonize(image):
|
||||
-----
|
||||
|
||||
This implementation gives different results than a medial
|
||||
axis transforrmation, which can be can be implemented using
|
||||
morphological operations. This implementation is generally much
|
||||
faster.
|
||||
axis transformation, which can be can be implemented using
|
||||
morphological operations. This implementation is generally much
|
||||
faster.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -55,7 +53,9 @@ def skeletonize(image):
|
||||
--------
|
||||
"""
|
||||
|
||||
# look up table
|
||||
# look up table - there is one entry for each of the 2^8=256 possible
|
||||
# combinations of 8 binary neighbours. 1's, 2's and 3's are candidates
|
||||
# for removal at each iteration of the algorithm.
|
||||
lut = [ 0,0,0,1,0,0,1,3,0,0,3,1,1,0,1,3,0,0,0,0,0,0,0,0,2,0,2,0,3,0,3,3,
|
||||
0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,2,0,0,0,3,0,2,2,
|
||||
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,0,0,0,0,0,0,0,
|
||||
@@ -73,9 +73,8 @@ def skeletonize(image):
|
||||
# - 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))
|
||||
if not np.all(np.in1d(skeleton.flat, (0, 1))):
|
||||
raise ValueError('Image contains values other than 0 and 1')
|
||||
|
||||
# create the mask that will assign a unique value based on the
|
||||
# arrangement of neighbouring pixels
|
||||
@@ -87,26 +86,36 @@ def skeletonize(image):
|
||||
while pixelRemoved:
|
||||
pixelRemoved = False;
|
||||
|
||||
# pass 1 - remove the 1's and 3's
|
||||
# assign each pixel a unique value based on its foreground neighbours
|
||||
neighbours = correlate(skeleton, mask, mode='constant')
|
||||
|
||||
# ignore background
|
||||
neighbours[skeleton == 0] = 0
|
||||
|
||||
# use LUT to categorize each foreground pixel as a 0, 1, 2 or 3
|
||||
codes = np.take(lut, neighbours)
|
||||
if np.any(codes == 1):
|
||||
|
||||
# pass 1 - remove the 1's and 3's
|
||||
code_mask = (codes == 1)
|
||||
if np.any(code_mask):
|
||||
pixelRemoved = True
|
||||
skeleton[codes == 1] = 0
|
||||
if np.any(codes == 3):
|
||||
skeleton[code_mask] = 0
|
||||
code_mask = (codes == 3)
|
||||
if np.any(code_mask):
|
||||
pixelRemoved = True
|
||||
skeleton[codes == 3] = 0
|
||||
skeleton[code_mask] = 0
|
||||
|
||||
# pass 2 - remove the 2's and 3's
|
||||
neighbours = correlate(skeleton, mask, mode='constant')
|
||||
neighbours[skeleton == 0] = 0
|
||||
codes = np.take(lut, neighbours)
|
||||
if np.any(codes == 2):
|
||||
code_mask = (codes == 2)
|
||||
if np.any(code_mask):
|
||||
pixelRemoved = True
|
||||
skeleton[codes == 2] = 0
|
||||
if np.any(codes == 3):
|
||||
skeleton[code_mask] = 0
|
||||
code_mask = (codes == 3)
|
||||
if np.any(code_mask):
|
||||
pixelRemoved = True
|
||||
skeleton[codes == 3] = 0
|
||||
skeleton[code_mask] = 0
|
||||
|
||||
return skeleton
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import unittest
|
||||
import numpy as np
|
||||
from scikits.image.morphology import skeletonize
|
||||
import numpy.testing
|
||||
@@ -8,39 +7,39 @@ from scikits.image.io import imread
|
||||
from scikits.image import data_dir
|
||||
import os.path
|
||||
|
||||
class TestSkeletonize(unittest.TestCase):
|
||||
class TestSkeletonize():
|
||||
def test_skeletonize_no_foreground(self):
|
||||
im = np.zeros((5,5))
|
||||
result = skeletonize.skeletonize(im)
|
||||
result = 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)
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
def test_skeletonize_wrong_dim2(self):
|
||||
im = np.zeros((5, 5, 5))
|
||||
self.assertRaises(ValueError, skeletonize.skeletonize, im)
|
||||
numpy.testing.assert_raises(ValueError, 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)
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
def test_skeletonize_unexpected_value(self):
|
||||
im = np.zeros((5, 5))
|
||||
im[0, 0] = 2
|
||||
self.assertRaises(ValueError, skeletonize.skeletonize, im)
|
||||
numpy.testing.assert_raises(ValueError, skeletonize, im)
|
||||
|
||||
def test_skeletonize_all_foreground(self):
|
||||
im = np.ones((3,4))
|
||||
result = skeletonize.skeletonize(im)
|
||||
result = skeletonize(im)
|
||||
|
||||
def test_skeletonize_single_point(self):
|
||||
im = np.zeros((5, 5), np.uint8)
|
||||
im[3, 3] = 1
|
||||
result = skeletonize.skeletonize(im)
|
||||
result = skeletonize(im)
|
||||
numpy.testing.assert_array_equal(result, im)
|
||||
|
||||
def test_skeletonize_already_thinned(self):
|
||||
@@ -48,7 +47,7 @@ class TestSkeletonize(unittest.TestCase):
|
||||
im[3,1:-1] = 1
|
||||
im[2, -1] = 1
|
||||
im[4, 0] = 1
|
||||
result = skeletonize.skeletonize(im)
|
||||
result = skeletonize(im)
|
||||
numpy.testing.assert_array_equal(result, im)
|
||||
|
||||
def test_skeletonize_output(self):
|
||||
@@ -56,7 +55,7 @@ class TestSkeletonize(unittest.TestCase):
|
||||
|
||||
# make black the foreground
|
||||
im = (im==0)
|
||||
result = skeletonize.skeletonize(im)
|
||||
result = skeletonize(im)
|
||||
|
||||
expected = np.load(os.path.join(data_dir, "bw_text_skeleton.npy"))
|
||||
numpy.testing.assert_array_equal(result, expected)
|
||||
@@ -83,15 +82,14 @@ class TestSkeletonize(unittest.TestCase):
|
||||
circle2 = (ic - 135)**2 + (ir - 150)**2 < 20**2
|
||||
image[circle1] = 1
|
||||
image[circle2] = 0
|
||||
result = skeletonize.skeletonize(image)
|
||||
result = skeletonize(image)
|
||||
|
||||
# there should never be a 2x2 block of foreground pixels
|
||||
# in a skeleton
|
||||
# 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))
|
||||
assert not numpy.any(blocks == 4)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
np.testing.run_module_suite()
|
||||
|
||||
Reference in New Issue
Block a user