diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 4612a35d..69634912 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -2,8 +2,10 @@ import warnings import numpy as np from scipy import ndimage from .selem import _default_selem +from .misc import default_fallback +@default_fallback def binary_erosion(image, selem=None, out=None): """Return fast binary morphological erosion of an image. @@ -32,10 +34,6 @@ def binary_erosion(image, selem=None, out=None): """ - # Default structure element - if selem is None: - selem = _default_selem(image.ndim) - selem = (selem != 0) selem_sum = np.sum(selem) @@ -52,6 +50,7 @@ def binary_erosion(image, selem=None, out=None): return np.equal(conv, selem_sum, out=out) +@default_fallback def binary_dilation(image, selem=None, out=None): """Return fast binary morphological dilation of an image. @@ -81,10 +80,6 @@ def binary_dilation(image, selem=None, out=None): """ - # Default structure element - if selem is None: - selem = _default_selem(image.ndim) - selem = (selem != 0) if np.sum(selem) <= 255: @@ -100,6 +95,7 @@ def binary_dilation(image, selem=None, out=None): return np.not_equal(conv, 0, out=out) +@default_fallback def binary_opening(image, selem=None, out=None): """Return fast binary morphological opening of an image. @@ -133,6 +129,7 @@ def binary_opening(image, selem=None, out=None): return out +@default_fallback def binary_closing(image, selem=None, out=None): """Return fast binary morphological closing of an image. diff --git a/skimage/morphology/grey.py b/skimage/morphology/grey.py index 565ec9a6..0d0290c4 100644 --- a/skimage/morphology/grey.py +++ b/skimage/morphology/grey.py @@ -2,7 +2,7 @@ import warnings from skimage import img_as_ubyte from scipy import ndimage from .selem import _default_selem - +from .misc import default_fallback from . import cmorph @@ -11,6 +11,7 @@ __all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat', 'black_tophat'] +@default_fallback def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): """Return greyscale morphological erosion of an image. @@ -56,9 +57,9 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): """ - # Default structure element - if selem is None: - selem = _default_selem(image.ndim) + # If image has more than 2 dimensions, use scipy.ndimage + if image.ndim > 2: + return ndimage.morphology.grey_erosion(image, footprint=selem, out=out) if image is out: raise NotImplementedError("In-place erosion not supported!") @@ -68,6 +69,7 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): shift_x=shift_x, shift_y=shift_y) +@default_fallback def dilation(image, selem=None, out=None, shift_x=False, shift_y=False): """Return greyscale morphological dilation of an image. @@ -114,18 +116,20 @@ def dilation(image, selem=None, out=None, shift_x=False, shift_y=False): """ - # Default structure element - if selem is None: - selem = _default_selem(image.ndim) + # If image has more than 2 dimensions, use scipy.ndimage + if image.ndim > 2: + return ndimage.morphology.grey_dilation(image, footprint=selem,out=out) if image is out: raise NotImplementedError("In-place dilation not supported!") + image = img_as_ubyte(image) selem = img_as_ubyte(selem) return cmorph._dilate(image, selem, out=out, shift_x=shift_x, shift_y=shift_y) +@default_fallback def opening(image, selem=None, out=None): """Return greyscale morphological opening of an image. @@ -169,10 +173,6 @@ def opening(image, selem=None, 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 @@ -182,6 +182,7 @@ def opening(image, selem=None, out=None): return out +@default_fallback def closing(image, selem=None, out=None): """Return greyscale morphological closing of an image. @@ -225,10 +226,6 @@ def closing(image, selem=None, 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 @@ -238,6 +235,7 @@ def closing(image, selem=None, out=None): return out +@default_fallback def white_tophat(image, selem=None, out=None): """Return white top hat of an image. @@ -280,10 +278,6 @@ def white_tophat(image, selem=None, 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.") @@ -292,6 +286,7 @@ def white_tophat(image, selem=None, out=None): return out +@default_fallback def black_tophat(image, selem=None, out=None): """Return black top hat of an image. @@ -335,10 +330,6 @@ def black_tophat(image, selem=None, 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/misc.py b/skimage/morphology/misc.py index dc8290d2..f61d4e56 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -1,5 +1,33 @@ import numpy as np import scipy.ndimage as nd +from .selem import _default_selem + +skimage2ndimage = {x: 'grey_' + x + for x in ('erosion','dilation','opening','closing')} +skimage2ndimage.update({x: x + for x in ('binary_erosion','binary_dilation', + 'binary_opening','binary_closing', + 'black_tophat','white_tophat')}) + + +def default_fallback(func): + def func_out(image, selem=None, out=None, **kwargs): + # Default structure element + if selem is None: + selem = _default_selem(image.ndim) + + # If image has more than 2 dimensions, use scipy.ndimage + if image.ndim > 2: + function = getattr(nd, skimage2ndimage[func.__name__]) + try: + return function(image, footprint=selem, output=out, **kwargs) + except TypeError: + # nd.binary_* take structure instead of footprint + return function(image, structure=selem, output=out, **kwargs) + else: + return func(image, selem=selem, out=out, **kwargs) + + return func_out def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False): diff --git a/skimage/morphology/selem.py b/skimage/morphology/selem.py index 1822c8b3..5e0606c0 100644 --- a/skimage/morphology/selem.py +++ b/skimage/morphology/selem.py @@ -311,10 +311,3 @@ def _default_selem(ndim): """ return ndimage.morphology.generate_binary_structure(ndim, 1) - - - - - - - diff --git a/skimage/morphology/tests/test_grey.py b/skimage/morphology/tests/test_grey.py index 359a2832..840b9c42 100644 --- a/skimage/morphology/tests/test_grey.py +++ b/skimage/morphology/tests/test_grey.py @@ -142,6 +142,30 @@ def test_default_selem(): im_test = function(image) yield testing.assert_array_equal, im_expected, im_test +def test_3d_fallback_default_selem(): + # 3x3x3 cube inside a 7x7x7 image: + image = np.zeros((7, 7, 7), np.bool) + image[2:-2, 2:-2, 2:-2] = 1 + + opened = morphology.opening(image) + + # expect a "hyper-cross" centered in the 5x5x5: + image_expected = np.zeros((7, 7, 7), dtype=bool) + image_expected[2:5, 2:5, 2:5] = ndimage.generate_binary_structure(3, 1) + testing.assert_array_equal(opened, image_expected) + +def test_3d_fallback_default_selem(): + # 3x3x3 cube inside a 7x7x7 image: + image = np.zeros((7, 7, 7), np.bool) + image[2:-2, 2:-2, 2:-2] = 1 + + cube = np.ones((3, 3, 3),dtype=np.uint8) + + for function in [grey.closing, grey.opening]: + new_image = function(image, cube) + yield testing.assert_array_equal, new_image, image + + class TestDTypes(): def setUp(self):