diff --git a/skimage/morphology/__init__.py b/skimage/morphology/__init__.py index 2cbfcd37..c8fafe6f 100644 --- a/skimage/morphology/__init__.py +++ b/skimage/morphology/__init__.py @@ -8,7 +8,7 @@ from .selem import square, rectangle, diamond, disk, cube, octahedron, ball from .ccomp import label from .watershed import watershed, is_local_maximum from ._skeletonize import skeletonize, medial_axis -from .convex_hull import convex_hull_image +from .convex_hull import convex_hull_image, convex_hull_object from .greyreconstruct import reconstruction from .misc import remove_small_objects @@ -42,5 +42,6 @@ __all__ = ['binary_erosion', 'skeletonize', 'medial_axis', 'convex_hull_image', + 'convex_hull_object', 'reconstruction', 'remove_small_objects'] diff --git a/skimage/morphology/convex_hull.py b/skimage/morphology/convex_hull.py index 08ff0e04..28ff204b 100644 --- a/skimage/morphology/convex_hull.py +++ b/skimage/morphology/convex_hull.py @@ -1,8 +1,9 @@ -__all__ = ['convex_hull_image'] +__all__ = ['convex_hull_image', 'convex_hull_object'] import numpy as np from ._pnpoly import grid_points_inside_poly from ._convex_hull import possible_hull +from skimage.morphology import label def convex_hull_image(image): @@ -14,12 +15,12 @@ def convex_hull_image(image): Parameters ---------- image : ndarray - Binary input image. This array is cast to bool before processing. + Binary input image. This array is cast to bool before processing. Returns ------- - hull : ndarray of uint8 - Binary image with pixels in convex hull set to 255. + hull : ndarray of bool + Binary image with pixels in convex hull set to True. References ---------- @@ -64,3 +65,45 @@ def convex_hull_image(image): mask = grid_points_inside_poly(image.shape[:2], v) return mask + + +def convex_hull_object(image, neighbors=8): + """Compute the convex hull image of individual objects in a binary image. + + The convex hull is the set of pixels included in the smallest convex + polygon that surround all white pixels in the input image. + + Parameters + ---------- + image : ndarray + Binary input image. + neighbors : {4, 8}, int + Whether to use 4- or 8-connectivity. + + Returns + ------- + hull : ndarray of bool + Binary image with pixels in convex hull set to True. + + Note + ---- + This function uses skimage.morphology.label to define unique objects, + finds the convex hull of each using convex_hull_image, and combines + these regions with logical OR. Be aware the convex hulls of unconnected + objects may overlap in the result. If this is suspected, consider using + convex_hull_image separately on each object. + + """ + + if neighbors != 4 and neighbors != 8: + raise ValueError('Neighbors must be either 4 or 8.') + + labeled_im = label(image, neighbors, background=0) + convex_obj = np.zeros(image.shape, dtype=bool) + convex_img = np.zeros(image.shape, dtype=bool) + + for i in range(0, labeled_im.max() + 1): + convex_obj = convex_hull_image(labeled_im == i) + convex_img = np.logical_or(convex_img, convex_obj) + + return convex_img diff --git a/skimage/morphology/tests/test_convex_hull.py b/skimage/morphology/tests/test_convex_hull.py index 9ab514d0..075762ed 100644 --- a/skimage/morphology/tests/test_convex_hull.py +++ b/skimage/morphology/tests/test_convex_hull.py @@ -1,7 +1,7 @@ import numpy as np -from numpy.testing import assert_array_equal +from numpy.testing import assert_array_equal, assert_raises from numpy.testing.decorators import skipif -from skimage.morphology import convex_hull_image +from skimage.morphology import convex_hull_image, convex_hull_object from skimage.morphology._convex_hull import possible_hull try: @@ -65,5 +65,47 @@ def test_possible_hull(): ph = possible_hull(image) assert_array_equal(ph, expected) + +@skipif(not scipy_spatial) +def test_object(): + image = np.array( + [[0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 1, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 1, 0], + [1, 0, 0, 0, 0, 0, 1, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool) + + expected4 = np.array( + [[0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 1, 0, 1], + [1, 1, 1, 0, 0, 0, 0, 1, 0], + [1, 1, 0, 0, 0, 0, 1, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool) + + assert_array_equal(convex_hull_object(image, 4), expected4) + + expected8 = np.array( + [[0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0, 1, 1, 1], + [1, 1, 0, 0, 0, 0, 1, 1, 1], + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool) + + assert_array_equal(convex_hull_object(image, 8), expected8) + + assert_raises(ValueError, convex_hull_object, image, 7) + if __name__ == "__main__": np.testing.run_module_suite()