From 2b91c259d632462f1e0cbb57c9df754e73d201c0 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Sat, 12 Jul 2014 15:22:48 -0700 Subject: [PATCH 01/13] Add fallback decorator for 3D images We don't support images greater than 2D, so fall back on ndimage --- skimage/morphology/binary.py | 13 ++++------ skimage/morphology/grey.py | 37 ++++++++++----------------- skimage/morphology/misc.py | 28 ++++++++++++++++++++ skimage/morphology/selem.py | 7 ----- skimage/morphology/tests/test_grey.py | 24 +++++++++++++++++ 5 files changed, 71 insertions(+), 38 deletions(-) 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): From ea3e65f25a4b847c66a4e645fb52831391284726 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Sat, 12 Jul 2014 16:54:35 -0700 Subject: [PATCH 02/13] Add 3d-fallback tests for binary functions --- skimage/morphology/tests/test_binary.py | 24 ++++++++++++++++++++++++ skimage/morphology/tests/test_grey.py | 7 ++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/skimage/morphology/tests/test_binary.py b/skimage/morphology/tests/test_binary.py index 6ac5e8d8..c0c4c682 100644 --- a/skimage/morphology/tests/test_binary.py +++ b/skimage/morphology/tests/test_binary.py @@ -4,6 +4,7 @@ from numpy import testing from skimage import data, color from skimage.util import img_as_bool from skimage.morphology import binary, grey, selem +from scipy import ndimage lena = color.rgb2gray(data.lena()) @@ -86,5 +87,28 @@ 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 = binary.binary_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_cube_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 [binary.binary_closing, binary.binary_opening]: + new_image = function(image, cube) + yield testing.assert_array_equal, new_image, image + if __name__ == '__main__': testing.run_module_suite() diff --git a/skimage/morphology/tests/test_grey.py b/skimage/morphology/tests/test_grey.py index 840b9c42..191613c4 100644 --- a/skimage/morphology/tests/test_grey.py +++ b/skimage/morphology/tests/test_grey.py @@ -2,6 +2,7 @@ import os.path import numpy as np from numpy import testing +from scipy import ndimage import skimage from skimage import data_dir @@ -147,19 +148,19 @@ def test_3d_fallback_default_selem(): image = np.zeros((7, 7, 7), np.bool) image[2:-2, 2:-2, 2:-2] = 1 - opened = morphology.opening(image) + opened = grey.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(): +def test_3d_fallback_cube_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) + cube = np.ones((3, 3, 3), dtype=np.uint8) for function in [grey.closing, grey.opening]: new_image = function(image, cube) From f1a8132fbfadc8621f81423e238dc50f42027ff6 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Sun, 13 Jul 2014 11:28:27 -0700 Subject: [PATCH 03/13] Add doctrings and comments in response to reviewers --- skimage/morphology/binary.py | 17 +++++++++++++++- skimage/morphology/grey.py | 35 ++++++++++++++++++++++----------- skimage/morphology/misc.py | 38 +++++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 69634912..5324b0f5 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -1,10 +1,13 @@ import warnings import numpy as np from scipy import ndimage -from .selem import _default_selem from .misc import default_fallback +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def binary_erosion(image, selem=None, out=None): """Return fast binary morphological erosion of an image. @@ -50,6 +53,10 @@ def binary_erosion(image, selem=None, out=None): return np.equal(conv, selem_sum, out=out) +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def binary_dilation(image, selem=None, out=None): """Return fast binary morphological dilation of an image. @@ -95,6 +102,10 @@ def binary_dilation(image, selem=None, out=None): return np.not_equal(conv, 0, out=out) +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def binary_opening(image, selem=None, out=None): """Return fast binary morphological opening of an image. @@ -129,6 +140,10 @@ def binary_opening(image, selem=None, out=None): return out +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @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 0d0290c4..e09872cb 100644 --- a/skimage/morphology/grey.py +++ b/skimage/morphology/grey.py @@ -1,7 +1,5 @@ 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 @@ -10,7 +8,10 @@ from . import cmorph __all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat', 'black_tophat'] - +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): """Return greyscale morphological erosion of an image. @@ -57,10 +58,6 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): """ - # 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!") image = img_as_ubyte(image) @@ -69,6 +66,10 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): shift_x=shift_x, shift_y=shift_y) +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def dilation(image, selem=None, out=None, shift_x=False, shift_y=False): """Return greyscale morphological dilation of an image. @@ -116,10 +117,6 @@ def dilation(image, selem=None, out=None, shift_x=False, shift_y=False): """ - # 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!") @@ -129,6 +126,10 @@ def dilation(image, selem=None, out=None, shift_x=False, shift_y=False): shift_x=shift_x, shift_y=shift_y) +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def opening(image, selem=None, out=None): """Return greyscale morphological opening of an image. @@ -182,6 +183,10 @@ def opening(image, selem=None, out=None): return out +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def closing(image, selem=None, out=None): """Return greyscale morphological closing of an image. @@ -235,6 +240,10 @@ def closing(image, selem=None, out=None): return out +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def white_tophat(image, selem=None, out=None): """Return white top hat of an image. @@ -286,6 +295,10 @@ def white_tophat(image, selem=None, out=None): return out +# Our functions only work in 2D, so for 3D or higher input we should fall back +# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring +# element of the appropriate dimension for each of these functions. +# The `default_callback` provides all these. @default_fallback def black_tophat(image, selem=None, out=None): """Return black top hat of an image. diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index f61d4e56..db922fd7 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -2,6 +2,8 @@ import numpy as np import scipy.ndimage as nd from .selem import _default_selem +# Our function names don't exactly correspond to ndimages. +# These dictionaries translate from our names to scipy's. skimage2ndimage = {x: 'grey_' + x for x in ('erosion','dilation','opening','closing')} skimage2ndimage.update({x: x @@ -9,9 +11,43 @@ skimage2ndimage.update({x: x 'binary_opening','binary_closing', 'black_tophat','white_tophat')}) - def default_fallback(func): + """Decorator to fall back on ndimage for images with more than 2 dimensions + + Parameters + ---------- + func : function + A morphology function such as erosion, dilation, opening, closing, + white_tophat, or black_tophat. + + Returns + ------- + func_out : function + If the image dimentionality is greater than 2D, the ndimage + function is returned, otherwise skimage function is used. + """ + def func_out(image, selem=None, out=None, **kwargs): + """Select a function appropriate for the image dimensionality + + Parameters + ---------- + image : ndarray + Image array. + selem : ndarray, optional + The neighborhood expressed as a 2-D array of 1's and 0's. + If None, use cross-shaped structuring element (connectivity=1). + out : ndarray of bool, optional + The array to store the result of the morphology. If None is + passed, a new array will be allocated. + + Returns + ------- + func_out : function + If the image dimentionality is greater than 2D, the ndimage + function is returned, otherwise skimage function is used. + """ + # Default structure element if selem is None: selem = _default_selem(image.ndim) From d9909c4db927091fe9b6e6d30c27ecb6bb23a162 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Sun, 13 Jul 2014 12:36:05 -0700 Subject: [PATCH 04/13] Add tests for ndimage fallback for 3D white_tophat & black_tophat --- skimage/morphology/tests/test_grey.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/skimage/morphology/tests/test_grey.py b/skimage/morphology/tests/test_grey.py index 191613c4..f9a7305d 100644 --- a/skimage/morphology/tests/test_grey.py +++ b/skimage/morphology/tests/test_grey.py @@ -166,6 +166,25 @@ def test_3d_fallback_cube_selem(): new_image = function(image, cube) yield testing.assert_array_equal, new_image, image +def test_3d_fallback_white_tophat(): + image = np.zeros((7, 7, 7), dtype=bool) + image[2, 2:4, 2:4] = 1 + image[3, 2:5, 2:5] = 1 + image[4, 3:5, 3:5] = 1 + new_image = grey.white_tophat(image) + footprint = ndimage.generate_binary_structure(3,1) + image_expected = ndimage.white_tophat(image,footprint=footprint) + testing.assert_array_equal(new_image, image_expected) + +def test_3d_fallback_black_tophat(): + image = np.ones((7, 7, 7), dtype=bool) + image[2, 2:4, 2:4] = 0 + image[3, 2:5, 2:5] = 0 + image[4, 3:5, 3:5] = 0 + new_image = grey.black_tophat(image) + footprint = ndimage.generate_binary_structure(3,1) + image_expected = ndimage.black_tophat(image,footprint=footprint) + testing.assert_array_equal(new_image, image_expected) class TestDTypes(): From 042d85ad435ab61092c1a05cf2733c6f4333aa41 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Sun, 13 Jul 2014 16:31:58 -0700 Subject: [PATCH 05/13] Adjust hanging indentation whitespace in dict comprehension --- skimage/morphology/misc.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index db922fd7..26d40a72 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -5,11 +5,11 @@ from .selem import _default_selem # Our function names don't exactly correspond to ndimages. # These dictionaries translate from our names to scipy's. skimage2ndimage = {x: 'grey_' + x - for x in ('erosion','dilation','opening','closing')} + 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')}) + for x in ('binary_erosion','binary_dilation', + 'binary_opening','binary_closing', + 'black_tophat','white_tophat')}) def default_fallback(func): """Decorator to fall back on ndimage for images with more than 2 dimensions From b677c400df7c37293f62f285776b928e59af3bcf Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Sun, 13 Jul 2014 16:55:06 -0700 Subject: [PATCH 06/13] Add whitespace following comma --- skimage/morphology/misc.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index 26d40a72..40ec6c25 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -3,13 +3,14 @@ import scipy.ndimage as nd from .selem import _default_selem # Our function names don't exactly correspond to ndimages. -# These dictionaries translate from our names to scipy's. -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')}) +# This dictionary translates from our names to scipy's. +funcs = ('erosion', 'dilation', 'opening', 'closing') +skimage2ndimage = {x: 'grey_' + x for x in funcs} + +# These function names are the same in ndimage. +funcs = ('binary_erosion', 'binary_dilation', 'binary_opening', + 'binary_closing', 'black_tophat', 'white_tophat') +skimage2ndimage.update({x: x for x in funcs}) def default_fallback(func): """Decorator to fall back on ndimage for images with more than 2 dimensions From a1519a015ead5d164a2d2c87a61c7ee41fbf5502 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Sun, 13 Jul 2014 19:59:43 -0700 Subject: [PATCH 07/13] Change binary morphology functions to output numpy.bool dtype --- skimage/morphology/binary.py | 4 +-- skimage/morphology/tests/test_binary.py | 40 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 5324b0f5..69d3eb08 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -49,7 +49,7 @@ def binary_erosion(image, selem=None, out=None): ndimage.convolve(binary, selem, mode='constant', cval=1, output=conv) if out is None: - out = conv + out = np.empty_like(conv, dtype=np.bool) return np.equal(conv, selem_sum, out=out) @@ -98,7 +98,7 @@ def binary_dilation(image, selem=None, out=None): ndimage.convolve(binary, selem, mode='constant', cval=0, output=conv) if out is None: - out = conv + out = np.empty_like(conv, dtype=np.bool) return np.not_equal(conv, 0, out=out) diff --git a/skimage/morphology/tests/test_binary.py b/skimage/morphology/tests/test_binary.py index c0c4c682..e0db7416 100644 --- a/skimage/morphology/tests/test_binary.py +++ b/skimage/morphology/tests/test_binary.py @@ -110,5 +110,45 @@ def test_3d_fallback_cube_selem(): new_image = function(image, cube) yield testing.assert_array_equal, new_image, image +def test_binary_output_2d(): + image = np.zeros((9, 9), np.uint16) + image[2:-2, 2:-2] = 2**14 + image[3:-3, 3:-3] = 2**15 + image[4, 4] = 2**16-1 + + bin_opened = binary.binary_opening(image) + bin_closed = binary.binary_closing(image) + + int_opened = np.empty_like(image, dtype=np.uint8) + int_closed = np.empty_like(image, dtype=np.uint8) + binary.binary_opening(image, out=int_opened) + binary.binary_closing(image, out=int_closed) + + testing.assert_equal(bin_opened.dtype, np.bool) + testing.assert_equal(bin_closed.dtype, np.bool) + + testing.assert_equal(int_opened.dtype, np.uint8) + testing.assert_equal(int_closed.dtype, np.uint8) + +def test_binary_output_3d(): + image = np.zeros((9, 9, 9), np.uint16) + image[2:-2, 2:-2, 2:-2] = 2**14 + image[3:-3, 3:-3, 3:-3] = 2**15 + image[4, 4, 4] = 2**16-1 + + bin_opened = binary.binary_opening(image) + bin_closed = binary.binary_closing(image) + + int_opened = np.empty_like(image, dtype=np.uint8) + int_closed = np.empty_like(image, dtype=np.uint8) + binary.binary_opening(image, out=int_opened) + binary.binary_closing(image, out=int_closed) + + testing.assert_equal(bin_opened.dtype, np.bool) + testing.assert_equal(bin_closed.dtype, np.bool) + + testing.assert_equal(int_opened.dtype, np.uint8) + testing.assert_equal(int_closed.dtype, np.uint8) + if __name__ == '__main__': testing.run_module_suite() From 001a63bd21c3afe652458b021bcefeddb760831d Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Tue, 15 Jul 2014 08:03:31 -0700 Subject: [PATCH 08/13] Remove repeated comments --- skimage/morphology/binary.py | 12 ------------ skimage/morphology/grey.py | 24 ------------------------ 2 files changed, 36 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 69d3eb08..216e3690 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -53,10 +53,6 @@ def binary_erosion(image, selem=None, out=None): return np.equal(conv, selem_sum, out=out) -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @default_fallback def binary_dilation(image, selem=None, out=None): """Return fast binary morphological dilation of an image. @@ -102,10 +98,6 @@ def binary_dilation(image, selem=None, out=None): return np.not_equal(conv, 0, out=out) -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @default_fallback def binary_opening(image, selem=None, out=None): """Return fast binary morphological opening of an image. @@ -140,10 +132,6 @@ def binary_opening(image, selem=None, out=None): return out -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @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 e09872cb..19a4a346 100644 --- a/skimage/morphology/grey.py +++ b/skimage/morphology/grey.py @@ -8,10 +8,6 @@ from . import cmorph __all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat', 'black_tophat'] -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @default_fallback def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): """Return greyscale morphological erosion of an image. @@ -66,10 +62,6 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False): shift_x=shift_x, shift_y=shift_y) -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @default_fallback def dilation(image, selem=None, out=None, shift_x=False, shift_y=False): """Return greyscale morphological dilation of an image. @@ -126,10 +118,6 @@ def dilation(image, selem=None, out=None, shift_x=False, shift_y=False): shift_x=shift_x, shift_y=shift_y) -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @default_fallback def opening(image, selem=None, out=None): """Return greyscale morphological opening of an image. @@ -183,10 +171,6 @@ def opening(image, selem=None, out=None): return out -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @default_fallback def closing(image, selem=None, out=None): """Return greyscale morphological closing of an image. @@ -240,10 +224,6 @@ def closing(image, selem=None, out=None): return out -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @default_fallback def white_tophat(image, selem=None, out=None): """Return white top hat of an image. @@ -295,10 +275,6 @@ def white_tophat(image, selem=None, out=None): return out -# Our functions only work in 2D, so for 3D or higher input we should fall back -# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring -# element of the appropriate dimension for each of these functions. -# The `default_callback` provides all these. @default_fallback def black_tophat(image, selem=None, out=None): """Return black top hat of an image. From 8c2b9b1784efbe43fe01da6ebea81643673a853b Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Tue, 15 Jul 2014 08:07:30 -0700 Subject: [PATCH 09/13] Updated docstrings --- skimage/morphology/misc.py | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index 40ec6c25..181fe5a4 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -14,7 +14,9 @@ skimage2ndimage.update({x: x for x in funcs}) def default_fallback(func): """Decorator to fall back on ndimage for images with more than 2 dimensions - + Decorator also provides a default structuring element, `selem`, with the + appropriate dimensionality if none is specified. + Parameters ---------- func : function @@ -29,26 +31,6 @@ def default_fallback(func): """ def func_out(image, selem=None, out=None, **kwargs): - """Select a function appropriate for the image dimensionality - - Parameters - ---------- - image : ndarray - Image array. - selem : ndarray, optional - The neighborhood expressed as a 2-D array of 1's and 0's. - If None, use cross-shaped structuring element (connectivity=1). - out : ndarray of bool, optional - The array to store the result of the morphology. If None is - passed, a new array will be allocated. - - Returns - ------- - func_out : function - If the image dimentionality is greater than 2D, the ndimage - function is returned, otherwise skimage function is used. - """ - # Default structure element if selem is None: selem = _default_selem(image.ndim) From 7976520e7af7102e76fd9139617b7c953535a623 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Tue, 15 Jul 2014 08:25:17 -0700 Subject: [PATCH 10/13] Add test that the 2D results correspond to scipy's Remove trailing whitespace --- skimage/morphology/misc.py | 7 ++--- skimage/morphology/tests/test_binary.py | 34 ++++++++++++++++++------- skimage/morphology/tests/test_grey.py | 18 ++++++++++++- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/skimage/morphology/misc.py b/skimage/morphology/misc.py index 181fe5a4..94aadf7a 100644 --- a/skimage/morphology/misc.py +++ b/skimage/morphology/misc.py @@ -8,15 +8,16 @@ funcs = ('erosion', 'dilation', 'opening', 'closing') skimage2ndimage = {x: 'grey_' + x for x in funcs} # These function names are the same in ndimage. -funcs = ('binary_erosion', 'binary_dilation', 'binary_opening', +funcs = ('binary_erosion', 'binary_dilation', 'binary_opening', 'binary_closing', 'black_tophat', 'white_tophat') skimage2ndimage.update({x: x for x in funcs}) def default_fallback(func): """Decorator to fall back on ndimage for images with more than 2 dimensions + Decorator also provides a default structuring element, `selem`, with the appropriate dimensionality if none is specified. - + Parameters ---------- func : function @@ -29,7 +30,7 @@ def default_fallback(func): If the image dimentionality is greater than 2D, the ndimage function is returned, otherwise skimage function is used. """ - + def func_out(image, selem=None, out=None, **kwargs): # Default structure element if selem is None: diff --git a/skimage/morphology/tests/test_binary.py b/skimage/morphology/tests/test_binary.py index e0db7416..a8284103 100644 --- a/skimage/morphology/tests/test_binary.py +++ b/skimage/morphology/tests/test_binary.py @@ -110,26 +110,42 @@ def test_3d_fallback_cube_selem(): new_image = function(image, cube) yield testing.assert_array_equal, new_image, image +def test_2d_ndimage_equivalence(): + image = np.zeros((9, 9), np.uint16) + image[2:-2, 2:-2] = 2**14 + image[3:-3, 3:-3] = 2**15 + image[4, 4] = 2**16-1 + + bin_opened = binary.binary_opening(image) + bin_closed = binary.binary_closing(image) + + selem = ndimage.generate_binary_structure(2, 1) + ndimage_opened = ndimage.binary_opening(image, structure=selem) + ndimage_closed = ndimage.binary_closing(image, structure=selem) + + testing.assert_array_equal(bin_opened, ndimage_opened) + testing.assert_array_equal(bin_closed, ndimage_closed) + def test_binary_output_2d(): image = np.zeros((9, 9), np.uint16) image[2:-2, 2:-2] = 2**14 image[3:-3, 3:-3] = 2**15 image[4, 4] = 2**16-1 - + bin_opened = binary.binary_opening(image) bin_closed = binary.binary_closing(image) - + int_opened = np.empty_like(image, dtype=np.uint8) int_closed = np.empty_like(image, dtype=np.uint8) binary.binary_opening(image, out=int_opened) binary.binary_closing(image, out=int_closed) - + testing.assert_equal(bin_opened.dtype, np.bool) testing.assert_equal(bin_closed.dtype, np.bool) - + testing.assert_equal(int_opened.dtype, np.uint8) testing.assert_equal(int_closed.dtype, np.uint8) - + def test_binary_output_3d(): image = np.zeros((9, 9, 9), np.uint16) image[2:-2, 2:-2, 2:-2] = 2**14 @@ -138,17 +154,17 @@ def test_binary_output_3d(): bin_opened = binary.binary_opening(image) bin_closed = binary.binary_closing(image) - + int_opened = np.empty_like(image, dtype=np.uint8) int_closed = np.empty_like(image, dtype=np.uint8) binary.binary_opening(image, out=int_opened) binary.binary_closing(image, out=int_closed) - + testing.assert_equal(bin_opened.dtype, np.bool) testing.assert_equal(bin_closed.dtype, np.bool) - + testing.assert_equal(int_opened.dtype, np.uint8) - testing.assert_equal(int_closed.dtype, np.uint8) + testing.assert_equal(int_closed.dtype, np.uint8) if __name__ == '__main__': testing.run_module_suite() diff --git a/skimage/morphology/tests/test_grey.py b/skimage/morphology/tests/test_grey.py index f9a7305d..9f8e95b3 100644 --- a/skimage/morphology/tests/test_grey.py +++ b/skimage/morphology/tests/test_grey.py @@ -175,7 +175,7 @@ def test_3d_fallback_white_tophat(): footprint = ndimage.generate_binary_structure(3,1) image_expected = ndimage.white_tophat(image,footprint=footprint) testing.assert_array_equal(new_image, image_expected) - + def test_3d_fallback_black_tophat(): image = np.ones((7, 7, 7), dtype=bool) image[2, 2:4, 2:4] = 0 @@ -186,6 +186,22 @@ def test_3d_fallback_black_tophat(): image_expected = ndimage.black_tophat(image,footprint=footprint) testing.assert_array_equal(new_image, image_expected) +def test_2d_ndimage_equivalence(): + image = np.zeros((9, 9), np.uint16) + image[2:-2, 2:-2] = 2**14 + image[3:-3, 3:-3] = 2**15 + image[4, 4] = 2**16-1 + + opened = grey.opening(image) + closed = grey.closing(image) + + selem = ndimage.generate_binary_structure(2, 1) + ndimage_opened = ndimage.grey_opening(image, structure=selem) + ndimage_closed = ndimage.grey_closing(image, structure=selem) + + testing.assert_array_equal(opened, ndimage_opened) + testing.assert_array_equal(closed, ndimage_closed) + class TestDTypes(): def setUp(self): From 4449c7788402e46edbf839ce1718c45291c2ecb4 Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Tue, 15 Jul 2014 08:39:34 -0700 Subject: [PATCH 11/13] Fix calls to ndimage to use footprint argument --- skimage/morphology/tests/test_grey.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/morphology/tests/test_grey.py b/skimage/morphology/tests/test_grey.py index 9f8e95b3..ecc0e89e 100644 --- a/skimage/morphology/tests/test_grey.py +++ b/skimage/morphology/tests/test_grey.py @@ -196,8 +196,8 @@ def test_2d_ndimage_equivalence(): closed = grey.closing(image) selem = ndimage.generate_binary_structure(2, 1) - ndimage_opened = ndimage.grey_opening(image, structure=selem) - ndimage_closed = ndimage.grey_closing(image, structure=selem) + ndimage_opened = ndimage.grey_opening(image, footprint=selem) + ndimage_closed = ndimage.grey_closing(image, footprint=selem) testing.assert_array_equal(opened, ndimage_opened) testing.assert_array_equal(closed, ndimage_closed) From eb4725826d4cbedcd8b375746f0ed8a6da6f9ead Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Wed, 16 Jul 2014 19:57:25 -0700 Subject: [PATCH 12/13] Change test of ndimage equivalence to use uint8 test image --- skimage/morphology/tests/test_grey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/morphology/tests/test_grey.py b/skimage/morphology/tests/test_grey.py index ecc0e89e..b7703ec2 100644 --- a/skimage/morphology/tests/test_grey.py +++ b/skimage/morphology/tests/test_grey.py @@ -187,7 +187,7 @@ def test_3d_fallback_black_tophat(): testing.assert_array_equal(new_image, image_expected) def test_2d_ndimage_equivalence(): - image = np.zeros((9, 9), np.uint16) + image = np.zeros((9, 9), np.uint8) image[2:-2, 2:-2] = 2**14 image[3:-3, 3:-3] = 2**15 image[4, 4] = 2**16-1 From 6e8ae37f0726c3e8a71534cd6b61e48cae90d5fd Mon Sep 17 00:00:00 2001 From: Nelson Brown Date: Wed, 16 Jul 2014 20:03:37 -0700 Subject: [PATCH 13/13] Change ndimage test image to fit uint8 range --- skimage/morphology/tests/test_grey.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/morphology/tests/test_grey.py b/skimage/morphology/tests/test_grey.py index b7703ec2..83d10b45 100644 --- a/skimage/morphology/tests/test_grey.py +++ b/skimage/morphology/tests/test_grey.py @@ -188,9 +188,9 @@ def test_3d_fallback_black_tophat(): def test_2d_ndimage_equivalence(): image = np.zeros((9, 9), np.uint8) - image[2:-2, 2:-2] = 2**14 - image[3:-3, 3:-3] = 2**15 - image[4, 4] = 2**16-1 + image[2:-2, 2:-2] = 128 + image[3:-3, 3:-3] = 196 + image[4, 4] = 255 opened = grey.opening(image) closed = grey.closing(image)