From bb732c922585073645a444dcbbccfcdf3969b744 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Fri, 11 Jul 2014 13:50:55 -0700 Subject: [PATCH] Add default structuring element to morphology --- skimage/morphology/binary.py | 19 +++++++--- skimage/morphology/grey.py | 46 ++++++++++++++++++++----- skimage/morphology/selem.py | 21 +++++++++++ skimage/morphology/tests/test_binary.py | 25 ++++++++++++++ skimage/morphology/tests/test_grey.py | 26 ++++++++++++++ 5 files changed, 124 insertions(+), 13 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 24c5c0ea..636717af 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -1,9 +1,10 @@ import warnings import numpy as np from scipy import ndimage +from .selem import _default_selem -def binary_erosion(image, selem, out=None): +def binary_erosion(image, selem=None, out=None): """Return fast binary morphological erosion of an image. This function returns the same result as greyscale erosion but performs @@ -29,6 +30,11 @@ def binary_erosion(image, selem, out=None): The result of the morphological erosion with values in ``[0, 1]``. """ + + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + selem = (selem != 0) selem_sum = np.sum(selem) @@ -45,7 +51,7 @@ def binary_erosion(image, selem, out=None): return np.equal(conv, selem_sum, out=out) -def binary_dilation(image, selem, out=None): +def binary_dilation(image, selem=None, out=None): """Return fast binary morphological dilation of an image. This function returns the same result as greyscale dilation but performs @@ -72,6 +78,11 @@ def binary_dilation(image, selem, out=None): The result of the morphological dilation with values in ``[0, 1]``. """ + + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + selem = (selem != 0) if np.sum(selem) <= 255: @@ -87,7 +98,7 @@ def binary_dilation(image, selem, out=None): return np.not_equal(conv, 0, out=out) -def binary_opening(image, selem, out=None): +def binary_opening(image, selem=None, out=None): """Return fast binary morphological opening of an image. This function returns the same result as greyscale opening but performs @@ -119,7 +130,7 @@ def binary_opening(image, selem, out=None): return out -def binary_closing(image, selem, out=None): +def binary_closing(image, selem=None, out=None): """Return fast binary morphological closing of an image. This function returns the same result as greyscale closing but performs diff --git a/skimage/morphology/grey.py b/skimage/morphology/grey.py index 13e9dfbb..7fb1c8c9 100644 --- a/skimage/morphology/grey.py +++ b/skimage/morphology/grey.py @@ -1,5 +1,8 @@ import warnings from skimage import img_as_ubyte +from scipy import ndimage +from .selem import _default_selem + from . import cmorph @@ -8,7 +11,7 @@ __all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat', 'black_tophat'] -def erosion(image, selem, out=None, shift_x=False, shift_y=False): +def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): """Return greyscale morphological erosion of an image. Morphological erosion sets a pixel at (i,j) to the minimum over all pixels @@ -21,7 +24,7 @@ def erosion(image, selem, out=None, shift_x=False, shift_y=False): Image array. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. - out : ndarray + out : ndarrays The array to store the result of the morphology. If None is passed, a new array will be allocated. shift_x, shift_y : bool @@ -52,6 +55,10 @@ def erosion(image, selem, out=None, shift_x=False, shift_y=False): """ + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + if image is out: raise NotImplementedError("In-place erosion not supported!") image = img_as_ubyte(image) @@ -60,7 +67,7 @@ def erosion(image, selem, out=None, shift_x=False, shift_y=False): shift_x=shift_x, shift_y=shift_y) -def dilation(image, selem, out=None, shift_x=False, shift_y=False): +def dilation(image, selem=None, out=None, shift_x=False, shift_y=False): """Return greyscale morphological dilation of an image. Morphological dilation sets a pixel at (i,j) to the maximum over all pixels @@ -105,6 +112,10 @@ def dilation(image, selem, out=None, shift_x=False, shift_y=False): """ + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + if image is out: raise NotImplementedError("In-place dilation not supported!") image = img_as_ubyte(image) @@ -113,7 +124,7 @@ def dilation(image, selem, out=None, shift_x=False, shift_y=False): shift_x=shift_x, shift_y=shift_y) -def opening(image, selem, out=None): +def opening(image, selem=None, out=None): """Return greyscale morphological opening of an image. The morphological opening on an image is defined as an erosion followed by @@ -154,7 +165,11 @@ def opening(image, selem, out=None): [0, 0, 0, 0, 0]], dtype=uint8) """ - + + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + h, w = selem.shape shift_x = True if (w % 2) == 0 else False shift_y = True if (h % 2) == 0 else False @@ -164,7 +179,7 @@ def opening(image, selem, out=None): return out -def closing(image, selem, out=None): +def closing(image, selem=None, out=None): """Return greyscale morphological closing of an image. The morphological closing on an image is defined as a dilation followed by @@ -206,6 +221,10 @@ def closing(image, selem, out=None): """ + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + h, w = selem.shape shift_x = True if (w % 2) == 0 else False shift_y = True if (h % 2) == 0 else False @@ -215,7 +234,7 @@ def closing(image, selem, out=None): return out -def white_tophat(image, selem, out=None): +def white_tophat(image, selem=None, out=None): """Return white top hat of an image. The white top hat of an image is defined as the image minus its @@ -254,7 +273,12 @@ def white_tophat(image, selem, out=None): [0, 0, 1, 0, 0], [0, 0, 0, 0, 0]], dtype=uint8) - """ + """ + + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + if image is out: raise NotImplementedError("Cannot perform white top hat in place.") @@ -263,7 +287,7 @@ def white_tophat(image, selem, out=None): return out -def black_tophat(image, selem, out=None): +def black_tophat(image, selem=None, out=None): """Return black top hat of an image. The black top hat of an image is defined as its morphological closing minus @@ -305,6 +329,10 @@ def black_tophat(image, selem, out=None): """ + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + if image is out: raise NotImplementedError("Cannot perform white top hat in place.") diff --git a/skimage/morphology/selem.py b/skimage/morphology/selem.py index 7e566773..24adfce6 100644 --- a/skimage/morphology/selem.py +++ b/skimage/morphology/selem.py @@ -4,6 +4,7 @@ """ import numpy as np +from scipy import ndimage def square(width, dtype=np.uint8): @@ -290,3 +291,23 @@ def star(a, dtype=np.uint8): selem = selem_square + selem_rotated selem[selem > 0] = 1 return selem.astype(dtype) + + +def _default_selem(ndim): + """ + Generates a cross-shaped structuring element (connectivity=1). This is the + default structuring element (selem) if no selem was specified. + + Parameters + ---------- + ndim : int + Number of dimensions of the image. + + Returns + ------- + selem : ndarray + The structuring element where elements of the neighborhood + are 1 and 0 otherwise. + + """ + return ndimage.morphology.generate_binary_structure(ndim, 1) \ No newline at end of file diff --git a/skimage/morphology/tests/test_binary.py b/skimage/morphology/tests/test_binary.py index deab3d82..e6d80321 100644 --- a/skimage/morphology/tests/test_binary.py +++ b/skimage/morphology/tests/test_binary.py @@ -64,5 +64,30 @@ def test_out_argument(): testing.assert_(np.any(out != out_saved)) testing.assert_array_equal(out, func(img, strel)) +def test_default_selem(): + + functions = [binary.binary_erosion, binary.binary_dilation, + binary.binary_opening, binary.binary_closing] + + strel = selem.diamond(radius=1) + image = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 1, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 1, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], np.uint8) + + for function in functions: + im_expected = function(image, strel) + im_test = function(image) + yield testing.assert_array_equal, im_expected, im_test + if __name__ == '__main__': testing.run_module_suite() diff --git a/skimage/morphology/tests/test_grey.py b/skimage/morphology/tests/test_grey.py index f0099ee5..d298b1eb 100644 --- a/skimage/morphology/tests/test_grey.py +++ b/skimage/morphology/tests/test_grey.py @@ -119,7 +119,33 @@ class TestEccentricStructuringElements(): tophat = grey.black_tophat(self.white_pixel, s) assert np.all(tophat == 0) +def test_default_selem(): + functions = [grey.erosion, grey.dilation, + grey.opening, grey.closing, + grey.white_tophat, grey.black_tophat] + + strel = selem.diamond(radius=1) + image = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 1, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 1, 0, 0], + [0, 0, 1, 1, 1, 0, 0, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], np.uint8) + + for function in functions: + im_expected = function(image, strel) + im_test = function(image) + yield testing.assert_array_equal, im_expected, im_test + + class TestDTypes(): def setUp(self):