Skip to content

Commit

Permalink
Merge pull request #3946 from grlee77/ndimage_gray_morphology
Browse files Browse the repository at this point in the history
Add remaining greyscale morphology operations to `cupyx.scipy.ndimage`
  • Loading branch information
mergify[bot] committed Sep 17, 2020
2 parents 2351f05 + 4f69ed3 commit dbb7cb0
Show file tree
Hide file tree
Showing 3 changed files with 362 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cupyx/scipy/ndimage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@
from cupyx.scipy.ndimage.morphology import grey_dilation # NOQA
from cupyx.scipy.ndimage.morphology import grey_closing # NOQA
from cupyx.scipy.ndimage.morphology import grey_opening # NOQA
from cupyx.scipy.ndimage.morphology import morphological_gradient # NOQA
from cupyx.scipy.ndimage.morphology import morphological_laplace # NOQA
from cupyx.scipy.ndimage.morphology import white_tophat # NOQA
from cupyx.scipy.ndimage.morphology import black_tophat # NOQA
237 changes: 237 additions & 0 deletions cupyx/scipy/ndimage/morphology.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,3 +763,240 @@ def grey_opening(input, size=None, footprint=None, structure=None,
origin)
return grey_dilation(tmp, size, footprint, structure, output, mode, cval,
origin)


def morphological_gradient(
input,
size=None,
footprint=None,
structure=None,
output=None,
mode='reflect',
cval=0.0,
origin=0,
):
"""
Multidimensional morphological gradient.
The morphological gradient is calculated as the difference between a
dilation and an erosion of the input with a given structuring element.
Args:
input (cupy.ndarray): The input array.
size (tuple of ints): Shape of a flat and full structuring element used
for the morphological gradient. Optional if ``footprint`` or
``structure`` is provided.
footprint (array of ints): Positions of non-infinite elements of a flat
structuring element used for morphological gradient. Non-zero
values give the set of neighbors of the center over which opening
is chosen.
structure (array of ints): Structuring element used for the
morphological gradient. ``structure`` may be a non-flat
structuring element.
output (cupy.ndarray, dtype or None): The array in which to place the
output.
mode (str): The array borders are handled according to the given mode
(``'reflect'``, ``'constant'``, ``'nearest'``, ``'mirror'``,
``'wrap'``). Default is ``'reflect'``.
cval (scalar): Value to fill past edges of input if mode is
``constant``. Default is ``0.0``.
origin (scalar or tuple of scalar): The origin parameter controls the
placement of the filter, relative to the center of the current
element of the input. Default of 0 is equivalent to
``(0,)*input.ndim``.
Returns:
cupy.ndarray: The morphological gradient of the input.
.. seealso:: :func:`scipy.ndimage.morphological_gradient`
"""
tmp = grey_dilation(
input, size, footprint, structure, None, mode, cval, origin
)
if isinstance(output, cupy.ndarray):
grey_erosion(
input, size, footprint, structure, output, mode, cval, origin
)
return cupy.subtract(tmp, output, output)
else:
return tmp - grey_erosion(
input, size, footprint, structure, None, mode, cval, origin
)


def morphological_laplace(
input,
size=None,
footprint=None,
structure=None,
output=None,
mode='reflect',
cval=0.0,
origin=0,
):
"""
Multidimensional morphological laplace.
Args:
input (cupy.ndarray): The input array.
size (tuple of ints): Shape of a flat and full structuring element used
for the morphological laplace. Optional if ``footprint`` or
``structure`` is provided.
footprint (array of ints): Positions of non-infinite elements of a flat
structuring element used for morphological laplace. Non-zero
values give the set of neighbors of the center over which opening
is chosen.
structure (array of ints): Structuring element used for the
morphological laplace. ``structure`` may be a non-flat
structuring element.
output (cupy.ndarray, dtype or None): The array in which to place the
output.
mode (str): The array borders are handled according to the given mode
(``'reflect'``, ``'constant'``, ``'nearest'``, ``'mirror'``,
``'wrap'``). Default is ``'reflect'``.
cval (scalar): Value to fill past edges of input if mode is
``constant``. Default is ``0.0``.
origin (scalar or tuple of scalar): The origin parameter controls the
placement of the filter, relative to the center of the current
element of the input. Default of 0 is equivalent to
``(0,)*input.ndim``.
Returns:
cupy.ndarray: The morphological laplace of the input.
.. seealso:: :func:`scipy.ndimage.morphological_laplace`
"""
tmp1 = grey_dilation(
input, size, footprint, structure, None, mode, cval, origin
)
if isinstance(output, cupy.ndarray):
grey_erosion(
input, size, footprint, structure, output, mode, cval, origin
)
cupy.add(tmp1, output, output)
cupy.subtract(output, input, output)
return cupy.subtract(output, input, output)
else:
tmp2 = grey_erosion(
input, size, footprint, structure, None, mode, cval, origin
)
cupy.add(tmp1, tmp2, tmp2)
cupy.subtract(tmp2, input, tmp2)
cupy.subtract(tmp2, input, tmp2)
return tmp2


def white_tophat(
input,
size=None,
footprint=None,
structure=None,
output=None,
mode='reflect',
cval=0.0,
origin=0,
):
"""
Multidimensional white tophat filter.
Args:
input (cupy.ndarray): The input array.
size (tuple of ints): Shape of a flat and full structuring element used
for the white tophat. Optional if ``footprint`` or ``structure`` is
provided.
footprint (array of ints): Positions of non-infinite elements of a flat
structuring element used for the white tophat. Non-zero values
give the set of neighbors of the center over which opening is
chosen.
structure (array of ints): Structuring element used for the white
tophat. ``structure`` may be a non-flat structuring element.
output (cupy.ndarray, dtype or None): The array in which to place the
output.
mode (str): The array borders are handled according to the given mode
(``'reflect'``, ``'constant'``, ``'nearest'``, ``'mirror'``,
``'wrap'``). Default is ``'reflect'``.
cval (scalar): Value to fill past edges of input if mode is
``constant``. Default is ``0.0``.
origin (scalar or tuple of scalar): The origin parameter controls the
placement of the filter, relative to the center of the current
element of the input. Default of 0 is equivalent to
``(0,)*input.ndim``.
Returns:
cupy.ndarray: Result of the filter of ``input`` with ``structure``.
.. seealso:: :func:`scipy.ndimage.white_tophat`
"""
if (size is not None) and (footprint is not None):
warnings.warn(
'ignoring size because footprint is set', UserWarning, stacklevel=2
)
tmp = grey_erosion(
input, size, footprint, structure, None, mode, cval, origin
)
tmp = grey_dilation(
tmp, size, footprint, structure, output, mode, cval, origin
)
if input.dtype == numpy.bool_ and tmp.dtype == numpy.bool_:
cupy.bitwise_xor(input, tmp, out=tmp)
else:
cupy.subtract(input, tmp, out=tmp)
return tmp


def black_tophat(
input,
size=None,
footprint=None,
structure=None,
output=None,
mode='reflect',
cval=0.0,
origin=0,
):
"""
Multidimensional black tophat filter.
Args:
input (cupy.ndarray): The input array.
size (tuple of ints): Shape of a flat and full structuring element used
for the black tophat. Optional if ``footprint`` or ``structure`` is
provided.
footprint (array of ints): Positions of non-infinite elements of a flat
structuring element used for the black tophat. Non-zero values
give the set of neighbors of the center over which opening is
chosen.
structure (array of ints): Structuring element used for the black
tophat. ``structure`` may be a non-flat structuring element.
output (cupy.ndarray, dtype or None): The array in which to place the
output.
mode (str): The array borders are handled according to the given mode
(``'reflect'``, ``'constant'``, ``'nearest'``, ``'mirror'``,
``'wrap'``). Default is ``'reflect'``.
cval (scalar): Value to fill past edges of input if mode is
``constant``. Default is ``0.0``.
origin (scalar or tuple of scalar): The origin parameter controls the
placement of the filter, relative to the center of the current
element of the input. Default of 0 is equivalent to
``(0,)*input.ndim``.
Returns:
cupy.ndarry : Result of the filter of ``input`` with ``structure``.
.. seealso:: :func:`scipy.ndimage.black_tophat`
"""
if (size is not None) and (footprint is not None):
warnings.warn(
'ignoring size because footprint is set', UserWarning, stacklevel=2
)
tmp = grey_dilation(
input, size, footprint, structure, None, mode, cval, origin
)
tmp = grey_erosion(
tmp, size, footprint, structure, output, mode, cval, origin
)
if input.dtype == numpy.bool_ and tmp.dtype == numpy.bool_:
cupy.bitwise_xor(tmp, input, out=tmp)
else:
cupy.subtract(tmp, input, out=tmp)
return tmp
121 changes: 121 additions & 0 deletions tests/cupyx_tests/scipy_tests/ndimage_tests/test_morphology.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,3 +459,124 @@ def _filter(self, xp, scp, x):
def test_grey_closing_and_opening(self, xp, scp):
x = testing.shaped_random(self.shape, xp, self.x_dtype)
return self._filter(xp, scp, x)


@testing.parameterize(*(
testing.product({
'x_dtype': [numpy.int32],
'origin': [-1, 0, 1],
'filter': ['morphological_gradient', 'morphological_laplace'],
'mode': ['reflect', 'constant'],
'output': [None],
'size': [(3, 3), (4, 3)],
'footprint': [None, 'random'],
'structure': [None, 'random']}
) + testing.product({
'x_dtype': [numpy.int32, numpy.float64],
'origin': [0],
'filter': ['morphological_gradient', 'morphological_laplace'],
'mode': ['reflect', 'constant', 'nearest', 'mirror', 'wrap'],
'output': [None, numpy.float32, 'zeros'],
'size': [3],
'footprint': [None, 'random'],
'structure': [None, 'random']}
))
)
@testing.gpu
@testing.with_requires('scipy')
class MorphologicalGradientAndLaplace(unittest.TestCase):

def _filter(self, xp, scp, x):
filter = getattr(scp.ndimage, self.filter)
if xp.isscalar(self.size):
shape = (self.size,) * x.ndim
else:
shape = tuple(self.size)
if self.footprint is None:
footprint = None
else:
r = testing.shaped_random(shape, xp, scale=1)
footprint = xp.where(r < .5, 1, 0)
if not footprint.any():
footprint = xp.ones(shape)
if self.structure is None:
structure = None
else:
structure = testing.shaped_random(shape, xp, dtype=xp.int32)
if self.output == 'zeros':
output = xp.zeros_like(x)
else:
output = self.output
return filter(x, self.size, footprint, structure,
output=output, mode=self.mode, cval=0.0,
origin=self.origin)

@testing.numpy_cupy_array_equal(scipy_name='scp')
def test_morphological_gradient_and_laplace(self, xp, scp):
x = xp.zeros((7, 7), dtype=self.x_dtype)
x[2:5, 2:5] = 1
x[4, 4] = 2
x[2, 3] = 3
if self.x_dtype == self.output:
raise unittest.SkipTest('redundant')
return self._filter(xp, scp, x)


@testing.parameterize(*(
testing.product({
'x_dtype': [numpy.int32],
'shape': [(5, 7)],
'origin': [-1, 0, 1],
'filter': ['white_tophat', 'black_tophat'],
'mode': ['reflect', 'constant'],
'output': [None],
'size': [(3, 3), (4, 3)],
'footprint': [None, 'random'],
'structure': [None, 'random']}
) + testing.product({
'x_dtype': [numpy.int32, numpy.float64],
'shape': [(6, 8)],
'origin': [0],
'filter': ['white_tophat', 'black_tophat'],
'mode': ['reflect', 'constant', 'nearest', 'mirror', 'wrap'],
'output': [None, numpy.float32, 'zeros'],
'size': [3],
'footprint': [None, 'random'],
'structure': [None, 'random']}
))
)
@testing.gpu
@testing.with_requires('scipy')
class WhiteTophatAndBlackTopHat(unittest.TestCase):

def _filter(self, xp, scp, x):
filter = getattr(scp.ndimage, self.filter)
if xp.isscalar(self.size):
shape = (self.size,) * x.ndim
else:
shape = tuple(self.size)
if self.footprint is None:
footprint = None
else:
r = testing.shaped_random(shape, xp, scale=1)
footprint = xp.where(r < .5, 1, 0)
if not footprint.any():
footprint = xp.ones(shape)
if self.structure is None:
structure = None
else:
structure = testing.shaped_random(shape, xp, dtype=xp.int32)
if self.output == 'zeros':
output = xp.zeros_like(x)
else:
output = self.output
return filter(x, self.size, footprint, structure,
output=output, mode=self.mode, cval=0.0,
origin=self.origin)

@testing.numpy_cupy_array_equal(scipy_name='scp')
def test_white_tophat_and_black_tophat(self, xp, scp):
x = testing.shaped_random(self.shape, xp, self.x_dtype)
if self.x_dtype == self.output:
raise unittest.SkipTest('redundant')
return self._filter(xp, scp, x)

0 comments on commit dbb7cb0

Please sign in to comment.