Wrap scipy.ndimage grey morphology functions

This commit is contained in:
Juan Nunez-Iglesias
2015-01-22 11:29:08 +11:00
parent 9b16b35d96
commit 56764f8209
2 changed files with 90 additions and 77 deletions
+84 -57
View File
@@ -1,17 +1,53 @@
"""
Grayscale morphological operations
"""
from skimage import img_as_ubyte
from .misc import default_fallback
from . import cmorph
import numpy as np
from scipy import ndimage as nd
from .misc import default_selem
__all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat',
'black_tophat']
@default_fallback
def _shift_selem(selem, shift_x, shift_y):
"""Shift the binary image `selem` in the left and/or up.
This only affects structuring elements with even number of rows or
columns.
Parameters
----------
selem : 2D array, shape (M, N)
The input structuring element.
shift_x, shift_y : bool
Whether to move `selem` along each axis.
Returns
-------
out : 2D array, shape (M + int(shift_x), N + int(shift_y))
The shifted structuring element.
"""
if selem.ndim > 2:
# do nothing for 3D or higher structuring elements
return selem
m, n = selem.shape
if m % 2 == 0:
extra_row = np.zeros((1, n), selem.dtype)
if shift_x:
selem = np.vstack((selem, extra_row))
else:
selem = np.vstack((extra_row, selem))
m += 1
if n % 2 == 0:
extra_col = np.zeros((m, 1), selem.dtype)
if shift_y:
selem = np.hstack((selem, extra_col))
else:
selem = np.hstack((extra_col, selem))
return selem
@default_selem
def erosion(image, selem=None, out=None, shift_x=False, shift_y=False):
"""Return greyscale morphological erosion of an image.
@@ -24,7 +60,7 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False):
image : ndarray
Image array.
selem : ndarray, optional
The neighborhood expressed as a 2-D array of 1's and 0's.
The neighborhood expressed as an array of 1's and 0's.
If None, use cross-shaped structuring element (connectivity=1).
out : ndarrays, optional
The array to store the result of the morphology. If None is
@@ -35,7 +71,7 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False):
Returns
-------
eroded : uint8 array
eroded : array, same shape as `image`
The result of the morphological erosion.
Notes
@@ -62,16 +98,15 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False):
[0, 0, 0, 0, 0]], dtype=uint8)
"""
if image is out:
raise NotImplementedError("In-place erosion not supported!")
image = img_as_ubyte(image)
selem = img_as_ubyte(selem)
return cmorph._erode(image, selem, out=out,
shift_x=shift_x, shift_y=shift_y)
selem = np.array(selem)
selem = _shift_selem(selem, shift_x, shift_y)
if out is None:
out = np.empty_like(image)
nd.grey_erosion(image, footprint=selem, output=out)
return out
@default_fallback
@default_selem
def dilation(image, selem=None, out=None, shift_x=False, shift_y=False):
"""Return greyscale morphological dilation of an image.
@@ -123,17 +158,15 @@ def dilation(image, selem=None, out=None, shift_x=False, shift_y=False):
[0, 0, 0, 0, 0]], dtype=uint8)
"""
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)
selem = np.array(selem)
selem = _shift_selem(selem, shift_x, shift_y)
if out is None:
out = np.empty_like(image)
nd.grey_dilation(image, footprint=selem, output=out)
return out
@default_fallback
@default_selem
def opening(image, selem=None, out=None):
"""Return greyscale morphological opening of an image.
@@ -147,7 +180,7 @@ def opening(image, selem=None, out=None):
image : ndarray
Image array.
selem : ndarray, optional
The neighborhood expressed as a 2-D array of 1's and 0's.
The neighborhood expressed as an array of 1's and 0's.
If None, use cross-shaped structuring element (connectivity=1).
out : ndarray, optional
The array to store the result of the morphology. If None
@@ -155,7 +188,7 @@ def opening(image, selem=None, out=None):
Returns
-------
opening : uint8 array
opening : array
The result of the morphological opening.
Examples
@@ -176,17 +209,13 @@ def opening(image, selem=None, out=None):
[0, 0, 0, 0, 0]], dtype=uint8)
"""
h, w = selem.shape
shift_x = True if (w % 2) == 0 else False
shift_y = True if (h % 2) == 0 else False
eroded = erosion(image, selem)
out = dilation(eroded, selem, out=out, shift_x=shift_x, shift_y=shift_y)
# note: shift_x, shift_y do nothing if selem side length is odd
out = dilation(eroded, selem, out=out, shift_x=True, shift_y=True)
return out
@default_fallback
@default_selem
def closing(image, selem=None, out=None):
"""Return greyscale morphological closing of an image.
@@ -200,7 +229,7 @@ def closing(image, selem=None, out=None):
image : ndarray
Image array.
selem : ndarray, optional
The neighborhood expressed as a 2-D array of 1's and 0's.
The neighborhood expressed as an array of 1's and 0's.
If None, use cross-shaped structuring element (connectivity=1).
out : ndarray, optional
The array to store the result of the morphology. If None,
@@ -208,7 +237,7 @@ def closing(image, selem=None, out=None):
Returns
-------
closing : uint8 array
closing : array
The result of the morphological closing.
Examples
@@ -229,17 +258,13 @@ def closing(image, selem=None, out=None):
[0, 0, 0, 0, 0]], dtype=uint8)
"""
h, w = selem.shape
shift_x = True if (w % 2) == 0 else False
shift_y = True if (h % 2) == 0 else False
dilated = dilation(image, selem)
out = erosion(dilated, selem, out=out, shift_x=shift_x, shift_y=shift_y)
# note: shift_x, shift_y do nothing if selem side length is odd
out = erosion(dilated, selem, out=out, shift_x=True, shift_y=True)
return out
@default_fallback
@default_selem
def white_tophat(image, selem=None, out=None):
"""Return white top hat of an image.
@@ -252,7 +277,7 @@ def white_tophat(image, selem=None, out=None):
image : ndarray
Image array.
selem : ndarray, optional
The neighborhood expressed as a 2-D array of 1's and 0's.
The neighborhood expressed as an array of 1's and 0's.
If None, use cross-shaped structuring element (connectivity=1).
out : ndarray, optional
The array to store the result of the morphology. If None
@@ -260,7 +285,7 @@ def white_tophat(image, selem=None, out=None):
Returns
-------
opening : uint8 array
out : array
The result of the morphological white top hat.
Examples
@@ -281,16 +306,18 @@ def white_tophat(image, selem=None, out=None):
[0, 0, 0, 0, 0]], dtype=uint8)
"""
if image is out:
raise NotImplementedError("Cannot perform white top hat in place.")
out = opening(image, selem, out=out)
out = image - out
selem = np.array(selem)
if out is image:
opened = opening(image, selem)
out -= opened
return out
elif out is None:
out = np.empty_like(image)
out = nd.white_tophat(image, footprint=selem, output=out)
return out
@default_fallback
@default_selem
def black_tophat(image, selem=None, out=None):
"""Return black top hat of an image.
@@ -333,10 +360,10 @@ def black_tophat(image, selem=None, out=None):
[0, 0, 0, 0, 0]], dtype=uint8)
"""
if image is out:
raise NotImplementedError("Cannot perform white top hat in place.")
if out is image:
original = image.copy()
else:
original = image
out = closing(image, selem, out=out)
out = out - image
out -= original
return out
+6 -20
View File
@@ -15,11 +15,8 @@ funcs = ('binary_erosion', 'binary_dilation', 'binary_opening',
skimage2ndimage.update(dict((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.
def default_selem(func):
"""Decorator to add a default structuring element to morphology functions.
Parameters
----------
@@ -30,25 +27,14 @@ def default_fallback(func):
Returns
-------
func_out : function
If the image dimensionality is greater than 2D, the ndimage
function is returned, otherwise skimage function is used.
The function, using a default structuring element of same dimension
as the input image with connectivity 1.
"""
@functools.wraps(func)
def func_out(image, selem=None, out=None, **kwargs):
# Default structure element
def func_out(image, selem=None, *args, **kwargs):
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(image, selem=selem, *args, **kwargs)
return func_out