|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import warnings |
|
import operator |
|
|
|
import numpy as np |
|
from . import _ni_support |
|
from . import _nd_image |
|
from . import _filters |
|
|
|
__all__ = ['iterate_structure', 'generate_binary_structure', 'binary_erosion', |
|
'binary_dilation', 'binary_opening', 'binary_closing', |
|
'binary_hit_or_miss', 'binary_propagation', 'binary_fill_holes', |
|
'grey_erosion', 'grey_dilation', 'grey_opening', 'grey_closing', |
|
'morphological_gradient', 'morphological_laplace', 'white_tophat', |
|
'black_tophat', 'distance_transform_bf', 'distance_transform_cdt', |
|
'distance_transform_edt'] |
|
|
|
|
|
def _center_is_true(structure, origin): |
|
structure = np.asarray(structure) |
|
coor = tuple([oo + ss // 2 for ss, oo in zip(structure.shape, |
|
origin)]) |
|
return bool(structure[coor]) |
|
|
|
|
|
def iterate_structure(structure, iterations, origin=None): |
|
""" |
|
Iterate a structure by dilating it with itself. |
|
|
|
Parameters |
|
---------- |
|
structure : array_like |
|
Structuring element (an array of bools, for example), to be dilated with |
|
itself. |
|
iterations : int |
|
number of dilations performed on the structure with itself |
|
origin : optional |
|
If origin is None, only the iterated structure is returned. If |
|
not, a tuple of the iterated structure and the modified origin is |
|
returned. |
|
|
|
Returns |
|
------- |
|
iterate_structure : ndarray of bools |
|
A new structuring element obtained by dilating `structure` |
|
(`iterations` - 1) times with itself. |
|
|
|
See Also |
|
-------- |
|
generate_binary_structure |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> struct = ndimage.generate_binary_structure(2, 1) |
|
>>> struct.astype(int) |
|
array([[0, 1, 0], |
|
[1, 1, 1], |
|
[0, 1, 0]]) |
|
>>> ndimage.iterate_structure(struct, 2).astype(int) |
|
array([[0, 0, 1, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[1, 1, 1, 1, 1], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 1, 0, 0]]) |
|
>>> ndimage.iterate_structure(struct, 3).astype(int) |
|
array([[0, 0, 0, 1, 0, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[1, 1, 1, 1, 1, 1, 1], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 0, 1, 0, 0, 0]]) |
|
|
|
""" |
|
structure = np.asarray(structure) |
|
if iterations < 2: |
|
return structure.copy() |
|
ni = iterations - 1 |
|
shape = [ii + ni * (ii - 1) for ii in structure.shape] |
|
pos = [ni * (structure.shape[ii] // 2) for ii in range(len(shape))] |
|
slc = tuple(slice(pos[ii], pos[ii] + structure.shape[ii], None) |
|
for ii in range(len(shape))) |
|
out = np.zeros(shape, bool) |
|
out[slc] = structure != 0 |
|
out = binary_dilation(out, structure, iterations=ni) |
|
if origin is None: |
|
return out |
|
else: |
|
origin = _ni_support._normalize_sequence(origin, structure.ndim) |
|
origin = [iterations * o for o in origin] |
|
return out, origin |
|
|
|
|
|
def generate_binary_structure(rank, connectivity): |
|
""" |
|
Generate a binary structure for binary morphological operations. |
|
|
|
Parameters |
|
---------- |
|
rank : int |
|
Number of dimensions of the array to which the structuring element |
|
will be applied, as returned by `np.ndim`. |
|
connectivity : int |
|
`connectivity` determines which elements of the output array belong |
|
to the structure, i.e., are considered as neighbors of the central |
|
element. Elements up to a squared distance of `connectivity` from |
|
the center are considered neighbors. `connectivity` may range from 1 |
|
(no diagonal elements are neighbors) to `rank` (all elements are |
|
neighbors). |
|
|
|
Returns |
|
------- |
|
output : ndarray of bools |
|
Structuring element which may be used for binary morphological |
|
operations, with `rank` dimensions and all dimensions equal to 3. |
|
|
|
See Also |
|
-------- |
|
iterate_structure, binary_dilation, binary_erosion |
|
|
|
Notes |
|
----- |
|
`generate_binary_structure` can only create structuring elements with |
|
dimensions equal to 3, i.e., minimal dimensions. For larger structuring |
|
elements, that are useful e.g., for eroding large objects, one may either |
|
use `iterate_structure`, or create directly custom arrays with |
|
numpy functions such as `numpy.ones`. |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> struct = ndimage.generate_binary_structure(2, 1) |
|
>>> struct |
|
array([[False, True, False], |
|
[ True, True, True], |
|
[False, True, False]], dtype=bool) |
|
>>> a = np.zeros((5,5)) |
|
>>> a[2, 2] = 1 |
|
>>> a |
|
array([[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 1., 0., 0.], |
|
[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 0., 0., 0.]]) |
|
>>> b = ndimage.binary_dilation(a, structure=struct).astype(a.dtype) |
|
>>> b |
|
array([[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 1., 0., 0.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 0., 0., 1., 0., 0.], |
|
[ 0., 0., 0., 0., 0.]]) |
|
>>> ndimage.binary_dilation(b, structure=struct).astype(a.dtype) |
|
array([[ 0., 0., 1., 0., 0.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 1., 1., 1., 1., 1.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 0., 0., 1., 0., 0.]]) |
|
>>> struct = ndimage.generate_binary_structure(2, 2) |
|
>>> struct |
|
array([[ True, True, True], |
|
[ True, True, True], |
|
[ True, True, True]], dtype=bool) |
|
>>> struct = ndimage.generate_binary_structure(3, 1) |
|
>>> struct # no diagonal elements |
|
array([[[False, False, False], |
|
[False, True, False], |
|
[False, False, False]], |
|
[[False, True, False], |
|
[ True, True, True], |
|
[False, True, False]], |
|
[[False, False, False], |
|
[False, True, False], |
|
[False, False, False]]], dtype=bool) |
|
|
|
""" |
|
if connectivity < 1: |
|
connectivity = 1 |
|
if rank < 1: |
|
return np.array(True, dtype=bool) |
|
output = np.fabs(np.indices([3] * rank) - 1) |
|
output = np.add.reduce(output, 0) |
|
return output <= connectivity |
|
|
|
|
|
def _binary_erosion(input, structure, iterations, mask, output, |
|
border_value, origin, invert, brute_force, axes): |
|
try: |
|
iterations = operator.index(iterations) |
|
except TypeError as e: |
|
raise TypeError('iterations parameter should be an integer') from e |
|
|
|
input = np.asarray(input) |
|
ndim = input.ndim |
|
if np.iscomplexobj(input): |
|
raise TypeError('Complex type not supported') |
|
axes = _ni_support._check_axes(axes, input.ndim) |
|
num_axes = len(axes) |
|
if structure is None: |
|
structure = generate_binary_structure(num_axes, 1) |
|
else: |
|
structure = np.asarray(structure, dtype=bool) |
|
if ndim > num_axes: |
|
structure = _filters._expand_footprint(ndim, axes, structure, |
|
footprint_name="structure") |
|
|
|
if structure.ndim != input.ndim: |
|
raise RuntimeError('structure and input must have same dimensionality') |
|
if not structure.flags.contiguous: |
|
structure = structure.copy() |
|
if structure.size < 1: |
|
raise RuntimeError('structure must not be empty') |
|
if mask is not None: |
|
mask = np.asarray(mask) |
|
if mask.shape != input.shape: |
|
raise RuntimeError('mask and input must have equal sizes') |
|
origin = _ni_support._normalize_sequence(origin, num_axes) |
|
origin = _filters._expand_origin(ndim, axes, origin) |
|
cit = _center_is_true(structure, origin) |
|
if isinstance(output, np.ndarray): |
|
if np.iscomplexobj(output): |
|
raise TypeError('Complex output type not supported') |
|
else: |
|
output = bool |
|
output = _ni_support._get_output(output, input) |
|
temp_needed = np.may_share_memory(input, output) |
|
if temp_needed: |
|
|
|
temp = output |
|
output = _ni_support._get_output(output.dtype, input) |
|
if iterations == 1: |
|
_nd_image.binary_erosion(input, structure, mask, output, |
|
border_value, origin, invert, cit, 0) |
|
elif cit and not brute_force: |
|
changed, coordinate_list = _nd_image.binary_erosion( |
|
input, structure, mask, output, |
|
border_value, origin, invert, cit, 1) |
|
structure = structure[tuple([slice(None, None, -1)] * |
|
structure.ndim)] |
|
for ii in range(len(origin)): |
|
origin[ii] = -origin[ii] |
|
if not structure.shape[ii] & 1: |
|
origin[ii] -= 1 |
|
if mask is not None: |
|
mask = np.asarray(mask, dtype=np.int8) |
|
if not structure.flags.contiguous: |
|
structure = structure.copy() |
|
_nd_image.binary_erosion2(output, structure, mask, iterations - 1, |
|
origin, invert, coordinate_list) |
|
else: |
|
tmp_in = np.empty_like(input, dtype=bool) |
|
tmp_out = output |
|
if iterations >= 1 and not iterations & 1: |
|
tmp_in, tmp_out = tmp_out, tmp_in |
|
changed = _nd_image.binary_erosion( |
|
input, structure, mask, tmp_out, |
|
border_value, origin, invert, cit, 0) |
|
ii = 1 |
|
while ii < iterations or (iterations < 1 and changed): |
|
tmp_in, tmp_out = tmp_out, tmp_in |
|
changed = _nd_image.binary_erosion( |
|
tmp_in, structure, mask, tmp_out, |
|
border_value, origin, invert, cit, 0) |
|
ii += 1 |
|
if temp_needed: |
|
temp[...] = output |
|
output = temp |
|
return output |
|
|
|
|
|
def binary_erosion(input, structure=None, iterations=1, mask=None, output=None, |
|
border_value=0, origin=0, brute_force=False, *, axes=None): |
|
""" |
|
Multidimensional binary erosion with a given structuring element. |
|
|
|
Binary erosion is a mathematical morphology operation used for image |
|
processing. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Binary image to be eroded. Non-zero (True) elements form |
|
the subset to be eroded. |
|
structure : array_like, optional |
|
Structuring element used for the erosion. Non-zero elements are |
|
considered True. If no structuring element is provided, an element |
|
is generated with a square connectivity equal to one. |
|
iterations : int, optional |
|
The erosion is repeated `iterations` times (one, by default). |
|
If iterations is less than 1, the erosion is repeated until the |
|
result does not change anymore. |
|
mask : array_like, optional |
|
If a mask is given, only those elements with a True value at |
|
the corresponding mask element are modified at each iteration. |
|
output : ndarray, optional |
|
Array of the same shape as input, into which the output is placed. |
|
By default, a new array is created. |
|
border_value : int (cast to 0 or 1), optional |
|
Value at the border in the output array. |
|
origin : int or tuple of ints, optional |
|
Placement of the filter, by default 0. |
|
brute_force : boolean, optional |
|
Memory condition: if False, only the pixels whose value was changed in |
|
the last iteration are tracked as candidates to be updated (eroded) in |
|
the current iteration; if True all pixels are considered as candidates |
|
for erosion, regardless of what happened in the previous iteration. |
|
False by default. |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
binary_erosion : ndarray of bools |
|
Erosion of the input by the structuring element. |
|
|
|
See Also |
|
-------- |
|
grey_erosion, binary_dilation, binary_closing, binary_opening, |
|
generate_binary_structure |
|
|
|
Notes |
|
----- |
|
Erosion [1]_ is a mathematical morphology operation [2]_ that uses a |
|
structuring element for shrinking the shapes in an image. The binary |
|
erosion of an image by a structuring element is the locus of the points |
|
where a superimposition of the structuring element centered on the point |
|
is entirely contained in the set of non-zero elements of the image. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Erosion_%28morphology%29 |
|
.. [2] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((7,7), dtype=int) |
|
>>> a[1:6, 2:5] = 1 |
|
>>> a |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.binary_erosion(a).astype(a.dtype) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 1, 0, 0, 0], |
|
[0, 0, 0, 1, 0, 0, 0], |
|
[0, 0, 0, 1, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> #Erosion removes objects smaller than the structure |
|
>>> ndimage.binary_erosion(a, structure=np.ones((5,5))).astype(a.dtype) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
return _binary_erosion(input, structure, iterations, mask, |
|
output, border_value, origin, 0, brute_force, axes) |
|
|
|
|
|
def binary_dilation(input, structure=None, iterations=1, mask=None, |
|
output=None, border_value=0, origin=0, |
|
brute_force=False, *, axes=None): |
|
""" |
|
Multidimensional binary dilation with the given structuring element. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Binary array_like to be dilated. Non-zero (True) elements form |
|
the subset to be dilated. |
|
structure : array_like, optional |
|
Structuring element used for the dilation. Non-zero elements are |
|
considered True. If no structuring element is provided an element |
|
is generated with a square connectivity equal to one. |
|
iterations : int, optional |
|
The dilation is repeated `iterations` times (one, by default). |
|
If iterations is less than 1, the dilation is repeated until the |
|
result does not change anymore. Only an integer of iterations is |
|
accepted. |
|
mask : array_like, optional |
|
If a mask is given, only those elements with a True value at |
|
the corresponding mask element are modified at each iteration. |
|
output : ndarray, optional |
|
Array of the same shape as input, into which the output is placed. |
|
By default, a new array is created. |
|
border_value : int (cast to 0 or 1), optional |
|
Value at the border in the output array. |
|
origin : int or tuple of ints, optional |
|
Placement of the filter, by default 0. |
|
brute_force : boolean, optional |
|
Memory condition: if False, only the pixels whose value was changed in |
|
the last iteration are tracked as candidates to be updated (dilated) |
|
in the current iteration; if True all pixels are considered as |
|
candidates for dilation, regardless of what happened in the previous |
|
iteration. False by default. |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
binary_dilation : ndarray of bools |
|
Dilation of the input by the structuring element. |
|
|
|
See Also |
|
-------- |
|
grey_dilation, binary_erosion, binary_closing, binary_opening, |
|
generate_binary_structure |
|
|
|
Notes |
|
----- |
|
Dilation [1]_ is a mathematical morphology operation [2]_ that uses a |
|
structuring element for expanding the shapes in an image. The binary |
|
dilation of an image by a structuring element is the locus of the points |
|
covered by the structuring element, when its center lies within the |
|
non-zero points of the image. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Dilation_%28morphology%29 |
|
.. [2] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((5, 5)) |
|
>>> a[2, 2] = 1 |
|
>>> a |
|
array([[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 1., 0., 0.], |
|
[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 0., 0., 0.]]) |
|
>>> ndimage.binary_dilation(a) |
|
array([[False, False, False, False, False], |
|
[False, False, True, False, False], |
|
[False, True, True, True, False], |
|
[False, False, True, False, False], |
|
[False, False, False, False, False]], dtype=bool) |
|
>>> ndimage.binary_dilation(a).astype(a.dtype) |
|
array([[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 1., 0., 0.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 0., 0., 1., 0., 0.], |
|
[ 0., 0., 0., 0., 0.]]) |
|
>>> # 3x3 structuring element with connectivity 1, used by default |
|
>>> struct1 = ndimage.generate_binary_structure(2, 1) |
|
>>> struct1 |
|
array([[False, True, False], |
|
[ True, True, True], |
|
[False, True, False]], dtype=bool) |
|
>>> # 3x3 structuring element with connectivity 2 |
|
>>> struct2 = ndimage.generate_binary_structure(2, 2) |
|
>>> struct2 |
|
array([[ True, True, True], |
|
[ True, True, True], |
|
[ True, True, True]], dtype=bool) |
|
>>> ndimage.binary_dilation(a, structure=struct1).astype(a.dtype) |
|
array([[ 0., 0., 0., 0., 0.], |
|
[ 0., 0., 1., 0., 0.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 0., 0., 1., 0., 0.], |
|
[ 0., 0., 0., 0., 0.]]) |
|
>>> ndimage.binary_dilation(a, structure=struct2).astype(a.dtype) |
|
array([[ 0., 0., 0., 0., 0.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 0., 0., 0., 0., 0.]]) |
|
>>> ndimage.binary_dilation(a, structure=struct1,\\ |
|
... iterations=2).astype(a.dtype) |
|
array([[ 0., 0., 1., 0., 0.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 1., 1., 1., 1., 1.], |
|
[ 0., 1., 1., 1., 0.], |
|
[ 0., 0., 1., 0., 0.]]) |
|
|
|
""" |
|
input = np.asarray(input) |
|
axes = _ni_support._check_axes(axes, input.ndim) |
|
num_axes = len(axes) |
|
if structure is None: |
|
structure = generate_binary_structure(num_axes, 1) |
|
origin = _ni_support._normalize_sequence(origin, num_axes) |
|
structure = np.asarray(structure) |
|
structure = structure[tuple([slice(None, None, -1)] * |
|
structure.ndim)] |
|
for ii in range(len(origin)): |
|
origin[ii] = -origin[ii] |
|
if not structure.shape[ii] & 1: |
|
origin[ii] -= 1 |
|
|
|
return _binary_erosion(input, structure, iterations, mask, |
|
output, border_value, origin, 1, brute_force, axes) |
|
|
|
|
|
def binary_opening(input, structure=None, iterations=1, output=None, |
|
origin=0, mask=None, border_value=0, brute_force=False, *, |
|
axes=None): |
|
""" |
|
Multidimensional binary opening with the given structuring element. |
|
|
|
The *opening* of an input image by a structuring element is the |
|
*dilation* of the *erosion* of the image by the structuring element. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Binary array_like to be opened. Non-zero (True) elements form |
|
the subset to be opened. |
|
structure : array_like, optional |
|
Structuring element used for the opening. Non-zero elements are |
|
considered True. If no structuring element is provided an element |
|
is generated with a square connectivity equal to one (i.e., only |
|
nearest neighbors are connected to the center, diagonally-connected |
|
elements are not considered neighbors). |
|
iterations : int, optional |
|
The erosion step of the opening, then the dilation step are each |
|
repeated `iterations` times (one, by default). If `iterations` is |
|
less than 1, each operation is repeated until the result does |
|
not change anymore. Only an integer of iterations is accepted. |
|
output : ndarray, optional |
|
Array of the same shape as input, into which the output is placed. |
|
By default, a new array is created. |
|
origin : int or tuple of ints, optional |
|
Placement of the filter, by default 0. |
|
mask : array_like, optional |
|
If a mask is given, only those elements with a True value at |
|
the corresponding mask element are modified at each iteration. |
|
|
|
.. versionadded:: 1.1.0 |
|
border_value : int (cast to 0 or 1), optional |
|
Value at the border in the output array. |
|
|
|
.. versionadded:: 1.1.0 |
|
brute_force : boolean, optional |
|
Memory condition: if False, only the pixels whose value was changed in |
|
the last iteration are tracked as candidates to be updated in the |
|
current iteration; if true all pixels are considered as candidates for |
|
update, regardless of what happened in the previous iteration. |
|
False by default. |
|
|
|
.. versionadded:: 1.1.0 |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
binary_opening : ndarray of bools |
|
Opening of the input by the structuring element. |
|
|
|
See Also |
|
-------- |
|
grey_opening, binary_closing, binary_erosion, binary_dilation, |
|
generate_binary_structure |
|
|
|
Notes |
|
----- |
|
*Opening* [1]_ is a mathematical morphology operation [2]_ that |
|
consists in the succession of an erosion and a dilation of the |
|
input with the same structuring element. Opening, therefore, removes |
|
objects smaller than the structuring element. |
|
|
|
Together with *closing* (`binary_closing`), opening can be used for |
|
noise removal. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Opening_%28morphology%29 |
|
.. [2] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((5,5), dtype=int) |
|
>>> a[1:4, 1:4] = 1; a[4, 4] = 1 |
|
>>> a |
|
array([[0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 1]]) |
|
>>> # Opening removes small objects |
|
>>> ndimage.binary_opening(a, structure=np.ones((3,3))).astype(int) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0]]) |
|
>>> # Opening can also smooth corners |
|
>>> ndimage.binary_opening(a).astype(int) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 0]]) |
|
>>> # Opening is the dilation of the erosion of the input |
|
>>> ndimage.binary_erosion(a).astype(int) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0]]) |
|
>>> ndimage.binary_dilation(ndimage.binary_erosion(a)).astype(int) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
input = np.asarray(input) |
|
axes = _ni_support._check_axes(axes, input.ndim) |
|
num_axes = len(axes) |
|
if structure is None: |
|
structure = generate_binary_structure(num_axes, 1) |
|
|
|
tmp = binary_erosion(input, structure, iterations, mask, None, |
|
border_value, origin, brute_force, axes=axes) |
|
return binary_dilation(tmp, structure, iterations, mask, output, |
|
border_value, origin, brute_force, axes=axes) |
|
|
|
|
|
def binary_closing(input, structure=None, iterations=1, output=None, |
|
origin=0, mask=None, border_value=0, brute_force=False, *, |
|
axes=None): |
|
""" |
|
Multidimensional binary closing with the given structuring element. |
|
|
|
The *closing* of an input image by a structuring element is the |
|
*erosion* of the *dilation* of the image by the structuring element. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Binary array_like to be closed. Non-zero (True) elements form |
|
the subset to be closed. |
|
structure : array_like, optional |
|
Structuring element used for the closing. Non-zero elements are |
|
considered True. If no structuring element is provided an element |
|
is generated with a square connectivity equal to one (i.e., only |
|
nearest neighbors are connected to the center, diagonally-connected |
|
elements are not considered neighbors). |
|
iterations : int, optional |
|
The dilation step of the closing, then the erosion step are each |
|
repeated `iterations` times (one, by default). If iterations is |
|
less than 1, each operations is repeated until the result does |
|
not change anymore. Only an integer of iterations is accepted. |
|
output : ndarray, optional |
|
Array of the same shape as input, into which the output is placed. |
|
By default, a new array is created. |
|
origin : int or tuple of ints, optional |
|
Placement of the filter, by default 0. |
|
mask : array_like, optional |
|
If a mask is given, only those elements with a True value at |
|
the corresponding mask element are modified at each iteration. |
|
|
|
.. versionadded:: 1.1.0 |
|
border_value : int (cast to 0 or 1), optional |
|
Value at the border in the output array. |
|
|
|
.. versionadded:: 1.1.0 |
|
brute_force : boolean, optional |
|
Memory condition: if False, only the pixels whose value was changed in |
|
the last iteration are tracked as candidates to be updated in the |
|
current iteration; if true al pixels are considered as candidates for |
|
update, regardless of what happened in the previous iteration. |
|
False by default. |
|
|
|
.. versionadded:: 1.1.0 |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
binary_closing : ndarray of bools |
|
Closing of the input by the structuring element. |
|
|
|
See Also |
|
-------- |
|
grey_closing, binary_opening, binary_dilation, binary_erosion, |
|
generate_binary_structure |
|
|
|
Notes |
|
----- |
|
*Closing* [1]_ is a mathematical morphology operation [2]_ that |
|
consists in the succession of a dilation and an erosion of the |
|
input with the same structuring element. Closing therefore fills |
|
holes smaller than the structuring element. |
|
|
|
Together with *opening* (`binary_opening`), closing can be used for |
|
noise removal. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Closing_%28morphology%29 |
|
.. [2] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((5,5), dtype=int) |
|
>>> a[1:-1, 1:-1] = 1; a[2,2] = 0 |
|
>>> a |
|
array([[0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 0, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0]]) |
|
>>> # Closing removes small holes |
|
>>> ndimage.binary_closing(a).astype(int) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0]]) |
|
>>> # Closing is the erosion of the dilation of the input |
|
>>> ndimage.binary_dilation(a).astype(int) |
|
array([[0, 1, 1, 1, 0], |
|
[1, 1, 1, 1, 1], |
|
[1, 1, 1, 1, 1], |
|
[1, 1, 1, 1, 1], |
|
[0, 1, 1, 1, 0]]) |
|
>>> ndimage.binary_erosion(ndimage.binary_dilation(a)).astype(int) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0]]) |
|
|
|
|
|
>>> a = np.zeros((7,7), dtype=int) |
|
>>> a[1:6, 2:5] = 1; a[1:3,3] = 0 |
|
>>> a |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 1, 0, 0], |
|
[0, 0, 1, 0, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> # In addition to removing holes, closing can also |
|
>>> # coarsen boundaries with fine hollows. |
|
>>> ndimage.binary_closing(a).astype(int) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.binary_closing(a, structure=np.ones((2,2))).astype(int) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
input = np.asarray(input) |
|
axes = _ni_support._check_axes(axes, input.ndim) |
|
num_axes = len(axes) |
|
if structure is None: |
|
structure = generate_binary_structure(num_axes, 1) |
|
|
|
tmp = binary_dilation(input, structure, iterations, mask, None, |
|
border_value, origin, brute_force, axes=axes) |
|
return binary_erosion(tmp, structure, iterations, mask, output, |
|
border_value, origin, brute_force, axes=axes) |
|
|
|
|
|
def binary_hit_or_miss(input, structure1=None, structure2=None, |
|
output=None, origin1=0, origin2=None, *, axes=None): |
|
""" |
|
Multidimensional binary hit-or-miss transform. |
|
|
|
The hit-or-miss transform finds the locations of a given pattern |
|
inside the input image. |
|
|
|
Parameters |
|
---------- |
|
input : array_like (cast to booleans) |
|
Binary image where a pattern is to be detected. |
|
structure1 : array_like (cast to booleans), optional |
|
Part of the structuring element to be fitted to the foreground |
|
(non-zero elements) of `input`. If no value is provided, a |
|
structure of square connectivity 1 is chosen. |
|
structure2 : array_like (cast to booleans), optional |
|
Second part of the structuring element that has to miss completely |
|
the foreground. If no value is provided, the complementary of |
|
`structure1` is taken. |
|
output : ndarray, optional |
|
Array of the same shape as input, into which the output is placed. |
|
By default, a new array is created. |
|
origin1 : int or tuple of ints, optional |
|
Placement of the first part of the structuring element `structure1`, |
|
by default 0 for a centered structure. |
|
origin2 : int or tuple of ints, optional |
|
Placement of the second part of the structuring element `structure2`, |
|
by default 0 for a centered structure. If a value is provided for |
|
`origin1` and not for `origin2`, then `origin2` is set to `origin1`. |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If `origin1` or `origin2` tuples are provided, their |
|
length must match the number of axes. |
|
|
|
Returns |
|
------- |
|
binary_hit_or_miss : ndarray |
|
Hit-or-miss transform of `input` with the given structuring |
|
element (`structure1`, `structure2`). |
|
|
|
See Also |
|
-------- |
|
binary_erosion |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Hit-or-miss_transform |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((7,7), dtype=int) |
|
>>> a[1, 1] = 1; a[2:4, 2:4] = 1; a[4:6, 4:6] = 1 |
|
>>> a |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 1, 0, 0, 0], |
|
[0, 0, 1, 1, 0, 0, 0], |
|
[0, 0, 0, 0, 1, 1, 0], |
|
[0, 0, 0, 0, 1, 1, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> structure1 = np.array([[1, 0, 0], [0, 1, 1], [0, 1, 1]]) |
|
>>> structure1 |
|
array([[1, 0, 0], |
|
[0, 1, 1], |
|
[0, 1, 1]]) |
|
>>> # Find the matches of structure1 in the array a |
|
>>> ndimage.binary_hit_or_miss(a, structure1=structure1).astype(int) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> # Change the origin of the filter |
|
>>> # origin1=1 is equivalent to origin1=(1,1) here |
|
>>> ndimage.binary_hit_or_miss(a, structure1=structure1,\\ |
|
... origin1=1).astype(int) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 1, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 1, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
input = np.asarray(input) |
|
axes = _ni_support._check_axes(axes, input.ndim) |
|
num_axes = len(axes) |
|
if structure1 is None: |
|
structure1 = generate_binary_structure(num_axes, 1) |
|
else: |
|
structure1 = np.asarray(structure1) |
|
if structure2 is None: |
|
structure2 = np.logical_not(structure1) |
|
origin1 = _ni_support._normalize_sequence(origin1, num_axes) |
|
if origin2 is None: |
|
origin2 = origin1 |
|
else: |
|
origin2 = _ni_support._normalize_sequence(origin2, num_axes) |
|
|
|
tmp1 = _binary_erosion(input, structure1, 1, None, None, 0, origin1, |
|
0, False, axes) |
|
inplace = isinstance(output, np.ndarray) |
|
result = _binary_erosion(input, structure2, 1, None, output, 0, |
|
origin2, 1, False, axes) |
|
if inplace: |
|
np.logical_not(output, output) |
|
np.logical_and(tmp1, output, output) |
|
else: |
|
np.logical_not(result, result) |
|
return np.logical_and(tmp1, result) |
|
|
|
|
|
def binary_propagation(input, structure=None, mask=None, |
|
output=None, border_value=0, origin=0, *, axes=None): |
|
""" |
|
Multidimensional binary propagation with the given structuring element. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Binary image to be propagated inside `mask`. |
|
structure : array_like, optional |
|
Structuring element used in the successive dilations. The output |
|
may depend on the structuring element, especially if `mask` has |
|
several connex components. If no structuring element is |
|
provided, an element is generated with a squared connectivity equal |
|
to one. |
|
mask : array_like, optional |
|
Binary mask defining the region into which `input` is allowed to |
|
propagate. |
|
output : ndarray, optional |
|
Array of the same shape as input, into which the output is placed. |
|
By default, a new array is created. |
|
border_value : int (cast to 0 or 1), optional |
|
Value at the border in the output array. |
|
origin : int or tuple of ints, optional |
|
Placement of the filter, by default 0. |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
binary_propagation : ndarray |
|
Binary propagation of `input` inside `mask`. |
|
|
|
Notes |
|
----- |
|
This function is functionally equivalent to calling binary_dilation |
|
with the number of iterations less than one: iterative dilation until |
|
the result does not change anymore. |
|
|
|
The succession of an erosion and propagation inside the original image |
|
can be used instead of an *opening* for deleting small objects while |
|
keeping the contours of larger objects untouched. |
|
|
|
References |
|
---------- |
|
.. [1] http://cmm.ensmp.fr/~serra/cours/pdf/en/ch6en.pdf, slide 15. |
|
.. [2] I.T. Young, J.J. Gerbrands, and L.J. van Vliet, "Fundamentals of |
|
image processing", 1998 |
|
ftp://qiftp.tudelft.nl/DIPimage/docs/FIP2.3.pdf |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> input = np.zeros((8, 8), dtype=int) |
|
>>> input[2, 2] = 1 |
|
>>> mask = np.zeros((8, 8), dtype=int) |
|
>>> mask[1:4, 1:4] = mask[4, 4] = mask[6:8, 6:8] = 1 |
|
>>> input |
|
array([[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0]]) |
|
>>> mask |
|
array([[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 1, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 1, 1], |
|
[0, 0, 0, 0, 0, 0, 1, 1]]) |
|
>>> ndimage.binary_propagation(input, mask=mask).astype(int) |
|
array([[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.binary_propagation(input, mask=mask,\\ |
|
... structure=np.ones((3,3))).astype(int) |
|
array([[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 1, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0, 0]]) |
|
|
|
>>> # Comparison between opening and erosion+propagation |
|
>>> a = np.zeros((6,6), dtype=int) |
|
>>> a[2:5, 2:5] = 1; a[0, 0] = 1; a[5, 5] = 1 |
|
>>> a |
|
array([[1, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 1, 1, 0], |
|
[0, 0, 1, 1, 1, 0], |
|
[0, 0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0, 1]]) |
|
>>> ndimage.binary_opening(a).astype(int) |
|
array([[0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0], |
|
[0, 0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 0, 0]]) |
|
>>> b = ndimage.binary_erosion(a) |
|
>>> b.astype(int) |
|
array([[0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.binary_propagation(b, mask=a).astype(int) |
|
array([[0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 1, 1, 0], |
|
[0, 0, 1, 1, 1, 0], |
|
[0, 0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
return binary_dilation(input, structure, -1, mask, output, |
|
border_value, origin, axes=axes) |
|
|
|
|
|
def binary_fill_holes(input, structure=None, output=None, origin=0, *, |
|
axes=None): |
|
""" |
|
Fill the holes in binary objects. |
|
|
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
N-D binary array with holes to be filled |
|
structure : array_like, optional |
|
Structuring element used in the computation; large-size elements |
|
make computations faster but may miss holes separated from the |
|
background by thin regions. The default element (with a square |
|
connectivity equal to one) yields the intuitive result where all |
|
holes in the input have been filled. |
|
output : ndarray, optional |
|
Array of the same shape as input, into which the output is placed. |
|
By default, a new array is created. |
|
origin : int, tuple of ints, optional |
|
Position of the structuring element. |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
out : ndarray |
|
Transformation of the initial image `input` where holes have been |
|
filled. |
|
|
|
See Also |
|
-------- |
|
binary_dilation, binary_propagation, label |
|
|
|
Notes |
|
----- |
|
The algorithm used in this function consists in invading the complementary |
|
of the shapes in `input` from the outer boundary of the image, |
|
using binary dilations. Holes are not connected to the boundary and are |
|
therefore not invaded. The result is the complementary subset of the |
|
invaded region. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((5, 5), dtype=int) |
|
>>> a[1:4, 1:4] = 1 |
|
>>> a[2,2] = 0 |
|
>>> a |
|
array([[0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 0, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0]]) |
|
>>> ndimage.binary_fill_holes(a).astype(int) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0]]) |
|
>>> # Too big structuring element |
|
>>> ndimage.binary_fill_holes(a, structure=np.ones((5,5))).astype(int) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 1, 0, 1, 0], |
|
[0, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
input = np.asarray(input) |
|
mask = np.logical_not(input) |
|
tmp = np.zeros(mask.shape, bool) |
|
inplace = isinstance(output, np.ndarray) |
|
if inplace: |
|
binary_dilation(tmp, structure, -1, mask, output, 1, origin, axes=axes) |
|
np.logical_not(output, output) |
|
else: |
|
output = binary_dilation(tmp, structure, -1, mask, None, 1, |
|
origin, axes=axes) |
|
np.logical_not(output, output) |
|
return output |
|
|
|
|
|
def grey_erosion(input, size=None, footprint=None, structure=None, |
|
output=None, mode="reflect", cval=0.0, origin=0, *, |
|
axes=None): |
|
""" |
|
Calculate a greyscale erosion, using either a structuring element, |
|
or a footprint corresponding to a flat structuring element. |
|
|
|
Grayscale erosion is a mathematical morphology operation. For the |
|
simple case of a full and flat structuring element, it can be viewed |
|
as a minimum filter over a sliding window. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Array over which the grayscale erosion is to be computed. |
|
size : tuple of ints |
|
Shape of a flat and full structuring element used for the grayscale |
|
erosion. Optional if `footprint` or `structure` is provided. |
|
footprint : array of ints, optional |
|
Positions of non-infinite elements of a flat structuring element |
|
used for the grayscale erosion. Non-zero values give the set of |
|
neighbors of the center over which the minimum is chosen. |
|
structure : array of ints, optional |
|
Structuring element used for the grayscale erosion. `structure` |
|
may be a non-flat structuring element. The `structure` array applies a |
|
subtractive offset for each pixel in the neighborhood. |
|
output : array, optional |
|
An array used for storing the output of the erosion may be provided. |
|
mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional |
|
The `mode` parameter determines how the array borders are |
|
handled, where `cval` is the value when mode is equal to |
|
'constant'. Default is 'reflect' |
|
cval : scalar, optional |
|
Value to fill past edges of input if `mode` is 'constant'. Default |
|
is 0.0. |
|
origin : scalar, optional |
|
The `origin` parameter controls the placement of the filter. |
|
Default 0 |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
output : ndarray |
|
Grayscale erosion of `input`. |
|
|
|
See Also |
|
-------- |
|
binary_erosion, grey_dilation, grey_opening, grey_closing |
|
generate_binary_structure, minimum_filter |
|
|
|
Notes |
|
----- |
|
The grayscale erosion of an image input by a structuring element s defined |
|
over a domain E is given by: |
|
|
|
(input+s)(x) = min {input(y) - s(x-y), for y in E} |
|
|
|
In particular, for structuring elements defined as |
|
s(y) = 0 for y in E, the grayscale erosion computes the minimum of the |
|
input image inside a sliding window defined by E. |
|
|
|
Grayscale erosion [1]_ is a *mathematical morphology* operation [2]_. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Erosion_%28morphology%29 |
|
.. [2] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((7,7), dtype=int) |
|
>>> a[1:6, 1:6] = 3 |
|
>>> a[4,4] = 2; a[2,3] = 1 |
|
>>> a |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 3, 3, 3, 3, 3, 0], |
|
[0, 3, 3, 1, 3, 3, 0], |
|
[0, 3, 3, 3, 3, 3, 0], |
|
[0, 3, 3, 3, 2, 3, 0], |
|
[0, 3, 3, 3, 3, 3, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.grey_erosion(a, size=(3,3)) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 3, 2, 2, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> footprint = ndimage.generate_binary_structure(2, 1) |
|
>>> footprint |
|
array([[False, True, False], |
|
[ True, True, True], |
|
[False, True, False]], dtype=bool) |
|
>>> # Diagonally-connected elements are not considered neighbors |
|
>>> ndimage.grey_erosion(a, footprint=footprint) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 3, 1, 2, 0, 0], |
|
[0, 0, 3, 2, 2, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
if size is None and footprint is None and structure is None: |
|
raise ValueError("size, footprint, or structure must be specified") |
|
|
|
return _filters._min_or_max_filter(input, size, footprint, structure, |
|
output, mode, cval, origin, 1, |
|
axes=axes) |
|
|
|
|
|
def grey_dilation(input, size=None, footprint=None, structure=None, |
|
output=None, mode="reflect", cval=0.0, origin=0, *, |
|
axes=None): |
|
""" |
|
Calculate a greyscale dilation, using either a structuring element, |
|
or a footprint corresponding to a flat structuring element. |
|
|
|
Grayscale dilation is a mathematical morphology operation. For the |
|
simple case of a full and flat structuring element, it can be viewed |
|
as a maximum filter over a sliding window. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Array over which the grayscale dilation is to be computed. |
|
size : tuple of ints |
|
Shape of a flat and full structuring element used for the grayscale |
|
dilation. Optional if `footprint` or `structure` is provided. |
|
footprint : array of ints, optional |
|
Positions of non-infinite elements of a flat structuring element |
|
used for the grayscale dilation. Non-zero values give the set of |
|
neighbors of the center over which the maximum is chosen. |
|
structure : array of ints, optional |
|
Structuring element used for the grayscale dilation. `structure` |
|
may be a non-flat structuring element. The `structure` array applies an |
|
additive offset for each pixel in the neighborhood. |
|
output : array, optional |
|
An array used for storing the output of the dilation may be provided. |
|
mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional |
|
The `mode` parameter determines how the array borders are |
|
handled, where `cval` is the value when mode is equal to |
|
'constant'. Default is 'reflect' |
|
cval : scalar, optional |
|
Value to fill past edges of input if `mode` is 'constant'. Default |
|
is 0.0. |
|
origin : scalar, optional |
|
The `origin` parameter controls the placement of the filter. |
|
Default 0 |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
grey_dilation : ndarray |
|
Grayscale dilation of `input`. |
|
|
|
See Also |
|
-------- |
|
binary_dilation, grey_erosion, grey_closing, grey_opening |
|
generate_binary_structure, maximum_filter |
|
|
|
Notes |
|
----- |
|
The grayscale dilation of an image input by a structuring element s defined |
|
over a domain E is given by: |
|
|
|
(input+s)(x) = max {input(y) + s(x-y), for y in E} |
|
|
|
In particular, for structuring elements defined as |
|
s(y) = 0 for y in E, the grayscale dilation computes the maximum of the |
|
input image inside a sliding window defined by E. |
|
|
|
Grayscale dilation [1]_ is a *mathematical morphology* operation [2]_. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Dilation_%28morphology%29 |
|
.. [2] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((7,7), dtype=int) |
|
>>> a[2:5, 2:5] = 1 |
|
>>> a[4,4] = 2; a[2,3] = 3 |
|
>>> a |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 3, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 2, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.grey_dilation(a, size=(3,3)) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 3, 3, 3, 1, 0], |
|
[0, 1, 3, 3, 3, 1, 0], |
|
[0, 1, 3, 3, 3, 2, 0], |
|
[0, 1, 1, 2, 2, 2, 0], |
|
[0, 1, 1, 2, 2, 2, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.grey_dilation(a, footprint=np.ones((3,3))) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 3, 3, 3, 1, 0], |
|
[0, 1, 3, 3, 3, 1, 0], |
|
[0, 1, 3, 3, 3, 2, 0], |
|
[0, 1, 1, 2, 2, 2, 0], |
|
[0, 1, 1, 2, 2, 2, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> s = ndimage.generate_binary_structure(2,1) |
|
>>> s |
|
array([[False, True, False], |
|
[ True, True, True], |
|
[False, True, False]], dtype=bool) |
|
>>> ndimage.grey_dilation(a, footprint=s) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 3, 1, 0, 0], |
|
[0, 1, 3, 3, 3, 1, 0], |
|
[0, 1, 1, 3, 2, 1, 0], |
|
[0, 1, 1, 2, 2, 2, 0], |
|
[0, 0, 1, 1, 2, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.grey_dilation(a, size=(3,3), structure=np.ones((3,3))) |
|
array([[1, 1, 1, 1, 1, 1, 1], |
|
[1, 2, 4, 4, 4, 2, 1], |
|
[1, 2, 4, 4, 4, 2, 1], |
|
[1, 2, 4, 4, 4, 3, 1], |
|
[1, 2, 2, 3, 3, 3, 1], |
|
[1, 2, 2, 3, 3, 3, 1], |
|
[1, 1, 1, 1, 1, 1, 1]]) |
|
|
|
""" |
|
if size is None and footprint is None and structure is None: |
|
raise ValueError("size, footprint, or structure must be specified") |
|
if structure is not None: |
|
structure = np.asarray(structure) |
|
structure = structure[tuple([slice(None, None, -1)] * |
|
structure.ndim)] |
|
if footprint is not None: |
|
footprint = np.asarray(footprint) |
|
footprint = footprint[tuple([slice(None, None, -1)] * |
|
footprint.ndim)] |
|
|
|
input = np.asarray(input) |
|
axes = _ni_support._check_axes(axes, input.ndim) |
|
origin = _ni_support._normalize_sequence(origin, len(axes)) |
|
for ii in range(len(origin)): |
|
origin[ii] = -origin[ii] |
|
if footprint is not None: |
|
sz = footprint.shape[ii] |
|
elif structure is not None: |
|
sz = structure.shape[ii] |
|
elif np.isscalar(size): |
|
sz = size |
|
else: |
|
sz = size[ii] |
|
if not sz & 1: |
|
origin[ii] -= 1 |
|
|
|
return _filters._min_or_max_filter(input, size, footprint, structure, |
|
output, mode, cval, origin, 0, |
|
axes=axes) |
|
|
|
|
|
def grey_opening(input, size=None, footprint=None, structure=None, |
|
output=None, mode="reflect", cval=0.0, origin=0, *, |
|
axes=None): |
|
""" |
|
Multidimensional grayscale opening. |
|
|
|
A grayscale opening consists in the succession of a grayscale erosion, |
|
and a grayscale dilation. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Array over which the grayscale opening is to be computed. |
|
size : tuple of ints |
|
Shape of a flat and full structuring element used for the grayscale |
|
opening. Optional if `footprint` or `structure` is provided. |
|
footprint : array of ints, optional |
|
Positions of non-infinite elements of a flat structuring element |
|
used for the grayscale opening. |
|
structure : array of ints, optional |
|
Structuring element used for the grayscale opening. `structure` |
|
may be a non-flat structuring element. The `structure` array applies |
|
offsets to the pixels in a neighborhood (the offset is additive during |
|
dilation and subtractive during erosion). |
|
output : array, optional |
|
An array used for storing the output of the opening may be provided. |
|
mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional |
|
The `mode` parameter determines how the array borders are |
|
handled, where `cval` is the value when mode is equal to |
|
'constant'. Default is 'reflect' |
|
cval : scalar, optional |
|
Value to fill past edges of input if `mode` is 'constant'. Default |
|
is 0.0. |
|
origin : scalar, optional |
|
The `origin` parameter controls the placement of the filter. |
|
Default 0 |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
grey_opening : ndarray |
|
Result of the grayscale opening of `input` with `structure`. |
|
|
|
See Also |
|
-------- |
|
binary_opening, grey_dilation, grey_erosion, grey_closing |
|
generate_binary_structure |
|
|
|
Notes |
|
----- |
|
The action of a grayscale opening with a flat structuring element amounts |
|
to smoothen high local maxima, whereas binary opening erases small objects. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.arange(36).reshape((6,6)) |
|
>>> a[3, 3] = 50 |
|
>>> a |
|
array([[ 0, 1, 2, 3, 4, 5], |
|
[ 6, 7, 8, 9, 10, 11], |
|
[12, 13, 14, 15, 16, 17], |
|
[18, 19, 20, 50, 22, 23], |
|
[24, 25, 26, 27, 28, 29], |
|
[30, 31, 32, 33, 34, 35]]) |
|
>>> ndimage.grey_opening(a, size=(3,3)) |
|
array([[ 0, 1, 2, 3, 4, 4], |
|
[ 6, 7, 8, 9, 10, 10], |
|
[12, 13, 14, 15, 16, 16], |
|
[18, 19, 20, 22, 22, 22], |
|
[24, 25, 26, 27, 28, 28], |
|
[24, 25, 26, 27, 28, 28]]) |
|
>>> # Note that the local maximum a[3,3] has disappeared |
|
|
|
""" |
|
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, axes=axes) |
|
return grey_dilation(tmp, size, footprint, structure, output, mode, |
|
cval, origin, axes=axes) |
|
|
|
|
|
def grey_closing(input, size=None, footprint=None, structure=None, |
|
output=None, mode="reflect", cval=0.0, origin=0, *, |
|
axes=None): |
|
""" |
|
Multidimensional grayscale closing. |
|
|
|
A grayscale closing consists in the succession of a grayscale dilation, |
|
and a grayscale erosion. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Array over which the grayscale closing is to be computed. |
|
size : tuple of ints |
|
Shape of a flat and full structuring element used for the grayscale |
|
closing. Optional if `footprint` or `structure` is provided. |
|
footprint : array of ints, optional |
|
Positions of non-infinite elements of a flat structuring element |
|
used for the grayscale closing. |
|
structure : array of ints, optional |
|
Structuring element used for the grayscale closing. `structure` |
|
may be a non-flat structuring element. The `structure` array applies |
|
offsets to the pixels in a neighborhood (the offset is additive during |
|
dilation and subtractive during erosion) |
|
output : array, optional |
|
An array used for storing the output of the closing may be provided. |
|
mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional |
|
The `mode` parameter determines how the array borders are |
|
handled, where `cval` is the value when mode is equal to |
|
'constant'. Default is 'reflect' |
|
cval : scalar, optional |
|
Value to fill past edges of input if `mode` is 'constant'. Default |
|
is 0.0. |
|
origin : scalar, optional |
|
The `origin` parameter controls the placement of the filter. |
|
Default 0 |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
grey_closing : ndarray |
|
Result of the grayscale closing of `input` with `structure`. |
|
|
|
See Also |
|
-------- |
|
binary_closing, grey_dilation, grey_erosion, grey_opening, |
|
generate_binary_structure |
|
|
|
Notes |
|
----- |
|
The action of a grayscale closing with a flat structuring element amounts |
|
to smoothen deep local minima, whereas binary closing fills small holes. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.arange(36).reshape((6,6)) |
|
>>> a[3,3] = 0 |
|
>>> a |
|
array([[ 0, 1, 2, 3, 4, 5], |
|
[ 6, 7, 8, 9, 10, 11], |
|
[12, 13, 14, 15, 16, 17], |
|
[18, 19, 20, 0, 22, 23], |
|
[24, 25, 26, 27, 28, 29], |
|
[30, 31, 32, 33, 34, 35]]) |
|
>>> ndimage.grey_closing(a, size=(3,3)) |
|
array([[ 7, 7, 8, 9, 10, 11], |
|
[ 7, 7, 8, 9, 10, 11], |
|
[13, 13, 14, 15, 16, 17], |
|
[19, 19, 20, 20, 22, 23], |
|
[25, 25, 26, 27, 28, 29], |
|
[31, 31, 32, 33, 34, 35]]) |
|
>>> # Note that the local minimum a[3,3] has disappeared |
|
|
|
""" |
|
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, axes=axes) |
|
return grey_erosion(tmp, size, footprint, structure, output, mode, |
|
cval, origin, axes=axes) |
|
|
|
|
|
def morphological_gradient(input, size=None, footprint=None, structure=None, |
|
output=None, mode="reflect", cval=0.0, origin=0, *, |
|
axes=None): |
|
""" |
|
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. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Array over which to compute the morphlogical gradient. |
|
size : tuple of ints |
|
Shape of a flat and full structuring element used for the mathematical |
|
morphology operations. Optional if `footprint` or `structure` is |
|
provided. A larger `size` yields a more blurred gradient. |
|
footprint : array of ints, optional |
|
Positions of non-infinite elements of a flat structuring element |
|
used for the morphology operations. Larger footprints |
|
give a more blurred morphological gradient. |
|
structure : array of ints, optional |
|
Structuring element used for the morphology operations. `structure` may |
|
be a non-flat structuring element. The `structure` array applies |
|
offsets to the pixels in a neighborhood (the offset is additive during |
|
dilation and subtractive during erosion) |
|
output : array, optional |
|
An array used for storing the output of the morphological gradient |
|
may be provided. |
|
mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional |
|
The `mode` parameter determines how the array borders are |
|
handled, where `cval` is the value when mode is equal to |
|
'constant'. Default is 'reflect' |
|
cval : scalar, optional |
|
Value to fill past edges of input if `mode` is 'constant'. Default |
|
is 0.0. |
|
origin : scalar, optional |
|
The `origin` parameter controls the placement of the filter. |
|
Default 0 |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
morphological_gradient : ndarray |
|
Morphological gradient of `input`. |
|
|
|
See Also |
|
-------- |
|
grey_dilation, grey_erosion, gaussian_gradient_magnitude |
|
|
|
Notes |
|
----- |
|
For a flat structuring element, the morphological gradient |
|
computed at a given point corresponds to the maximal difference |
|
between elements of the input among the elements covered by the |
|
structuring element centered on the point. |
|
|
|
References |
|
---------- |
|
.. [1] https://en.wikipedia.org/wiki/Mathematical_morphology |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.zeros((7,7), dtype=int) |
|
>>> a[2:5, 2:5] = 1 |
|
>>> ndimage.morphological_gradient(a, size=(3,3)) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 1, 1, 0, 1, 1, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> # The morphological gradient is computed as the difference |
|
>>> # between a dilation and an erosion |
|
>>> ndimage.grey_dilation(a, size=(3,3)) -\\ |
|
... ndimage.grey_erosion(a, size=(3,3)) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 1, 1, 0, 1, 1, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 1, 1, 1, 1, 1, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> a = np.zeros((7,7), dtype=int) |
|
>>> a[2:5, 2:5] = 1 |
|
>>> a[4,4] = 2; a[2,3] = 3 |
|
>>> a |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 1, 3, 1, 0, 0], |
|
[0, 0, 1, 1, 1, 0, 0], |
|
[0, 0, 1, 1, 2, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
>>> ndimage.morphological_gradient(a, size=(3,3)) |
|
array([[0, 0, 0, 0, 0, 0, 0], |
|
[0, 1, 3, 3, 3, 1, 0], |
|
[0, 1, 3, 3, 3, 1, 0], |
|
[0, 1, 3, 2, 3, 2, 0], |
|
[0, 1, 1, 2, 2, 2, 0], |
|
[0, 1, 1, 2, 2, 2, 0], |
|
[0, 0, 0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
tmp = grey_dilation(input, size, footprint, structure, None, mode, |
|
cval, origin, axes=axes) |
|
if isinstance(output, np.ndarray): |
|
grey_erosion(input, size, footprint, structure, output, mode, |
|
cval, origin, axes=axes) |
|
return np.subtract(tmp, output, output) |
|
else: |
|
return (tmp - grey_erosion(input, size, footprint, structure, |
|
None, mode, cval, origin, axes=axes)) |
|
|
|
|
|
def morphological_laplace(input, size=None, footprint=None, structure=None, |
|
output=None, mode="reflect", cval=0.0, origin=0, *, |
|
axes=None): |
|
""" |
|
Multidimensional morphological laplace. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Input. |
|
size : tuple of ints |
|
Shape of a flat and full structuring element used for the mathematical |
|
morphology operations. Optional if `footprint` or `structure` is |
|
provided. |
|
footprint : array of ints, optional |
|
Positions of non-infinite elements of a flat structuring element |
|
used for the morphology operations. |
|
structure : array of ints, optional |
|
Structuring element used for the morphology operations. `structure` may |
|
be a non-flat structuring element. The `structure` array applies |
|
offsets to the pixels in a neighborhood (the offset is additive during |
|
dilation and subtractive during erosion) |
|
output : ndarray, optional |
|
An output array can optionally be provided. |
|
mode : {'reflect','constant','nearest','mirror', 'wrap'}, optional |
|
The mode parameter determines how the array borders are handled. |
|
For 'constant' mode, values beyond borders are set to be `cval`. |
|
Default is 'reflect'. |
|
cval : scalar, optional |
|
Value to fill past edges of input if mode is 'constant'. |
|
Default is 0.0 |
|
origin : origin, optional |
|
The origin parameter controls the placement of the filter. |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
morphological_laplace : ndarray |
|
Output |
|
|
|
""" |
|
tmp1 = grey_dilation(input, size, footprint, structure, None, mode, |
|
cval, origin, axes=axes) |
|
if isinstance(output, np.ndarray): |
|
grey_erosion(input, size, footprint, structure, output, mode, |
|
cval, origin, axes=axes) |
|
np.add(tmp1, output, output) |
|
np.subtract(output, input, output) |
|
return np.subtract(output, input, output) |
|
else: |
|
tmp2 = grey_erosion(input, size, footprint, structure, None, mode, |
|
cval, origin, axes=axes) |
|
np.add(tmp1, tmp2, tmp2) |
|
np.subtract(tmp2, input, tmp2) |
|
np.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, *, |
|
axes=None): |
|
""" |
|
Multidimensional white tophat filter. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Input. |
|
size : tuple of ints |
|
Shape of a flat and full structuring element used for the filter. |
|
Optional if `footprint` or `structure` is provided. |
|
footprint : array of ints, optional |
|
Positions of elements of a flat structuring element |
|
used for the white tophat filter. |
|
structure : array of ints, optional |
|
Structuring element used for the filter. `structure` may be a non-flat |
|
structuring element. The `structure` array applies offsets to the |
|
pixels in a neighborhood (the offset is additive during dilation and |
|
subtractive during erosion) |
|
output : array, optional |
|
An array used for storing the output of the filter may be provided. |
|
mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional |
|
The `mode` parameter determines how the array borders are |
|
handled, where `cval` is the value when mode is equal to |
|
'constant'. Default is 'reflect' |
|
cval : scalar, optional |
|
Value to fill past edges of input if `mode` is 'constant'. |
|
Default is 0.0. |
|
origin : scalar, optional |
|
The `origin` parameter controls the placement of the filter. |
|
Default is 0. |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
output : ndarray |
|
Result of the filter of `input` with `structure`. |
|
|
|
See Also |
|
-------- |
|
black_tophat |
|
|
|
Examples |
|
-------- |
|
Subtract gray background from a bright peak. |
|
|
|
>>> from scipy.ndimage import generate_binary_structure, white_tophat |
|
>>> import numpy as np |
|
>>> square = generate_binary_structure(rank=2, connectivity=3) |
|
>>> bright_on_gray = np.array([[2, 3, 3, 3, 2], |
|
... [3, 4, 5, 4, 3], |
|
... [3, 5, 9, 5, 3], |
|
... [3, 4, 5, 4, 3], |
|
... [2, 3, 3, 3, 2]]) |
|
>>> white_tophat(input=bright_on_gray, structure=square) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 1, 5, 1, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
input = np.asarray(input) |
|
|
|
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, axes=axes) |
|
tmp = grey_dilation(tmp, size, footprint, structure, output, mode, |
|
cval, origin, axes=axes) |
|
if tmp is None: |
|
tmp = output |
|
|
|
if input.dtype == np.bool_ and tmp.dtype == np.bool_: |
|
np.bitwise_xor(input, tmp, out=tmp) |
|
else: |
|
np.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, *, axes=None): |
|
""" |
|
Multidimensional black tophat filter. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Input. |
|
size : tuple of ints, optional |
|
Shape of a flat and full structuring element used for the filter. |
|
Optional if `footprint` or `structure` is provided. |
|
footprint : array of ints, optional |
|
Positions of non-infinite elements of a flat structuring element |
|
used for the black tophat filter. |
|
structure : array of ints, optional |
|
Structuring element used for the filter. `structure` may be a non-flat |
|
structuring element. The `structure` array applies offsets to the |
|
pixels in a neighborhood (the offset is additive during dilation and |
|
subtractive during erosion) |
|
output : array, optional |
|
An array used for storing the output of the filter may be provided. |
|
mode : {'reflect', 'constant', 'nearest', 'mirror', 'wrap'}, optional |
|
The `mode` parameter determines how the array borders are |
|
handled, where `cval` is the value when mode is equal to |
|
'constant'. Default is 'reflect' |
|
cval : scalar, optional |
|
Value to fill past edges of input if `mode` is 'constant'. Default |
|
is 0.0. |
|
origin : scalar, optional |
|
The `origin` parameter controls the placement of the filter. |
|
Default 0 |
|
axes : tuple of int or None |
|
The axes over which to apply the filter. If None, `input` is filtered |
|
along all axes. If an `origin` tuple is provided, its length must match |
|
the number of axes. |
|
|
|
Returns |
|
------- |
|
black_tophat : ndarray |
|
Result of the filter of `input` with `structure`. |
|
|
|
See Also |
|
-------- |
|
white_tophat, grey_opening, grey_closing |
|
|
|
Examples |
|
-------- |
|
Change dark peak to bright peak and subtract background. |
|
|
|
>>> from scipy.ndimage import generate_binary_structure, black_tophat |
|
>>> import numpy as np |
|
>>> square = generate_binary_structure(rank=2, connectivity=3) |
|
>>> dark_on_gray = np.array([[7, 6, 6, 6, 7], |
|
... [6, 5, 4, 5, 6], |
|
... [6, 4, 0, 4, 6], |
|
... [6, 5, 4, 5, 6], |
|
... [7, 6, 6, 6, 7]]) |
|
>>> black_tophat(input=dark_on_gray, structure=square) |
|
array([[0, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 1, 5, 1, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 0]]) |
|
|
|
""" |
|
input = np.asarray(input) |
|
|
|
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, axes=axes) |
|
tmp = grey_erosion(tmp, size, footprint, structure, output, mode, |
|
cval, origin, axes=axes) |
|
if tmp is None: |
|
tmp = output |
|
|
|
if input.dtype == np.bool_ and tmp.dtype == np.bool_: |
|
np.bitwise_xor(tmp, input, out=tmp) |
|
else: |
|
np.subtract(tmp, input, out=tmp) |
|
return tmp |
|
|
|
|
|
def distance_transform_bf(input, metric="euclidean", sampling=None, |
|
return_distances=True, return_indices=False, |
|
distances=None, indices=None): |
|
""" |
|
Distance transform function by a brute force algorithm. |
|
|
|
This function calculates the distance transform of the `input`, by |
|
replacing each foreground (non-zero) element, with its |
|
shortest distance to the background (any zero-valued element). |
|
|
|
In addition to the distance transform, the feature transform can |
|
be calculated. In this case the index of the closest background |
|
element to each foreground element is returned in a separate array. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Input |
|
metric : {'euclidean', 'taxicab', 'chessboard'}, optional |
|
'cityblock' and 'manhattan' are also valid, and map to 'taxicab'. |
|
The default is 'euclidean'. |
|
sampling : float, or sequence of float, optional |
|
This parameter is only used when `metric` is 'euclidean'. |
|
Spacing of elements along each dimension. If a sequence, must be of |
|
length equal to the input rank; if a single number, this is used for |
|
all axes. If not specified, a grid spacing of unity is implied. |
|
return_distances : bool, optional |
|
Whether to calculate the distance transform. |
|
Default is True. |
|
return_indices : bool, optional |
|
Whether to calculate the feature transform. |
|
Default is False. |
|
distances : ndarray, optional |
|
An output array to store the calculated distance transform, instead of |
|
returning it. |
|
`return_distances` must be True. |
|
It must be the same shape as `input`, and of type float64 if `metric` |
|
is 'euclidean', uint32 otherwise. |
|
indices : int32 ndarray, optional |
|
An output array to store the calculated feature transform, instead of |
|
returning it. |
|
`return_indicies` must be True. |
|
Its shape must be ``(input.ndim,) + input.shape``. |
|
|
|
Returns |
|
------- |
|
distances : ndarray, optional |
|
The calculated distance transform. Returned only when |
|
`return_distances` is True and `distances` is not supplied. |
|
It will have the same shape as the input array. |
|
indices : int32 ndarray, optional |
|
The calculated feature transform. It has an input-shaped array for each |
|
dimension of the input. See distance_transform_edt documentation for an |
|
example. |
|
Returned only when `return_indices` is True and `indices` is not |
|
supplied. |
|
|
|
See Also |
|
-------- |
|
distance_transform_cdt : Faster distance transform for taxicab and |
|
chessboard metrics |
|
distance_transform_edt : Faster distance transform for euclidean metric |
|
|
|
Notes |
|
----- |
|
This function employs a slow brute force algorithm. See also the |
|
function `distance_transform_cdt` for more efficient taxicab [1]_ and |
|
chessboard algorithms [2]_. |
|
|
|
References |
|
---------- |
|
.. [1] Taxicab distance. Wikipedia, 2023. |
|
https://en.wikipedia.org/wiki/Taxicab_geometry |
|
.. [2] Chessboard distance. Wikipedia, 2023. |
|
https://en.wikipedia.org/wiki/Chebyshev_distance |
|
|
|
Examples |
|
-------- |
|
Import the necessary modules. |
|
|
|
>>> import numpy as np |
|
>>> from scipy.ndimage import distance_transform_bf |
|
>>> import matplotlib.pyplot as plt |
|
>>> from mpl_toolkits.axes_grid1 import ImageGrid |
|
|
|
First, we create a toy binary image. |
|
|
|
>>> def add_circle(center_x, center_y, radius, image, fillvalue=1): |
|
... # fill circular area with 1 |
|
... xx, yy = np.mgrid[:image.shape[0], :image.shape[1]] |
|
... circle = (xx - center_x) ** 2 + (yy - center_y) ** 2 |
|
... circle_shape = np.sqrt(circle) < radius |
|
... image[circle_shape] = fillvalue |
|
... return image |
|
>>> image = np.zeros((100, 100), dtype=np.uint8) |
|
>>> image[35:65, 20:80] = 1 |
|
>>> image = add_circle(28, 65, 10, image) |
|
>>> image = add_circle(37, 30, 10, image) |
|
>>> image = add_circle(70, 45, 20, image) |
|
>>> image = add_circle(45, 80, 10, image) |
|
|
|
Next, we set up the figure. |
|
|
|
>>> fig = plt.figure(figsize=(8, 8)) # set up the figure structure |
|
>>> grid = ImageGrid(fig, 111, nrows_ncols=(2, 2), axes_pad=(0.4, 0.3), |
|
... label_mode="1", share_all=True, |
|
... cbar_location="right", cbar_mode="each", |
|
... cbar_size="7%", cbar_pad="2%") |
|
>>> for ax in grid: |
|
... ax.axis('off') # remove axes from images |
|
|
|
The top left image is the original binary image. |
|
|
|
>>> binary_image = grid[0].imshow(image, cmap='gray') |
|
>>> cbar_binary_image = grid.cbar_axes[0].colorbar(binary_image) |
|
>>> cbar_binary_image.set_ticks([0, 1]) |
|
>>> grid[0].set_title("Binary image: foreground in white") |
|
|
|
The distance transform calculates the distance between foreground pixels |
|
and the image background according to a distance metric. Available metrics |
|
in `distance_transform_bf` are: ``euclidean`` (default), ``taxicab`` |
|
and ``chessboard``. The top right image contains the distance transform |
|
based on the ``euclidean`` metric. |
|
|
|
>>> distance_transform_euclidean = distance_transform_bf(image) |
|
>>> euclidean_transform = grid[1].imshow(distance_transform_euclidean, |
|
... cmap='gray') |
|
>>> cbar_euclidean = grid.cbar_axes[1].colorbar(euclidean_transform) |
|
>>> colorbar_ticks = [0, 10, 20] |
|
>>> cbar_euclidean.set_ticks(colorbar_ticks) |
|
>>> grid[1].set_title("Euclidean distance") |
|
|
|
The lower left image contains the distance transform using the ``taxicab`` |
|
metric. |
|
|
|
>>> distance_transform_taxicab = distance_transform_bf(image, |
|
... metric='taxicab') |
|
>>> taxicab_transformation = grid[2].imshow(distance_transform_taxicab, |
|
... cmap='gray') |
|
>>> cbar_taxicab = grid.cbar_axes[2].colorbar(taxicab_transformation) |
|
>>> cbar_taxicab.set_ticks(colorbar_ticks) |
|
>>> grid[2].set_title("Taxicab distance") |
|
|
|
Finally, the lower right image contains the distance transform using the |
|
``chessboard`` metric. |
|
|
|
>>> distance_transform_cb = distance_transform_bf(image, |
|
... metric='chessboard') |
|
>>> chessboard_transformation = grid[3].imshow(distance_transform_cb, |
|
... cmap='gray') |
|
>>> cbar_taxicab = grid.cbar_axes[3].colorbar(chessboard_transformation) |
|
>>> cbar_taxicab.set_ticks(colorbar_ticks) |
|
>>> grid[3].set_title("Chessboard distance") |
|
>>> plt.show() |
|
|
|
""" |
|
ft_inplace = isinstance(indices, np.ndarray) |
|
dt_inplace = isinstance(distances, np.ndarray) |
|
_distance_tranform_arg_check( |
|
dt_inplace, ft_inplace, return_distances, return_indices |
|
) |
|
|
|
tmp1 = np.asarray(input) != 0 |
|
struct = generate_binary_structure(tmp1.ndim, tmp1.ndim) |
|
tmp2 = binary_dilation(tmp1, struct) |
|
tmp2 = np.logical_xor(tmp1, tmp2) |
|
tmp1 = tmp1.astype(np.int8) - tmp2.astype(np.int8) |
|
metric = metric.lower() |
|
if metric == 'euclidean': |
|
metric = 1 |
|
elif metric in ['taxicab', 'cityblock', 'manhattan']: |
|
metric = 2 |
|
elif metric == 'chessboard': |
|
metric = 3 |
|
else: |
|
raise RuntimeError('distance metric not supported') |
|
if sampling is not None: |
|
sampling = _ni_support._normalize_sequence(sampling, tmp1.ndim) |
|
sampling = np.asarray(sampling, dtype=np.float64) |
|
if not sampling.flags.contiguous: |
|
sampling = sampling.copy() |
|
if return_indices: |
|
ft = np.zeros(tmp1.shape, dtype=np.int32) |
|
else: |
|
ft = None |
|
if return_distances: |
|
if distances is None: |
|
if metric == 1: |
|
dt = np.zeros(tmp1.shape, dtype=np.float64) |
|
else: |
|
dt = np.zeros(tmp1.shape, dtype=np.uint32) |
|
else: |
|
if distances.shape != tmp1.shape: |
|
raise RuntimeError('distances array has wrong shape') |
|
if metric == 1: |
|
if distances.dtype.type != np.float64: |
|
raise RuntimeError('distances array must be float64') |
|
else: |
|
if distances.dtype.type != np.uint32: |
|
raise RuntimeError('distances array must be uint32') |
|
dt = distances |
|
else: |
|
dt = None |
|
|
|
_nd_image.distance_transform_bf(tmp1, metric, sampling, dt, ft) |
|
if return_indices: |
|
if isinstance(indices, np.ndarray): |
|
if indices.dtype.type != np.int32: |
|
raise RuntimeError('indices array must be int32') |
|
if indices.shape != (tmp1.ndim,) + tmp1.shape: |
|
raise RuntimeError('indices array has wrong shape') |
|
tmp2 = indices |
|
else: |
|
tmp2 = np.indices(tmp1.shape, dtype=np.int32) |
|
ft = np.ravel(ft) |
|
for ii in range(tmp2.shape[0]): |
|
rtmp = np.ravel(tmp2[ii, ...])[ft] |
|
rtmp.shape = tmp1.shape |
|
tmp2[ii, ...] = rtmp |
|
ft = tmp2 |
|
|
|
|
|
result = [] |
|
if return_distances and not dt_inplace: |
|
result.append(dt) |
|
if return_indices and not ft_inplace: |
|
result.append(ft) |
|
|
|
if len(result) == 2: |
|
return tuple(result) |
|
elif len(result) == 1: |
|
return result[0] |
|
else: |
|
return None |
|
|
|
|
|
def distance_transform_cdt(input, metric='chessboard', return_distances=True, |
|
return_indices=False, distances=None, indices=None): |
|
""" |
|
Distance transform for chamfer type of transforms. |
|
|
|
This function calculates the distance transform of the `input`, by |
|
replacing each foreground (non-zero) element, with its |
|
shortest distance to the background (any zero-valued element). |
|
|
|
In addition to the distance transform, the feature transform can |
|
be calculated. In this case the index of the closest background |
|
element to each foreground element is returned in a separate array. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Input. Values of 0 are treated as background. |
|
metric : {'chessboard', 'taxicab'} or array_like, optional |
|
The `metric` determines the type of chamfering that is done. If the |
|
`metric` is equal to 'taxicab' a structure is generated using |
|
`generate_binary_structure` with a squared distance equal to 1. If |
|
the `metric` is equal to 'chessboard', a `metric` is generated |
|
using `generate_binary_structure` with a squared distance equal to |
|
the dimensionality of the array. These choices correspond to the |
|
common interpretations of the 'taxicab' and the 'chessboard' |
|
distance metrics in two dimensions. |
|
A custom metric may be provided, in the form of a matrix where |
|
each dimension has a length of three. |
|
'cityblock' and 'manhattan' are also valid, and map to 'taxicab'. |
|
The default is 'chessboard'. |
|
return_distances : bool, optional |
|
Whether to calculate the distance transform. |
|
Default is True. |
|
return_indices : bool, optional |
|
Whether to calculate the feature transform. |
|
Default is False. |
|
distances : int32 ndarray, optional |
|
An output array to store the calculated distance transform, instead of |
|
returning it. |
|
`return_distances` must be True. |
|
It must be the same shape as `input`. |
|
indices : int32 ndarray, optional |
|
An output array to store the calculated feature transform, instead of |
|
returning it. |
|
`return_indicies` must be True. |
|
Its shape must be ``(input.ndim,) + input.shape``. |
|
|
|
Returns |
|
------- |
|
distances : int32 ndarray, optional |
|
The calculated distance transform. Returned only when |
|
`return_distances` is True, and `distances` is not supplied. |
|
It will have the same shape as the input array. |
|
indices : int32 ndarray, optional |
|
The calculated feature transform. It has an input-shaped array for each |
|
dimension of the input. See distance_transform_edt documentation for an |
|
example. |
|
Returned only when `return_indices` is True, and `indices` is not |
|
supplied. |
|
|
|
See Also |
|
-------- |
|
distance_transform_edt : Fast distance transform for euclidean metric |
|
distance_transform_bf : Distance transform for different metrics using |
|
a slower brute force algorithm |
|
|
|
Examples |
|
-------- |
|
Import the necessary modules. |
|
|
|
>>> import numpy as np |
|
>>> from scipy.ndimage import distance_transform_cdt |
|
>>> import matplotlib.pyplot as plt |
|
>>> from mpl_toolkits.axes_grid1 import ImageGrid |
|
|
|
First, we create a toy binary image. |
|
|
|
>>> def add_circle(center_x, center_y, radius, image, fillvalue=1): |
|
... # fill circular area with 1 |
|
... xx, yy = np.mgrid[:image.shape[0], :image.shape[1]] |
|
... circle = (xx - center_x) ** 2 + (yy - center_y) ** 2 |
|
... circle_shape = np.sqrt(circle) < radius |
|
... image[circle_shape] = fillvalue |
|
... return image |
|
>>> image = np.zeros((100, 100), dtype=np.uint8) |
|
>>> image[35:65, 20:80] = 1 |
|
>>> image = add_circle(28, 65, 10, image) |
|
>>> image = add_circle(37, 30, 10, image) |
|
>>> image = add_circle(70, 45, 20, image) |
|
>>> image = add_circle(45, 80, 10, image) |
|
|
|
Next, we set up the figure. |
|
|
|
>>> fig = plt.figure(figsize=(5, 15)) |
|
>>> grid = ImageGrid(fig, 111, nrows_ncols=(3, 1), axes_pad=(0.5, 0.3), |
|
... label_mode="1", share_all=True, |
|
... cbar_location="right", cbar_mode="each", |
|
... cbar_size="7%", cbar_pad="2%") |
|
>>> for ax in grid: |
|
... ax.axis('off') |
|
>>> top, middle, bottom = grid |
|
>>> colorbar_ticks = [0, 10, 20] |
|
|
|
The top image contains the original binary image. |
|
|
|
>>> binary_image = top.imshow(image, cmap='gray') |
|
>>> cbar_binary_image = top.cax.colorbar(binary_image) |
|
>>> cbar_binary_image.set_ticks([0, 1]) |
|
>>> top.set_title("Binary image: foreground in white") |
|
|
|
The middle image contains the distance transform using the ``taxicab`` |
|
metric. |
|
|
|
>>> distance_taxicab = distance_transform_cdt(image, metric="taxicab") |
|
>>> taxicab_transform = middle.imshow(distance_taxicab, cmap='gray') |
|
>>> cbar_taxicab = middle.cax.colorbar(taxicab_transform) |
|
>>> cbar_taxicab.set_ticks(colorbar_ticks) |
|
>>> middle.set_title("Taxicab metric") |
|
|
|
The bottom image contains the distance transform using the ``chessboard`` |
|
metric. |
|
|
|
>>> distance_chessboard = distance_transform_cdt(image, |
|
... metric="chessboard") |
|
>>> chessboard_transform = bottom.imshow(distance_chessboard, cmap='gray') |
|
>>> cbar_chessboard = bottom.cax.colorbar(chessboard_transform) |
|
>>> cbar_chessboard.set_ticks(colorbar_ticks) |
|
>>> bottom.set_title("Chessboard metric") |
|
>>> plt.tight_layout() |
|
>>> plt.show() |
|
|
|
""" |
|
ft_inplace = isinstance(indices, np.ndarray) |
|
dt_inplace = isinstance(distances, np.ndarray) |
|
_distance_tranform_arg_check( |
|
dt_inplace, ft_inplace, return_distances, return_indices |
|
) |
|
input = np.asarray(input) |
|
if isinstance(metric, str): |
|
if metric in ['taxicab', 'cityblock', 'manhattan']: |
|
rank = input.ndim |
|
metric = generate_binary_structure(rank, 1) |
|
elif metric == 'chessboard': |
|
rank = input.ndim |
|
metric = generate_binary_structure(rank, rank) |
|
else: |
|
raise ValueError('invalid metric provided') |
|
else: |
|
try: |
|
metric = np.asarray(metric) |
|
except Exception as e: |
|
raise ValueError('invalid metric provided') from e |
|
for s in metric.shape: |
|
if s != 3: |
|
raise ValueError('metric sizes must be equal to 3') |
|
|
|
if not metric.flags.contiguous: |
|
metric = metric.copy() |
|
if dt_inplace: |
|
if distances.dtype.type != np.int32: |
|
raise ValueError('distances must be of int32 type') |
|
if distances.shape != input.shape: |
|
raise ValueError('distances has wrong shape') |
|
dt = distances |
|
dt[...] = np.where(input, -1, 0).astype(np.int32) |
|
else: |
|
dt = np.where(input, -1, 0).astype(np.int32) |
|
|
|
rank = dt.ndim |
|
if return_indices: |
|
ft = np.arange(dt.size, dtype=np.int32) |
|
ft.shape = dt.shape |
|
else: |
|
ft = None |
|
|
|
_nd_image.distance_transform_op(metric, dt, ft) |
|
dt = dt[tuple([slice(None, None, -1)] * rank)] |
|
if return_indices: |
|
ft = ft[tuple([slice(None, None, -1)] * rank)] |
|
_nd_image.distance_transform_op(metric, dt, ft) |
|
dt = dt[tuple([slice(None, None, -1)] * rank)] |
|
if return_indices: |
|
ft = ft[tuple([slice(None, None, -1)] * rank)] |
|
ft = np.ravel(ft) |
|
if ft_inplace: |
|
if indices.dtype.type != np.int32: |
|
raise ValueError('indices array must be int32') |
|
if indices.shape != (dt.ndim,) + dt.shape: |
|
raise ValueError('indices array has wrong shape') |
|
tmp = indices |
|
else: |
|
tmp = np.indices(dt.shape, dtype=np.int32) |
|
for ii in range(tmp.shape[0]): |
|
rtmp = np.ravel(tmp[ii, ...])[ft] |
|
rtmp.shape = dt.shape |
|
tmp[ii, ...] = rtmp |
|
ft = tmp |
|
|
|
|
|
result = [] |
|
if return_distances and not dt_inplace: |
|
result.append(dt) |
|
if return_indices and not ft_inplace: |
|
result.append(ft) |
|
|
|
if len(result) == 2: |
|
return tuple(result) |
|
elif len(result) == 1: |
|
return result[0] |
|
else: |
|
return None |
|
|
|
|
|
def distance_transform_edt(input, sampling=None, return_distances=True, |
|
return_indices=False, distances=None, indices=None): |
|
""" |
|
Exact Euclidean distance transform. |
|
|
|
This function calculates the distance transform of the `input`, by |
|
replacing each foreground (non-zero) element, with its |
|
shortest distance to the background (any zero-valued element). |
|
|
|
In addition to the distance transform, the feature transform can |
|
be calculated. In this case the index of the closest background |
|
element to each foreground element is returned in a separate array. |
|
|
|
Parameters |
|
---------- |
|
input : array_like |
|
Input data to transform. Can be any type but will be converted |
|
into binary: 1 wherever input equates to True, 0 elsewhere. |
|
sampling : float, or sequence of float, optional |
|
Spacing of elements along each dimension. If a sequence, must be of |
|
length equal to the input rank; if a single number, this is used for |
|
all axes. If not specified, a grid spacing of unity is implied. |
|
return_distances : bool, optional |
|
Whether to calculate the distance transform. |
|
Default is True. |
|
return_indices : bool, optional |
|
Whether to calculate the feature transform. |
|
Default is False. |
|
distances : float64 ndarray, optional |
|
An output array to store the calculated distance transform, instead of |
|
returning it. |
|
`return_distances` must be True. |
|
It must be the same shape as `input`. |
|
indices : int32 ndarray, optional |
|
An output array to store the calculated feature transform, instead of |
|
returning it. |
|
`return_indicies` must be True. |
|
Its shape must be ``(input.ndim,) + input.shape``. |
|
|
|
Returns |
|
------- |
|
distances : float64 ndarray, optional |
|
The calculated distance transform. Returned only when |
|
`return_distances` is True and `distances` is not supplied. |
|
It will have the same shape as the input array. |
|
indices : int32 ndarray, optional |
|
The calculated feature transform. It has an input-shaped array for each |
|
dimension of the input. See example below. |
|
Returned only when `return_indices` is True and `indices` is not |
|
supplied. |
|
|
|
Notes |
|
----- |
|
The Euclidean distance transform gives values of the Euclidean |
|
distance:: |
|
|
|
n |
|
y_i = sqrt(sum (x[i]-b[i])**2) |
|
i |
|
|
|
where b[i] is the background point (value 0) with the smallest |
|
Euclidean distance to input points x[i], and n is the |
|
number of dimensions. |
|
|
|
Examples |
|
-------- |
|
>>> from scipy import ndimage |
|
>>> import numpy as np |
|
>>> a = np.array(([0,1,1,1,1], |
|
... [0,0,1,1,1], |
|
... [0,1,1,1,1], |
|
... [0,1,1,1,0], |
|
... [0,1,1,0,0])) |
|
>>> ndimage.distance_transform_edt(a) |
|
array([[ 0. , 1. , 1.4142, 2.2361, 3. ], |
|
[ 0. , 0. , 1. , 2. , 2. ], |
|
[ 0. , 1. , 1.4142, 1.4142, 1. ], |
|
[ 0. , 1. , 1.4142, 1. , 0. ], |
|
[ 0. , 1. , 1. , 0. , 0. ]]) |
|
|
|
With a sampling of 2 units along x, 1 along y: |
|
|
|
>>> ndimage.distance_transform_edt(a, sampling=[2,1]) |
|
array([[ 0. , 1. , 2. , 2.8284, 3.6056], |
|
[ 0. , 0. , 1. , 2. , 3. ], |
|
[ 0. , 1. , 2. , 2.2361, 2. ], |
|
[ 0. , 1. , 2. , 1. , 0. ], |
|
[ 0. , 1. , 1. , 0. , 0. ]]) |
|
|
|
Asking for indices as well: |
|
|
|
>>> edt, inds = ndimage.distance_transform_edt(a, return_indices=True) |
|
>>> inds |
|
array([[[0, 0, 1, 1, 3], |
|
[1, 1, 1, 1, 3], |
|
[2, 2, 1, 3, 3], |
|
[3, 3, 4, 4, 3], |
|
[4, 4, 4, 4, 4]], |
|
[[0, 0, 1, 1, 4], |
|
[0, 1, 1, 1, 4], |
|
[0, 0, 1, 4, 4], |
|
[0, 0, 3, 3, 4], |
|
[0, 0, 3, 3, 4]]], dtype=int32) |
|
|
|
With arrays provided for inplace outputs: |
|
|
|
>>> indices = np.zeros(((np.ndim(a),) + a.shape), dtype=np.int32) |
|
>>> ndimage.distance_transform_edt(a, return_indices=True, indices=indices) |
|
array([[ 0. , 1. , 1.4142, 2.2361, 3. ], |
|
[ 0. , 0. , 1. , 2. , 2. ], |
|
[ 0. , 1. , 1.4142, 1.4142, 1. ], |
|
[ 0. , 1. , 1.4142, 1. , 0. ], |
|
[ 0. , 1. , 1. , 0. , 0. ]]) |
|
>>> indices |
|
array([[[0, 0, 1, 1, 3], |
|
[1, 1, 1, 1, 3], |
|
[2, 2, 1, 3, 3], |
|
[3, 3, 4, 4, 3], |
|
[4, 4, 4, 4, 4]], |
|
[[0, 0, 1, 1, 4], |
|
[0, 1, 1, 1, 4], |
|
[0, 0, 1, 4, 4], |
|
[0, 0, 3, 3, 4], |
|
[0, 0, 3, 3, 4]]], dtype=int32) |
|
|
|
""" |
|
ft_inplace = isinstance(indices, np.ndarray) |
|
dt_inplace = isinstance(distances, np.ndarray) |
|
_distance_tranform_arg_check( |
|
dt_inplace, ft_inplace, return_distances, return_indices |
|
) |
|
|
|
|
|
input = np.atleast_1d(np.where(input, 1, 0).astype(np.int8)) |
|
if sampling is not None: |
|
sampling = _ni_support._normalize_sequence(sampling, input.ndim) |
|
sampling = np.asarray(sampling, dtype=np.float64) |
|
if not sampling.flags.contiguous: |
|
sampling = sampling.copy() |
|
|
|
if ft_inplace: |
|
ft = indices |
|
if ft.shape != (input.ndim,) + input.shape: |
|
raise RuntimeError('indices array has wrong shape') |
|
if ft.dtype.type != np.int32: |
|
raise RuntimeError('indices array must be int32') |
|
else: |
|
ft = np.zeros((input.ndim,) + input.shape, dtype=np.int32) |
|
|
|
_nd_image.euclidean_feature_transform(input, sampling, ft) |
|
|
|
if return_distances: |
|
dt = ft - np.indices(input.shape, dtype=ft.dtype) |
|
dt = dt.astype(np.float64) |
|
if sampling is not None: |
|
for ii in range(len(sampling)): |
|
dt[ii, ...] *= sampling[ii] |
|
np.multiply(dt, dt, dt) |
|
if dt_inplace: |
|
dt = np.add.reduce(dt, axis=0) |
|
if distances.shape != dt.shape: |
|
raise RuntimeError('distances array has wrong shape') |
|
if distances.dtype.type != np.float64: |
|
raise RuntimeError('distances array must be float64') |
|
np.sqrt(dt, distances) |
|
else: |
|
dt = np.add.reduce(dt, axis=0) |
|
dt = np.sqrt(dt) |
|
|
|
|
|
result = [] |
|
if return_distances and not dt_inplace: |
|
result.append(dt) |
|
if return_indices and not ft_inplace: |
|
result.append(ft) |
|
|
|
if len(result) == 2: |
|
return tuple(result) |
|
elif len(result) == 1: |
|
return result[0] |
|
else: |
|
return None |
|
|
|
|
|
def _distance_tranform_arg_check(distances_out, indices_out, |
|
return_distances, return_indices): |
|
"""Raise a RuntimeError if the arguments are invalid""" |
|
error_msgs = [] |
|
if (not return_distances) and (not return_indices): |
|
error_msgs.append( |
|
'at least one of return_distances/return_indices must be True') |
|
if distances_out and not return_distances: |
|
error_msgs.append( |
|
'return_distances must be True if distances is supplied' |
|
) |
|
if indices_out and not return_indices: |
|
error_msgs.append('return_indices must be True if indices is supplied') |
|
if error_msgs: |
|
raise RuntimeError(', '.join(error_msgs)) |
|
|