From b022dd6dfbd0b5ecc8349e54faec23bac0b8e5db Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 12 Oct 2013 19:10:23 +0200 Subject: [PATCH 01/11] Simplify output handling for binary morphology. --- skimage/morphology/binary.py | 68 +++++++------------------ skimage/morphology/tests/test_binary.py | 21 -------- 2 files changed, 18 insertions(+), 71 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 11a14d49..81294d80 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -3,46 +3,15 @@ import numpy as np from scipy import ndimage -def _convolve(image, selem, out, cval): - - # determine the smallest integer dtype which does not overflow - selem = selem != 0 - selem_sum = np.sum(selem) - if selem_sum < 2 ** 8: - out_dtype = np.uint8 - else: - out_dtype = np.intp - - if out is None: - out = np.zeros_like(image, dtype=out_dtype) - else: - warnings.warn('Parameter `out` is deprecated and it does not equal ' - 'the output image if the sum of the structuring element ' - 'overflows the dtype of `out`.') - iinfo = np.iinfo(out.dtype) - if iinfo.max - iinfo.min < selem_sum: - raise ValueError('Sum of structuring (=%d) element results in ' - 'overflow for dtype of `out`. You must raise the ' - 'bit-depth.') - - conv = ndimage.convolve(image > 0, selem, output=out, - mode='constant', cval=cval) - - if conv is not None: - out = conv - - return out, selem_sum - - def binary_erosion(image, selem, out=None): """Return fast binary morphological erosion of an image. This function returns the same result as greyscale erosion but performs faster for binary images. - Morphological erosion sets a pixel at (i,j) to the minimum over all pixels - in the neighborhood centered at (i,j). Erosion shrinks bright regions and - enlarges dark regions. + Morphological erosion sets a pixel at ``(i,j)`` to the minimum over all + pixels in the neighborhood centered at ``(i,j)``. Erosion shrinks bright + regions and enlarges dark regions. Parameters ---------- @@ -50,20 +19,20 @@ def binary_erosion(image, selem, out=None): Image array. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. - out : ndarray + out : ndarray of bool The array to store the result of the morphology. If None is passed, a new array will be allocated. Returns ------- - eroded : bool array + eroded : ndarray of bool The result of the morphological erosion. """ - - out, selem_sum = _convolve(image, selem, out, 1) - return np.array(np.equal(out, selem_sum, out=out), dtype=np.bool, - copy=False) + selem = (selem != 0) + selem_sum = np.sum(selem) + out = ndimage.convolve(image > 0, selem, mode='constant', cval=1) + return np.equal(out, selem_sum, out=out) def binary_dilation(image, selem, out=None): @@ -83,19 +52,19 @@ def binary_dilation(image, selem, out=None): Image array. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. - out : ndarray + out : ndarray of bool The array to store the result of the morphology. If None, is passed, a new array will be allocated. Returns ------- - dilated : bool array + dilated : ndarray of bool The result of the morphological dilation. """ - - out, _ = _convolve(image, selem, out, 0) - return np.array(np.not_equal(out, 0, out=out), dtype=np.bool, copy=False) + selem = (selem != 0) + out = ndimage.convolve(image > 0, selem, mode='constant', cval=0) + return np.not_equal(out, 0, out=out) def binary_opening(image, selem, out=None): @@ -115,17 +84,16 @@ def binary_opening(image, selem, out=None): Image array. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. - out : ndarray + out : ndarray of bool The array to store the result of the morphology. If None is passed, a new array will be allocated. Returns ------- - opening : bool array + opening : ndarray of bool The result of the morphological opening. """ - eroded = binary_erosion(image, selem) out = binary_dilation(eroded, selem, out=out) return out @@ -148,13 +116,13 @@ def binary_closing(image, selem, out=None): Image array. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. - out : ndarray + out : ndarray of bool The array to store the result of the morphology. If None, is passed, a new array will be allocated. Returns ------- - closing : bool array + closing : ndarray of bool The result of the morphological closing. """ diff --git a/skimage/morphology/tests/test_binary.py b/skimage/morphology/tests/test_binary.py index 5f831669..2f47c917 100644 --- a/skimage/morphology/tests/test_binary.py +++ b/skimage/morphology/tests/test_binary.py @@ -17,27 +17,6 @@ def test_non_square_image(): testing.assert_array_equal(binary_res, grey_res) -def test_selem_overflow(): - strel = np.ones((17, 17), dtype=np.uint8) - img = np.zeros((20, 20)) - img[2:19, 2:19] = 1 - binary_res = binary.binary_erosion(img, strel) - grey_res = img_as_bool(grey.erosion(img, strel)) - testing.assert_array_equal(binary_res, grey_res) - - -def test_selem_overflow_exception(): - strel = np.ones((17, 17), dtype=np.uint8) - img = np.zeros((20, 20)) - img[2:19, 2:19] = 1 - out = np.zeros((20, 20), dtype=np.uint8) - testing.assert_raises(ValueError, binary.binary_erosion, img, strel, out) - out = np.zeros((20, 20), dtype=np.uint16) - binary_res = binary.binary_erosion(img, strel, out=out) - grey_res = img_as_bool(grey.erosion(img, strel)) - testing.assert_array_equal(binary_res, grey_res) - - def test_binary_erosion(): strel = selem.square(3) binary_res = binary.binary_erosion(bw_lena, strel) From 9ff4316cbbfa76741d3f67860213956e2e4a2ea7 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 12 Oct 2013 19:23:54 +0200 Subject: [PATCH 02/11] Fix overflow in NumPy 1.7 as suggested by Julian Taylor. --- skimage/morphology/binary.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 81294d80..de002c7e 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -31,7 +31,13 @@ def binary_erosion(image, selem, out=None): """ selem = (selem != 0) selem_sum = np.sum(selem) - out = ndimage.convolve(image > 0, selem, mode='constant', cval=1) + + if selem_sum > 255: + binary = (image != 0).view(np.uint8) + else: + binary = (image != 0).astype(np.intp) + + out = ndimage.convolve(binary, selem, mode='constant', cval=1) return np.equal(out, selem_sum, out=out) @@ -63,7 +69,12 @@ def binary_dilation(image, selem, out=None): """ selem = (selem != 0) - out = ndimage.convolve(image > 0, selem, mode='constant', cval=0) + if np.sum(selem) > 255: + binary = (image != 0).view(np.uint8) + else: + binary = (image != 0).astype(np.intp) + + out = ndimage.convolve(binary, selem, mode='constant', cval=0) return np.not_equal(out, 0, out=out) From 9329d0ad56a055d0c96d38a5ee919af1361b568f Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 12 Oct 2013 19:44:14 +0200 Subject: [PATCH 03/11] Restore @ahojnnes's overflow test. Correctly assign out argument. --- skimage/morphology/binary.py | 18 ++++++++++++------ skimage/morphology/tests/test_binary.py | 9 +++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index de002c7e..5746c545 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -32,13 +32,16 @@ def binary_erosion(image, selem, out=None): selem = (selem != 0) selem_sum = np.sum(selem) - if selem_sum > 255: + if selem_sum <= 255: binary = (image != 0).view(np.uint8) else: binary = (image != 0).astype(np.intp) - out = ndimage.convolve(binary, selem, mode='constant', cval=1) - return np.equal(out, selem_sum, out=out) + conv = ndimage.convolve(binary, selem, mode='constant', cval=1) + + if out is None: + out = np.zeros_like(binary, dtype=bool) + return np.equal(conv, selem_sum, out=out) def binary_dilation(image, selem, out=None): @@ -69,13 +72,16 @@ def binary_dilation(image, selem, out=None): """ selem = (selem != 0) - if np.sum(selem) > 255: + if np.sum(selem) <= 255: binary = (image != 0).view(np.uint8) else: binary = (image != 0).astype(np.intp) - out = ndimage.convolve(binary, selem, mode='constant', cval=0) - return np.not_equal(out, 0, out=out) + conv = ndimage.convolve(binary, selem, mode='constant', cval=0) + + if out is None: + out = np.zeros_like(binary, dtype=bool) + return np.not_equal(conv, 0, out=out) def binary_opening(image, selem, out=None): diff --git a/skimage/morphology/tests/test_binary.py b/skimage/morphology/tests/test_binary.py index 2f47c917..dcddc066 100644 --- a/skimage/morphology/tests/test_binary.py +++ b/skimage/morphology/tests/test_binary.py @@ -45,5 +45,14 @@ def test_binary_opening(): testing.assert_array_equal(binary_res, grey_res) +def test_selem_overflow(): + strel = np.ones((17, 17), dtype=np.uint8) + img = np.zeros((20, 20)) + img[2:19, 2:19] = 1 + binary_res = binary.binary_erosion(img, strel) + grey_res = img_as_bool(grey.erosion(img, strel)) + testing.assert_array_equal(binary_res, grey_res) + + if __name__ == '__main__': testing.run_module_suite() From ceb2e4c5d4e861f5cbbd00b2e03b6a8d5e31af87 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 12 Oct 2013 19:51:00 +0200 Subject: [PATCH 04/11] Test that output argument is utilized. --- skimage/morphology/tests/test_binary.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/skimage/morphology/tests/test_binary.py b/skimage/morphology/tests/test_binary.py index dcddc066..e1521296 100644 --- a/skimage/morphology/tests/test_binary.py +++ b/skimage/morphology/tests/test_binary.py @@ -54,5 +54,14 @@ def test_selem_overflow(): testing.assert_array_equal(binary_res, grey_res) +def test_out_argument(): + for func in (binary.binary_erosion, binary.binary_dilation): + strel = np.ones((3, 3), dtype=np.uint8) + img = np.ones((10, 10)) + out = np.zeros_like(img) + out_saved = out.copy() + func(img, strel, out=out) + testing.assert_(np.any(out != out_saved)) + if __name__ == '__main__': testing.run_module_suite() From 4f74a007d9cd7ee34b2c41a01e450867c23ccfff Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 12 Oct 2013 19:57:35 +0200 Subject: [PATCH 05/11] Test that output argument is correct. --- skimage/morphology/tests/test_binary.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/morphology/tests/test_binary.py b/skimage/morphology/tests/test_binary.py index e1521296..deab3d82 100644 --- a/skimage/morphology/tests/test_binary.py +++ b/skimage/morphology/tests/test_binary.py @@ -62,6 +62,7 @@ def test_out_argument(): out_saved = out.copy() func(img, strel, out=out) testing.assert_(np.any(out != out_saved)) + testing.assert_array_equal(out, func(img, strel)) if __name__ == '__main__': testing.run_module_suite() From 6111831994c7db4dc5454884f440f0904dea3fba Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sat, 12 Oct 2013 21:55:13 +0200 Subject: [PATCH 06/11] Revert to >0 for determining binary values. Document that input should be binary. --- skimage/morphology/binary.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 5746c545..efd98ea1 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -16,7 +16,7 @@ def binary_erosion(image, selem, out=None): Parameters ---------- image : ndarray - Image array. + Binary input image. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray of bool @@ -33,9 +33,9 @@ def binary_erosion(image, selem, out=None): selem_sum = np.sum(selem) if selem_sum <= 255: - binary = (image != 0).view(np.uint8) + binary = (image > 0).view(np.uint8) else: - binary = (image != 0).astype(np.intp) + binary = (image > 0).astype(np.intp) conv = ndimage.convolve(binary, selem, mode='constant', cval=1) @@ -50,15 +50,15 @@ def binary_dilation(image, selem, out=None): This function returns the same result as greyscale dilation but performs faster for binary images. - Morphological dilation sets a pixel at (i,j) to the maximum over all pixels - in the neighborhood centered at (i,j). Dilation enlarges bright regions - and shrinks dark regions. + Morphological dilation sets a pixel at ``(i,j)`` to the maximum over all + pixels in the neighborhood centered at ``(i,j)``. Dilation enlarges bright + regions and shrinks dark regions. Parameters ---------- image : ndarray - Image array. + Binary input image. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray of bool @@ -73,9 +73,9 @@ def binary_dilation(image, selem, out=None): """ selem = (selem != 0) if np.sum(selem) <= 255: - binary = (image != 0).view(np.uint8) + binary = (image > 0).view(np.uint8) else: - binary = (image != 0).astype(np.intp) + binary = (image > 0).astype(np.intp) conv = ndimage.convolve(binary, selem, mode='constant', cval=0) @@ -98,7 +98,7 @@ def binary_opening(image, selem, out=None): Parameters ---------- image : ndarray - Image array. + Binary input image. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray of bool @@ -130,7 +130,7 @@ def binary_closing(image, selem, out=None): Parameters ---------- image : ndarray - Image array. + Binary input image. selem : ndarray The neighborhood expressed as a 2-D array of 1's and 0's. out : ndarray of bool From 89d703ca4678611f21456b2a3a5bf912cb80a1c5 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 14 Oct 2013 12:58:27 +0200 Subject: [PATCH 07/11] Pre-allocate memory for convolution and re-use for output, if no user-override specified. --- skimage/morphology/binary.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index efd98ea1..fe91f148 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -37,10 +37,11 @@ def binary_erosion(image, selem, out=None): else: binary = (image > 0).astype(np.intp) - conv = ndimage.convolve(binary, selem, mode='constant', cval=1) + conv = np.empty_like(image, dtype=np.intp) + ndimage.convolve(binary, selem, mode='constant', cval=1, output=conv) if out is None: - out = np.zeros_like(binary, dtype=bool) + out = conv return np.equal(conv, selem_sum, out=out) @@ -77,10 +78,11 @@ def binary_dilation(image, selem, out=None): else: binary = (image > 0).astype(np.intp) - conv = ndimage.convolve(binary, selem, mode='constant', cval=0) + conv = np.empty_like(image, dtype=np.intp) + ndimage.convolve(binary, selem, mode='constant', cval=0, output=conv) if out is None: - out = np.zeros_like(binary, dtype=bool) + out = conv return np.not_equal(conv, 0, out=out) From d5e2ab0b135dc959ff07c5eddf67af808d8c908e Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 14 Oct 2013 13:02:38 +0200 Subject: [PATCH 08/11] Allocate less memory, if possible. --- skimage/morphology/binary.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index fe91f148..6bd5d234 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -34,10 +34,11 @@ def binary_erosion(image, selem, out=None): if selem_sum <= 255: binary = (image > 0).view(np.uint8) + conv = np.empty_like(image, dtype=np.uint8) else: binary = (image > 0).astype(np.intp) + conv = np.empty_like(image, dtype=np.intp) - conv = np.empty_like(image, dtype=np.intp) ndimage.convolve(binary, selem, mode='constant', cval=1, output=conv) if out is None: @@ -75,10 +76,11 @@ def binary_dilation(image, selem, out=None): selem = (selem != 0) if np.sum(selem) <= 255: binary = (image > 0).view(np.uint8) + conv = np.empty_like(image, dtype=np.uint8) else: binary = (image > 0).astype(np.intp) + conv = np.empty_like(image, dtype=np.intp) - conv = np.empty_like(image, dtype=np.intp) ndimage.convolve(binary, selem, mode='constant', cval=0, output=conv) if out is None: From 2a944ef689314c8dee4b809a56647f9581178b92 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 14 Oct 2013 13:11:24 +0200 Subject: [PATCH 09/11] Update docs to match output. --- skimage/morphology/binary.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 6bd5d234..d74874a6 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -25,8 +25,8 @@ def binary_erosion(image, selem, out=None): Returns ------- - eroded : ndarray of bool - The result of the morphological erosion. + eroded : ndarray of bool or intp + The result of the morphological erosion with values in [0, 1]. """ selem = (selem != 0) @@ -69,8 +69,8 @@ def binary_dilation(image, selem, out=None): Returns ------- - dilated : ndarray of bool - The result of the morphological dilation. + dilated : ndarray of bool or intp + The result of the morphological dilation with values in [0, 1]. """ selem = (selem != 0) From f83c7a95e3eb42ee9cd4e48af7b51f0219b8097e Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 14 Oct 2013 13:58:04 +0200 Subject: [PATCH 10/11] Tweak markup of docstring. --- skimage/morphology/binary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index d74874a6..0b98ed6c 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -26,7 +26,7 @@ def binary_erosion(image, selem, out=None): Returns ------- eroded : ndarray of bool or intp - The result of the morphological erosion with values in [0, 1]. + The result of the morphological erosion with values in ``[0, 1]``. """ selem = (selem != 0) @@ -70,7 +70,7 @@ def binary_dilation(image, selem, out=None): Returns ------- dilated : ndarray of bool or intp - The result of the morphological dilation with values in [0, 1]. + The result of the morphological dilation with values in ``[0, 1]``. """ selem = (selem != 0) From d182286e53fee484e5e304bd879a9b5da1a3e7e0 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 14 Oct 2013 16:41:08 +0200 Subject: [PATCH 11/11] Always use uint8 for binary view. --- skimage/morphology/binary.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/skimage/morphology/binary.py b/skimage/morphology/binary.py index 0b98ed6c..89ae8057 100644 --- a/skimage/morphology/binary.py +++ b/skimage/morphology/binary.py @@ -33,12 +33,11 @@ def binary_erosion(image, selem, out=None): selem_sum = np.sum(selem) if selem_sum <= 255: - binary = (image > 0).view(np.uint8) conv = np.empty_like(image, dtype=np.uint8) else: - binary = (image > 0).astype(np.intp) conv = np.empty_like(image, dtype=np.intp) + binary = (image > 0).view(np.uint8) ndimage.convolve(binary, selem, mode='constant', cval=1, output=conv) if out is None: @@ -74,13 +73,13 @@ def binary_dilation(image, selem, out=None): """ selem = (selem != 0) + if np.sum(selem) <= 255: - binary = (image > 0).view(np.uint8) conv = np.empty_like(image, dtype=np.uint8) else: - binary = (image > 0).astype(np.intp) conv = np.empty_like(image, dtype=np.intp) + binary = (image > 0).view(np.uint8) ndimage.convolve(binary, selem, mode='constant', cval=0, output=conv) if out is None: