comment here
This commit is contained in:
68
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__init__.py
vendored
Normal file
68
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__init__.py
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
from .binary import (binary_closing, binary_dilation, binary_erosion,
|
||||
binary_opening)
|
||||
from .gray import (black_tophat, closing, dilation, erosion, opening,
|
||||
white_tophat)
|
||||
from .isotropic import (isotropic_erosion, isotropic_dilation,
|
||||
isotropic_opening, isotropic_closing)
|
||||
from .footprints import (ball, cube, diamond, disk, ellipse,
|
||||
footprint_from_sequence, octagon, octahedron,
|
||||
rectangle, square, star)
|
||||
from ..measure._label import label
|
||||
from ._skeletonize import medial_axis, skeletonize, skeletonize_3d, thin
|
||||
from .convex_hull import convex_hull_image, convex_hull_object
|
||||
from .grayreconstruct import reconstruction
|
||||
from .misc import remove_small_holes, remove_small_objects
|
||||
from .extrema import h_maxima, h_minima, local_minima, local_maxima
|
||||
from ._flood_fill import flood, flood_fill
|
||||
from .max_tree import (area_opening, area_closing, diameter_closing,
|
||||
diameter_opening, max_tree,
|
||||
max_tree_local_maxima)
|
||||
|
||||
__all__ = ['area_closing',
|
||||
'area_opening',
|
||||
'ball',
|
||||
'binary_closing',
|
||||
'binary_dilation',
|
||||
'binary_erosion',
|
||||
'binary_opening',
|
||||
'black_tophat',
|
||||
'closing',
|
||||
'convex_hull_image',
|
||||
'convex_hull_object',
|
||||
'cube',
|
||||
'diameter_closing',
|
||||
'diameter_opening',
|
||||
'diamond',
|
||||
'dilation',
|
||||
'disk',
|
||||
'ellipse',
|
||||
'erosion',
|
||||
'flood',
|
||||
'flood_fill',
|
||||
'footprint_from_sequence',
|
||||
'h_maxima',
|
||||
'h_minima',
|
||||
'isotropic_closing',
|
||||
'isotropic_dilation',
|
||||
'isotropic_erosion',
|
||||
'isotropic_opening',
|
||||
'label',
|
||||
'local_maxima',
|
||||
'local_minima',
|
||||
'max_tree',
|
||||
'max_tree_local_maxima',
|
||||
'medial_axis',
|
||||
'octagon',
|
||||
'octahedron',
|
||||
'opening',
|
||||
'reconstruction',
|
||||
'rectangle',
|
||||
'remove_small_holes',
|
||||
'remove_small_objects',
|
||||
'skeletonize',
|
||||
'skeletonize_3d',
|
||||
'square',
|
||||
'star',
|
||||
'thin',
|
||||
'white_tophat'
|
||||
]
|
||||
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/_util.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/_util.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/binary.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/binary.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/extrema.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/extrema.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/footprints.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/footprints.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/gray.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/gray.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/isotropic.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/isotropic.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/max_tree.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/max_tree.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/misc.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/__pycache__/misc.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_convex_hull.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_convex_hull.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_extrema_cy.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_extrema_cy.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
Binary file not shown.
290
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_flood_fill.py
vendored
Normal file
290
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_flood_fill.py
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
"""flood_fill.py - in place flood fill algorithm
|
||||
|
||||
This module provides a function to fill all equal (or within tolerance) values
|
||||
connected to a given seed point with a different value.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..util import crop
|
||||
from ._flood_fill_cy import _flood_fill_equal, _flood_fill_tolerance
|
||||
from ._util import (_offsets_to_raveled_neighbors, _resolve_neighborhood,
|
||||
_set_border_values,)
|
||||
|
||||
|
||||
def flood_fill(image, seed_point, new_value, *, footprint=None,
|
||||
connectivity=None, tolerance=None, in_place=False):
|
||||
"""Perform flood filling on an image.
|
||||
|
||||
Starting at a specific `seed_point`, connected points equal or within
|
||||
`tolerance` of the seed value are found, then set to `new_value`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
An n-dimensional array.
|
||||
seed_point : tuple or int
|
||||
The point in `image` used as the starting point for the flood fill. If
|
||||
the image is 1D, this point may be given as an integer.
|
||||
new_value : `image` type
|
||||
New value to set the entire fill. This must be chosen in agreement
|
||||
with the dtype of `image`.
|
||||
footprint : ndarray, optional
|
||||
The footprint (structuring element) used to determine the neighborhood
|
||||
of each evaluated pixel. It must contain only 1's and 0's, have the
|
||||
same number of dimensions as `image`. If not given, all adjacent pixels
|
||||
are considered as part of the neighborhood (fully connected).
|
||||
connectivity : int, optional
|
||||
A number used to determine the neighborhood of each evaluated pixel.
|
||||
Adjacent pixels whose squared distance from the center is less than or
|
||||
equal to `connectivity` are considered neighbors. Ignored if
|
||||
`footprint` is not None.
|
||||
tolerance : float or int, optional
|
||||
If None (default), adjacent values must be strictly equal to the
|
||||
value of `image` at `seed_point` to be filled. This is fastest.
|
||||
If a tolerance is provided, adjacent points with values within plus or
|
||||
minus tolerance from the seed point are filled (inclusive).
|
||||
in_place : bool, optional
|
||||
If True, flood filling is applied to `image` in place. If False, the
|
||||
flood filled result is returned without modifying the input `image`
|
||||
(default).
|
||||
|
||||
Returns
|
||||
-------
|
||||
filled : ndarray
|
||||
An array with the same shape as `image` is returned, with values in
|
||||
areas connected to and equal (or within tolerance of) the seed point
|
||||
replaced with `new_value`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The conceptual analogy of this operation is the 'paint bucket' tool in many
|
||||
raster graphics programs.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.morphology import flood_fill
|
||||
>>> image = np.zeros((4, 7), dtype=int)
|
||||
>>> image[1:3, 1:3] = 1
|
||||
>>> image[3, 0] = 1
|
||||
>>> image[1:3, 4:6] = 2
|
||||
>>> image[3, 6] = 3
|
||||
>>> image
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3]])
|
||||
|
||||
Fill connected ones with 5, with full connectivity (diagonals included):
|
||||
|
||||
>>> flood_fill(image, (1, 1), 5)
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 5, 5, 0, 2, 2, 0],
|
||||
[0, 5, 5, 0, 2, 2, 0],
|
||||
[5, 0, 0, 0, 0, 0, 3]])
|
||||
|
||||
Fill connected ones with 5, excluding diagonal points (connectivity 1):
|
||||
|
||||
>>> flood_fill(image, (1, 1), 5, connectivity=1)
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 5, 5, 0, 2, 2, 0],
|
||||
[0, 5, 5, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3]])
|
||||
|
||||
Fill with a tolerance:
|
||||
|
||||
>>> flood_fill(image, (0, 0), 5, tolerance=1)
|
||||
array([[5, 5, 5, 5, 5, 5, 5],
|
||||
[5, 5, 5, 5, 2, 2, 5],
|
||||
[5, 5, 5, 5, 2, 2, 5],
|
||||
[5, 5, 5, 5, 5, 5, 3]])
|
||||
"""
|
||||
mask = flood(image, seed_point, footprint=footprint,
|
||||
connectivity=connectivity, tolerance=tolerance)
|
||||
|
||||
if not in_place:
|
||||
image = image.copy()
|
||||
|
||||
image[mask] = new_value
|
||||
return image
|
||||
|
||||
|
||||
def flood(image, seed_point, *, footprint=None, connectivity=None,
|
||||
tolerance=None):
|
||||
"""Mask corresponding to a flood fill.
|
||||
|
||||
Starting at a specific `seed_point`, connected points equal or within
|
||||
`tolerance` of the seed value are found.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
An n-dimensional array.
|
||||
seed_point : tuple or int
|
||||
The point in `image` used as the starting point for the flood fill. If
|
||||
the image is 1D, this point may be given as an integer.
|
||||
footprint : ndarray, optional
|
||||
The footprint (structuring element) used to determine the neighborhood
|
||||
of each evaluated pixel. It must contain only 1's and 0's, have the
|
||||
same number of dimensions as `image`. If not given, all adjacent pixels
|
||||
are considered as part of the neighborhood (fully connected).
|
||||
connectivity : int, optional
|
||||
A number used to determine the neighborhood of each evaluated pixel.
|
||||
Adjacent pixels whose squared distance from the center is less than or
|
||||
equal to `connectivity` are considered neighbors. Ignored if
|
||||
`footprint` is not None.
|
||||
tolerance : float or int, optional
|
||||
If None (default), adjacent values must be strictly equal to the
|
||||
initial value of `image` at `seed_point`. This is fastest. If a value
|
||||
is given, a comparison will be done at every point and if within
|
||||
tolerance of the initial value will also be filled (inclusive).
|
||||
|
||||
Returns
|
||||
-------
|
||||
mask : ndarray
|
||||
A Boolean array with the same shape as `image` is returned, with True
|
||||
values for areas connected to and equal (or within tolerance of) the
|
||||
seed point. All other values are False.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The conceptual analogy of this operation is the 'paint bucket' tool in many
|
||||
raster graphics programs. This function returns just the mask
|
||||
representing the fill.
|
||||
|
||||
If indices are desired rather than masks for memory reasons, the user can
|
||||
simply run `numpy.nonzero` on the result, save the indices, and discard
|
||||
this mask.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.morphology import flood
|
||||
>>> image = np.zeros((4, 7), dtype=int)
|
||||
>>> image[1:3, 1:3] = 1
|
||||
>>> image[3, 0] = 1
|
||||
>>> image[1:3, 4:6] = 2
|
||||
>>> image[3, 6] = 3
|
||||
>>> image
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3]])
|
||||
|
||||
Fill connected ones with 5, with full connectivity (diagonals included):
|
||||
|
||||
>>> mask = flood(image, (1, 1))
|
||||
>>> image_flooded = image.copy()
|
||||
>>> image_flooded[mask] = 5
|
||||
>>> image_flooded
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 5, 5, 0, 2, 2, 0],
|
||||
[0, 5, 5, 0, 2, 2, 0],
|
||||
[5, 0, 0, 0, 0, 0, 3]])
|
||||
|
||||
Fill connected ones with 5, excluding diagonal points (connectivity 1):
|
||||
|
||||
>>> mask = flood(image, (1, 1), connectivity=1)
|
||||
>>> image_flooded = image.copy()
|
||||
>>> image_flooded[mask] = 5
|
||||
>>> image_flooded
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 5, 5, 0, 2, 2, 0],
|
||||
[0, 5, 5, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3]])
|
||||
|
||||
Fill with a tolerance:
|
||||
|
||||
>>> mask = flood(image, (0, 0), tolerance=1)
|
||||
>>> image_flooded = image.copy()
|
||||
>>> image_flooded[mask] = 5
|
||||
>>> image_flooded
|
||||
array([[5, 5, 5, 5, 5, 5, 5],
|
||||
[5, 5, 5, 5, 2, 2, 5],
|
||||
[5, 5, 5, 5, 2, 2, 5],
|
||||
[5, 5, 5, 5, 5, 5, 3]])
|
||||
"""
|
||||
# Correct start point in ravelled image - only copy if non-contiguous
|
||||
image = np.asarray(image)
|
||||
if image.flags.f_contiguous is True:
|
||||
order = 'F'
|
||||
elif image.flags.c_contiguous is True:
|
||||
order = 'C'
|
||||
else:
|
||||
image = np.ascontiguousarray(image)
|
||||
order = 'C'
|
||||
|
||||
# Shortcut for rank zero
|
||||
if 0 in image.shape:
|
||||
return np.zeros(image.shape, dtype=bool)
|
||||
|
||||
# Convenience for 1d input
|
||||
try:
|
||||
iter(seed_point)
|
||||
except TypeError:
|
||||
seed_point = (seed_point,)
|
||||
|
||||
seed_value = image[seed_point]
|
||||
seed_point = tuple(np.asarray(seed_point) % image.shape)
|
||||
|
||||
footprint = _resolve_neighborhood(
|
||||
footprint, connectivity, image.ndim, enforce_adjacency=False)
|
||||
center = tuple(s // 2 for s in footprint.shape)
|
||||
# Compute padding width as the maximum offset to neighbors on each axis.
|
||||
# Generates a 2-tuple of (pad_start, pad_end) for each axis.
|
||||
pad_width = [(np.max(np.abs(idx - c)),) * 2
|
||||
for idx, c in zip(np.nonzero(footprint), center)]
|
||||
|
||||
# Must annotate borders
|
||||
working_image = np.pad(image, pad_width, mode='constant',
|
||||
constant_values=image.min())
|
||||
# Stride-aware neighbors - works for both C- and Fortran-contiguity
|
||||
ravelled_seed_idx = np.ravel_multi_index(
|
||||
[i + pad_start
|
||||
for i, (pad_start, pad_end) in zip(seed_point, pad_width)],
|
||||
working_image.shape,
|
||||
order=order
|
||||
)
|
||||
neighbor_offsets = _offsets_to_raveled_neighbors(
|
||||
working_image.shape, footprint, center=center,
|
||||
order=order)
|
||||
|
||||
# Use a set of flags; see _flood_fill_cy.pyx for meanings
|
||||
flags = np.zeros(working_image.shape, dtype=np.uint8, order=order)
|
||||
_set_border_values(flags, value=2, border_width=pad_width)
|
||||
|
||||
try:
|
||||
if tolerance is not None:
|
||||
# Check if tolerance could create overflow problems
|
||||
try:
|
||||
max_value = np.finfo(working_image.dtype).max
|
||||
min_value = np.finfo(working_image.dtype).min
|
||||
except ValueError:
|
||||
max_value = np.iinfo(working_image.dtype).max
|
||||
min_value = np.iinfo(working_image.dtype).min
|
||||
|
||||
high_tol = min(max_value, seed_value + tolerance)
|
||||
low_tol = max(min_value, seed_value - tolerance)
|
||||
|
||||
_flood_fill_tolerance(working_image.ravel(order),
|
||||
flags.ravel(order),
|
||||
neighbor_offsets,
|
||||
ravelled_seed_idx,
|
||||
seed_value,
|
||||
low_tol,
|
||||
high_tol)
|
||||
else:
|
||||
_flood_fill_equal(working_image.ravel(order),
|
||||
flags.ravel(order),
|
||||
neighbor_offsets,
|
||||
ravelled_seed_idx,
|
||||
seed_value)
|
||||
except TypeError:
|
||||
if working_image.dtype == np.float16:
|
||||
# Provide the user with clearer error message
|
||||
raise TypeError("dtype of `image` is float16 which is not "
|
||||
"supported, try upcasting to float32")
|
||||
else:
|
||||
raise
|
||||
|
||||
# Output what the user requested; view does not create a new copy.
|
||||
return crop(flags, pad_width, copy=False).view(bool)
|
||||
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_flood_fill_cy.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_flood_fill_cy.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_grayreconstruct.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_grayreconstruct.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_max_tree.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_max_tree.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
Binary file not shown.
639
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_skeletonize.py
vendored
Normal file
639
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_skeletonize.py
vendored
Normal file
@@ -0,0 +1,639 @@
|
||||
"""
|
||||
Algorithms for computing the skeleton of a binary image
|
||||
"""
|
||||
import numpy as np
|
||||
from ..util import img_as_ubyte, crop
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .._shared.utils import check_nD
|
||||
from ._skeletonize_cy import (_fast_skeletonize, _skeletonize_loop,
|
||||
_table_lookup_index)
|
||||
from ._skeletonize_3d_cy import _compute_thin_image
|
||||
|
||||
|
||||
def skeletonize(image, *, method=None):
|
||||
"""Compute the skeleton of a binary image.
|
||||
|
||||
Thinning is used to reduce each connected component in a binary image
|
||||
to a single-pixel wide skeleton.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray, 2D or 3D
|
||||
An image containing the objects to be skeletonized. Zeros
|
||||
represent background, nonzero values are foreground.
|
||||
method : {'zhang', 'lee'}, optional
|
||||
Which algorithm to use. Zhang's algorithm [Zha84]_ only works for
|
||||
2D images, and is the default for 2D. Lee's algorithm [Lee94]_
|
||||
works for 2D or 3D images and is the default for 3D.
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray
|
||||
The thinned image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
medial_axis
|
||||
|
||||
References
|
||||
----------
|
||||
.. [Lee94] T.-C. Lee, R.L. Kashyap and C.-N. Chu, Building skeleton models
|
||||
via 3-D medial surface/axis thinning algorithms.
|
||||
Computer Vision, Graphics, and Image Processing, 56(6):462-478, 1994.
|
||||
|
||||
.. [Zha84] A fast parallel algorithm for thinning digital patterns,
|
||||
T. Y. Zhang and C. Y. Suen, Communications of the ACM,
|
||||
March 1984, Volume 27, Number 3.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> X, Y = np.ogrid[0:9, 0:9]
|
||||
>>> ellipse = (1./3 * (X - 4)**2 + (Y - 4)**2 < 3**2).astype(np.uint8)
|
||||
>>> ellipse
|
||||
array([[0, 0, 0, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
|
||||
>>> skel = skeletonize(ellipse)
|
||||
>>> skel.astype(np.uint8)
|
||||
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, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 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]], dtype=uint8)
|
||||
|
||||
"""
|
||||
|
||||
if image.ndim == 2 and (method is None or method == 'zhang'):
|
||||
skeleton = skeletonize_2d(image.astype(bool, copy=False))
|
||||
elif image.ndim == 3 and method == 'zhang':
|
||||
raise ValueError('skeletonize method "zhang" only works for 2D '
|
||||
'images.')
|
||||
elif image.ndim == 3 or (image.ndim == 2 and method == 'lee'):
|
||||
skeleton = skeletonize_3d(image)
|
||||
else:
|
||||
raise ValueError(f'skeletonize requires a 2D or 3D image as input, '
|
||||
f'got {image.ndim}D.')
|
||||
return skeleton
|
||||
|
||||
|
||||
def skeletonize_2d(image):
|
||||
"""Return the skeleton of a 2D binary image.
|
||||
|
||||
Thinning is used to reduce each connected component in a binary image
|
||||
to a single-pixel wide skeleton.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : numpy.ndarray
|
||||
A binary image containing the objects to be skeletonized. '1'
|
||||
represents foreground, and '0' represents background. It
|
||||
also accepts arrays of boolean values where True is foreground.
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray
|
||||
A matrix containing the thinned image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
medial_axis
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm [Zha84]_ works by making successive passes of the image,
|
||||
removing pixels on object borders. This continues until no
|
||||
more pixels can be removed. The image is correlated with a
|
||||
mask that assigns each pixel a number in the range [0...255]
|
||||
corresponding to each possible pattern of its 8 neighboring
|
||||
pixels. A look up table is then used to assign the pixels a
|
||||
value of 0, 1, 2 or 3, which are selectively removed during
|
||||
the iterations.
|
||||
|
||||
Note that this algorithm will give different results than a
|
||||
medial axis transform, which is also often referred to as
|
||||
"skeletonization".
|
||||
|
||||
References
|
||||
----------
|
||||
.. [Zha84] A fast parallel algorithm for thinning digital patterns,
|
||||
T. Y. Zhang and C. Y. Suen, Communications of the ACM,
|
||||
March 1984, Volume 27, Number 3.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> X, Y = np.ogrid[0:9, 0:9]
|
||||
>>> ellipse = (1./3 * (X - 4)**2 + (Y - 4)**2 < 3**2).astype(np.uint8)
|
||||
>>> ellipse
|
||||
array([[0, 0, 0, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
|
||||
>>> skel = skeletonize(ellipse)
|
||||
>>> skel.astype(np.uint8)
|
||||
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, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 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]], dtype=uint8)
|
||||
|
||||
"""
|
||||
|
||||
if image.ndim != 2:
|
||||
raise ValueError("Zhang's skeletonize method requires a 2D array")
|
||||
|
||||
return _fast_skeletonize(image)
|
||||
|
||||
|
||||
# --------- Skeletonization and thinning based on Guo and Hall 1989 ---------
|
||||
|
||||
def _generate_thin_luts():
|
||||
"""generate LUTs for thinning algorithm (for reference)"""
|
||||
|
||||
def nabe(n):
|
||||
return np.array([n >> i & 1 for i in range(0, 9)]).astype(bool)
|
||||
|
||||
def G1(n):
|
||||
s = 0
|
||||
bits = nabe(n)
|
||||
for i in (0, 2, 4, 6):
|
||||
if not(bits[i]) and (bits[i + 1] or bits[(i + 2) % 8]):
|
||||
s += 1
|
||||
return s == 1
|
||||
|
||||
g1_lut = np.array([G1(n) for n in range(256)])
|
||||
|
||||
def G2(n):
|
||||
n1, n2 = 0, 0
|
||||
bits = nabe(n)
|
||||
for k in (1, 3, 5, 7):
|
||||
if bits[k] or bits[k - 1]:
|
||||
n1 += 1
|
||||
if bits[k] or bits[(k + 1) % 8]:
|
||||
n2 += 1
|
||||
return min(n1, n2) in [2, 3]
|
||||
|
||||
g2_lut = np.array([G2(n) for n in range(256)])
|
||||
|
||||
g12_lut = g1_lut & g2_lut
|
||||
|
||||
def G3(n):
|
||||
bits = nabe(n)
|
||||
return not((bits[1] or bits[2] or not(bits[7])) and bits[0])
|
||||
|
||||
def G3p(n):
|
||||
bits = nabe(n)
|
||||
return not((bits[5] or bits[6] or not(bits[3])) and bits[4])
|
||||
|
||||
g3_lut = np.array([G3(n) for n in range(256)])
|
||||
g3p_lut = np.array([G3p(n) for n in range(256)])
|
||||
|
||||
g123_lut = g12_lut & g3_lut
|
||||
g123p_lut = g12_lut & g3p_lut
|
||||
|
||||
return g123_lut, g123p_lut
|
||||
|
||||
|
||||
G123_LUT = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
|
||||
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1,
|
||||
0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
|
||||
0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 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, 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, 1,
|
||||
1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
|
||||
0, 1, 1, 0, 0, 1, 0, 0, 0], dtype=bool)
|
||||
|
||||
G123P_LUT = np.array([0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 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, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0,
|
||||
0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 0, 1, 0, 1, 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, 1, 0,
|
||||
1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 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, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1,
|
||||
0, 1, 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], dtype=bool)
|
||||
|
||||
|
||||
def thin(image, max_num_iter=None):
|
||||
"""
|
||||
Perform morphological thinning of a binary image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : binary (M, N) ndarray
|
||||
The image to be thinned.
|
||||
max_num_iter : int, number of iterations, optional
|
||||
Regardless of the value of this parameter, the thinned image
|
||||
is returned immediately if an iteration produces no change.
|
||||
If this parameter is specified it thus sets an upper bound on
|
||||
the number of iterations performed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray of bool
|
||||
Thinned image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skeletonize, medial_axis
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm [1]_ works by making multiple passes over the image,
|
||||
removing pixels matching a set of criteria designed to thin
|
||||
connected regions while preserving eight-connected components and
|
||||
2 x 2 squares [2]_. In each of the two sub-iterations the algorithm
|
||||
correlates the intermediate skeleton image with a neighborhood mask,
|
||||
then looks up each neighborhood in a lookup table indicating whether
|
||||
the central pixel should be deleted in that sub-iteration.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Z. Guo and R. W. Hall, "Parallel thinning with
|
||||
two-subiteration algorithms," Comm. ACM, vol. 32, no. 3,
|
||||
pp. 359-373, 1989. :DOI:`10.1145/62065.62074`
|
||||
.. [2] Lam, L., Seong-Whan Lee, and Ching Y. Suen, "Thinning
|
||||
Methodologies-A Comprehensive Survey," IEEE Transactions on
|
||||
Pattern Analysis and Machine Intelligence, Vol 14, No. 9,
|
||||
p. 879, 1992. :DOI:`10.1109/34.161346`
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> square = np.zeros((7, 7), dtype=np.uint8)
|
||||
>>> square[1:-1, 2:-2] = 1
|
||||
>>> square[0, 1] = 1
|
||||
>>> square
|
||||
array([[0, 1, 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]], dtype=uint8)
|
||||
>>> skel = thin(square)
|
||||
>>> skel.astype(np.uint8)
|
||||
array([[0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 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]], dtype=uint8)
|
||||
"""
|
||||
# check that image is 2d
|
||||
check_nD(image, 2)
|
||||
|
||||
# convert image to uint8 with values in {0, 1}
|
||||
skel = np.asanyarray(image, dtype=bool).astype(np.uint8)
|
||||
|
||||
# neighborhood mask
|
||||
mask = np.array([[ 8, 4, 2],
|
||||
[16, 0, 1],
|
||||
[32, 64, 128]], dtype=np.uint8)
|
||||
|
||||
# iterate until convergence, up to the iteration limit
|
||||
max_num_iter = max_num_iter or np.inf
|
||||
num_iter = 0
|
||||
n_pts_old, n_pts_new = np.inf, np.sum(skel)
|
||||
while n_pts_old != n_pts_new and num_iter < max_num_iter:
|
||||
n_pts_old = n_pts_new
|
||||
|
||||
# perform the two "subiterations" described in the paper
|
||||
for lut in [G123_LUT, G123P_LUT]:
|
||||
# correlate image with neighborhood mask
|
||||
N = ndi.correlate(skel, mask, mode='constant')
|
||||
# take deletion decision from this subiteration's LUT
|
||||
D = np.take(lut, N)
|
||||
# perform deletion
|
||||
skel[D] = 0
|
||||
|
||||
n_pts_new = np.sum(skel) # count points after thinning
|
||||
num_iter += 1
|
||||
|
||||
return skel.astype(bool)
|
||||
|
||||
|
||||
# --------- Skeletonization by medial axis transform --------
|
||||
|
||||
_eight_connect = ndi.generate_binary_structure(2, 2)
|
||||
|
||||
|
||||
def medial_axis(image, mask=None, return_distance=False, *, random_state=None):
|
||||
"""Compute the medial axis transform of a binary image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : binary ndarray, shape (M, N)
|
||||
The image of the shape to be skeletonized.
|
||||
mask : binary ndarray, shape (M, N), optional
|
||||
If a mask is given, only those elements in `image` with a true
|
||||
value in `mask` are used for computing the medial axis.
|
||||
return_distance : bool, optional
|
||||
If true, the distance transform is returned as well as the skeleton.
|
||||
random_state : {None, int, `numpy.random.Generator`}, optional
|
||||
If `random_state` is None the `numpy.random.Generator` singleton is
|
||||
used.
|
||||
If `random_state` is an int, a new ``Generator`` instance is used,
|
||||
seeded with `random_state`.
|
||||
If `random_state` is already a ``Generator`` instance then that
|
||||
instance is used.
|
||||
|
||||
.. versionadded:: 0.19
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray of bools
|
||||
Medial axis transform of the image
|
||||
dist : ndarray of ints, optional
|
||||
Distance transform of the image (only returned if `return_distance`
|
||||
is True)
|
||||
|
||||
See Also
|
||||
--------
|
||||
skeletonize
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm computes the medial axis transform of an image
|
||||
as the ridges of its distance transform.
|
||||
|
||||
The different steps of the algorithm are as follows
|
||||
* A lookup table is used, that assigns 0 or 1 to each configuration of
|
||||
the 3x3 binary square, whether the central pixel should be removed
|
||||
or kept. We want a point to be removed if it has more than one neighbor
|
||||
and if removing it does not change the number of connected components.
|
||||
|
||||
* The distance transform to the background is computed, as well as
|
||||
the cornerness of the pixel.
|
||||
|
||||
* The foreground (value of 1) points are ordered by
|
||||
the distance transform, then the cornerness.
|
||||
|
||||
* A cython function is called to reduce the image to its skeleton. It
|
||||
processes pixels in the order determined at the previous step, and
|
||||
removes or maintains a pixel according to the lookup table. Because
|
||||
of the ordering, it is possible to process all pixels in only one
|
||||
pass.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> square = np.zeros((7, 7), dtype=np.uint8)
|
||||
>>> square[1:-1, 2:-2] = 1
|
||||
>>> square
|
||||
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]], dtype=uint8)
|
||||
>>> medial_axis(square).astype(np.uint8)
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 1, 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, 1, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
global _eight_connect
|
||||
if mask is None:
|
||||
masked_image = image.astype(bool)
|
||||
else:
|
||||
masked_image = image.astype(bool).copy()
|
||||
masked_image[~mask] = False
|
||||
#
|
||||
# Build lookup table - three conditions
|
||||
# 1. Keep only positive pixels (center_is_foreground array).
|
||||
# AND
|
||||
# 2. Keep if removing the pixel results in a different connectivity
|
||||
# (if the number of connected components is different with and
|
||||
# without the central pixel)
|
||||
# OR
|
||||
# 3. Keep if # pixels in neighborhood is 2 or less
|
||||
# Note that table is independent of image
|
||||
center_is_foreground = (np.arange(512) & 2**4).astype(bool)
|
||||
table = (center_is_foreground # condition 1.
|
||||
&
|
||||
(np.array([ndi.label(_pattern_of(index), _eight_connect)[1] !=
|
||||
ndi.label(_pattern_of(index & ~ 2**4),
|
||||
_eight_connect)[1]
|
||||
for index in range(512)]) # condition 2
|
||||
|
|
||||
np.array([np.sum(_pattern_of(index)) < 3 for index in range(512)]))
|
||||
# condition 3
|
||||
)
|
||||
|
||||
# Build distance transform
|
||||
distance = ndi.distance_transform_edt(masked_image)
|
||||
if return_distance:
|
||||
store_distance = distance.copy()
|
||||
|
||||
# Corners
|
||||
# The processing order along the edge is critical to the shape of the
|
||||
# resulting skeleton: if you process a corner first, that corner will
|
||||
# be eroded and the skeleton will miss the arm from that corner. Pixels
|
||||
# with fewer neighbors are more "cornery" and should be processed last.
|
||||
# We use a cornerness_table lookup table where the score of a
|
||||
# configuration is the number of background (0-value) pixels in the
|
||||
# 3x3 neighborhood
|
||||
cornerness_table = np.array([9 - np.sum(_pattern_of(index))
|
||||
for index in range(512)])
|
||||
corner_score = _table_lookup(masked_image, cornerness_table)
|
||||
|
||||
# Define arrays for inner loop
|
||||
i, j = np.mgrid[0:image.shape[0], 0:image.shape[1]]
|
||||
result = masked_image.copy()
|
||||
distance = distance[result]
|
||||
i = np.ascontiguousarray(i[result], dtype=np.intp)
|
||||
j = np.ascontiguousarray(j[result], dtype=np.intp)
|
||||
result = np.ascontiguousarray(result, np.uint8)
|
||||
|
||||
# Determine the order in which pixels are processed.
|
||||
# We use a random # for tiebreaking. Assign each pixel in the image a
|
||||
# predictable, random # so that masking doesn't affect arbitrary choices
|
||||
# of skeletons
|
||||
#
|
||||
generator = np.random.default_rng(random_state)
|
||||
tiebreaker = generator.permutation(np.arange(masked_image.sum()))
|
||||
order = np.lexsort((tiebreaker,
|
||||
corner_score[masked_image],
|
||||
distance))
|
||||
order = np.ascontiguousarray(order, dtype=np.int32)
|
||||
|
||||
table = np.ascontiguousarray(table, dtype=np.uint8)
|
||||
# Remove pixels not belonging to the medial axis
|
||||
_skeletonize_loop(result, i, j, order, table)
|
||||
|
||||
result = result.astype(bool)
|
||||
if mask is not None:
|
||||
result[~mask] = image[~mask]
|
||||
if return_distance:
|
||||
return result, store_distance
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def _pattern_of(index):
|
||||
"""
|
||||
Return the pattern represented by an index value
|
||||
Byte decomposition of index
|
||||
"""
|
||||
return np.array([[index & 2**0, index & 2**1, index & 2**2],
|
||||
[index & 2**3, index & 2**4, index & 2**5],
|
||||
[index & 2**6, index & 2**7, index & 2**8]], bool)
|
||||
|
||||
|
||||
def _table_lookup(image, table):
|
||||
"""
|
||||
Perform a morphological transform on an image, directed by its
|
||||
neighbors
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
A binary image
|
||||
table : ndarray
|
||||
A 512-element table giving the transform of each pixel given
|
||||
the values of that pixel and its 8-connected neighbors.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : ndarray of same shape as `image`
|
||||
Transformed image
|
||||
|
||||
Notes
|
||||
-----
|
||||
The pixels are numbered like this::
|
||||
|
||||
0 1 2
|
||||
3 4 5
|
||||
6 7 8
|
||||
|
||||
The index at a pixel is the sum of 2**<pixel-number> for pixels
|
||||
that evaluate to true.
|
||||
"""
|
||||
#
|
||||
# We accumulate into the indexer to get the index into the table
|
||||
# at each point in the image
|
||||
#
|
||||
if image.shape[0] < 3 or image.shape[1] < 3:
|
||||
image = image.astype(bool)
|
||||
indexer = np.zeros(image.shape, int)
|
||||
indexer[1:, 1:] += image[:-1, :-1] * 2**0
|
||||
indexer[1:, :] += image[:-1, :] * 2**1
|
||||
indexer[1:, :-1] += image[:-1, 1:] * 2**2
|
||||
|
||||
indexer[:, 1:] += image[:, :-1] * 2**3
|
||||
indexer[:, :] += image[:, :] * 2**4
|
||||
indexer[:, :-1] += image[:, 1:] * 2**5
|
||||
|
||||
indexer[:-1, 1:] += image[1:, :-1] * 2**6
|
||||
indexer[:-1, :] += image[1:, :] * 2**7
|
||||
indexer[:-1, :-1] += image[1:, 1:] * 2**8
|
||||
else:
|
||||
indexer = _table_lookup_index(np.ascontiguousarray(image, np.uint8))
|
||||
image = table[indexer]
|
||||
return image
|
||||
|
||||
|
||||
def skeletonize_3d(image):
|
||||
"""Compute the skeleton of a binary image.
|
||||
|
||||
Thinning is used to reduce each connected component in a binary image
|
||||
to a single-pixel wide skeleton.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray, 2D or 3D
|
||||
A binary image containing the objects to be skeletonized. Zeros
|
||||
represent background, nonzero values are foreground.
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray
|
||||
The thinned image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skeletonize, medial_axis
|
||||
|
||||
Notes
|
||||
-----
|
||||
The method of [Lee94]_ uses an octree data structure to examine a 3x3x3
|
||||
neighborhood of a pixel. The algorithm proceeds by iteratively sweeping
|
||||
over the image, and removing pixels at each iteration until the image
|
||||
stops changing. Each iteration consists of two steps: first, a list of
|
||||
candidates for removal is assembled; then pixels from this list are
|
||||
rechecked sequentially, to better preserve connectivity of the image.
|
||||
|
||||
The algorithm this function implements is different from the algorithms
|
||||
used by either `skeletonize` or `medial_axis`, thus for 2D images the
|
||||
results produced by this function are generally different.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [Lee94] T.-C. Lee, R.L. Kashyap and C.-N. Chu, Building skeleton models
|
||||
via 3-D medial surface/axis thinning algorithms.
|
||||
Computer Vision, Graphics, and Image Processing, 56(6):462-478, 1994.
|
||||
|
||||
"""
|
||||
# make sure the image is 3D or 2D
|
||||
if image.ndim < 2 or image.ndim > 3:
|
||||
raise ValueError("skeletonize_3d can only handle 2D or 3D images; "
|
||||
f"got image.ndim = {image.ndim} instead.")
|
||||
image = np.ascontiguousarray(image)
|
||||
image = img_as_ubyte(image, force_copy=False)
|
||||
|
||||
# make an in image 3D and pad it w/ zeros to simplify dealing w/ boundaries
|
||||
# NB: careful here to not clobber the original *and* minimize copying
|
||||
image_o = image
|
||||
if image.ndim == 2:
|
||||
image_o = image[np.newaxis, ...]
|
||||
image_o = np.pad(image_o, pad_width=1, mode='constant')
|
||||
|
||||
# normalize to binary
|
||||
maxval = image_o.max()
|
||||
image_o[image_o != 0] = 1
|
||||
|
||||
# do the computation
|
||||
image_o = np.asarray(_compute_thin_image(image_o))
|
||||
|
||||
# crop it back and restore the original intensity range
|
||||
image_o = crop(image_o, crop_width=1)
|
||||
if image.ndim == 2:
|
||||
image_o = image_o[0]
|
||||
image_o *= maxval
|
||||
|
||||
return image_o
|
||||
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_skeletonize_3d_cy.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_skeletonize_3d_cy.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
Binary file not shown.
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_skeletonize_cy.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_skeletonize_cy.cpython-311-x86_64-linux-gnu.so
vendored
Executable file
Binary file not shown.
333
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_util.py
vendored
Normal file
333
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/_util.py
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
"""Utility functions used in the morphology subpackage."""
|
||||
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
|
||||
def _validate_connectivity(image_dim, connectivity, offset):
|
||||
"""Convert any valid connectivity to a footprint and offset.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_dim : int
|
||||
The number of dimensions of the input image.
|
||||
connectivity : int, array, or None
|
||||
The neighborhood connectivity. An integer is interpreted as in
|
||||
``scipy.ndimage.generate_binary_structure``, as the maximum number
|
||||
of orthogonal steps to reach a neighbor. An array is directly
|
||||
interpreted as a footprint and its shape is validated against
|
||||
the input image shape. ``None`` is interpreted as a connectivity of 1.
|
||||
offset : tuple of int, or None
|
||||
The coordinates of the center of the footprint.
|
||||
|
||||
Returns
|
||||
-------
|
||||
c_connectivity : array of bool
|
||||
The footprint (structuring element) corresponding to the input
|
||||
`connectivity`.
|
||||
offset : array of int
|
||||
The offset corresponding to the center of the footprint.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError:
|
||||
If the image dimension and the connectivity or offset dimensions don't
|
||||
match.
|
||||
"""
|
||||
if connectivity is None:
|
||||
connectivity = 1
|
||||
|
||||
if np.isscalar(connectivity):
|
||||
c_connectivity = ndi.generate_binary_structure(image_dim, connectivity)
|
||||
else:
|
||||
c_connectivity = np.array(connectivity, bool)
|
||||
if c_connectivity.ndim != image_dim:
|
||||
raise ValueError("Connectivity dimension must be same as image")
|
||||
|
||||
if offset is None:
|
||||
if any([x % 2 == 0 for x in c_connectivity.shape]):
|
||||
raise ValueError("Connectivity array must have an unambiguous "
|
||||
"center")
|
||||
|
||||
offset = np.array(c_connectivity.shape) // 2
|
||||
|
||||
return c_connectivity, offset
|
||||
|
||||
|
||||
def _raveled_offsets_and_distances(
|
||||
image_shape,
|
||||
*,
|
||||
footprint=None,
|
||||
connectivity=1,
|
||||
center=None,
|
||||
spacing=None,
|
||||
order='C',
|
||||
):
|
||||
"""Compute offsets to neighboring pixels in raveled coordinate space.
|
||||
|
||||
This function also returns the corresponding distances from the center
|
||||
pixel given a spacing (assumed to be 1 along each axis by default).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_shape : tuple of int
|
||||
The shape of the image for which the offsets are being computed.
|
||||
footprint : array of bool
|
||||
The footprint of the neighborhood, expressed as an n-dimensional array
|
||||
of 1s and 0s. If provided, the connectivity argument is ignored.
|
||||
connectivity : {1, ..., ndim}
|
||||
The square connectivity of the neighborhood: the number of orthogonal
|
||||
steps allowed to consider a pixel a neighbor. See
|
||||
`scipy.ndimage.generate_binary_structure`. Ignored if footprint is
|
||||
provided.
|
||||
center : tuple of int
|
||||
Tuple of indices to the center of the footprint. If not provided, it
|
||||
is assumed to be the center of the footprint, either provided or
|
||||
generated by the connectivity argument.
|
||||
spacing : tuple of float
|
||||
The spacing between pixels/voxels along each axis.
|
||||
order : 'C' or 'F'
|
||||
The ordering of the array, either C or Fortran ordering.
|
||||
|
||||
Returns
|
||||
-------
|
||||
raveled_offsets : ndarray
|
||||
Linear offsets to a samples neighbors in the raveled image, sorted by
|
||||
their distance from the center.
|
||||
distances : ndarray
|
||||
The pixel distances corresponding to each offset.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function will return values even if `image_shape` contains a dimension
|
||||
length that is smaller than `footprint`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> off, d = _raveled_offsets_and_distances(
|
||||
... (4, 5), footprint=np.ones((4, 3)), center=(1, 1)
|
||||
... )
|
||||
>>> off
|
||||
array([-5, -1, 1, 5, -6, -4, 4, 6, 10, 9, 11])
|
||||
>>> d[0]
|
||||
1.0
|
||||
>>> d[-1] # distance from (1, 1) to (3, 2)
|
||||
2.236...
|
||||
"""
|
||||
ndim = len(image_shape)
|
||||
if footprint is None:
|
||||
footprint = ndi.generate_binary_structure(
|
||||
rank=ndim, connectivity=connectivity
|
||||
)
|
||||
if center is None:
|
||||
center = tuple(s // 2 for s in footprint.shape)
|
||||
|
||||
if not footprint.ndim == ndim == len(center):
|
||||
raise ValueError(
|
||||
"number of dimensions in image shape, footprint and its"
|
||||
"center index does not match")
|
||||
|
||||
offsets = np.stack([(idx - c)
|
||||
for idx, c in zip(np.nonzero(footprint), center)],
|
||||
axis=-1)
|
||||
|
||||
if order == 'F':
|
||||
offsets = offsets[:, ::-1]
|
||||
image_shape = image_shape[::-1]
|
||||
elif order != 'C':
|
||||
raise ValueError("order must be 'C' or 'F'")
|
||||
|
||||
# Scale offsets in each dimension and sum
|
||||
ravel_factors = image_shape[1:] + (1,)
|
||||
ravel_factors = np.cumprod(ravel_factors[::-1])[::-1]
|
||||
raveled_offsets = (offsets * ravel_factors).sum(axis=1)
|
||||
|
||||
# Sort by distance
|
||||
if spacing is None:
|
||||
spacing = np.ones(ndim)
|
||||
weighted_offsets = offsets * spacing
|
||||
distances = np.sqrt(np.sum(weighted_offsets**2, axis=1))
|
||||
sorted_raveled_offsets = raveled_offsets[np.argsort(distances)]
|
||||
sorted_distances = np.sort(distances)
|
||||
|
||||
# If any dimension in image_shape is smaller than footprint.shape
|
||||
# duplicates might occur, remove them
|
||||
if any(x < y for x, y in zip(image_shape, footprint.shape)):
|
||||
# np.unique reorders, which we don't want
|
||||
_, indices = np.unique(sorted_raveled_offsets, return_index=True)
|
||||
sorted_raveled_offsets = sorted_raveled_offsets[np.sort(indices)]
|
||||
sorted_distances = sorted_distances[np.sort(indices)]
|
||||
|
||||
# Remove "offset to center"
|
||||
sorted_raveled_offsets = sorted_raveled_offsets[1:]
|
||||
sorted_distances = sorted_distances[1:]
|
||||
|
||||
return sorted_raveled_offsets, sorted_distances
|
||||
|
||||
|
||||
def _offsets_to_raveled_neighbors(image_shape, footprint, center, order='C'):
|
||||
"""Compute offsets to a samples neighbors if the image would be raveled.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_shape : tuple
|
||||
The shape of the image for which the offsets are computed.
|
||||
footprint : ndarray
|
||||
The footprint (structuring element) determining the neighborhood
|
||||
expressed as an n-D array of 1's and 0's.
|
||||
center : tuple
|
||||
Tuple of indices to the center of `footprint`.
|
||||
order : {"C", "F"}, optional
|
||||
Whether the image described by `image_shape` is in row-major (C-style)
|
||||
or column-major (Fortran-style) order.
|
||||
|
||||
Returns
|
||||
-------
|
||||
raveled_offsets : ndarray
|
||||
Linear offsets to a samples neighbors in the raveled image, sorted by
|
||||
their distance from the center.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function will return values even if `image_shape` contains a dimension
|
||||
length that is smaller than `footprint`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> _offsets_to_raveled_neighbors((4, 5), np.ones((4, 3)), (1, 1))
|
||||
array([-5, -1, 1, 5, -6, -4, 4, 6, 10, 9, 11])
|
||||
>>> _offsets_to_raveled_neighbors((2, 3, 2), np.ones((3, 3, 3)), (1, 1, 1))
|
||||
array([ 2, -6, 1, -1, 6, -2, 3, 8, -3, -4, 7, -5, -7, -8, 5, 4, -9,
|
||||
9])
|
||||
"""
|
||||
raveled_offsets = _raveled_offsets_and_distances(
|
||||
image_shape, footprint=footprint, center=center, order=order
|
||||
)[0]
|
||||
|
||||
return raveled_offsets
|
||||
|
||||
|
||||
def _resolve_neighborhood(footprint, connectivity, ndim,
|
||||
enforce_adjacency=True):
|
||||
"""Validate or create a footprint (structuring element).
|
||||
|
||||
Depending on the values of `connectivity` and `footprint` this function
|
||||
either creates a new footprint (`footprint` is None) using `connectivity`
|
||||
or validates the given footprint (`footprint` is not None).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
footprint : ndarray
|
||||
The footprint (structuring) element used to determine the neighborhood
|
||||
of each evaluated pixel (``True`` denotes a connected pixel). It must
|
||||
be a boolean array and have the same number of dimensions as `image`.
|
||||
If neither `footprint` nor `connectivity` are given, all adjacent
|
||||
pixels are considered as part of the neighborhood.
|
||||
connectivity : int
|
||||
A number used to determine the neighborhood of each evaluated pixel.
|
||||
Adjacent pixels whose squared distance from the center is less than or
|
||||
equal to `connectivity` are considered neighbors. Ignored if
|
||||
`footprint` is not None.
|
||||
ndim : int
|
||||
Number of dimensions `footprint` ought to have.
|
||||
enforce_adjacency : bool
|
||||
A boolean that determines whether footprint must only specify direct
|
||||
neighbors.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray
|
||||
Validated or new footprint specifying the neighborhood.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> _resolve_neighborhood(None, 1, 2)
|
||||
array([[False, True, False],
|
||||
[ True, True, True],
|
||||
[False, True, False]])
|
||||
>>> _resolve_neighborhood(None, None, 3).shape
|
||||
(3, 3, 3)
|
||||
"""
|
||||
if footprint is None:
|
||||
if connectivity is None:
|
||||
connectivity = ndim
|
||||
footprint = ndi.generate_binary_structure(ndim, connectivity)
|
||||
else:
|
||||
# Validate custom structured element
|
||||
footprint = np.asarray(footprint, dtype=bool)
|
||||
# Must specify neighbors for all dimensions
|
||||
if footprint.ndim != ndim:
|
||||
raise ValueError(
|
||||
"number of dimensions in image and footprint do not"
|
||||
"match"
|
||||
)
|
||||
# Must only specify direct neighbors
|
||||
if enforce_adjacency and any(s != 3 for s in footprint.shape):
|
||||
raise ValueError("dimension size in footprint is not 3")
|
||||
elif any((s % 2 != 1) for s in footprint.shape):
|
||||
raise ValueError("footprint size must be odd along all dimensions")
|
||||
|
||||
return footprint
|
||||
|
||||
|
||||
def _set_border_values(image, value, border_width=1):
|
||||
"""Set edge values along all axes to a constant value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The array to modify inplace.
|
||||
value : scalar
|
||||
The value to use. Should be compatible with `image`'s dtype.
|
||||
border_width : int or sequence of tuples
|
||||
A sequence with one 2-tuple per axis where the first and second values
|
||||
are the width of the border at the start and end of the axis,
|
||||
respectively. If an int is provided, a uniform border width along all
|
||||
axes is used.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> image = np.zeros((4, 5), dtype=int)
|
||||
>>> _set_border_values(image, 1)
|
||||
>>> image
|
||||
array([[1, 1, 1, 1, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1]])
|
||||
>>> image = np.zeros((8, 8), dtype=int)
|
||||
>>> _set_border_values(image, 1, border_width=((1, 1), (2, 3)))
|
||||
>>> image
|
||||
array([[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1]])
|
||||
"""
|
||||
if np.isscalar(border_width):
|
||||
border_width = ((border_width, border_width),) * image.ndim
|
||||
elif len(border_width) != image.ndim:
|
||||
raise ValueError('length of `border_width` must match image.ndim')
|
||||
for axis, npad in enumerate(border_width):
|
||||
if len(npad) != 2:
|
||||
raise ValueError('each sequence in `border_width` must have '
|
||||
'length 2')
|
||||
w_start, w_end = npad
|
||||
if w_start == w_end == 0:
|
||||
continue
|
||||
elif w_start == w_end == 1:
|
||||
# Index first and last element in the current dimension
|
||||
sl = (slice(None),) * axis + ((0, -1),) + (...,)
|
||||
image[sl] = value
|
||||
continue
|
||||
if w_start > 0:
|
||||
# set first w_start entries along axis to value
|
||||
sl = (slice(None),) * axis + (slice(0, w_start),) + (...,)
|
||||
image[sl] = value
|
||||
if w_end > 0:
|
||||
# set last w_end entries along axis to value
|
||||
sl = (slice(None),) * axis + (slice(-w_end, None),) + (...,)
|
||||
image[sl] = value
|
||||
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/ball_decompositions.npy
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/ball_decompositions.npy
vendored
Normal file
Binary file not shown.
249
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/binary.py
vendored
Normal file
249
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/binary.py
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
"""
|
||||
Binary morphological operations
|
||||
"""
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .footprints import _footprint_is_sequence
|
||||
from .misc import default_footprint
|
||||
|
||||
|
||||
def _iterate_binary_func(binary_func, image, footprint, out):
|
||||
"""Helper to call `binary_func` for each footprint in a sequence.
|
||||
|
||||
binary_func is a binary morphology function that accepts "structure",
|
||||
"output" and "iterations" keyword arguments
|
||||
(e.g. `scipy.ndimage.binary_erosion`).
|
||||
"""
|
||||
fp, num_iter = footprint[0]
|
||||
binary_func(image, structure=fp, output=out, iterations=num_iter)
|
||||
for fp, num_iter in footprint[1:]:
|
||||
# Note: out.copy() because the computation cannot be in-place!
|
||||
# SciPy <= 1.7 did not automatically make a copy if needed.
|
||||
binary_func(out.copy(), structure=fp, output=out, iterations=num_iter)
|
||||
return out
|
||||
|
||||
|
||||
# The default_footprint decorator provides a diamond footprint as
|
||||
# default with the same dimension as the input image and size 3 along each
|
||||
# axis.
|
||||
@default_footprint
|
||||
def binary_erosion(image, footprint=None, out=None):
|
||||
"""Return fast binary morphological erosion of an image.
|
||||
|
||||
This function returns the same result as grayscale erosion but performs
|
||||
faster for binary images.
|
||||
|
||||
Morphological erosion sets a pixel at ``(i,j)`` to the minimum over all
|
||||
pixels in the neighborhood centered at ``(i,j)``. Erosion shrinks bright
|
||||
regions and enlarges dark regions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Binary input image.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None is
|
||||
passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
eroded : ndarray of bool or uint
|
||||
The result of the morphological erosion taking values in
|
||||
``[False, True]``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.isotropic_erosion
|
||||
|
||||
"""
|
||||
if out is None:
|
||||
out = np.empty(image.shape, dtype=bool)
|
||||
|
||||
if _footprint_is_sequence(footprint):
|
||||
binary_func = functools.partial(ndi.binary_erosion, border_value=True)
|
||||
return _iterate_binary_func(binary_func, image, footprint, out)
|
||||
|
||||
ndi.binary_erosion(image, structure=footprint, output=out,
|
||||
border_value=True)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def binary_dilation(image, footprint=None, out=None):
|
||||
"""Return fast binary morphological dilation of an image.
|
||||
|
||||
This function returns the same result as grayscale dilation but performs
|
||||
faster for binary images.
|
||||
|
||||
Morphological dilation sets a pixel at ``(i,j)`` to the maximum over all
|
||||
pixels in the neighborhood centered at ``(i,j)``. Dilation enlarges bright
|
||||
regions and shrinks dark regions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Binary input image.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None is
|
||||
passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dilated : ndarray of bool or uint
|
||||
The result of the morphological dilation with values in
|
||||
``[False, True]``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.isotropic_dilation
|
||||
|
||||
"""
|
||||
if out is None:
|
||||
out = np.empty(image.shape, dtype=bool)
|
||||
|
||||
if _footprint_is_sequence(footprint):
|
||||
return _iterate_binary_func(ndi.binary_dilation, image, footprint, out)
|
||||
|
||||
ndi.binary_dilation(image, structure=footprint, output=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def binary_opening(image, footprint=None, out=None):
|
||||
"""Return fast binary morphological opening of an image.
|
||||
|
||||
This function returns the same result as grayscale opening but performs
|
||||
faster for binary images.
|
||||
|
||||
The morphological opening on an image is defined as an erosion followed by
|
||||
a dilation. Opening can remove small bright spots (i.e. "salt") and connect
|
||||
small dark cracks. This tends to "open" up (dark) gaps between (bright)
|
||||
features.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Binary input image.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
opening : ndarray of bool
|
||||
The result of the morphological opening.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.isotropic_opening
|
||||
|
||||
"""
|
||||
eroded = binary_erosion(image, footprint)
|
||||
out = binary_dilation(eroded, footprint, out=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def binary_closing(image, footprint=None, out=None):
|
||||
"""Return fast binary morphological closing of an image.
|
||||
|
||||
This function returns the same result as grayscale closing but performs
|
||||
faster for binary images.
|
||||
|
||||
The morphological closing on an image is defined as a dilation followed by
|
||||
an erosion. Closing can remove small dark spots (i.e. "pepper") and connect
|
||||
small bright cracks. This tends to "close" up (dark) gaps between (bright)
|
||||
features.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Binary input image.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None,
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
closing : ndarray of bool
|
||||
The result of the morphological closing.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.isotropic_closing
|
||||
|
||||
"""
|
||||
dilated = binary_dilation(image, footprint)
|
||||
out = binary_erosion(dilated, footprint, out=out)
|
||||
return out
|
||||
215
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/convex_hull.py
vendored
Normal file
215
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/convex_hull.py
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
"""Convex Hull."""
|
||||
from itertools import product
|
||||
import numpy as np
|
||||
from scipy.spatial import ConvexHull, QhullError
|
||||
from ..measure.pnpoly import grid_points_in_poly
|
||||
from ._convex_hull import possible_hull
|
||||
from ..measure._label import label
|
||||
from ..util import unique_rows
|
||||
from .._shared.utils import warn
|
||||
|
||||
__all__ = ['convex_hull_image', 'convex_hull_object']
|
||||
|
||||
|
||||
def _offsets_diamond(ndim):
|
||||
offsets = np.zeros((2 * ndim, ndim))
|
||||
for vertex, (axis, offset) in enumerate(product(range(ndim), (-0.5, 0.5))):
|
||||
offsets[vertex, axis] = offset
|
||||
return offsets
|
||||
|
||||
|
||||
def _check_coords_in_hull(gridcoords, hull_equations, tolerance):
|
||||
r"""Checks all the coordinates for inclusiveness in the convex hull.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
gridcoords : (M, N) ndarray
|
||||
Coordinates of ``N`` points in ``M`` dimensions.
|
||||
hull_equations : (M, N) ndarray
|
||||
Hyperplane equations of the facets of the convex hull.
|
||||
tolerance : float
|
||||
Tolerance when determining whether a point is inside the hull. Due
|
||||
to numerical floating point errors, a tolerance of 0 can result in
|
||||
some points erroneously being classified as being outside the hull.
|
||||
|
||||
Returns
|
||||
-------
|
||||
coords_in_hull : ndarray of bool
|
||||
Binary 1D ndarray representing points in n-dimensional space
|
||||
with value ``True`` set for points inside the convex hull.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Checking the inclusiveness of coordinates in a convex hull requires
|
||||
intermediate calculations of dot products which are memory-intensive.
|
||||
Thus, the convex hull equations are checked individually with all
|
||||
coordinates to keep within the memory limit.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://github.com/scikit-image/scikit-image/issues/5019
|
||||
|
||||
"""
|
||||
ndim, n_coords = gridcoords.shape
|
||||
n_hull_equations = hull_equations.shape[0]
|
||||
coords_in_hull = np.ones(n_coords, dtype=bool)
|
||||
|
||||
# Pre-allocate arrays to cache intermediate results for reducing overheads
|
||||
dot_array = np.empty(n_coords, dtype=np.float64)
|
||||
test_ineq_temp = np.empty(n_coords, dtype=np.float64)
|
||||
coords_single_ineq = np.empty(n_coords, dtype=bool)
|
||||
|
||||
# A point is in the hull if it satisfies all of the hull's inequalities
|
||||
for idx in range(n_hull_equations):
|
||||
# Tests a hyperplane equation on all coordinates of volume
|
||||
np.dot(hull_equations[idx, :ndim], gridcoords, out=dot_array)
|
||||
np.add(dot_array, hull_equations[idx, ndim:], out=test_ineq_temp)
|
||||
np.less(test_ineq_temp, tolerance, out=coords_single_ineq)
|
||||
coords_in_hull *= coords_single_ineq
|
||||
|
||||
return coords_in_hull
|
||||
|
||||
|
||||
def convex_hull_image(image, offset_coordinates=True, tolerance=1e-10,
|
||||
include_borders=True):
|
||||
"""Compute the convex hull image of a binary image.
|
||||
|
||||
The convex hull is the set of pixels included in the smallest convex
|
||||
polygon that surround all white pixels in the input image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : array
|
||||
Binary input image. This array is cast to bool before processing.
|
||||
offset_coordinates : bool, optional
|
||||
If ``True``, a pixel at coordinate, e.g., (4, 7) will be represented
|
||||
by coordinates (3.5, 7), (4.5, 7), (4, 6.5), and (4, 7.5). This adds
|
||||
some "extent" to a pixel when computing the hull.
|
||||
tolerance : float, optional
|
||||
Tolerance when determining whether a point is inside the hull. Due
|
||||
to numerical floating point errors, a tolerance of 0 can result in
|
||||
some points erroneously being classified as being outside the hull.
|
||||
include_borders: bool, optional
|
||||
If ``False``, vertices/edges are excluded from the final hull mask.
|
||||
|
||||
Returns
|
||||
-------
|
||||
hull : (M, N) array of bool
|
||||
Binary image with pixels in convex hull set to True.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://blogs.mathworks.com/steve/2011/10/04/binary-image-convex-hull-algorithm-notes/
|
||||
|
||||
"""
|
||||
ndim = image.ndim
|
||||
if np.count_nonzero(image) == 0:
|
||||
warn("Input image is entirely zero, no valid convex hull. "
|
||||
"Returning empty image", UserWarning)
|
||||
return np.zeros(image.shape, dtype=bool)
|
||||
# In 2D, we do an optimisation by choosing only pixels that are
|
||||
# the starting or ending pixel of a row or column. This vastly
|
||||
# limits the number of coordinates to examine for the virtual hull.
|
||||
if ndim == 2:
|
||||
coords = possible_hull(np.ascontiguousarray(image, dtype=np.uint8))
|
||||
else:
|
||||
coords = np.transpose(np.nonzero(image))
|
||||
if offset_coordinates:
|
||||
# when offsetting, we multiply number of vertices by 2 * ndim.
|
||||
# therefore, we reduce the number of coordinates by using a
|
||||
# convex hull on the original set, before offsetting.
|
||||
try:
|
||||
hull0 = ConvexHull(coords)
|
||||
except QhullError as err:
|
||||
warn(f"Failed to get convex hull image. "
|
||||
f"Returning empty image, see error message below:\n"
|
||||
f"{err}")
|
||||
return np.zeros(image.shape, dtype=bool)
|
||||
coords = hull0.points[hull0.vertices]
|
||||
|
||||
# Add a vertex for the middle of each pixel edge
|
||||
if offset_coordinates:
|
||||
offsets = _offsets_diamond(image.ndim)
|
||||
coords = (coords[:, np.newaxis, :] + offsets).reshape(-1, ndim)
|
||||
|
||||
# repeated coordinates can *sometimes* cause problems in
|
||||
# scipy.spatial.ConvexHull, so we remove them.
|
||||
coords = unique_rows(coords)
|
||||
|
||||
# Find the convex hull
|
||||
try:
|
||||
hull = ConvexHull(coords)
|
||||
except QhullError as err:
|
||||
warn(f"Failed to get convex hull image. "
|
||||
f"Returning empty image, see error message below:\n"
|
||||
f"{err}")
|
||||
return np.zeros(image.shape, dtype=bool)
|
||||
vertices = hull.points[hull.vertices]
|
||||
|
||||
# If 2D, use fast Cython function to locate convex hull pixels
|
||||
if ndim == 2:
|
||||
labels = grid_points_in_poly(image.shape, vertices, binarize=False)
|
||||
# If include_borders is True, we include vertices (2) and edge
|
||||
# points (3) in the mask, otherwise only the inside of the hull (1)
|
||||
mask = labels >= 1 if include_borders else labels == 1
|
||||
else:
|
||||
gridcoords = np.reshape(np.mgrid[tuple(map(slice, image.shape))],
|
||||
(ndim, -1))
|
||||
|
||||
coords_in_hull = _check_coords_in_hull(gridcoords,
|
||||
hull.equations, tolerance)
|
||||
mask = np.reshape(coords_in_hull, image.shape)
|
||||
|
||||
return mask
|
||||
|
||||
|
||||
def convex_hull_object(image, *, connectivity=2):
|
||||
r"""Compute the convex hull image of individual objects in a binary image.
|
||||
|
||||
The convex hull is the set of pixels included in the smallest convex
|
||||
polygon that surround all white pixels in the input image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N) ndarray
|
||||
Binary input image.
|
||||
connectivity : {1, 2}, int, optional
|
||||
Determines the neighbors of each pixel. Adjacent elements
|
||||
within a squared distance of ``connectivity`` from pixel center
|
||||
are considered neighbors.::
|
||||
|
||||
1-connectivity 2-connectivity
|
||||
[ ] [ ] [ ] [ ]
|
||||
| \ | /
|
||||
[ ]--[x]--[ ] [ ]--[x]--[ ]
|
||||
| / | \
|
||||
[ ] [ ] [ ] [ ]
|
||||
|
||||
Returns
|
||||
-------
|
||||
hull : ndarray of bool
|
||||
Binary image with pixels inside convex hull set to ``True``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function uses ``skimage.morphology.label`` to define unique objects,
|
||||
finds the convex hull of each using ``convex_hull_image``, and combines
|
||||
these regions with logical OR. Be aware the convex hulls of unconnected
|
||||
objects may overlap in the result. If this is suspected, consider using
|
||||
convex_hull_image separately on each object or adjust ``connectivity``.
|
||||
"""
|
||||
if image.ndim > 2:
|
||||
raise ValueError("Input must be a 2D image")
|
||||
|
||||
if connectivity not in (1, 2):
|
||||
raise ValueError('`connectivity` must be either 1 or 2.')
|
||||
|
||||
labeled_im = label(image, connectivity=connectivity, background=0)
|
||||
convex_obj = np.zeros(image.shape, dtype=bool)
|
||||
convex_img = np.zeros(image.shape, dtype=bool)
|
||||
|
||||
for i in range(1, labeled_im.max() + 1):
|
||||
convex_obj = convex_hull_image(labeled_im == i)
|
||||
convex_img = np.logical_or(convex_img, convex_obj)
|
||||
|
||||
return convex_img
|
||||
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/disk_decompositions.npy
vendored
Normal file
BIN
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/disk_decompositions.npy
vendored
Normal file
Binary file not shown.
545
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/extrema.py
vendored
Normal file
545
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/extrema.py
vendored
Normal file
@@ -0,0 +1,545 @@
|
||||
"""extrema.py - local minima and maxima
|
||||
|
||||
This module provides functions to find local maxima and minima of an image.
|
||||
Here, local maxima (minima) are defined as connected sets of pixels with equal
|
||||
gray level which is strictly greater (smaller) than the gray level of all
|
||||
pixels in direct neighborhood of the connected set. In addition, the module
|
||||
provides the related functions h-maxima and h-minima.
|
||||
|
||||
Soille, P. (2003). Morphological Image Analysis: Principles and Applications
|
||||
(2nd ed.), Chapter 6. Springer-Verlag New York, Inc.
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from .._shared.utils import warn
|
||||
from ..util import dtype_limits, invert, crop
|
||||
from . import grayreconstruct, _util
|
||||
from ._extrema_cy import _local_maxima
|
||||
|
||||
|
||||
def _add_constant_clip(image, const_value):
|
||||
"""Add constant to the image while handling overflow issues gracefully.
|
||||
"""
|
||||
min_dtype, max_dtype = dtype_limits(image, clip_negative=False)
|
||||
|
||||
if const_value > (max_dtype - min_dtype):
|
||||
raise ValueError("The added constant is not compatible"
|
||||
"with the image data type.")
|
||||
|
||||
result = image + const_value
|
||||
result[image > max_dtype-const_value] = max_dtype
|
||||
return(result)
|
||||
|
||||
|
||||
def _subtract_constant_clip(image, const_value):
|
||||
"""Subtract constant from image while handling underflow issues.
|
||||
"""
|
||||
min_dtype, max_dtype = dtype_limits(image, clip_negative=False)
|
||||
|
||||
if const_value > (max_dtype-min_dtype):
|
||||
raise ValueError("The subtracted constant is not compatible"
|
||||
"with the image data type.")
|
||||
|
||||
result = image - const_value
|
||||
result[image < (const_value + min_dtype)] = min_dtype
|
||||
return(result)
|
||||
|
||||
|
||||
def h_maxima(image, h, footprint=None):
|
||||
"""Determine all maxima of the image with height >= h.
|
||||
|
||||
The local maxima are defined as connected sets of pixels with equal
|
||||
gray level strictly greater than the gray level of all pixels in direct
|
||||
neighborhood of the set.
|
||||
|
||||
A local maximum M of height h is a local maximum for which
|
||||
there is at least one path joining M with an equal or higher local maximum
|
||||
on which the minimal value is f(M) - h (i.e. the values along the path
|
||||
are not decreasing by more than h with respect to the maximum's value)
|
||||
and no path to an equal or higher local maximum for which the minimal
|
||||
value is greater.
|
||||
|
||||
The global maxima of the image are also found by this function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the maxima are to be calculated.
|
||||
h : unsigned integer
|
||||
The minimal height of all extracted maxima.
|
||||
footprint : ndarray, optional
|
||||
The neighborhood expressed as an n-D array of 1's and 0's.
|
||||
Default is the ball of radius 1 according to the maximum norm
|
||||
(i.e. a 3x3 square for 2D images, a 3x3x3 cube for 3D images, etc.)
|
||||
|
||||
Returns
|
||||
-------
|
||||
h_max : ndarray
|
||||
The local maxima of height >= h and the global maxima.
|
||||
The resulting image is a binary image, where pixels belonging to
|
||||
the determined maxima take value 1, the others take value 0.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.extrema.h_minima
|
||||
skimage.morphology.extrema.local_maxima
|
||||
skimage.morphology.extrema.local_minima
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Soille, P., "Morphological Image Analysis: Principles and
|
||||
Applications" (Chapter 6), 2nd edition (2003), ISBN 3540429883.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import extrema
|
||||
|
||||
We create an image (quadratic function with a maximum in the center and
|
||||
4 additional constant maxima.
|
||||
The heights of the maxima are: 1, 21, 41, 61, 81
|
||||
|
||||
>>> w = 10
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 20 - 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:4,2:4] = 40; f[2:4,7:9] = 60; f[7:9,2:4] = 80; f[7:9,7:9] = 100
|
||||
>>> f = f.astype(int)
|
||||
|
||||
We can calculate all maxima with a height of at least 40:
|
||||
|
||||
>>> maxima = extrema.h_maxima(f, 40)
|
||||
|
||||
The resulting image will contain 3 local maxima.
|
||||
"""
|
||||
|
||||
# Check for h value that is larger then range of the image. If this
|
||||
# is True then there are no h-maxima in the image.
|
||||
if h > np.ptp(image):
|
||||
return np.zeros(image.shape, dtype=np.uint8)
|
||||
|
||||
# Check for floating point h value. For this to work properly
|
||||
# we need to explicitly convert image to float64.
|
||||
#
|
||||
# FIXME: This could give incorrect results if image is int64 and
|
||||
# has a very high dynamic range. The dtype of image is
|
||||
# changed to float64, and different integer values could
|
||||
# become the same float due to rounding.
|
||||
#
|
||||
# >>> ii64 = np.iinfo(np.int64)
|
||||
# >>> a = np.array([ii64.max, ii64.max - 2])
|
||||
# >>> a[0] == a[1]
|
||||
# False
|
||||
# >>> b = a.astype(np.float64)
|
||||
# >>> b[0] == b[1]
|
||||
# True
|
||||
#
|
||||
if np.issubdtype(type(h), np.floating) and \
|
||||
np.issubdtype(image.dtype, np.integer):
|
||||
if ((h % 1) != 0):
|
||||
warn('possible precision loss converting image to '
|
||||
'floating point. To silence this warning, '
|
||||
'ensure image and h have same data type.',
|
||||
stacklevel=2)
|
||||
image = image.astype(float)
|
||||
else:
|
||||
h = image.dtype.type(h)
|
||||
|
||||
if (h == 0):
|
||||
raise ValueError("h = 0 is ambiguous, use local_maxima() "
|
||||
"instead?")
|
||||
|
||||
if np.issubdtype(image.dtype, np.floating):
|
||||
# The purpose of the resolution variable is to allow for the
|
||||
# small rounding errors that inevitably occur when doing
|
||||
# floating point arithmetic. We want shifted_img to be
|
||||
# guaranteed to be h less than image. If we only subtract h
|
||||
# there may be pixels were shifted_img ends up being
|
||||
# slightly greater than image - h.
|
||||
#
|
||||
# The resolution is scaled based on the pixel values in the
|
||||
# image because floating point precision is relative. A
|
||||
# very large value of 1.0e10 will have a large precision,
|
||||
# say +-1.0e4, and a very small value of 1.0e-10 will have
|
||||
# a very small precision, say +-1.0e-16.
|
||||
#
|
||||
resolution = 2 * np.finfo(image.dtype).resolution * np.abs(image)
|
||||
shifted_img = image - h - resolution
|
||||
else:
|
||||
shifted_img = _subtract_constant_clip(image, h)
|
||||
|
||||
rec_img = grayreconstruct.reconstruction(shifted_img, image,
|
||||
method='dilation',
|
||||
footprint=footprint)
|
||||
residue_img = image - rec_img
|
||||
return (residue_img >= h).astype(np.uint8)
|
||||
|
||||
|
||||
def h_minima(image, h, footprint=None):
|
||||
"""Determine all minima of the image with depth >= h.
|
||||
|
||||
The local minima are defined as connected sets of pixels with equal
|
||||
gray level strictly smaller than the gray levels of all pixels in direct
|
||||
neighborhood of the set.
|
||||
|
||||
A local minimum M of depth h is a local minimum for which
|
||||
there is at least one path joining M with an equal or lower local minimum
|
||||
on which the maximal value is f(M) + h (i.e. the values along the path
|
||||
are not increasing by more than h with respect to the minimum's value)
|
||||
and no path to an equal or lower local minimum for which the maximal
|
||||
value is smaller.
|
||||
|
||||
The global minima of the image are also found by this function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the minima are to be calculated.
|
||||
h : unsigned integer
|
||||
The minimal depth of all extracted minima.
|
||||
footprint : ndarray, optional
|
||||
The neighborhood expressed as an n-D array of 1's and 0's.
|
||||
Default is the ball of radius 1 according to the maximum norm
|
||||
(i.e. a 3x3 square for 2D images, a 3x3x3 cube for 3D images, etc.)
|
||||
|
||||
Returns
|
||||
-------
|
||||
h_min : ndarray
|
||||
The local minima of depth >= h and the global minima.
|
||||
The resulting image is a binary image, where pixels belonging to
|
||||
the determined minima take value 1, the others take value 0.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.extrema.h_maxima
|
||||
skimage.morphology.extrema.local_maxima
|
||||
skimage.morphology.extrema.local_minima
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Soille, P., "Morphological Image Analysis: Principles and
|
||||
Applications" (Chapter 6), 2nd edition (2003), ISBN 3540429883.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import extrema
|
||||
|
||||
We create an image (quadratic function with a minimum in the center and
|
||||
4 additional constant maxima.
|
||||
The depth of the minima are: 1, 21, 41, 61, 81
|
||||
|
||||
>>> w = 10
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 180 + 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:4,2:4] = 160; f[2:4,7:9] = 140; f[7:9,2:4] = 120; f[7:9,7:9] = 100
|
||||
>>> f = f.astype(int)
|
||||
|
||||
We can calculate all minima with a depth of at least 40:
|
||||
|
||||
>>> minima = extrema.h_minima(f, 40)
|
||||
|
||||
The resulting image will contain 3 local minima.
|
||||
"""
|
||||
if h > np.ptp(image):
|
||||
return np.zeros(image.shape, dtype=np.uint8)
|
||||
|
||||
if np.issubdtype(type(h), np.floating) and \
|
||||
np.issubdtype(image.dtype, np.integer):
|
||||
if ((h % 1) != 0):
|
||||
warn('possible precision loss converting image to '
|
||||
'floating point. To silence this warning, '
|
||||
'ensure image and h have same data type.',
|
||||
stacklevel=2)
|
||||
image = image.astype(float)
|
||||
else:
|
||||
h = image.dtype.type(h)
|
||||
|
||||
if (h == 0):
|
||||
raise ValueError("h = 0 is ambiguous, use local_minima() "
|
||||
"instead?")
|
||||
|
||||
if np.issubdtype(image.dtype, np.floating):
|
||||
resolution = 2 * np.finfo(image.dtype).resolution * np.abs(image)
|
||||
shifted_img = image + h + resolution
|
||||
else:
|
||||
shifted_img = _add_constant_clip(image, h)
|
||||
|
||||
rec_img = grayreconstruct.reconstruction(shifted_img, image,
|
||||
method='erosion',
|
||||
footprint=footprint)
|
||||
residue_img = rec_img - image
|
||||
return (residue_img >= h).astype(np.uint8)
|
||||
|
||||
|
||||
def local_maxima(image, footprint=None, connectivity=None, indices=False,
|
||||
allow_borders=True):
|
||||
"""Find local maxima of n-dimensional array.
|
||||
|
||||
The local maxima are defined as connected sets of pixels with equal gray
|
||||
level (plateaus) strictly greater than the gray levels of all pixels in the
|
||||
neighborhood.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
An n-dimensional array.
|
||||
footprint : ndarray, optional
|
||||
The footprint (structuring element) used to determine the neighborhood
|
||||
of each evaluated pixel (``True`` denotes a connected pixel). It must
|
||||
be a boolean array and have the same number of dimensions as `image`.
|
||||
If neither `footprint` nor `connectivity` are given, all adjacent
|
||||
pixels are considered as part of the neighborhood.
|
||||
connectivity : int, optional
|
||||
A number used to determine the neighborhood of each evaluated pixel.
|
||||
Adjacent pixels whose squared distance from the center is less than or
|
||||
equal to `connectivity` are considered neighbors. Ignored if
|
||||
`footprint` is not None.
|
||||
indices : bool, optional
|
||||
If True, the output will be a tuple of one-dimensional arrays
|
||||
representing the indices of local maxima in each dimension. If False,
|
||||
the output will be a boolean array with the same shape as `image`.
|
||||
allow_borders : bool, optional
|
||||
If true, plateaus that touch the image border are valid maxima.
|
||||
|
||||
Returns
|
||||
-------
|
||||
maxima : ndarray or tuple[ndarray]
|
||||
If `indices` is false, a boolean array with the same shape as `image`
|
||||
is returned with ``True`` indicating the position of local maxima
|
||||
(``False`` otherwise). If `indices` is true, a tuple of one-dimensional
|
||||
arrays containing the coordinates (indices) of all found maxima.
|
||||
|
||||
Warns
|
||||
-----
|
||||
UserWarning
|
||||
If `allow_borders` is false and any dimension of the given `image` is
|
||||
shorter than 3 samples, maxima can't exist and a warning is shown.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.local_minima
|
||||
skimage.morphology.h_maxima
|
||||
skimage.morphology.h_minima
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function operates on the following ideas:
|
||||
|
||||
1. Make a first pass over the image's last dimension and flag candidates
|
||||
for local maxima by comparing pixels in only one direction.
|
||||
If the pixels aren't connected in the last dimension all pixels are
|
||||
flagged as candidates instead.
|
||||
|
||||
For each candidate:
|
||||
|
||||
2. Perform a flood-fill to find all connected pixels that have the same
|
||||
gray value and are part of the plateau.
|
||||
3. Consider the connected neighborhood of a plateau: if no bordering sample
|
||||
has a higher gray level, mark the plateau as a definite local maximum.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.morphology import local_maxima
|
||||
>>> image = np.zeros((4, 7), dtype=int)
|
||||
>>> image[1:3, 1:3] = 1
|
||||
>>> image[3, 0] = 1
|
||||
>>> image[1:3, 4:6] = 2
|
||||
>>> image[3, 6] = 3
|
||||
>>> image
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3]])
|
||||
|
||||
Find local maxima by comparing to all neighboring pixels (maximal
|
||||
connectivity):
|
||||
|
||||
>>> local_maxima(image)
|
||||
array([[False, False, False, False, False, False, False],
|
||||
[False, True, True, False, False, False, False],
|
||||
[False, True, True, False, False, False, False],
|
||||
[ True, False, False, False, False, False, True]])
|
||||
>>> local_maxima(image, indices=True)
|
||||
(array([1, 1, 2, 2, 3, 3]), array([1, 2, 1, 2, 0, 6]))
|
||||
|
||||
Find local maxima without comparing to diagonal pixels (connectivity 1):
|
||||
|
||||
>>> local_maxima(image, connectivity=1)
|
||||
array([[False, False, False, False, False, False, False],
|
||||
[False, True, True, False, True, True, False],
|
||||
[False, True, True, False, True, True, False],
|
||||
[ True, False, False, False, False, False, True]])
|
||||
|
||||
and exclude maxima that border the image edge:
|
||||
|
||||
>>> local_maxima(image, connectivity=1, allow_borders=False)
|
||||
array([[False, False, False, False, False, False, False],
|
||||
[False, True, True, False, True, True, False],
|
||||
[False, True, True, False, True, True, False],
|
||||
[False, False, False, False, False, False, False]])
|
||||
"""
|
||||
image = np.asarray(image, order="C")
|
||||
if image.size == 0:
|
||||
# Return early for empty input
|
||||
if indices:
|
||||
# Make sure that output is a tuple of 1 empty array per dimension
|
||||
return np.nonzero(image)
|
||||
else:
|
||||
return np.zeros(image.shape, dtype=bool)
|
||||
|
||||
if allow_borders:
|
||||
# Ensure that local maxima are always at least one smaller sample away
|
||||
# from the image border
|
||||
image = np.pad(image, 1, mode='constant', constant_values=image.min())
|
||||
|
||||
# Array of flags used to store the state of each pixel during evaluation.
|
||||
# See _extrema_cy.pyx for their meaning
|
||||
flags = np.zeros(image.shape, dtype=np.uint8)
|
||||
_util._set_border_values(flags, value=3)
|
||||
|
||||
if any(s < 3 for s in image.shape):
|
||||
# Warn and skip if any dimension is smaller than 3
|
||||
# -> no maxima can exist & footprint can't be applied
|
||||
warn(
|
||||
"maxima can't exist for an image with any dimension smaller 3 "
|
||||
"if borders aren't allowed",
|
||||
stacklevel=3
|
||||
)
|
||||
else:
|
||||
footprint = _util._resolve_neighborhood(footprint, connectivity,
|
||||
image.ndim)
|
||||
neighbor_offsets = _util._offsets_to_raveled_neighbors(
|
||||
image.shape, footprint, center=((1,) * image.ndim)
|
||||
)
|
||||
|
||||
try:
|
||||
_local_maxima(image.ravel(), flags.ravel(), neighbor_offsets)
|
||||
except TypeError:
|
||||
if image.dtype == np.float16:
|
||||
# Provide the user with clearer error message
|
||||
raise TypeError("dtype of `image` is float16 which is not "
|
||||
"supported, try upcasting to float32")
|
||||
else:
|
||||
raise # Otherwise raise original message
|
||||
|
||||
if allow_borders:
|
||||
# Revert padding performed at the beginning of the function
|
||||
flags = crop(flags, 1)
|
||||
else:
|
||||
# No padding was performed but set edge values back to 0
|
||||
_util._set_border_values(flags, value=0)
|
||||
|
||||
if indices:
|
||||
return np.nonzero(flags)
|
||||
else:
|
||||
return flags.view(bool)
|
||||
|
||||
|
||||
def local_minima(image, footprint=None, connectivity=None, indices=False,
|
||||
allow_borders=True):
|
||||
"""Find local minima of n-dimensional array.
|
||||
|
||||
The local minima are defined as connected sets of pixels with equal gray
|
||||
level (plateaus) strictly smaller than the gray levels of all pixels in the
|
||||
neighborhood.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
An n-dimensional array.
|
||||
footprint : ndarray, optional
|
||||
The footprint (structuring element) used to determine the neighborhood
|
||||
of each evaluated pixel (``True`` denotes a connected pixel). It must
|
||||
be a boolean array and have the same number of dimensions as `image`.
|
||||
If neither `footprint` nor `connectivity` are given, all adjacent
|
||||
pixels are considered as part of the neighborhood.
|
||||
connectivity : int, optional
|
||||
A number used to determine the neighborhood of each evaluated pixel.
|
||||
Adjacent pixels whose squared distance from the center is less than or
|
||||
equal to `connectivity` are considered neighbors. Ignored if
|
||||
`footprint` is not None.
|
||||
indices : bool, optional
|
||||
If True, the output will be a tuple of one-dimensional arrays
|
||||
representing the indices of local minima in each dimension. If False,
|
||||
the output will be a boolean array with the same shape as `image`.
|
||||
allow_borders : bool, optional
|
||||
If true, plateaus that touch the image border are valid minima.
|
||||
|
||||
Returns
|
||||
-------
|
||||
minima : ndarray or tuple[ndarray]
|
||||
If `indices` is false, a boolean array with the same shape as `image`
|
||||
is returned with ``True`` indicating the position of local minima
|
||||
(``False`` otherwise). If `indices` is true, a tuple of one-dimensional
|
||||
arrays containing the coordinates (indices) of all found minima.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.local_maxima
|
||||
skimage.morphology.h_maxima
|
||||
skimage.morphology.h_minima
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function operates on the following ideas:
|
||||
|
||||
1. Make a first pass over the image's last dimension and flag candidates
|
||||
for local minima by comparing pixels in only one direction.
|
||||
If the pixels aren't connected in the last dimension all pixels are
|
||||
flagged as candidates instead.
|
||||
|
||||
For each candidate:
|
||||
|
||||
2. Perform a flood-fill to find all connected pixels that have the same
|
||||
gray value and are part of the plateau.
|
||||
3. Consider the connected neighborhood of a plateau: if no bordering sample
|
||||
has a smaller gray level, mark the plateau as a definite local minimum.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.morphology import local_minima
|
||||
>>> image = np.zeros((4, 7), dtype=int)
|
||||
>>> image[1:3, 1:3] = -1
|
||||
>>> image[3, 0] = -1
|
||||
>>> image[1:3, 4:6] = -2
|
||||
>>> image[3, 6] = -3
|
||||
>>> image
|
||||
array([[ 0, 0, 0, 0, 0, 0, 0],
|
||||
[ 0, -1, -1, 0, -2, -2, 0],
|
||||
[ 0, -1, -1, 0, -2, -2, 0],
|
||||
[-1, 0, 0, 0, 0, 0, -3]])
|
||||
|
||||
Find local minima by comparing to all neighboring pixels (maximal
|
||||
connectivity):
|
||||
|
||||
>>> local_minima(image)
|
||||
array([[False, False, False, False, False, False, False],
|
||||
[False, True, True, False, False, False, False],
|
||||
[False, True, True, False, False, False, False],
|
||||
[ True, False, False, False, False, False, True]])
|
||||
>>> local_minima(image, indices=True)
|
||||
(array([1, 1, 2, 2, 3, 3]), array([1, 2, 1, 2, 0, 6]))
|
||||
|
||||
Find local minima without comparing to diagonal pixels (connectivity 1):
|
||||
|
||||
>>> local_minima(image, connectivity=1)
|
||||
array([[False, False, False, False, False, False, False],
|
||||
[False, True, True, False, True, True, False],
|
||||
[False, True, True, False, True, True, False],
|
||||
[ True, False, False, False, False, False, True]])
|
||||
|
||||
and exclude minima that border the image edge:
|
||||
|
||||
>>> local_minima(image, connectivity=1, allow_borders=False)
|
||||
array([[False, False, False, False, False, False, False],
|
||||
[False, True, True, False, True, True, False],
|
||||
[False, True, True, False, True, True, False],
|
||||
[False, False, False, False, False, False, False]])
|
||||
"""
|
||||
return local_maxima(
|
||||
image=invert(image),
|
||||
footprint=footprint,
|
||||
connectivity=connectivity,
|
||||
indices=indices,
|
||||
allow_borders=allow_borders
|
||||
)
|
||||
961
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/footprints.py
vendored
Normal file
961
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/footprints.py
vendored
Normal file
@@ -0,0 +1,961 @@
|
||||
import os
|
||||
from collections.abc import Sequence
|
||||
from numbers import Integral
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .. import draw
|
||||
from skimage import morphology
|
||||
|
||||
# Precomputed ball and disk decompositions were saved as 2D arrays where the
|
||||
# radius of the desired decomposition is used to index into the first axis of
|
||||
# the array. The values at a given radius corresponds to the number of
|
||||
# repetitions of 3 different types elementary of structuring elements.
|
||||
#
|
||||
# See _nsphere_series_decomposition for full details.
|
||||
_nsphere_decompositions = {}
|
||||
_nsphere_decompositions[2] = np.load(
|
||||
os.path.join(os.path.dirname(__file__), 'disk_decompositions.npy'))
|
||||
_nsphere_decompositions[3] = np.load(
|
||||
os.path.join(os.path.dirname(__file__), 'ball_decompositions.npy'))
|
||||
|
||||
|
||||
def _footprint_is_sequence(footprint):
|
||||
if hasattr(footprint, '__array_interface__'):
|
||||
return False
|
||||
|
||||
def _validate_sequence_element(t):
|
||||
return (
|
||||
isinstance(t, Sequence)
|
||||
and len(t) == 2
|
||||
and hasattr(t[0], '__array_interface__')
|
||||
and isinstance(t[1], Integral)
|
||||
)
|
||||
|
||||
if isinstance(footprint, Sequence):
|
||||
if not all(_validate_sequence_element(t) for t in footprint):
|
||||
raise ValueError(
|
||||
"All elements of footprint sequence must be a 2-tuple where "
|
||||
"the first element of the tuple is an ndarray and the second "
|
||||
"is an integer indicating the number of iterations."
|
||||
)
|
||||
else:
|
||||
raise ValueError("footprint must be either an ndarray or Sequence")
|
||||
return True
|
||||
|
||||
|
||||
def _shape_from_sequence(footprints, require_odd_size=False):
|
||||
"""Determine the shape of composite footprint
|
||||
|
||||
In the future if we only want to support odd-sized square, we may want to
|
||||
change this to require_odd_size
|
||||
"""
|
||||
if not _footprint_is_sequence(footprints):
|
||||
raise ValueError("expected a sequence of footprints")
|
||||
ndim = footprints[0][0].ndim
|
||||
shape = [0] * ndim
|
||||
|
||||
def _odd_size(size, require_odd_size):
|
||||
if require_odd_size and size % 2 == 0:
|
||||
raise ValueError(
|
||||
"expected all footprint elements to have odd size"
|
||||
)
|
||||
|
||||
for d in range(ndim):
|
||||
fp, nreps = footprints[0]
|
||||
_odd_size(fp.shape[d], require_odd_size)
|
||||
shape[d] = fp.shape[d] + (nreps - 1) * (fp.shape[d] - 1)
|
||||
for fp, nreps in footprints[1:]:
|
||||
_odd_size(fp.shape[d], require_odd_size)
|
||||
shape[d] += nreps * (fp.shape[d] - 1)
|
||||
return tuple(shape)
|
||||
|
||||
|
||||
def footprint_from_sequence(footprints):
|
||||
"""Convert a footprint sequence into an equivalent ndarray.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
footprints : tuple of 2-tuples
|
||||
A sequence of footprint tuples where the first element of each tuple
|
||||
is an array corresponding to a footprint and the second element is the
|
||||
number of times it is to be applied. Currently all footprints should
|
||||
have odd size.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray
|
||||
An single array equivalent to applying the sequence of `footprints`.
|
||||
"""
|
||||
|
||||
# Create a single pixel image of sufficient size and apply binary dilation.
|
||||
shape = _shape_from_sequence(footprints)
|
||||
imag = np.zeros(shape, dtype=bool)
|
||||
imag[tuple(s // 2 for s in shape)] = 1
|
||||
return morphology.binary_dilation(imag, footprints)
|
||||
|
||||
|
||||
def square(width, dtype=np.uint8, *, decomposition=None):
|
||||
"""Generates a flat, square-shaped footprint.
|
||||
|
||||
Every pixel along the perimeter has a chessboard distance
|
||||
no greater than radius (radius=floor(width/2)) pixels.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : int
|
||||
The width and height of the square.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
decomposition : {None, 'separable', 'sequence'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given an identical result to a single, larger footprint, but often with
|
||||
better computational performance. See Notes for more details.
|
||||
With 'separable', this function uses separable 1D footprints for each
|
||||
axis. Whether 'seqeunce' or 'separable' is computationally faster may
|
||||
be architecture-dependent.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray or tuple
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
When `decomposition` is None, this is just a numpy.ndarray. Otherwise,
|
||||
this will be a tuple whose length is equal to the number of unique
|
||||
structuring elements to apply (see Notes for more detail)
|
||||
|
||||
Notes
|
||||
-----
|
||||
When `decomposition` is not None, each element of the `footprint`
|
||||
tuple is a 2-tuple of the form ``(ndarray, num_iter)`` that specifies a
|
||||
footprint array and the number of iterations it is to be applied.
|
||||
|
||||
For binary morphology, using ``decomposition='sequence'`` or
|
||||
``decomposition='separable'`` were observed to give better performance than
|
||||
``decomposition=None``, with the magnitude of the performance increase
|
||||
rapidly increasing with footprint size. For grayscale morphology with
|
||||
square footprints, it is recommended to use ``decomposition=None`` since
|
||||
the internal SciPy functions that are called already have a fast
|
||||
implementation based on separable 1D sliding windows.
|
||||
|
||||
The 'sequence' decomposition mode only supports odd valued `width`. If
|
||||
`width` is even, the sequence used will be identical to the 'separable'
|
||||
mode.
|
||||
"""
|
||||
if decomposition is None:
|
||||
return np.ones((width, width), dtype=dtype)
|
||||
|
||||
if decomposition == 'separable' or width % 2 == 0:
|
||||
sequence = [(np.ones((width, 1), dtype=dtype), 1),
|
||||
(np.ones((1, width), dtype=dtype), 1)]
|
||||
elif decomposition == 'sequence':
|
||||
# only handles odd widths
|
||||
sequence = [(np.ones((3, 3), dtype=dtype), _decompose_size(width, 3))]
|
||||
else:
|
||||
raise ValueError(f"Unrecognized decomposition: {decomposition}")
|
||||
return tuple(sequence)
|
||||
|
||||
|
||||
def _decompose_size(size, kernel_size=3):
|
||||
"""Determine number of repeated iterations for a `kernel_size` kernel.
|
||||
|
||||
Returns how many repeated morphology operations with an element of size
|
||||
`kernel_size` is equivalent to a morphology with a single kernel of size
|
||||
`n`.
|
||||
|
||||
"""
|
||||
if kernel_size % 2 != 1:
|
||||
raise ValueError("only odd length kernel_size is supported")
|
||||
return 1 + (size - kernel_size) // (kernel_size - 1)
|
||||
|
||||
|
||||
def rectangle(nrows, ncols, dtype=np.uint8, *, decomposition=None):
|
||||
"""Generates a flat, rectangular-shaped footprint.
|
||||
|
||||
Every pixel in the rectangle generated for a given width and given height
|
||||
belongs to the neighborhood.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
nrows : int
|
||||
The number of rows of the rectangle.
|
||||
ncols : int
|
||||
The number of columns of the rectangle.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
decomposition : {None, 'separable', 'sequence'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given an identical result to a single, larger footprint, but often with
|
||||
better computational performance. See Notes for more details.
|
||||
With 'separable', this function uses separable 1D footprints for each
|
||||
axis. Whether 'sequence' or 'separable' is computationally faster may
|
||||
be architecture-dependent.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray or tuple
|
||||
A footprint consisting only of ones, i.e. every pixel belongs to the
|
||||
neighborhood. When `decomposition` is None, this is just a
|
||||
numpy.ndarray. Otherwise, this will be a tuple whose length is equal to
|
||||
the number of unique structuring elements to apply (see Notes for more
|
||||
detail)
|
||||
|
||||
Notes
|
||||
-----
|
||||
When `decomposition` is not None, each element of the `footprint`
|
||||
tuple is a 2-tuple of the form ``(ndarray, num_iter)`` that specifies a
|
||||
footprint array and the number of iterations it is to be applied.
|
||||
|
||||
For binary morphology, using ``decomposition='sequence'``
|
||||
was observed to give better performance, with the magnitude of the
|
||||
performance increase rapidly increasing with footprint size. For grayscale
|
||||
morphology with rectangular footprints, it is recommended to use
|
||||
``decomposition=None`` since the internal SciPy functions that are called
|
||||
already have a fast implementation based on separable 1D sliding windows.
|
||||
|
||||
The `sequence` decomposition mode only supports odd valued `nrows` and
|
||||
`ncols`. If either `nrows` or `ncols` is even, the sequence used will be
|
||||
identical to ``decomposition='separable'``.
|
||||
|
||||
- The use of ``width`` and ``height`` has been deprecated in
|
||||
version 0.18.0. Use ``nrows`` and ``ncols`` instead.
|
||||
"""
|
||||
if decomposition is None: # TODO: check optimal width setting here
|
||||
return np.ones((nrows, ncols), dtype=dtype)
|
||||
|
||||
even_rows = nrows % 2 == 0
|
||||
even_cols = ncols % 2 == 0
|
||||
if decomposition == 'separable' or even_rows or even_cols:
|
||||
sequence = [(np.ones((nrows, 1), dtype=dtype), 1),
|
||||
(np.ones((1, ncols), dtype=dtype), 1)]
|
||||
elif decomposition == 'sequence':
|
||||
# this branch only support odd nrows, ncols
|
||||
sq_size = 3
|
||||
sq_reps = _decompose_size(min(nrows, ncols), sq_size)
|
||||
sequence = [(np.ones((3, 3), dtype=dtype), sq_reps)]
|
||||
if nrows > ncols:
|
||||
nextra = nrows - ncols
|
||||
sequence.append(
|
||||
(np.ones((nextra + 1, 1), dtype=dtype), 1)
|
||||
)
|
||||
elif ncols > nrows:
|
||||
nextra = ncols - nrows
|
||||
sequence.append(
|
||||
(np.ones((1, nextra + 1), dtype=dtype), 1)
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unrecognized decomposition: {decomposition}")
|
||||
return tuple(sequence)
|
||||
|
||||
|
||||
def diamond(radius, dtype=np.uint8, *, decomposition=None):
|
||||
"""Generates a flat, diamond-shaped footprint.
|
||||
|
||||
A pixel is part of the neighborhood (i.e. labeled 1) if
|
||||
the city block/Manhattan distance between it and the center of
|
||||
the neighborhood is no greater than radius.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
The radius of the diamond-shaped footprint.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
decomposition : {None, 'sequence'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given an identical result to a single, larger footprint, but with
|
||||
better computational performance. See Notes for more details.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray or tuple
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
When `decomposition` is None, this is just a numpy.ndarray. Otherwise,
|
||||
this will be a tuple whose length is equal to the number of unique
|
||||
structuring elements to apply (see Notes for more detail)
|
||||
|
||||
Notes
|
||||
-----
|
||||
When `decomposition` is not None, each element of the `footprint`
|
||||
tuple is a 2-tuple of the form ``(ndarray, num_iter)`` that specifies a
|
||||
footprint array and the number of iterations it is to be applied.
|
||||
|
||||
For either binary or grayscale morphology, using
|
||||
``decomposition='sequence'`` was observed to have a performance benefit,
|
||||
with the magnitude of the benefit increasing with increasing footprint
|
||||
size.
|
||||
|
||||
"""
|
||||
if decomposition is None:
|
||||
L = np.arange(0, radius * 2 + 1)
|
||||
I, J = np.meshgrid(L, L)
|
||||
footprint = np.array(np.abs(I - radius) + np.abs(J - radius) <= radius,
|
||||
dtype=dtype)
|
||||
elif decomposition == 'sequence':
|
||||
fp = diamond(1, dtype=dtype, decomposition=None)
|
||||
nreps = _decompose_size(2 * radius + 1, fp.shape[0])
|
||||
footprint = ((fp, nreps),)
|
||||
else:
|
||||
raise ValueError(f"Unrecognized decomposition: {decomposition}")
|
||||
return footprint
|
||||
|
||||
|
||||
def _nsphere_series_decomposition(radius, ndim, dtype=np.uint8):
|
||||
"""Generate a sequence of footprints approximating an n-sphere.
|
||||
|
||||
Morphological operations with an n-sphere (hypersphere) footprint can be
|
||||
approximated by applying a series of smaller footprints of extent 3 along
|
||||
each axis. Specific solutions for this are given in [1]_ for the case of
|
||||
2D disks with radius 2 through 10.
|
||||
|
||||
Here we used n-dimensional extensions of the "square", "diamond" and
|
||||
"t-shaped" elements from that publication. All of these elementary elements
|
||||
have size ``(3,) * ndim``. We numerically computed the number of
|
||||
repetitions of each element that gives the closest match to the disk
|
||||
(in 2D) or ball (in 3D) computed with ``decomposition=None``.
|
||||
|
||||
The approach can be extended to higher dimensions, but we have only stored
|
||||
results for 2D and 3D at this point.
|
||||
|
||||
Empirically, the shapes at large radius approach a hexadecagon
|
||||
(16-sides [2]_) in 2D and a rhombicuboctahedron (26-faces, [3]_) in 3D.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Park, H and Chin R.T. Decomposition of structuring elements for
|
||||
optimal implementation of morphological operations. In Proceedings:
|
||||
1997 IEEE Workshop on Nonlinear Signal and Image Processing, London,
|
||||
UK.
|
||||
https://www.iwaenc.org/proceedings/1997/nsip97/pdf/scan/ns970226.pdf
|
||||
.. [2] https://en.wikipedia.org/wiki/Hexadecagon
|
||||
.. [3] https://en.wikipedia.org/wiki/Rhombicuboctahedron
|
||||
"""
|
||||
|
||||
if radius == 1:
|
||||
# for radius 1 just use the exact shape (3,) * ndim solution
|
||||
kwargs = dict(dtype=dtype, strict_radius=False, decomposition=None)
|
||||
if ndim == 2:
|
||||
return ((disk(1, **kwargs), 1),)
|
||||
elif ndim == 3:
|
||||
return ((ball(1, **kwargs), 1),)
|
||||
|
||||
# load precomputed decompositions
|
||||
if ndim not in _nsphere_decompositions:
|
||||
raise ValueError(
|
||||
"sequence decompositions are only currently available for "
|
||||
"2d disks or 3d balls"
|
||||
)
|
||||
precomputed_decompositions = _nsphere_decompositions[ndim]
|
||||
max_radius = precomputed_decompositions.shape[0]
|
||||
if radius > max_radius:
|
||||
raise ValueError(
|
||||
f"precomputed {ndim}D decomposition unavailable for "
|
||||
f"radius > {max_radius}"
|
||||
)
|
||||
num_t_series, num_diamond, num_square = precomputed_decompositions[radius]
|
||||
|
||||
sequence = []
|
||||
if num_t_series > 0:
|
||||
# shape (3, ) * ndim "T-shaped" footprints
|
||||
all_t = _t_shaped_element_series(ndim=ndim, dtype=dtype)
|
||||
[sequence.append((t, num_t_series)) for t in all_t]
|
||||
if num_diamond > 0:
|
||||
d = np.zeros((3,) * ndim, dtype=dtype)
|
||||
sl = [slice(1, 2)] * ndim
|
||||
for ax in range(ndim):
|
||||
sl[ax] = slice(None)
|
||||
d[tuple(sl)] = 1
|
||||
sl[ax] = slice(1, 2)
|
||||
sequence.append((d, num_diamond))
|
||||
if num_square > 0:
|
||||
sq = np.ones((3, ) * ndim, dtype=dtype)
|
||||
sequence.append((sq, num_square))
|
||||
return tuple(sequence)
|
||||
|
||||
|
||||
def _t_shaped_element_series(ndim=2, dtype=np.uint8):
|
||||
"""A series of T-shaped structuring elements.
|
||||
|
||||
In the 2D case this is a T-shaped element and its rotation at multiples of
|
||||
90 degrees. This series is used in efficient decompositions of disks of
|
||||
various radius as published in [1]_.
|
||||
|
||||
The generalization to the n-dimensional case can be performed by having the
|
||||
"top" of the T to extend in (ndim - 1) dimensions and then producing a
|
||||
series of rotations such that the bottom end of the T points along each of
|
||||
``2 * ndim`` orthogonal directions.
|
||||
"""
|
||||
if ndim == 2:
|
||||
# The n-dimensional case produces the same set of footprints, but
|
||||
# the 2D example is retained here for clarity.
|
||||
t0 = np.array([[1, 1, 1],
|
||||
[0, 1, 0],
|
||||
[0, 1, 0]], dtype=dtype)
|
||||
t90 = np.rot90(t0, 1)
|
||||
t180 = np.rot90(t0, 2)
|
||||
t270 = np.rot90(t0, 3)
|
||||
return t0, t90, t180, t270
|
||||
else:
|
||||
# ndimensional generalization of the 2D case above
|
||||
all_t = []
|
||||
for ax in range(ndim):
|
||||
for idx in [0, 2]:
|
||||
t = np.zeros((3,) * ndim, dtype=dtype)
|
||||
sl = [slice(None)] * ndim
|
||||
sl[ax] = slice(idx, idx + 1)
|
||||
t[tuple(sl)] = 1
|
||||
sl = [slice(1, 2)] * ndim
|
||||
sl[ax] = slice(None)
|
||||
t[tuple(sl)] = 1
|
||||
all_t.append(t)
|
||||
return tuple(all_t)
|
||||
|
||||
|
||||
def disk(radius, dtype=np.uint8, *, strict_radius=True, decomposition=None):
|
||||
"""Generates a flat, disk-shaped footprint.
|
||||
|
||||
A pixel is within the neighborhood if the Euclidean distance between
|
||||
it and the origin is no greater than radius (This is only approximately
|
||||
True, when `decomposition == 'sequence'`).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
The radius of the disk-shaped footprint.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
strict_radius : bool, optional
|
||||
If False, extend the radius by 0.5. This allows the circle to expand
|
||||
further within a cube that remains of size ``2 * radius + 1`` along
|
||||
each axis. This parameter is ignored if decomposition is not None.
|
||||
decomposition : {None, 'sequence', 'crosses'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given a result equivalent to a single, larger footprint, but with
|
||||
better computational performance. For disk footprints, the 'sequence'
|
||||
or 'crosses' decompositions are not always exactly equivalent to
|
||||
``decomposition=None``. See Notes for more details.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
|
||||
Notes
|
||||
-----
|
||||
When `decomposition` is not None, each element of the `footprint`
|
||||
tuple is a 2-tuple of the form ``(ndarray, num_iter)`` that specifies a
|
||||
footprint array and the number of iterations it is to be applied.
|
||||
|
||||
The disk produced by the ``decomposition='sequence'`` mode may not be
|
||||
identical to that with ``decomposition=None``. A disk footprint can be
|
||||
approximated by applying a series of smaller footprints of extent 3 along
|
||||
each axis. Specific solutions for this are given in [1]_ for the case of
|
||||
2D disks with radius 2 through 10. Here, we numerically computed the number
|
||||
of repetitions of each element that gives the closest match to the disk
|
||||
computed with kwargs ``strict_radius=False, decomposition=None``.
|
||||
|
||||
Empirically, the series decomposition at large radius approaches a
|
||||
hexadecagon (a 16-sided polygon [2]_). In [3]_, the authors demonstrate
|
||||
that a hexadecagon is the closest approximation to a disk that can be
|
||||
achieved for decomposition with footprints of shape (3, 3).
|
||||
|
||||
The disk produced by the ``decomposition='crosses'`` is often but not
|
||||
always identical to that with ``decomposition=None``. It tends to give a
|
||||
closer approximation than ``decomposition='sequence'``, at a performance
|
||||
that is fairly comparable. The individual cross-shaped elements are not
|
||||
limited to extent (3, 3) in size. Unlike the 'seqeuence' decomposition, the
|
||||
'crosses' decomposition can also accurately approximate the shape of disks
|
||||
with ``strict_radius=True``. The method is based on an adaption of
|
||||
algorithm 1 given in [4]_.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Park, H and Chin R.T. Decomposition of structuring elements for
|
||||
optimal implementation of morphological operations. In Proceedings:
|
||||
1997 IEEE Workshop on Nonlinear Signal and Image Processing, London,
|
||||
UK.
|
||||
https://www.iwaenc.org/proceedings/1997/nsip97/pdf/scan/ns970226.pdf
|
||||
.. [2] https://en.wikipedia.org/wiki/Hexadecagon
|
||||
.. [3] Vanrell, M and Vitrià, J. Optimal 3 × 3 decomposable disks for
|
||||
morphological transformations. Image and Vision Computing, Vol. 15,
|
||||
Issue 11, 1997.
|
||||
:DOI:`10.1016/S0262-8856(97)00026-7`
|
||||
.. [4] Li, D. and Ritter, G.X. Decomposition of Separable and Symmetric
|
||||
Convex Templates. Proc. SPIE 1350, Image Algebra and Morphological
|
||||
Image Processing, (1 November 1990).
|
||||
:DOI:`10.1117/12.23608`
|
||||
"""
|
||||
if decomposition is None:
|
||||
L = np.arange(-radius, radius + 1)
|
||||
X, Y = np.meshgrid(L, L)
|
||||
if not strict_radius:
|
||||
radius += 0.5
|
||||
return np.array((X ** 2 + Y ** 2) <= radius ** 2, dtype=dtype)
|
||||
elif decomposition == 'sequence':
|
||||
sequence = _nsphere_series_decomposition(radius, ndim=2, dtype=dtype)
|
||||
elif decomposition == 'crosses':
|
||||
fp = disk(radius, dtype, strict_radius=strict_radius,
|
||||
decomposition=None)
|
||||
sequence = _cross_decomposition(fp)
|
||||
return sequence
|
||||
|
||||
|
||||
def _cross(r0, r1, dtype=np.uint8):
|
||||
"""Cross-shaped structuring element of shape (r0, r1).
|
||||
|
||||
Only the central row and column are ones.
|
||||
"""
|
||||
s0 = int(2 * r0 + 1)
|
||||
s1 = int(2 * r1 + 1)
|
||||
c = np.zeros((s0, s1), dtype=dtype)
|
||||
if r1 != 0:
|
||||
c[r0, :] = 1
|
||||
if r0 != 0:
|
||||
c[:, r1] = 1
|
||||
return c
|
||||
|
||||
|
||||
def _cross_decomposition(footprint, dtype=np.uint8):
|
||||
""" Decompose a symmetric convex footprint into cross-shaped elements.
|
||||
|
||||
This is a decomposition of the footprint into a sequence of
|
||||
(possibly asymmetric) cross-shaped elements. This technique was proposed in
|
||||
[1]_ and corresponds roughly to algorithm 1 of that publication (some
|
||||
details had to be modified to get reliable operation).
|
||||
|
||||
.. [1] Li, D. and Ritter, G.X. Decomposition of Separable and Symmetric
|
||||
Convex Templates. Proc. SPIE 1350, Image Algebra and Morphological
|
||||
Image Processing, (1 November 1990).
|
||||
:DOI:`10.1117/12.23608`
|
||||
"""
|
||||
quadrant = footprint[footprint.shape[0] // 2:, footprint.shape[1] // 2:]
|
||||
col_sums = quadrant.sum(0, dtype=int)
|
||||
col_sums = np.concatenate((col_sums, np.asarray([0], dtype=int)))
|
||||
i_prev = 0
|
||||
idx = {}
|
||||
sum0 = 0
|
||||
for i in range(col_sums.size - 1):
|
||||
if col_sums[i] > col_sums[i + 1]:
|
||||
if i == 0:
|
||||
continue
|
||||
key = (col_sums[i_prev] - col_sums[i], i - i_prev)
|
||||
sum0 += key[0]
|
||||
if key not in idx:
|
||||
idx[key] = 1
|
||||
else:
|
||||
idx[key] += 1
|
||||
i_prev = i
|
||||
n = quadrant.shape[0] - 1 - sum0
|
||||
if n > 0:
|
||||
key = (n, 0)
|
||||
idx[key] = idx.get(key, 0) + 1
|
||||
return tuple([(_cross(r0, r1, dtype), n) for (r0, r1), n in idx.items()])
|
||||
|
||||
|
||||
def ellipse(width, height, dtype=np.uint8, *, decomposition=None):
|
||||
"""Generates a flat, ellipse-shaped footprint.
|
||||
|
||||
Every pixel along the perimeter of ellipse satisfies
|
||||
the equation ``(x/width+1)**2 + (y/height+1)**2 = 1``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : int
|
||||
The width of the ellipse-shaped footprint.
|
||||
height : int
|
||||
The height of the ellipse-shaped footprint.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
decomposition : {None, 'crosses'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given an identical result to a single, larger footprint, but with
|
||||
better computational performance. See Notes for more details.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
The footprint will have shape ``(2 * height + 1, 2 * width + 1)``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
When `decomposition` is not None, each element of the `footprint`
|
||||
tuple is a 2-tuple of the form ``(ndarray, num_iter)`` that specifies a
|
||||
footprint array and the number of iterations it is to be applied.
|
||||
|
||||
The ellipse produced by the ``decomposition='crosses'`` is often but not
|
||||
always identical to that with ``decomposition=None``. The method is based
|
||||
on an adaption of algorithm 1 given in [1]_.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Li, D. and Ritter, G.X. Decomposition of Separable and Symmetric
|
||||
Convex Templates. Proc. SPIE 1350, Image Algebra and Morphological
|
||||
Image Processing, (1 November 1990).
|
||||
:DOI:`10.1117/12.23608`
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.morphology import footprints
|
||||
>>> footprints.ellipse(5, 3)
|
||||
array([[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
if decomposition is None:
|
||||
footprint = np.zeros((2 * height + 1, 2 * width + 1), dtype=dtype)
|
||||
rows, cols = draw.ellipse(height, width, height + 1, width + 1)
|
||||
footprint[rows, cols] = 1
|
||||
return footprint
|
||||
elif decomposition == 'crosses':
|
||||
fp = ellipse(width, height, dtype, decomposition=None)
|
||||
sequence = _cross_decomposition(fp)
|
||||
return sequence
|
||||
|
||||
|
||||
def cube(width, dtype=np.uint8, *, decomposition=None):
|
||||
""" Generates a cube-shaped footprint.
|
||||
|
||||
This is the 3D equivalent of a square.
|
||||
Every pixel along the perimeter has a chessboard distance
|
||||
no greater than radius (radius=floor(width/2)) pixels.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : int
|
||||
The width, height and depth of the cube.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
decomposition : {None, 'separable', 'sequence'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given an identical result to a single, larger footprint, but often with
|
||||
better computational performance. See Notes for more details.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray or tuple
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
When `decomposition` is None, this is just a numpy.ndarray. Otherwise,
|
||||
this will be a tuple whose length is equal to the number of unique
|
||||
structuring elements to apply (see Notes for more detail)
|
||||
|
||||
Notes
|
||||
-----
|
||||
When `decomposition` is not None, each element of the `footprint`
|
||||
tuple is a 2-tuple of the form ``(ndarray, num_iter)`` that specifies a
|
||||
footprint array and the number of iterations it is to be applied.
|
||||
|
||||
For binary morphology, using ``decomposition='sequence'``
|
||||
was observed to give better performance, with the magnitude of the
|
||||
performance increase rapidly increasing with footprint size. For grayscale
|
||||
morphology with square footprints, it is recommended to use
|
||||
``decomposition=None`` since the internal SciPy functions that are called
|
||||
already have a fast implementation based on separable 1D sliding windows.
|
||||
|
||||
The 'sequence' decomposition mode only supports odd valued `width`. If
|
||||
`width` is even, the sequence used will be identical to the 'separable'
|
||||
mode.
|
||||
"""
|
||||
if decomposition is None:
|
||||
return np.ones((width, width, width), dtype=dtype)
|
||||
|
||||
if decomposition == 'separable' or width % 2 == 0:
|
||||
sequence = [(np.ones((width, 1, 1), dtype=dtype), 1),
|
||||
(np.ones((1, width, 1), dtype=dtype), 1),
|
||||
(np.ones((1, 1, width), dtype=dtype), 1)]
|
||||
elif decomposition == 'sequence':
|
||||
# only handles odd widths
|
||||
sequence = [
|
||||
(np.ones((3, 3, 3), dtype=dtype), _decompose_size(width, 3))
|
||||
]
|
||||
else:
|
||||
raise ValueError(f"Unrecognized decomposition: {decomposition}")
|
||||
return tuple(sequence)
|
||||
|
||||
|
||||
def octahedron(radius, dtype=np.uint8, *, decomposition=None):
|
||||
"""Generates a octahedron-shaped footprint.
|
||||
|
||||
This is the 3D equivalent of a diamond.
|
||||
A pixel is part of the neighborhood (i.e. labeled 1) if
|
||||
the city block/Manhattan distance between it and the center of
|
||||
the neighborhood is no greater than radius.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
The radius of the octahedron-shaped footprint.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
decomposition : {None, 'sequence'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given an identical result to a single, larger footprint, but with
|
||||
better computational performance. See Notes for more details.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray or tuple
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
When `decomposition` is None, this is just a numpy.ndarray. Otherwise,
|
||||
this will be a tuple whose length is equal to the number of unique
|
||||
structuring elements to apply (see Notes for more detail)
|
||||
|
||||
Notes
|
||||
-----
|
||||
When `decomposition` is not None, each element of the `footprint`
|
||||
tuple is a 2-tuple of the form ``(ndarray, num_iter)`` that specifies a
|
||||
footprint array and the number of iterations it is to be applied.
|
||||
|
||||
For either binary or grayscale morphology, using
|
||||
``decomposition='sequence'`` was observed to have a performance benefit,
|
||||
with the magnitude of the benefit increasing with increasing footprint
|
||||
size.
|
||||
"""
|
||||
# note that in contrast to diamond(), this method allows non-integer radii
|
||||
if decomposition is None:
|
||||
n = 2 * radius + 1
|
||||
Z, Y, X = np.mgrid[-radius:radius:n * 1j,
|
||||
-radius:radius:n * 1j,
|
||||
-radius:radius:n * 1j]
|
||||
s = np.abs(X) + np.abs(Y) + np.abs(Z)
|
||||
footprint = np.array(s <= radius, dtype=dtype)
|
||||
elif decomposition == 'sequence':
|
||||
fp = octahedron(1, dtype=dtype, decomposition=None)
|
||||
nreps = _decompose_size(2 * radius + 1, fp.shape[0])
|
||||
footprint = ((fp, nreps),)
|
||||
else:
|
||||
raise ValueError(f"Unrecognized decomposition: {decomposition}")
|
||||
return footprint
|
||||
|
||||
|
||||
def ball(radius, dtype=np.uint8, *, strict_radius=True, decomposition=None):
|
||||
"""Generates a ball-shaped footprint.
|
||||
|
||||
This is the 3D equivalent of a disk.
|
||||
A pixel is within the neighborhood if the Euclidean distance between
|
||||
it and the origin is no greater than radius.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
The radius of the ball-shaped footprint.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
strict_radius : bool, optional
|
||||
If False, extend the radius by 0.5. This allows the circle to expand
|
||||
further within a cube that remains of size ``2 * radius + 1`` along
|
||||
each axis. This parameter is ignored if decomposition is not None.
|
||||
decomposition : {None, 'sequence'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given a result equivalent to a single, larger footprint, but with
|
||||
better computational performance. For ball footprints, the sequence
|
||||
decomposition is not exactly equivalent to decomposition=None.
|
||||
See Notes for more details.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray or tuple
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The disk produced by the decomposition='sequence' mode is not identical
|
||||
to that with decomposition=None. Here we extend the approach taken in [1]_
|
||||
for disks to the 3D case, using 3-dimensional extensions of the "square",
|
||||
"diamond" and "t-shaped" elements from that publication. All of these
|
||||
elementary elements have size ``(3,) * ndim``. We numerically computed the
|
||||
number of repetitions of each element that gives the closest match to the
|
||||
ball computed with kwargs ``strict_radius=False, decomposition=None``.
|
||||
|
||||
Empirically, the equivalent composite footprint to the sequence
|
||||
decomposition approaches a rhombicuboctahedron (26-faces [2]_).
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Park, H and Chin R.T. Decomposition of structuring elements for
|
||||
optimal implementation of morphological operations. In Proceedings:
|
||||
1997 IEEE Workshop on Nonlinear Signal and Image Processing, London,
|
||||
UK.
|
||||
https://www.iwaenc.org/proceedings/1997/nsip97/pdf/scan/ns970226.pdf
|
||||
.. [2] https://en.wikipedia.org/wiki/Rhombicuboctahedron
|
||||
"""
|
||||
if decomposition is None:
|
||||
n = 2 * radius + 1
|
||||
Z, Y, X = np.mgrid[-radius:radius:n * 1j,
|
||||
-radius:radius:n * 1j,
|
||||
-radius:radius:n * 1j]
|
||||
s = X ** 2 + Y ** 2 + Z ** 2
|
||||
if not strict_radius:
|
||||
radius += 0.5
|
||||
return np.array(s <= radius * radius, dtype=dtype)
|
||||
elif decomposition == 'sequence':
|
||||
sequence = _nsphere_series_decomposition(radius, ndim=3, dtype=dtype)
|
||||
else:
|
||||
raise ValueError(f"Unrecognized decomposition: {decomposition}")
|
||||
return sequence
|
||||
|
||||
|
||||
def octagon(m, n, dtype=np.uint8, *, decomposition=None):
|
||||
"""Generates an octagon shaped footprint.
|
||||
|
||||
For a given size of (m) horizontal and vertical sides
|
||||
and a given (n) height or width of slanted sides octagon is generated.
|
||||
The slanted sides are 45 or 135 degrees to the horizontal axis
|
||||
and hence the widths and heights are equal. The overall size of the
|
||||
footprint along a single axis will be ``m + 2 * n``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
m : int
|
||||
The size of the horizontal and vertical sides.
|
||||
n : int
|
||||
The height or width of the slanted sides.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
decomposition : {None, 'sequence'}, optional
|
||||
If None, a single array is returned. For 'sequence', a tuple of smaller
|
||||
footprints is returned. Applying this series of smaller footprints will
|
||||
given an identical result to a single, larger footprint, but with
|
||||
better computational performance. See Notes for more details.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray or tuple
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
When `decomposition` is None, this is just a numpy.ndarray. Otherwise,
|
||||
this will be a tuple whose length is equal to the number of unique
|
||||
structuring elements to apply (see Notes for more detail)
|
||||
|
||||
Notes
|
||||
-----
|
||||
When `decomposition` is not None, each element of the `footprint`
|
||||
tuple is a 2-tuple of the form ``(ndarray, num_iter)`` that specifies a
|
||||
footprint array and the number of iterations it is to be applied.
|
||||
|
||||
For either binary or grayscale morphology, using
|
||||
``decomposition='sequence'`` was observed to have a performance benefit,
|
||||
with the magnitude of the benefit increasing with increasing footprint
|
||||
size.
|
||||
"""
|
||||
if m == n == 0:
|
||||
raise ValueError("m and n cannot both be zero")
|
||||
|
||||
# TODO?: warn about even footprint size when m is even
|
||||
|
||||
if decomposition is None:
|
||||
from . import convex_hull_image
|
||||
footprint = np.zeros((m + 2 * n, m + 2 * n))
|
||||
footprint[0, n] = 1
|
||||
footprint[n, 0] = 1
|
||||
footprint[0, m + n - 1] = 1
|
||||
footprint[m + n - 1, 0] = 1
|
||||
footprint[-1, n] = 1
|
||||
footprint[n, -1] = 1
|
||||
footprint[-1, m + n - 1] = 1
|
||||
footprint[m + n - 1, -1] = 1
|
||||
footprint = convex_hull_image(footprint).astype(dtype)
|
||||
elif decomposition == 'sequence':
|
||||
# special handling for edge cases with small m and/or n
|
||||
if m <= 2 and n <= 2:
|
||||
return ((octagon(m, n, dtype=dtype, decomposition=None), 1),)
|
||||
|
||||
# general approach for larger m and/or n
|
||||
if m == 0:
|
||||
m = 2
|
||||
n -= 1
|
||||
sequence = []
|
||||
if m > 1:
|
||||
sequence += list(square(m, dtype=dtype, decomposition='sequence'))
|
||||
if n > 0:
|
||||
sequence += [(diamond(1, dtype=dtype, decomposition=None), n)]
|
||||
footprint = tuple(sequence)
|
||||
else:
|
||||
raise ValueError(f"Unrecognized decomposition: {decomposition}")
|
||||
return footprint
|
||||
|
||||
|
||||
def star(a, dtype=np.uint8):
|
||||
"""Generates a star shaped footprint.
|
||||
|
||||
Start has 8 vertices and is an overlap of square of size `2*a + 1`
|
||||
with its 45 degree rotated version.
|
||||
The slanted sides are 45 or 135 degrees to the horizontal axis.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : int
|
||||
Parameter deciding the size of the star structural element. The side
|
||||
of the square array returned is `2*a + 1 + 2*floor(a / 2)`.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type, optional
|
||||
The data type of the footprint.
|
||||
|
||||
Returns
|
||||
-------
|
||||
footprint : ndarray
|
||||
The footprint where elements of the neighborhood are 1 and 0 otherwise.
|
||||
|
||||
"""
|
||||
from . import convex_hull_image
|
||||
|
||||
if a == 1:
|
||||
bfilter = np.zeros((3, 3), dtype)
|
||||
bfilter[:] = 1
|
||||
return bfilter
|
||||
|
||||
m = 2 * a + 1
|
||||
n = a // 2
|
||||
footprint_square = np.zeros((m + 2 * n, m + 2 * n))
|
||||
footprint_square[n: m + n, n: m + n] = 1
|
||||
|
||||
c = (m + 2 * n - 1) // 2
|
||||
footprint_rotated = np.zeros((m + 2 * n, m + 2 * n))
|
||||
footprint_rotated[0, c] = footprint_rotated[-1, c] = 1
|
||||
footprint_rotated[c, 0] = footprint_rotated[c, -1] = 1
|
||||
footprint_rotated = convex_hull_image(footprint_rotated).astype(int)
|
||||
|
||||
footprint = footprint_square + footprint_rotated
|
||||
footprint[footprint > 0] = 1
|
||||
|
||||
return footprint.astype(dtype)
|
||||
633
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/gray.py
vendored
Normal file
633
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/gray.py
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
"""
|
||||
Grayscale morphological operations
|
||||
"""
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from ..util import crop
|
||||
from .footprints import _footprint_is_sequence, _shape_from_sequence
|
||||
from .misc import default_footprint
|
||||
|
||||
__all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat',
|
||||
'black_tophat']
|
||||
|
||||
|
||||
def _iterate_gray_func(gray_func, image, footprints, out):
|
||||
"""Helper to call `binary_func` for each footprint in a sequence.
|
||||
|
||||
binary_func is a binary morphology function that accepts "structure",
|
||||
"output" and "iterations" keyword arguments
|
||||
(e.g. `scipy.ndimage.binary_erosion`).
|
||||
"""
|
||||
fp, num_iter = footprints[0]
|
||||
gray_func(image, footprint=fp, output=out)
|
||||
for _ in range(1, num_iter):
|
||||
gray_func(out.copy(), footprint=fp, output=out)
|
||||
for fp, num_iter in footprints[1:]:
|
||||
# Note: out.copy() because the computation cannot be in-place!
|
||||
for _ in range(num_iter):
|
||||
gray_func(out.copy(), footprint=fp, output=out)
|
||||
return out
|
||||
|
||||
|
||||
def _shift_footprint(footprint, shift_x, shift_y):
|
||||
"""Shift the binary image `footprint` in the left and/or up.
|
||||
|
||||
This only affects 2D footprints with even number of rows
|
||||
or columns.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
footprint : 2D array, shape (M, N)
|
||||
The input footprint.
|
||||
shift_x, shift_y : bool
|
||||
Whether to move `footprint` along each axis.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : 2D array, shape (M + int(shift_x), N + int(shift_y))
|
||||
The shifted footprint.
|
||||
"""
|
||||
if footprint.ndim != 2:
|
||||
# do nothing for 1D or 3D or higher footprints
|
||||
return footprint
|
||||
m, n = footprint.shape
|
||||
if m % 2 == 0:
|
||||
extra_row = np.zeros((1, n), footprint.dtype)
|
||||
if shift_x:
|
||||
footprint = np.vstack((footprint, extra_row))
|
||||
else:
|
||||
footprint = np.vstack((extra_row, footprint))
|
||||
m += 1
|
||||
if n % 2 == 0:
|
||||
extra_col = np.zeros((m, 1), footprint.dtype)
|
||||
if shift_y:
|
||||
footprint = np.hstack((footprint, extra_col))
|
||||
else:
|
||||
footprint = np.hstack((extra_col, footprint))
|
||||
return footprint
|
||||
|
||||
|
||||
def _invert_footprint(footprint):
|
||||
"""Change the order of the values in `footprint`.
|
||||
|
||||
This is a patch for the *weird* footprint inversion in
|
||||
`ndi.grey_morphology` [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
footprint : array
|
||||
The input footprint.
|
||||
|
||||
Returns
|
||||
-------
|
||||
inverted : array, same shape and type as `footprint`
|
||||
The footprint, in opposite order.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> footprint = np.array([[0, 0, 0], [0, 1, 1], [0, 1, 1]], np.uint8)
|
||||
>>> _invert_footprint(footprint)
|
||||
array([[1, 1, 0],
|
||||
[1, 1, 0],
|
||||
[0, 0, 0]], dtype=uint8)
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://github.com/scipy/scipy/blob/ec20ababa400e39ac3ffc9148c01ef86d5349332/scipy/ndimage/morphology.py#L1285 # noqa
|
||||
"""
|
||||
inverted = footprint[(slice(None, None, -1),) * footprint.ndim]
|
||||
return inverted
|
||||
|
||||
|
||||
def pad_for_eccentric_footprints(func):
|
||||
"""Pad input images for certain morphological operations.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable
|
||||
A morphological function, either opening or closing, that
|
||||
supports eccentric footprints. Its parameters must
|
||||
include at least `image`, `footprint`, and `out`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func_out : callable
|
||||
The same function, but correctly padding the input image before
|
||||
applying the input function.
|
||||
|
||||
See Also
|
||||
--------
|
||||
opening, closing.
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def func_out(image, footprint, out=None, *args, **kwargs):
|
||||
pad_widths = []
|
||||
padding = False
|
||||
if out is None:
|
||||
out = np.empty_like(image)
|
||||
if _footprint_is_sequence(footprint):
|
||||
# Note: in practice none of our built-in footprint sequences will
|
||||
# require padding (all are symmetric and have odd sizes)
|
||||
footprint_shape = _shape_from_sequence(footprint)
|
||||
else:
|
||||
footprint_shape = footprint.shape
|
||||
for axis_len in footprint_shape:
|
||||
if axis_len % 2 == 0:
|
||||
axis_pad_width = axis_len - 1
|
||||
padding = True
|
||||
else:
|
||||
axis_pad_width = 0
|
||||
pad_widths.append((axis_pad_width,) * 2)
|
||||
if padding:
|
||||
image = np.pad(image, pad_widths, mode='edge')
|
||||
out_temp = np.empty_like(image)
|
||||
else:
|
||||
out_temp = out
|
||||
out_temp = func(image, footprint, out=out_temp, *args, **kwargs)
|
||||
if padding:
|
||||
out[:] = crop(out_temp, pad_widths)
|
||||
else:
|
||||
out = out_temp
|
||||
return out
|
||||
return func_out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def erosion(image, footprint=None, out=None, shift_x=False, shift_y=False):
|
||||
"""Return grayscale morphological erosion of an image.
|
||||
|
||||
Morphological erosion sets a pixel at (i,j) to the minimum over all pixels
|
||||
in the neighborhood centered at (i,j). Erosion shrinks bright regions and
|
||||
enlarges dark regions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image array.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarrays, optional
|
||||
The array to store the result of the morphology. If None is
|
||||
passed, a new array will be allocated.
|
||||
shift_x, shift_y : bool, optional
|
||||
shift footprint about center point. This only affects
|
||||
eccentric footprints (i.e. footprint with even numbered
|
||||
sides).
|
||||
|
||||
Returns
|
||||
-------
|
||||
eroded : array, same shape as `image`
|
||||
The result of the morphological erosion.
|
||||
|
||||
Notes
|
||||
-----
|
||||
For ``uint8`` (and ``uint16`` up to a certain bit-depth) data, the
|
||||
lower algorithm complexity makes the `skimage.filters.rank.minimum`
|
||||
function more efficient for larger images and footprints.
|
||||
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Erosion shrinks bright regions
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> bright_square = np.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]], dtype=np.uint8)
|
||||
>>> erosion(bright_square, square(3))
|
||||
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]], dtype=uint8)
|
||||
|
||||
"""
|
||||
if out is None:
|
||||
out = np.empty_like(image)
|
||||
|
||||
if _footprint_is_sequence(footprint):
|
||||
footprints = tuple((_shift_footprint(fp, shift_x, shift_y), n)
|
||||
for fp, n in footprint)
|
||||
return _iterate_gray_func(ndi.grey_erosion, image, footprints, out)
|
||||
|
||||
footprint = np.array(footprint)
|
||||
footprint = _shift_footprint(footprint, shift_x, shift_y)
|
||||
ndi.grey_erosion(image, footprint=footprint, output=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def dilation(image, footprint=None, out=None, shift_x=False, shift_y=False):
|
||||
"""Return grayscale morphological dilation of an image.
|
||||
|
||||
Morphological dilation sets the value of a pixel to the maximum over all
|
||||
pixel values within a local neighborhood centered about it. The values
|
||||
where the footprint is 1 define this neighborhood.
|
||||
Dilation enlarges bright regions and shrinks dark regions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image array.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None is
|
||||
passed, a new array will be allocated.
|
||||
shift_x, shift_y : bool, optional
|
||||
Shift footprint about center point. This only affects 2D
|
||||
eccentric footprints (i.e., footprints with even-numbered
|
||||
sides).
|
||||
|
||||
Returns
|
||||
-------
|
||||
dilated : uint8 array, same shape and type as `image`
|
||||
The result of the morphological dilation.
|
||||
|
||||
Notes
|
||||
-----
|
||||
For `uint8` (and `uint16` up to a certain bit-depth) data, the lower
|
||||
algorithm complexity makes the `skimage.filters.rank.maximum` function more
|
||||
efficient for larger images and footprints.
|
||||
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Dilation enlarges bright regions
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> bright_pixel = np.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]], dtype=np.uint8)
|
||||
>>> dilation(bright_pixel, square(3))
|
||||
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]], dtype=uint8)
|
||||
|
||||
"""
|
||||
if out is None:
|
||||
out = np.empty_like(image)
|
||||
|
||||
if _footprint_is_sequence(footprint):
|
||||
# shift and invert (see comment below) each footprint
|
||||
footprints = tuple(
|
||||
(_invert_footprint(_shift_footprint(fp, shift_x, shift_y)), n)
|
||||
for fp, n in footprint
|
||||
)
|
||||
return _iterate_gray_func(ndi.grey_dilation, image, footprints, out)
|
||||
|
||||
footprint = np.array(footprint)
|
||||
footprint = _shift_footprint(footprint, shift_x, shift_y)
|
||||
# Inside ndi.grey_dilation, the footprint is inverted,
|
||||
# e.g. `footprint = footprint[::-1, ::-1]` for 2D [1]_, for reasons unknown
|
||||
# to this author (@jni). To "patch" this behaviour, we invert our own
|
||||
# footprint before passing it to `ndi.grey_dilation`.
|
||||
# [1] https://github.com/scipy/scipy/blob/ec20ababa400e39ac3ffc9148c01ef86d5349332/scipy/ndimage/morphology.py#L1285 # noqa
|
||||
footprint = _invert_footprint(footprint)
|
||||
|
||||
ndi.grey_dilation(image, footprint=footprint, output=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
@pad_for_eccentric_footprints
|
||||
def opening(image, footprint=None, out=None):
|
||||
"""Return grayscale morphological opening of an image.
|
||||
|
||||
The morphological opening of an image is defined as an erosion followed by
|
||||
a dilation. Opening can remove small bright spots (i.e. "salt") and connect
|
||||
small dark cracks. This tends to "open" up (dark) gaps between (bright)
|
||||
features.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image array.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
opening : array, same shape and type as `image`
|
||||
The result of the morphological opening.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Open up gap between two bright regions (but also shrink regions)
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> bad_connection = np.array([[1, 0, 0, 0, 1],
|
||||
... [1, 1, 0, 1, 1],
|
||||
... [1, 1, 1, 1, 1],
|
||||
... [1, 1, 0, 1, 1],
|
||||
... [1, 0, 0, 0, 1]], dtype=np.uint8)
|
||||
>>> opening(bad_connection, square(3))
|
||||
array([[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 1, 1],
|
||||
[1, 1, 0, 1, 1],
|
||||
[1, 1, 0, 1, 1],
|
||||
[0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
eroded = erosion(image, footprint)
|
||||
# note: shift_x, shift_y do nothing if footprint side length is odd
|
||||
out = dilation(eroded, footprint, out=out, shift_x=True, shift_y=True)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
@pad_for_eccentric_footprints
|
||||
def closing(image, footprint=None, out=None):
|
||||
"""Return grayscale morphological closing of an image.
|
||||
|
||||
The morphological closing of an image is defined as a dilation followed by
|
||||
an erosion. Closing can remove small dark spots (i.e. "pepper") and connect
|
||||
small bright cracks. This tends to "close" up (dark) gaps between (bright)
|
||||
features.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image array.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None,
|
||||
a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
closing : array, same shape and type as `image`
|
||||
The result of the morphological closing.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Close a gap between two bright lines
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> broken_line = np.array([[0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0],
|
||||
... [1, 1, 0, 1, 1],
|
||||
... [0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
>>> closing(broken_line, square(3))
|
||||
array([[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
dilated = dilation(image, footprint)
|
||||
# note: shift_x, shift_y do nothing if footprint side length is odd
|
||||
out = erosion(dilated, footprint, out=out, shift_x=True, shift_y=True)
|
||||
return out
|
||||
|
||||
|
||||
def _white_tophat_seqence(image, footprints, out):
|
||||
"""Return white top hat for a sequence of footprints.
|
||||
|
||||
Like SciPy's implementation, but with ``ndi.grey_erosion`` and
|
||||
``ndi.grey_dilation`` wrapped with ``_iterate_gray_func``.
|
||||
"""
|
||||
tmp = _iterate_gray_func(ndi.grey_erosion, image, footprints, out)
|
||||
tmp = _iterate_gray_func(ndi.grey_dilation, tmp.copy(), footprints, out)
|
||||
if tmp is None:
|
||||
tmp = out
|
||||
if image.dtype == np.bool_ and tmp.dtype == np.bool_:
|
||||
np.bitwise_xor(image, tmp, out=tmp)
|
||||
else:
|
||||
np.subtract(image, tmp, out=tmp)
|
||||
return tmp
|
||||
|
||||
|
||||
@default_footprint
|
||||
def white_tophat(image, footprint=None, out=None):
|
||||
"""Return white top hat of an image.
|
||||
|
||||
The white top hat of an image is defined as the image minus its
|
||||
morphological opening. This operation returns the bright spots of the image
|
||||
that are smaller than the footprint.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image array.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : array, same shape and type as `image`
|
||||
The result of the morphological white top hat.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
See Also
|
||||
--------
|
||||
black_tophat
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Top-hat_transform
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Subtract gray background from bright peak
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> 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]], dtype=np.uint8)
|
||||
>>> white_tophat(bright_on_gray, square(3))
|
||||
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]], dtype=uint8)
|
||||
|
||||
"""
|
||||
if out is image:
|
||||
opened = opening(image, footprint)
|
||||
if np.issubdtype(opened.dtype, bool):
|
||||
np.logical_xor(out, opened, out=out)
|
||||
else:
|
||||
out -= opened
|
||||
return out
|
||||
elif out is None:
|
||||
out = np.empty_like(image)
|
||||
# promote bool to a type that allows arithmetic operations
|
||||
if isinstance(image, np.ndarray) and image.dtype == bool:
|
||||
image_ = image.view(dtype=np.uint8)
|
||||
else:
|
||||
image_ = image
|
||||
if isinstance(out, np.ndarray) and out.dtype == bool:
|
||||
out_ = out.view(dtype=np.uint8)
|
||||
else:
|
||||
out_ = out
|
||||
if _footprint_is_sequence(footprint):
|
||||
return _white_tophat_seqence(image_, footprint, out_)
|
||||
footprint = np.array(footprint)
|
||||
out_ = ndi.white_tophat(image_, footprint=footprint, output=out_)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def black_tophat(image, footprint=None, out=None):
|
||||
"""Return black top hat of an image.
|
||||
|
||||
The black top hat of an image is defined as its morphological closing minus
|
||||
the original image. This operation returns the dark spots of the image that
|
||||
are smaller than the footprint. Note that dark spots in the
|
||||
original image are bright spots after the black top hat.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image array.
|
||||
footprint : ndarray or tuple, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped footprint (connectivity=1). The footprint
|
||||
can also be provided as a sequence of smaller footprints as described
|
||||
in the notes below.
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : array, same shape and type as `image`
|
||||
The result of the morphological black top hat.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The footprint can also be a provided as a sequence of 2-tuples where the
|
||||
first element of each 2-tuple is a footprint ndarray and the second element
|
||||
is an integer describing the number of times it should be iterated. For
|
||||
example ``footprint=[(np.ones((9, 1)), 1), (np.ones((1, 9)), 1)]``
|
||||
would apply a 9x1 footprint followed by a 1x9 footprint resulting in a net
|
||||
effect that is the same as ``footprint=np.ones((9, 9))``, but with lower
|
||||
computational cost. Most of the builtin footprints such as
|
||||
``skimage.morphology.disk`` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
See Also
|
||||
--------
|
||||
white_tophat
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Top-hat_transform
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Change dark peak to bright peak and subtract background
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> 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]], dtype=np.uint8)
|
||||
>>> black_tophat(dark_on_gray, square(3))
|
||||
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]], dtype=uint8)
|
||||
|
||||
"""
|
||||
if out is image:
|
||||
original = image.copy()
|
||||
else:
|
||||
original = image
|
||||
out = closing(image, footprint, out=out)
|
||||
if np.issubdtype(out.dtype, np.bool_):
|
||||
np.logical_xor(out, original, out=out)
|
||||
else:
|
||||
out -= original
|
||||
return out
|
||||
207
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/grayreconstruct.py
vendored
Normal file
207
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/grayreconstruct.py
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
import numpy as np
|
||||
|
||||
from .._shared.utils import _supported_float_type
|
||||
from ..filters._rank_order import rank_order
|
||||
from ._grayreconstruct import reconstruction_loop
|
||||
|
||||
|
||||
def reconstruction(seed, mask, method='dilation', footprint=None, offset=None):
|
||||
"""Perform a morphological reconstruction of an image.
|
||||
|
||||
Morphological reconstruction by dilation is similar to basic morphological
|
||||
dilation: high-intensity values will replace nearby low-intensity values.
|
||||
The basic dilation operator, however, uses a footprint to
|
||||
determine how far a value in the input image can spread. In contrast,
|
||||
reconstruction uses two images: a "seed" image, which specifies the values
|
||||
that spread, and a "mask" image, which gives the maximum allowed value at
|
||||
each pixel. The mask image, like the footprint, limits the spread
|
||||
of high-intensity values. Reconstruction by erosion is simply the inverse:
|
||||
low-intensity values spread from the seed image and are limited by the mask
|
||||
image, which represents the minimum allowed value.
|
||||
|
||||
Alternatively, you can think of reconstruction as a way to isolate the
|
||||
connected regions of an image. For dilation, reconstruction connects
|
||||
regions marked by local maxima in the seed image: neighboring pixels
|
||||
less-than-or-equal-to those seeds are connected to the seeded region.
|
||||
Local maxima with values larger than the seed image will get truncated to
|
||||
the seed value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
seed : ndarray
|
||||
The seed image (a.k.a. marker image), which specifies the values that
|
||||
are dilated or eroded.
|
||||
mask : ndarray
|
||||
The maximum (dilation) / minimum (erosion) allowed value at each pixel.
|
||||
method : {'dilation'|'erosion'}, optional
|
||||
Perform reconstruction by dilation or erosion. In dilation (or
|
||||
erosion), the seed image is dilated (or eroded) until limited by the
|
||||
mask image. For dilation, each seed value must be less than or equal
|
||||
to the corresponding mask value; for erosion, the reverse is true.
|
||||
Default is 'dilation'.
|
||||
footprint : ndarray, optional
|
||||
The neighborhood expressed as an n-D array of 1's and 0's.
|
||||
Default is the n-D square of radius equal to 1 (i.e. a 3x3 square
|
||||
for 2D images, a 3x3x3 cube for 3D images, etc.)
|
||||
offset : ndarray, optional
|
||||
The coordinates of the center of the footprint.
|
||||
Default is located on the geometrical center of the footprint, in that
|
||||
case footprint dimensions must be odd.
|
||||
|
||||
Returns
|
||||
-------
|
||||
reconstructed : ndarray
|
||||
The result of morphological reconstruction.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import reconstruction
|
||||
|
||||
First, we create a sinusoidal mask image with peaks at middle and ends.
|
||||
|
||||
>>> x = np.linspace(0, 4 * np.pi)
|
||||
>>> y_mask = np.cos(x)
|
||||
|
||||
Then, we create a seed image initialized to the minimum mask value (for
|
||||
reconstruction by dilation, min-intensity values don't spread) and add
|
||||
"seeds" to the left and right peak, but at a fraction of peak value (1).
|
||||
|
||||
>>> y_seed = y_mask.min() * np.ones_like(x)
|
||||
>>> y_seed[0] = 0.5
|
||||
>>> y_seed[-1] = 0
|
||||
>>> y_rec = reconstruction(y_seed, y_mask)
|
||||
|
||||
The reconstructed image (or curve, in this case) is exactly the same as the
|
||||
mask image, except that the peaks are truncated to 0.5 and 0. The middle
|
||||
peak disappears completely: Since there were no seed values in this peak
|
||||
region, its reconstructed value is truncated to the surrounding value (-1).
|
||||
|
||||
As a more practical example, we try to extract the bright features of an
|
||||
image by subtracting a background image created by reconstruction.
|
||||
|
||||
>>> y, x = np.mgrid[:20:0.5, :20:0.5]
|
||||
>>> bumps = np.sin(x) + np.sin(y)
|
||||
|
||||
To create the background image, set the mask image to the original image,
|
||||
and the seed image to the original image with an intensity offset, `h`.
|
||||
|
||||
>>> h = 0.3
|
||||
>>> seed = bumps - h
|
||||
>>> background = reconstruction(seed, bumps)
|
||||
|
||||
The resulting reconstructed image looks exactly like the original image,
|
||||
but with the peaks of the bumps cut off. Subtracting this reconstructed
|
||||
image from the original image leaves just the peaks of the bumps
|
||||
|
||||
>>> hdome = bumps - background
|
||||
|
||||
This operation is known as the h-dome of the image and leaves features
|
||||
of height `h` in the subtracted image.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm is taken from [1]_. Applications for grayscale reconstruction
|
||||
are discussed in [2]_ and [3]_.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Robinson, "Efficient morphological reconstruction: a downhill
|
||||
filter", Pattern Recognition Letters 25 (2004) 1759-1767.
|
||||
.. [2] Vincent, L., "Morphological Grayscale Reconstruction in Image
|
||||
Analysis: Applications and Efficient Algorithms", IEEE Transactions
|
||||
on Image Processing (1993)
|
||||
.. [3] Soille, P., "Morphological Image Analysis: Principles and
|
||||
Applications", Chapter 6, 2nd edition (2003), ISBN 3540429883.
|
||||
"""
|
||||
assert tuple(seed.shape) == tuple(mask.shape)
|
||||
if method == 'dilation' and np.any(seed > mask):
|
||||
raise ValueError("Intensity of seed image must be less than that "
|
||||
"of the mask image for reconstruction by dilation.")
|
||||
elif method == 'erosion' and np.any(seed < mask):
|
||||
raise ValueError("Intensity of seed image must be greater than that "
|
||||
"of the mask image for reconstruction by erosion.")
|
||||
|
||||
if footprint is None:
|
||||
footprint = np.ones([3] * seed.ndim, dtype=bool)
|
||||
else:
|
||||
footprint = footprint.astype(bool, copy=True)
|
||||
|
||||
if offset is None:
|
||||
if not all([d % 2 == 1 for d in footprint.shape]):
|
||||
raise ValueError("Footprint dimensions must all be odd")
|
||||
offset = np.array([d // 2 for d in footprint.shape])
|
||||
else:
|
||||
if offset.ndim != footprint.ndim:
|
||||
raise ValueError("Offset and footprint ndims must be equal.")
|
||||
if not all([(0 <= o < d) for o, d in zip(offset, footprint.shape)]):
|
||||
raise ValueError("Offset must be included inside footprint")
|
||||
|
||||
# Cross out the center of the footprint
|
||||
footprint[tuple(slice(d, d + 1) for d in offset)] = False
|
||||
|
||||
# Make padding for edges of reconstructed image so we can ignore boundaries
|
||||
dims = np.zeros(seed.ndim + 1, dtype=int)
|
||||
dims[1:] = np.array(seed.shape) + (np.array(footprint.shape) - 1)
|
||||
dims[0] = 2
|
||||
inside_slices = tuple(slice(o, o + s) for o, s in zip(offset, seed.shape))
|
||||
# Set padded region to minimum image intensity and mask along first axis so
|
||||
# we can interleave image and mask pixels when sorting.
|
||||
if method == 'dilation':
|
||||
pad_value = np.min(seed)
|
||||
elif method == 'erosion':
|
||||
pad_value = np.max(seed)
|
||||
else:
|
||||
raise ValueError("Reconstruction method can be one of 'erosion' "
|
||||
f"or 'dilation'. Got '{method}'.")
|
||||
float_dtype = _supported_float_type(mask.dtype)
|
||||
images = np.full(dims, pad_value, dtype=float_dtype)
|
||||
images[(0, *inside_slices)] = seed
|
||||
images[(1, *inside_slices)] = mask
|
||||
|
||||
# determine whether image is large enough to require 64-bit integers
|
||||
isize = images.size
|
||||
# use -isize so we get a signed dtype rather than an unsigned one
|
||||
signed_int_dtype = np.result_type(np.min_scalar_type(-isize), np.int32)
|
||||
# the corresponding unsigned type has same char, but uppercase
|
||||
unsigned_int_dtype = np.dtype(signed_int_dtype.char.upper())
|
||||
|
||||
# Create a list of strides across the array to get the neighbors within
|
||||
# a flattened array
|
||||
value_stride = np.array(images.strides[1:]) // images.dtype.itemsize
|
||||
image_stride = images.strides[0] // images.dtype.itemsize
|
||||
footprint_mgrid = np.mgrid[[slice(-o, d - o)
|
||||
for d, o in zip(footprint.shape, offset)]]
|
||||
footprint_offsets = footprint_mgrid[:, footprint].transpose()
|
||||
nb_strides = np.array([np.sum(value_stride * footprint_offset)
|
||||
for footprint_offset in footprint_offsets],
|
||||
signed_int_dtype)
|
||||
images = images.reshape(-1)
|
||||
|
||||
# Erosion goes smallest to largest; dilation goes largest to smallest.
|
||||
index_sorted = np.argsort(images).astype(signed_int_dtype, copy=False)
|
||||
if method == 'dilation':
|
||||
index_sorted = index_sorted[::-1]
|
||||
|
||||
# Make a linked list of pixels sorted by value. -1 is the list terminator.
|
||||
prev = np.full(isize, -1, signed_int_dtype)
|
||||
next = np.full(isize, -1, signed_int_dtype)
|
||||
prev[index_sorted[1:]] = index_sorted[:-1]
|
||||
next[index_sorted[:-1]] = index_sorted[1:]
|
||||
|
||||
# Cython inner-loop compares the rank of pixel values.
|
||||
if method == 'dilation':
|
||||
value_rank, value_map = rank_order(images)
|
||||
elif method == 'erosion':
|
||||
value_rank, value_map = rank_order(-images)
|
||||
value_map = -value_map
|
||||
|
||||
start = index_sorted[0]
|
||||
value_rank = value_rank.astype(unsigned_int_dtype, copy=False)
|
||||
reconstruction_loop(value_rank, prev, next, nb_strides, start,
|
||||
image_stride)
|
||||
|
||||
# Reshape reconstructed image to original image shape and remove padding.
|
||||
rec_img = value_map[value_rank[:image_stride]]
|
||||
rec_img.shape = np.array(seed.shape) + (np.array(footprint.shape) - 1)
|
||||
return rec_img[inside_slices]
|
||||
193
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/isotropic.py
vendored
Normal file
193
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/isotropic.py
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
Binary morphological operations
|
||||
"""
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
|
||||
def isotropic_erosion(image, radius, out=None, spacing=None):
|
||||
"""Return binary morphological erosion of an image.
|
||||
|
||||
This function returns the same result as :func:`skimage.morphology.binary_erosion`
|
||||
but performs faster for large circular structuring elements.
|
||||
This works by applying a threshold to the exact Euclidean distance map
|
||||
of the image [1]_, [2]_.
|
||||
The implementation is based on: func:`scipy.ndimage.distance_transform_edt`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Binary input image.
|
||||
radius : float
|
||||
The radius by which regions should be eroded.
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None,
|
||||
a new array will be allocated.
|
||||
spacing : float, or sequence of float, optional
|
||||
Spacing of elements along each dimension.
|
||||
If a sequence, must be of length equal to the input's dimension (number of axes).
|
||||
If a single number, this value is used for all axes.
|
||||
If not specified, a grid spacing of unity is implied.
|
||||
|
||||
Returns
|
||||
-------
|
||||
eroded : ndarray of bool
|
||||
The result of the morphological erosion taking values in
|
||||
``[False, True]``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Cuisenaire, O. and Macq, B., "Fast Euclidean morphological operators
|
||||
using local distance transformation by propagation, and applications,"
|
||||
Image Processing And Its Applications, 1999. Seventh International
|
||||
Conference on (Conf. Publ. No. 465), 1999, pp. 856-860 vol.2.
|
||||
:DOI:`10.1049/cp:19990446`
|
||||
|
||||
.. [2] Ingemar Ragnemalm, Fast erosion and dilation by contour processing
|
||||
and thresholding of distance maps, Pattern Recognition Letters,
|
||||
Volume 13, Issue 3, 1992, Pages 161-166.
|
||||
:DOI:`10.1016/0167-8655(92)90055-5`
|
||||
"""
|
||||
|
||||
dist = ndi.distance_transform_edt(image, sampling=spacing)
|
||||
return np.greater(dist, radius, out=out)
|
||||
|
||||
|
||||
def isotropic_dilation(image, radius, out=None, spacing=None):
|
||||
"""Return binary morphological dilation of an image.
|
||||
|
||||
This function returns the same result as :func:`skimage.morphology.binary_dilation`
|
||||
but performs faster for large circular structuring elements.
|
||||
This works by applying a threshold to the exact Euclidean distance map
|
||||
of the inverted image [1]_, [2]_.
|
||||
The implementation is based on: func:`scipy.ndimage.distance_transform_edt`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Binary input image.
|
||||
radius : float
|
||||
The radius by which regions should be dilated.
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None is
|
||||
passed, a new array will be allocated.
|
||||
spacing : float, or sequence of float, optional
|
||||
Spacing of elements along each dimension.
|
||||
If a sequence, must be of length equal to the input's dimension (number of axes).
|
||||
If a single number, this value is used for all axes.
|
||||
If not specified, a grid spacing of unity is implied.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dilated : ndarray of bool
|
||||
The result of the morphological dilation with values in
|
||||
``[False, True]``.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Cuisenaire, O. and Macq, B., "Fast Euclidean morphological operators
|
||||
using local distance transformation by propagation, and applications,"
|
||||
Image Processing And Its Applications, 1999. Seventh International
|
||||
Conference on (Conf. Publ. No. 465), 1999, pp. 856-860 vol.2.
|
||||
:DOI:`10.1049/cp:19990446`
|
||||
|
||||
.. [2] Ingemar Ragnemalm, Fast erosion and dilation by contour processing
|
||||
and thresholding of distance maps, Pattern Recognition Letters,
|
||||
Volume 13, Issue 3, 1992, Pages 161-166.
|
||||
:DOI:`10.1016/0167-8655(92)90055-5`
|
||||
"""
|
||||
|
||||
dist = ndi.distance_transform_edt(np.logical_not(image), sampling=spacing)
|
||||
return np.less_equal(dist, radius, out=out)
|
||||
|
||||
|
||||
def isotropic_opening(image, radius, out=None, spacing=None):
|
||||
"""Return binary morphological opening of an image.
|
||||
|
||||
This function returns the same result as :func:`skimage.morphology.binary_opening`
|
||||
but performs faster for large circular structuring elements.
|
||||
This works by thresholding the exact Euclidean distance map [1]_, [2]_.
|
||||
The implementation is based on: func:`scipy.ndimage.distance_transform_edt`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Binary input image.
|
||||
radius : float
|
||||
The radius with which the regions should be opened.
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
spacing : float, or sequence of float, optional
|
||||
Spacing of elements along each dimension.
|
||||
If a sequence, must be of length equal to the input's dimension (number of axes).
|
||||
If a single number, this value is used for all axes.
|
||||
If not specified, a grid spacing of unity is implied.
|
||||
|
||||
Returns
|
||||
-------
|
||||
opened : ndarray of bool
|
||||
The result of the morphological opening.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Cuisenaire, O. and Macq, B., "Fast Euclidean morphological operators
|
||||
using local distance transformation by propagation, and applications,"
|
||||
Image Processing And Its Applications, 1999. Seventh International
|
||||
Conference on (Conf. Publ. No. 465), 1999, pp. 856-860 vol.2.
|
||||
:DOI:`10.1049/cp:19990446`
|
||||
|
||||
.. [2] Ingemar Ragnemalm, Fast erosion and dilation by contour processing
|
||||
and thresholding of distance maps, Pattern Recognition Letters,
|
||||
Volume 13, Issue 3, 1992, Pages 161-166.
|
||||
:DOI:`10.1016/0167-8655(92)90055-5`
|
||||
"""
|
||||
|
||||
eroded = isotropic_erosion(image, radius, out=out, spacing=spacing)
|
||||
return isotropic_dilation(eroded, radius, out=out, spacing=spacing)
|
||||
|
||||
|
||||
def isotropic_closing(image, radius, out=None, spacing=None):
|
||||
"""Return binary morphological closing of an image.
|
||||
|
||||
This function returns the same result as binary :func:`skimage.morphology.binary_closing`
|
||||
but performs faster for large circular structuring elements.
|
||||
This works by thresholding the exact Euclidean distance map [1]_, [2]_.
|
||||
The implementation is based on: func:`scipy.ndimage.distance_transform_edt`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Binary input image.
|
||||
radius : float
|
||||
The radius with which the regions should be closed.
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None,
|
||||
is passed, a new array will be allocated.
|
||||
spacing : float, or sequence of float, optional
|
||||
Spacing of elements along each dimension.
|
||||
If a sequence, must be of length equal to the input's dimension (number of axes).
|
||||
If a single number, this value is used for all axes.
|
||||
If not specified, a grid spacing of unity is implied.
|
||||
|
||||
Returns
|
||||
-------
|
||||
closed : ndarray of bool
|
||||
The result of the morphological closing.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Cuisenaire, O. and Macq, B., "Fast Euclidean morphological operators
|
||||
using local distance transformation by propagation, and applications,"
|
||||
Image Processing And Its Applications, 1999. Seventh International
|
||||
Conference on (Conf. Publ. No. 465), 1999, pp. 856-860 vol.2.
|
||||
:DOI:`10.1049/cp:19990446`
|
||||
|
||||
.. [2] Ingemar Ragnemalm, Fast erosion and dilation by contour processing
|
||||
and thresholding of distance maps, Pattern Recognition Letters,
|
||||
Volume 13, Issue 3, 1992, Pages 161-166.
|
||||
:DOI:`10.1016/0167-8655(92)90055-5`
|
||||
"""
|
||||
|
||||
dilated = isotropic_dilation(image, radius, out=out, spacing=spacing)
|
||||
return isotropic_erosion(dilated, radius, out=out, spacing=spacing)
|
||||
664
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/max_tree.py
vendored
Normal file
664
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/max_tree.py
vendored
Normal file
@@ -0,0 +1,664 @@
|
||||
"""max_tree.py - max_tree representation of images.
|
||||
|
||||
This module provides operators based on the max-tree representation of images.
|
||||
A grayscale image can be seen as a pile of nested sets, each of which is the
|
||||
result of a threshold operation. These sets can be efficiently represented by
|
||||
max-trees, where the inclusion relation between connected components at
|
||||
different levels are represented by parent-child relationships.
|
||||
|
||||
These representations allow efficient implementations of many algorithms, such
|
||||
as attribute operators. Unlike morphological openings and closings, these
|
||||
operators do not require a fixed footprint, but rather act with a flexible
|
||||
footprint that meets a certain criterion.
|
||||
|
||||
This implementation provides functions for:
|
||||
1. max-tree generation
|
||||
2. area openings / closings
|
||||
3. diameter openings / closings
|
||||
4. local maxima
|
||||
|
||||
References:
|
||||
.. [1] Salembier, P., Oliveras, A., & Garrido, L. (1998). Antiextensive
|
||||
Connected Operators for Image and Sequence Processing.
|
||||
IEEE Transactions on Image Processing, 7(4), 555-570.
|
||||
:DOI:10.1109/83.663500
|
||||
.. [2] Berger, C., Geraud, T., Levillain, R., Widynski, N., Baillard, A.,
|
||||
Bertin, E. (2007). Effective Component Tree Computation with
|
||||
Application to Pattern Recognition in Astronomical Imaging.
|
||||
In International Conference on Image Processing (ICIP) (pp. 41-44).
|
||||
:DOI:10.1109/ICIP.2007.4379949
|
||||
.. [3] Najman, L., & Couprie, M. (2006). Building the component tree in
|
||||
quasi-linear time. IEEE Transactions on Image Processing, 15(11),
|
||||
3531-3539.
|
||||
:DOI:10.1109/TIP.2006.877518
|
||||
.. [4] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:10.1109/TIP.2014.2336551
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ._util import _validate_connectivity, _offsets_to_raveled_neighbors
|
||||
from ..util import invert
|
||||
|
||||
from . import _max_tree
|
||||
|
||||
unsigned_int_types = [np.uint8, np.uint16, np.uint32, np.uint64]
|
||||
signed_int_types = [np.int8, np.int16, np.int32, np.int64]
|
||||
signed_float_types = [np.float16, np.float32, np.float64]
|
||||
|
||||
|
||||
# building the max tree.
|
||||
def max_tree(image, connectivity=1):
|
||||
"""Build the max tree from an image.
|
||||
|
||||
Component trees represent the hierarchical structure of the connected
|
||||
components resulting from sequential thresholding operations applied to an
|
||||
image. A connected component at one level is parent of a component at a
|
||||
higher level if the latter is included in the first. A max-tree is an
|
||||
efficient representation of a component tree. A connected component at
|
||||
one level is represented by one reference pixel at this level, which is
|
||||
parent to all other pixels at that level and to the reference pixel at the
|
||||
level above. The max-tree is the basis for many morphological operators,
|
||||
namely connected operators.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the max-tree is to be calculated.
|
||||
This image can be of any type.
|
||||
connectivity : unsigned int, optional
|
||||
The neighborhood connectivity. The integer represents the maximum
|
||||
number of orthogonal steps to reach a neighbor. In 2D, it is 1 for
|
||||
a 4-neighborhood and 2 for a 8-neighborhood. Default value is 1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
parent : ndarray, int64
|
||||
Array of same shape as image. The value of each pixel is the index of
|
||||
its parent in the ravelled array.
|
||||
tree_traverser : 1D array, int64
|
||||
The ordered pixel indices (referring to the ravelled array). The pixels
|
||||
are ordered such that every pixel is preceded by its parent (except for
|
||||
the root which has no parent).
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Salembier, P., Oliveras, A., & Garrido, L. (1998). Antiextensive
|
||||
Connected Operators for Image and Sequence Processing.
|
||||
IEEE Transactions on Image Processing, 7(4), 555-570.
|
||||
:DOI:`10.1109/83.663500`
|
||||
.. [2] Berger, C., Geraud, T., Levillain, R., Widynski, N., Baillard, A.,
|
||||
Bertin, E. (2007). Effective Component Tree Computation with
|
||||
Application to Pattern Recognition in Astronomical Imaging.
|
||||
In International Conference on Image Processing (ICIP) (pp. 41-44).
|
||||
:DOI:`10.1109/ICIP.2007.4379949`
|
||||
.. [3] Najman, L., & Couprie, M. (2006). Building the component tree in
|
||||
quasi-linear time. IEEE Transactions on Image Processing, 15(11),
|
||||
3531-3539.
|
||||
:DOI:`10.1109/TIP.2006.877518`
|
||||
.. [4] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:`10.1109/TIP.2014.2336551`
|
||||
|
||||
Examples
|
||||
--------
|
||||
We create a small sample image (Figure 1 from [4]) and build the max-tree.
|
||||
|
||||
>>> image = np.array([[15, 13, 16], [12, 12, 10], [16, 12, 14]])
|
||||
>>> P, S = max_tree(image, connectivity=2)
|
||||
"""
|
||||
# User defined masks are not allowed, as there might be more than one
|
||||
# connected component in the mask (and therefore not a single tree that
|
||||
# represents the image). Mask here is an image that is 0 on the border
|
||||
# and 1 everywhere else.
|
||||
mask = np.ones(image.shape)
|
||||
for k in range(len(image.shape)):
|
||||
np.moveaxis(mask, k, 0)[0] = 0
|
||||
np.moveaxis(mask, k, 0)[-1] = 0
|
||||
|
||||
neighbors, offset = _validate_connectivity(image.ndim, connectivity,
|
||||
offset=None)
|
||||
|
||||
# initialization of the parent image
|
||||
parent = np.zeros(image.shape, dtype=np.int64)
|
||||
|
||||
# flat_neighborhood contains a list of offsets allowing one to find the
|
||||
# neighbors in the ravelled image.
|
||||
flat_neighborhood = _offsets_to_raveled_neighbors(image.shape, neighbors,
|
||||
offset).astype(np.int32)
|
||||
|
||||
# pixels need to be sorted according to their gray level.
|
||||
tree_traverser = np.argsort(image.ravel()).astype(np.int64)
|
||||
|
||||
# call of cython function.
|
||||
_max_tree._max_tree(image.ravel(), mask.ravel().astype(np.uint8),
|
||||
flat_neighborhood, offset.astype(np.int32),
|
||||
np.array(image.shape, dtype=np.int32),
|
||||
parent.ravel(), tree_traverser)
|
||||
|
||||
return parent, tree_traverser
|
||||
|
||||
|
||||
def area_opening(image, area_threshold=64, connectivity=1,
|
||||
parent=None, tree_traverser=None):
|
||||
"""Perform an area opening of the image.
|
||||
|
||||
Area opening removes all bright structures of an image with
|
||||
a surface smaller than area_threshold.
|
||||
The output image is thus the largest image smaller than the input
|
||||
for which all local maxima have at least a surface of
|
||||
area_threshold pixels.
|
||||
|
||||
Area openings are similar to morphological openings, but
|
||||
they do not use a fixed footprint, but rather a deformable
|
||||
one, with surface = area_threshold. Consequently, the area_opening
|
||||
with area_threshold=1 is the identity.
|
||||
|
||||
In the binary case, area openings are equivalent to
|
||||
remove_small_objects; this operator is thus extended to gray-level images.
|
||||
|
||||
Technically, this operator is based on the max-tree representation of
|
||||
the image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the area_opening is to be calculated.
|
||||
This image can be of any type.
|
||||
area_threshold : unsigned int
|
||||
The size parameter (number of pixels). The default value is arbitrarily
|
||||
chosen to be 64.
|
||||
connectivity : unsigned int, optional
|
||||
The neighborhood connectivity. The integer represents the maximum
|
||||
number of orthogonal steps to reach a neighbor. In 2D, it is 1 for
|
||||
a 4-neighborhood and 2 for a 8-neighborhood. Default value is 1.
|
||||
parent : ndarray, int64, optional
|
||||
Parent image representing the max tree of the image. The
|
||||
value of each pixel is the index of its parent in the ravelled array.
|
||||
tree_traverser : 1D array, int64, optional
|
||||
The ordered pixel indices (referring to the ravelled array). The pixels
|
||||
are ordered such that every pixel is preceded by its parent (except for
|
||||
the root which has no parent).
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : ndarray
|
||||
Output image of the same shape and type as the input image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.area_closing
|
||||
skimage.morphology.diameter_opening
|
||||
skimage.morphology.diameter_closing
|
||||
skimage.morphology.max_tree
|
||||
skimage.morphology.remove_small_objects
|
||||
skimage.morphology.remove_small_holes
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Vincent L., Proc. "Grayscale area openings and closings,
|
||||
their efficient implementation and applications",
|
||||
EURASIP Workshop on Mathematical Morphology and its
|
||||
Applications to Signal Processing, Barcelona, Spain, pp.22-27,
|
||||
May 1993.
|
||||
.. [2] Soille, P., "Morphological Image Analysis: Principles and
|
||||
Applications" (Chapter 6), 2nd edition (2003), ISBN 3540429883.
|
||||
:DOI:10.1007/978-3-662-05088-0
|
||||
.. [3] Salembier, P., Oliveras, A., & Garrido, L. (1998). Antiextensive
|
||||
Connected Operators for Image and Sequence Processing.
|
||||
IEEE Transactions on Image Processing, 7(4), 555-570.
|
||||
:DOI:10.1109/83.663500
|
||||
.. [4] Najman, L., & Couprie, M. (2006). Building the component tree in
|
||||
quasi-linear time. IEEE Transactions on Image Processing, 15(11),
|
||||
3531-3539.
|
||||
:DOI:10.1109/TIP.2006.877518
|
||||
.. [5] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:10.1109/TIP.2014.2336551
|
||||
|
||||
Examples
|
||||
--------
|
||||
We create an image (quadratic function with a maximum in the center and
|
||||
4 additional local maxima.
|
||||
|
||||
>>> w = 12
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 20 - 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:3,1:5] = 40; f[2:4,9:11] = 60; f[9:11,2:4] = 80
|
||||
>>> f[9:10,9:11] = 100; f[10,10] = 100
|
||||
>>> f = f.astype(int)
|
||||
|
||||
We can calculate the area opening:
|
||||
|
||||
>>> open = area_opening(f, 8, connectivity=1)
|
||||
|
||||
The peaks with a surface smaller than 8 are removed.
|
||||
"""
|
||||
output = image.copy()
|
||||
|
||||
if parent is None or tree_traverser is None:
|
||||
parent, tree_traverser = max_tree(image, connectivity)
|
||||
|
||||
area = _max_tree._compute_area(image.ravel(),
|
||||
parent.ravel(), tree_traverser)
|
||||
|
||||
_max_tree._direct_filter(image.ravel(), output.ravel(), parent.ravel(),
|
||||
tree_traverser, area, area_threshold)
|
||||
return output
|
||||
|
||||
|
||||
def diameter_opening(image, diameter_threshold=8, connectivity=1,
|
||||
parent=None, tree_traverser=None):
|
||||
"""Perform a diameter opening of the image.
|
||||
|
||||
Diameter opening removes all bright structures of an image with
|
||||
maximal extension smaller than diameter_threshold. The maximal
|
||||
extension is defined as the maximal extension of the bounding box.
|
||||
The operator is also called Bounding Box Opening. In practice,
|
||||
the result is similar to a morphological opening, but long and thin
|
||||
structures are not removed.
|
||||
|
||||
Technically, this operator is based on the max-tree representation of
|
||||
the image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the area_opening is to be calculated.
|
||||
This image can be of any type.
|
||||
diameter_threshold : unsigned int
|
||||
The maximal extension parameter (number of pixels). The default value
|
||||
is 8.
|
||||
connectivity : unsigned int, optional
|
||||
The neighborhood connectivity. The integer represents the maximum
|
||||
number of orthogonal steps to reach a neighbor. In 2D, it is 1 for
|
||||
a 4-neighborhood and 2 for a 8-neighborhood. Default value is 1.
|
||||
parent : ndarray, int64, optional
|
||||
Parent image representing the max tree of the image. The
|
||||
value of each pixel is the index of its parent in the ravelled array.
|
||||
tree_traverser : 1D array, int64, optional
|
||||
The ordered pixel indices (referring to the ravelled array). The pixels
|
||||
are ordered such that every pixel is preceded by its parent (except for
|
||||
the root which has no parent).
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : ndarray
|
||||
Output image of the same shape and type as the input image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.area_opening
|
||||
skimage.morphology.area_closing
|
||||
skimage.morphology.diameter_closing
|
||||
skimage.morphology.max_tree
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Walter, T., & Klein, J.-C. (2002). Automatic Detection of
|
||||
Microaneurysms in Color Fundus Images of the Human Retina by Means
|
||||
of the Bounding Box Closing. In A. Colosimo, P. Sirabella,
|
||||
A. Giuliani (Eds.), Medical Data Analysis. Lecture Notes in Computer
|
||||
Science, vol 2526, pp. 210-220. Springer Berlin Heidelberg.
|
||||
:DOI:`10.1007/3-540-36104-9_23`
|
||||
.. [2] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:`10.1109/TIP.2014.2336551`
|
||||
|
||||
Examples
|
||||
--------
|
||||
We create an image (quadratic function with a maximum in the center and
|
||||
4 additional local maxima.
|
||||
|
||||
>>> w = 12
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 20 - 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:3,1:5] = 40; f[2:4,9:11] = 60; f[9:11,2:4] = 80
|
||||
>>> f[9:10,9:11] = 100; f[10,10] = 100
|
||||
>>> f = f.astype(int)
|
||||
|
||||
We can calculate the diameter opening:
|
||||
|
||||
>>> open = diameter_opening(f, 3, connectivity=1)
|
||||
|
||||
The peaks with a maximal extension of 2 or less are removed.
|
||||
The remaining peaks have all a maximal extension of at least 3.
|
||||
"""
|
||||
output = image.copy()
|
||||
|
||||
if parent is None or tree_traverser is None:
|
||||
parent, tree_traverser = max_tree(image, connectivity)
|
||||
|
||||
diam = _max_tree._compute_extension(image.ravel(),
|
||||
np.array(image.shape, dtype=np.int32),
|
||||
parent.ravel(), tree_traverser)
|
||||
|
||||
_max_tree._direct_filter(image.ravel(), output.ravel(), parent.ravel(),
|
||||
tree_traverser, diam, diameter_threshold)
|
||||
return output
|
||||
|
||||
|
||||
def area_closing(image, area_threshold=64, connectivity=1,
|
||||
parent=None, tree_traverser=None):
|
||||
"""Perform an area closing of the image.
|
||||
|
||||
Area closing removes all dark structures of an image with
|
||||
a surface smaller than area_threshold.
|
||||
The output image is larger than or equal to the input image
|
||||
for every pixel and all local minima have at least a surface of
|
||||
area_threshold pixels.
|
||||
|
||||
Area closings are similar to morphological closings, but
|
||||
they do not use a fixed footprint, but rather a deformable
|
||||
one, with surface = area_threshold.
|
||||
|
||||
In the binary case, area closings are equivalent to
|
||||
remove_small_holes; this operator is thus extended to gray-level images.
|
||||
|
||||
Technically, this operator is based on the max-tree representation of
|
||||
the image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the area_closing is to be calculated.
|
||||
This image can be of any type.
|
||||
area_threshold : unsigned int
|
||||
The size parameter (number of pixels). The default value is arbitrarily
|
||||
chosen to be 64.
|
||||
connectivity : unsigned int, optional
|
||||
The neighborhood connectivity. The integer represents the maximum
|
||||
number of orthogonal steps to reach a neighbor. In 2D, it is 1 for
|
||||
a 4-neighborhood and 2 for a 8-neighborhood. Default value is 1.
|
||||
parent : ndarray, int64, optional
|
||||
Parent image representing the max tree of the inverted image. The
|
||||
value of each pixel is the index of its parent in the ravelled array.
|
||||
See Note for further details.
|
||||
tree_traverser : 1D array, int64, optional
|
||||
The ordered pixel indices (referring to the ravelled array). The pixels
|
||||
are ordered such that every pixel is preceded by its parent (except for
|
||||
the root which has no parent).
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : ndarray
|
||||
Output image of the same shape and type as input image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.area_opening
|
||||
skimage.morphology.diameter_opening
|
||||
skimage.morphology.diameter_closing
|
||||
skimage.morphology.max_tree
|
||||
skimage.morphology.remove_small_objects
|
||||
skimage.morphology.remove_small_holes
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Vincent L., Proc. "Grayscale area openings and closings,
|
||||
their efficient implementation and applications",
|
||||
EURASIP Workshop on Mathematical Morphology and its
|
||||
Applications to Signal Processing, Barcelona, Spain, pp.22-27,
|
||||
May 1993.
|
||||
.. [2] Soille, P., "Morphological Image Analysis: Principles and
|
||||
Applications" (Chapter 6), 2nd edition (2003), ISBN 3540429883.
|
||||
:DOI:`10.1007/978-3-662-05088-0`
|
||||
.. [3] Salembier, P., Oliveras, A., & Garrido, L. (1998). Antiextensive
|
||||
Connected Operators for Image and Sequence Processing.
|
||||
IEEE Transactions on Image Processing, 7(4), 555-570.
|
||||
:DOI:`10.1109/83.663500`
|
||||
.. [4] Najman, L., & Couprie, M. (2006). Building the component tree in
|
||||
quasi-linear time. IEEE Transactions on Image Processing, 15(11),
|
||||
3531-3539.
|
||||
:DOI:`10.1109/TIP.2006.877518`
|
||||
.. [5] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:`10.1109/TIP.2014.2336551`
|
||||
|
||||
Examples
|
||||
--------
|
||||
We create an image (quadratic function with a minimum in the center and
|
||||
4 additional local minima.
|
||||
|
||||
>>> w = 12
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 180 + 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:3,1:5] = 160; f[2:4,9:11] = 140; f[9:11,2:4] = 120
|
||||
>>> f[9:10,9:11] = 100; f[10,10] = 100
|
||||
>>> f = f.astype(int)
|
||||
|
||||
We can calculate the area closing:
|
||||
|
||||
>>> closed = area_closing(f, 8, connectivity=1)
|
||||
|
||||
All small minima are removed, and the remaining minima have at least
|
||||
a size of 8.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If a max-tree representation (parent and tree_traverser) are given to the
|
||||
function, they must be calculated from the inverted image for this
|
||||
function, i.e.:
|
||||
>>> P, S = max_tree(invert(f))
|
||||
>>> closed = diameter_closing(f, 3, parent=P, tree_traverser=S)
|
||||
"""
|
||||
# inversion of the input image
|
||||
image_inv = invert(image)
|
||||
output = image_inv.copy()
|
||||
|
||||
if parent is None or tree_traverser is None:
|
||||
parent, tree_traverser = max_tree(image_inv, connectivity)
|
||||
|
||||
area = _max_tree._compute_area(image_inv.ravel(),
|
||||
parent.ravel(), tree_traverser)
|
||||
|
||||
_max_tree._direct_filter(image_inv.ravel(), output.ravel(), parent.ravel(),
|
||||
tree_traverser, area, area_threshold)
|
||||
|
||||
# inversion of the output image
|
||||
output = invert(output)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def diameter_closing(image, diameter_threshold=8, connectivity=1,
|
||||
parent=None, tree_traverser=None):
|
||||
"""Perform a diameter closing of the image.
|
||||
|
||||
Diameter closing removes all dark structures of an image with
|
||||
maximal extension smaller than diameter_threshold. The maximal
|
||||
extension is defined as the maximal extension of the bounding box.
|
||||
The operator is also called Bounding Box Closing. In practice,
|
||||
the result is similar to a morphological closing, but long and thin
|
||||
structures are not removed.
|
||||
|
||||
Technically, this operator is based on the max-tree representation of
|
||||
the image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the diameter_closing is to be calculated.
|
||||
This image can be of any type.
|
||||
diameter_threshold : unsigned int
|
||||
The maximal extension parameter (number of pixels). The default value
|
||||
is 8.
|
||||
connectivity : unsigned int, optional
|
||||
The neighborhood connectivity. The integer represents the maximum
|
||||
number of orthogonal steps to reach a neighbor. In 2D, it is 1 for
|
||||
a 4-neighborhood and 2 for a 8-neighborhood. Default value is 1.
|
||||
parent : ndarray, int64, optional
|
||||
Precomputed parent image representing the max tree of the inverted
|
||||
image. This function is fast, if precomputed parent and tree_traverser
|
||||
are provided. See Note for further details.
|
||||
tree_traverser : 1D array, int64, optional
|
||||
Precomputed traverser, where the pixels are ordered such that every
|
||||
pixel is preceded by its parent (except for the root which has no
|
||||
parent). This function is fast, if precomputed parent and
|
||||
tree_traverser are provided. See Note for further details.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : ndarray
|
||||
Output image of the same shape and type as input image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.area_opening
|
||||
skimage.morphology.area_closing
|
||||
skimage.morphology.diameter_opening
|
||||
skimage.morphology.max_tree
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Walter, T., & Klein, J.-C. (2002). Automatic Detection of
|
||||
Microaneurysms in Color Fundus Images of the Human Retina by Means
|
||||
of the Bounding Box Closing. In A. Colosimo, P. Sirabella,
|
||||
A. Giuliani (Eds.), Medical Data Analysis. Lecture Notes in Computer
|
||||
Science, vol 2526, pp. 210-220. Springer Berlin Heidelberg.
|
||||
:DOI:`10.1007/3-540-36104-9_23`
|
||||
.. [2] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:`10.1109/TIP.2014.2336551`
|
||||
|
||||
Examples
|
||||
--------
|
||||
We create an image (quadratic function with a minimum in the center and
|
||||
4 additional local minima.
|
||||
|
||||
>>> w = 12
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 180 + 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:3,1:5] = 160; f[2:4,9:11] = 140; f[9:11,2:4] = 120
|
||||
>>> f[9:10,9:11] = 100; f[10,10] = 100
|
||||
>>> f = f.astype(int)
|
||||
|
||||
We can calculate the diameter closing:
|
||||
|
||||
>>> closed = diameter_closing(f, 3, connectivity=1)
|
||||
|
||||
All small minima with a maximal extension of 2 or less are removed.
|
||||
The remaining minima have all a maximal extension of at least 3.
|
||||
|
||||
Notes
|
||||
-----
|
||||
If a max-tree representation (parent and tree_traverser) are given to the
|
||||
function, they must be calculated from the inverted image for this
|
||||
function, i.e.:
|
||||
>>> P, S = max_tree(invert(f))
|
||||
>>> closed = diameter_closing(f, 3, parent=P, tree_traverser=S)
|
||||
"""
|
||||
# inversion of the input image
|
||||
image_inv = invert(image)
|
||||
output = image_inv.copy()
|
||||
|
||||
if parent is None or tree_traverser is None:
|
||||
parent, tree_traverser = max_tree(image_inv, connectivity)
|
||||
|
||||
diam = _max_tree._compute_extension(image_inv.ravel(),
|
||||
np.array(image_inv.shape,
|
||||
dtype=np.int32),
|
||||
parent.ravel(), tree_traverser)
|
||||
|
||||
_max_tree._direct_filter(image_inv.ravel(), output.ravel(), parent.ravel(),
|
||||
tree_traverser, diam, diameter_threshold)
|
||||
output = invert(output)
|
||||
return output
|
||||
|
||||
|
||||
def max_tree_local_maxima(image, connectivity=1,
|
||||
parent=None, tree_traverser=None):
|
||||
"""Determine all local maxima of the image.
|
||||
|
||||
The local maxima are defined as connected sets of pixels with equal
|
||||
gray level strictly greater than the gray levels of all pixels in direct
|
||||
neighborhood of the set. The function labels the local maxima.
|
||||
|
||||
Technically, the implementation is based on the max-tree representation
|
||||
of an image. The function is very efficient if the max-tree representation
|
||||
has already been computed. Otherwise, it is preferable to use
|
||||
the function local_maxima.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the maxima are to be calculated.
|
||||
connectivity : unsigned int, optional
|
||||
The neighborhood connectivity. The integer represents the maximum
|
||||
number of orthogonal steps to reach a neighbor. In 2D, it is 1 for
|
||||
a 4-neighborhood and 2 for a 8-neighborhood. Default value is 1.
|
||||
parent : ndarray, int64, optional
|
||||
The value of each pixel is the index of its parent in the ravelled
|
||||
array.
|
||||
tree_traverser : 1D array, int64, optional
|
||||
The ordered pixel indices (referring to the ravelled array). The pixels
|
||||
are ordered such that every pixel is preceded by its parent (except for
|
||||
the root which has no parent).
|
||||
|
||||
Returns
|
||||
-------
|
||||
local_max : ndarray, uint64
|
||||
Labeled local maxima of the image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.local_maxima
|
||||
skimage.morphology.max_tree
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Vincent L., Proc. "Grayscale area openings and closings,
|
||||
their efficient implementation and applications",
|
||||
EURASIP Workshop on Mathematical Morphology and its
|
||||
Applications to Signal Processing, Barcelona, Spain, pp.22-27,
|
||||
May 1993.
|
||||
.. [2] Soille, P., "Morphological Image Analysis: Principles and
|
||||
Applications" (Chapter 6), 2nd edition (2003), ISBN 3540429883.
|
||||
:DOI:`10.1007/978-3-662-05088-0`
|
||||
.. [3] Salembier, P., Oliveras, A., & Garrido, L. (1998). Antiextensive
|
||||
Connected Operators for Image and Sequence Processing.
|
||||
IEEE Transactions on Image Processing, 7(4), 555-570.
|
||||
:DOI:`10.1109/83.663500`
|
||||
.. [4] Najman, L., & Couprie, M. (2006). Building the component tree in
|
||||
quasi-linear time. IEEE Transactions on Image Processing, 15(11),
|
||||
3531-3539.
|
||||
:DOI:`10.1109/TIP.2006.877518`
|
||||
.. [5] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:`10.1109/TIP.2014.2336551`
|
||||
|
||||
Examples
|
||||
--------
|
||||
We create an image (quadratic function with a maximum in the center and
|
||||
4 additional constant maxima.
|
||||
|
||||
>>> w = 10
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 20 - 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:4,2:4] = 40; f[2:4,7:9] = 60; f[7:9,2:4] = 80; f[7:9,7:9] = 100
|
||||
>>> f = f.astype(int)
|
||||
|
||||
We can calculate all local maxima:
|
||||
|
||||
>>> maxima = max_tree_local_maxima(f)
|
||||
|
||||
The resulting image contains the labeled local maxima.
|
||||
"""
|
||||
|
||||
output = np.ones(image.shape, dtype=np.uint64)
|
||||
|
||||
if parent is None or tree_traverser is None:
|
||||
parent, tree_traverser = max_tree(image, connectivity)
|
||||
|
||||
_max_tree._max_tree_local_maxima(image.ravel(), output.ravel(),
|
||||
parent.ravel(), tree_traverser)
|
||||
|
||||
return output
|
||||
221
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/misc.py
vendored
Normal file
221
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/misc.py
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
"""Miscellaneous morphology functions."""
|
||||
import numpy as np
|
||||
import functools
|
||||
from scipy import ndimage as ndi
|
||||
from .._shared.utils import warn
|
||||
|
||||
# Our function names don't exactly correspond to ndimages.
|
||||
# This dictionary translates from our names to scipy's.
|
||||
funcs = ('erosion', 'dilation', 'opening', 'closing')
|
||||
skimage2ndimage = {x: 'grey_' + x for x in funcs}
|
||||
|
||||
# These function names are the same in ndimage.
|
||||
funcs = ('binary_erosion', 'binary_dilation', 'binary_opening',
|
||||
'binary_closing', 'black_tophat', 'white_tophat')
|
||||
skimage2ndimage.update({x: x for x in funcs})
|
||||
|
||||
|
||||
def default_footprint(func):
|
||||
"""Decorator to add a default footprint to morphology functions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : function
|
||||
A morphology function such as erosion, dilation, opening, closing,
|
||||
white_tophat, or black_tophat.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func_out : function
|
||||
The function, using a default footprint of same dimension
|
||||
as the input image with connectivity 1.
|
||||
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def func_out(image, footprint=None, *args, **kwargs):
|
||||
if footprint is None:
|
||||
footprint = ndi.generate_binary_structure(image.ndim, 1)
|
||||
return func(image, footprint=footprint, *args, **kwargs)
|
||||
|
||||
return func_out
|
||||
|
||||
|
||||
def _check_dtype_supported(ar):
|
||||
# Should use `issubdtype` for bool below, but there's a bug in numpy 1.7
|
||||
if not (ar.dtype == bool or np.issubdtype(ar.dtype, np.integer)):
|
||||
raise TypeError("Only bool or integer image types are supported. "
|
||||
f"Got {ar.dtype}.")
|
||||
|
||||
|
||||
def remove_small_objects(ar, min_size=64, connectivity=1, *, out=None):
|
||||
"""Remove objects smaller than the specified size.
|
||||
|
||||
Expects ar to be an array with labeled objects, and removes objects
|
||||
smaller than min_size. If `ar` is bool, the image is first labeled.
|
||||
This leads to potentially different behavior for bool and 0-and-1
|
||||
arrays.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ar : ndarray (arbitrary shape, int or bool type)
|
||||
The array containing the objects of interest. If the array type is
|
||||
int, the ints must be non-negative.
|
||||
min_size : int, optional (default: 64)
|
||||
The smallest allowable object size.
|
||||
connectivity : int, {1, 2, ..., ar.ndim}, optional (default: 1)
|
||||
The connectivity defining the neighborhood of a pixel. Used during
|
||||
labelling if `ar` is bool.
|
||||
out : ndarray
|
||||
Array of the same shape as `ar`, into which the output is
|
||||
placed. By default, a new array is created.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If the input array is of an invalid type, such as float or string.
|
||||
ValueError
|
||||
If the input array contains negative values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray, same shape and type as input `ar`
|
||||
The input array with small connected components removed.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import morphology
|
||||
>>> a = np.array([[0, 0, 0, 1, 0],
|
||||
... [1, 1, 1, 0, 0],
|
||||
... [1, 1, 1, 0, 1]], bool)
|
||||
>>> b = morphology.remove_small_objects(a, 6)
|
||||
>>> b
|
||||
array([[False, False, False, False, False],
|
||||
[ True, True, True, False, False],
|
||||
[ True, True, True, False, False]])
|
||||
>>> c = morphology.remove_small_objects(a, 7, connectivity=2)
|
||||
>>> c
|
||||
array([[False, False, False, True, False],
|
||||
[ True, True, True, False, False],
|
||||
[ True, True, True, False, False]])
|
||||
>>> d = morphology.remove_small_objects(a, 6, out=a)
|
||||
>>> d is a
|
||||
True
|
||||
|
||||
"""
|
||||
# Raising type error if not int or bool
|
||||
_check_dtype_supported(ar)
|
||||
|
||||
if out is None:
|
||||
out = ar.copy()
|
||||
else:
|
||||
out[:] = ar
|
||||
|
||||
if min_size == 0: # shortcut for efficiency
|
||||
return out
|
||||
|
||||
if out.dtype == bool:
|
||||
footprint = ndi.generate_binary_structure(ar.ndim, connectivity)
|
||||
ccs = np.zeros_like(ar, dtype=np.int32)
|
||||
ndi.label(ar, footprint, output=ccs)
|
||||
else:
|
||||
ccs = out
|
||||
|
||||
try:
|
||||
component_sizes = np.bincount(ccs.ravel())
|
||||
except ValueError:
|
||||
raise ValueError("Negative value labels are not supported. Try "
|
||||
"relabeling the input with `scipy.ndimage.label` or "
|
||||
"`skimage.morphology.label`.")
|
||||
|
||||
if len(component_sizes) == 2 and out.dtype != bool:
|
||||
warn("Only one label was provided to `remove_small_objects`. "
|
||||
"Did you mean to use a boolean array?")
|
||||
|
||||
too_small = component_sizes < min_size
|
||||
too_small_mask = too_small[ccs]
|
||||
out[too_small_mask] = 0
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def remove_small_holes(ar, area_threshold=64, connectivity=1, *, out=None):
|
||||
"""Remove contiguous holes smaller than the specified size.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ar : ndarray (arbitrary shape, int or bool type)
|
||||
The array containing the connected components of interest.
|
||||
area_threshold : int, optional (default: 64)
|
||||
The maximum area, in pixels, of a contiguous hole that will be filled.
|
||||
Replaces `min_size`.
|
||||
connectivity : int, {1, 2, ..., ar.ndim}, optional (default: 1)
|
||||
The connectivity defining the neighborhood of a pixel.
|
||||
out : ndarray
|
||||
Array of the same shape as `ar` and bool dtype, into which the
|
||||
output is placed. By default, a new array is created.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If the input array is of an invalid type, such as float or string.
|
||||
ValueError
|
||||
If the input array contains negative values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray, same shape and type as input `ar`
|
||||
The input array with small holes within connected components removed.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import morphology
|
||||
>>> a = np.array([[1, 1, 1, 1, 1, 0],
|
||||
... [1, 1, 1, 0, 1, 0],
|
||||
... [1, 0, 0, 1, 1, 0],
|
||||
... [1, 1, 1, 1, 1, 0]], bool)
|
||||
>>> b = morphology.remove_small_holes(a, 2)
|
||||
>>> b
|
||||
array([[ True, True, True, True, True, False],
|
||||
[ True, True, True, True, True, False],
|
||||
[ True, False, False, True, True, False],
|
||||
[ True, True, True, True, True, False]])
|
||||
>>> c = morphology.remove_small_holes(a, 2, connectivity=2)
|
||||
>>> c
|
||||
array([[ True, True, True, True, True, False],
|
||||
[ True, True, True, False, True, False],
|
||||
[ True, False, False, True, True, False],
|
||||
[ True, True, True, True, True, False]])
|
||||
>>> d = morphology.remove_small_holes(a, 2, out=a)
|
||||
>>> d is a
|
||||
True
|
||||
|
||||
Notes
|
||||
-----
|
||||
If the array type is int, it is assumed that it contains already-labeled
|
||||
objects. The labels are not kept in the output image (this function always
|
||||
outputs a bool image). It is suggested that labeling is completed after
|
||||
using this function.
|
||||
|
||||
"""
|
||||
_check_dtype_supported(ar)
|
||||
|
||||
# Creates warning if image is an integer image
|
||||
if ar.dtype != bool:
|
||||
warn("Any labeled images will be returned as a boolean array. "
|
||||
"Did you mean to use a boolean array?", UserWarning)
|
||||
|
||||
if out is not None:
|
||||
if out.dtype != bool:
|
||||
raise TypeError("out dtype must be bool")
|
||||
else:
|
||||
out = ar.astype(bool, copy=True)
|
||||
|
||||
# Creating the inverse of ar
|
||||
np.logical_not(ar, out=out)
|
||||
|
||||
# removing small objects from the inverse of ar
|
||||
out = remove_small_objects(out, area_threshold, connectivity, out=out)
|
||||
|
||||
np.logical_not(out, out=out)
|
||||
|
||||
return out
|
||||
0
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/__init__.py
vendored
Normal file
0
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/__init__.py
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
317
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_binary.py
vendored
Normal file
317
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_binary.py
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_equal, assert_equal
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from skimage import data, color, morphology
|
||||
from skimage.util import img_as_bool
|
||||
from skimage.morphology import binary, footprints, gray
|
||||
|
||||
|
||||
img = color.rgb2gray(data.astronaut())
|
||||
bw_img = img > 100 / 255.
|
||||
|
||||
|
||||
def test_non_square_image():
|
||||
footprint = morphology.square(3)
|
||||
binary_res = binary.binary_erosion(bw_img[:100, :200], footprint)
|
||||
gray_res = img_as_bool(gray.erosion(bw_img[:100, :200], footprint))
|
||||
assert_array_equal(binary_res, gray_res)
|
||||
|
||||
|
||||
def test_binary_erosion():
|
||||
footprint = morphology.square(3)
|
||||
binary_res = binary.binary_erosion(bw_img, footprint)
|
||||
gray_res = img_as_bool(gray.erosion(bw_img, footprint))
|
||||
assert_array_equal(binary_res, gray_res)
|
||||
|
||||
|
||||
def test_binary_dilation():
|
||||
footprint = morphology.square(3)
|
||||
binary_res = binary.binary_dilation(bw_img, footprint)
|
||||
gray_res = img_as_bool(gray.dilation(bw_img, footprint))
|
||||
assert_array_equal(binary_res, gray_res)
|
||||
|
||||
|
||||
def test_binary_closing():
|
||||
footprint = morphology.square(3)
|
||||
binary_res = binary.binary_closing(bw_img, footprint)
|
||||
gray_res = img_as_bool(gray.closing(bw_img, footprint))
|
||||
assert_array_equal(binary_res, gray_res)
|
||||
|
||||
|
||||
def test_binary_opening():
|
||||
footprint = morphology.square(3)
|
||||
binary_res = binary.binary_opening(bw_img, footprint)
|
||||
gray_res = img_as_bool(gray.opening(bw_img, footprint))
|
||||
assert_array_equal(binary_res, gray_res)
|
||||
|
||||
|
||||
def _get_decomp_test_data(function, ndim=2):
|
||||
if function == 'binary_erosion':
|
||||
img = np.ones((17, ) * ndim, dtype=np.uint8)
|
||||
img[8, 8] = 0
|
||||
elif function == 'binary_dilation':
|
||||
img = np.zeros((17, ) * ndim, dtype=np.uint8)
|
||||
img[8, 8] = 1
|
||||
else:
|
||||
img = data.binary_blobs(32, n_dim=ndim, seed=1)
|
||||
return img
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function",
|
||||
["binary_erosion", "binary_dilation", "binary_closing", "binary_opening"],
|
||||
)
|
||||
@pytest.mark.parametrize("size", (3, 4, 11))
|
||||
@pytest.mark.parametrize("decomposition", ['separable', 'sequence'])
|
||||
def test_square_decomposition(function, size, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.square(size, decomposition=None)
|
||||
footprint = footprints.square(size, decomposition=decomposition)
|
||||
img = _get_decomp_test_data(function)
|
||||
func = getattr(binary, function)
|
||||
expected = func(img, footprint=footprint_ndarray)
|
||||
out = func(img, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function",
|
||||
["binary_erosion", "binary_dilation", "binary_closing", "binary_opening"],
|
||||
)
|
||||
@pytest.mark.parametrize("nrows", (3, 4, 11))
|
||||
@pytest.mark.parametrize("ncols", (3, 4, 11))
|
||||
@pytest.mark.parametrize("decomposition", ['separable', 'sequence'])
|
||||
def test_rectangle_decomposition(function, nrows, ncols, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.rectangle(nrows, ncols, decomposition=None)
|
||||
footprint = footprints.rectangle(nrows, ncols, decomposition=decomposition)
|
||||
img = _get_decomp_test_data(function)
|
||||
func = getattr(binary, function)
|
||||
expected = func(img, footprint=footprint_ndarray)
|
||||
out = func(img, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function",
|
||||
["binary_erosion", "binary_dilation", "binary_closing", "binary_opening"],
|
||||
)
|
||||
@pytest.mark.parametrize("m", (0, 1, 2, 3, 4, 5))
|
||||
@pytest.mark.parametrize("n", (0, 1, 2, 3, 4, 5))
|
||||
@pytest.mark.parametrize("decomposition", ['sequence'])
|
||||
def test_octagon_decomposition(function, m, n, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
if m == 0 and n == 0:
|
||||
with pytest.raises(ValueError):
|
||||
footprints.octagon(m, n, decomposition=decomposition)
|
||||
else:
|
||||
footprint_ndarray = footprints.octagon(m, n, decomposition=None)
|
||||
footprint = footprints.octagon(m, n, decomposition=decomposition)
|
||||
img = _get_decomp_test_data(function)
|
||||
func = getattr(binary, function)
|
||||
expected = func(img, footprint=footprint_ndarray)
|
||||
out = func(img, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function",
|
||||
["binary_erosion", "binary_dilation", "binary_closing", "binary_opening"],
|
||||
)
|
||||
@pytest.mark.parametrize("radius", (1, 2, 5))
|
||||
@pytest.mark.parametrize("decomposition", ['sequence'])
|
||||
def test_diamond_decomposition(function, radius, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.diamond(radius, decomposition=None)
|
||||
footprint = footprints.diamond(radius, decomposition=decomposition)
|
||||
img = _get_decomp_test_data(function)
|
||||
func = getattr(binary, function)
|
||||
expected = func(img, footprint=footprint_ndarray)
|
||||
out = func(img, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function",
|
||||
["binary_erosion", "binary_dilation", "binary_closing", "binary_opening"],
|
||||
)
|
||||
@pytest.mark.parametrize("size", (3, 4, 5))
|
||||
@pytest.mark.parametrize("decomposition", ['separable', 'sequence'])
|
||||
def test_cube_decomposition(function, size, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.cube(size, decomposition=None)
|
||||
footprint = footprints.cube(size, decomposition=decomposition)
|
||||
img = _get_decomp_test_data(function, ndim=3)
|
||||
func = getattr(binary, function)
|
||||
expected = func(img, footprint=footprint_ndarray)
|
||||
out = func(img, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function",
|
||||
["binary_erosion", "binary_dilation", "binary_closing", "binary_opening"],
|
||||
)
|
||||
@pytest.mark.parametrize("radius", (1, 2, 3))
|
||||
@pytest.mark.parametrize("decomposition", ['sequence'])
|
||||
def test_octahedron_decomposition(function, radius, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.octahedron(radius, decomposition=None)
|
||||
footprint = footprints.octahedron(radius, decomposition=decomposition)
|
||||
img = _get_decomp_test_data(function, ndim=3)
|
||||
func = getattr(binary, function)
|
||||
expected = func(img, footprint=footprint_ndarray)
|
||||
out = func(img, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
def test_footprint_overflow():
|
||||
footprint = np.ones((17, 17), dtype=np.uint8)
|
||||
img = np.zeros((20, 20), dtype=bool)
|
||||
img[2:19, 2:19] = True
|
||||
binary_res = binary.binary_erosion(img, footprint)
|
||||
gray_res = img_as_bool(gray.erosion(img, footprint))
|
||||
assert_array_equal(binary_res, gray_res)
|
||||
|
||||
|
||||
def test_out_argument():
|
||||
for func in (binary.binary_erosion, binary.binary_dilation):
|
||||
footprint = np.ones((3, 3), dtype=np.uint8)
|
||||
img = np.ones((10, 10))
|
||||
out = np.zeros_like(img)
|
||||
out_saved = out.copy()
|
||||
func(img, footprint, out=out)
|
||||
assert np.any(out != out_saved)
|
||||
assert_array_equal(out, func(img, footprint))
|
||||
|
||||
|
||||
binary_functions = [binary.binary_erosion, binary.binary_dilation,
|
||||
binary.binary_opening, binary.binary_closing]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("function", binary_functions)
|
||||
def test_default_footprint(function):
|
||||
footprint = morphology.diamond(radius=1)
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 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]], np.uint8)
|
||||
im_expected = function(image, footprint)
|
||||
im_test = function(image)
|
||||
assert_array_equal(im_expected, im_test)
|
||||
|
||||
|
||||
def test_3d_fallback_default_footprint():
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
opened = binary.binary_opening(image)
|
||||
|
||||
# expect a "hyper-cross" centered in the 5x5x5:
|
||||
image_expected = np.zeros((7, 7, 7), dtype=bool)
|
||||
image_expected[2:5, 2:5, 2:5] = ndi.generate_binary_structure(3, 1)
|
||||
assert_array_equal(opened, image_expected)
|
||||
|
||||
|
||||
binary_3d_fallback_functions = [binary.binary_opening, binary.binary_closing]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("function", binary_3d_fallback_functions)
|
||||
def test_3d_fallback_cube_footprint(function):
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
cube = np.ones((3, 3, 3), dtype=np.uint8)
|
||||
|
||||
new_image = function(image, cube)
|
||||
assert_array_equal(new_image, image)
|
||||
|
||||
|
||||
def test_2d_ndimage_equivalence():
|
||||
image = np.zeros((9, 9), np.uint16)
|
||||
image[2:-2, 2:-2] = 2**14
|
||||
image[3:-3, 3:-3] = 2**15
|
||||
image[4, 4] = 2**16-1
|
||||
|
||||
bin_opened = binary.binary_opening(image)
|
||||
bin_closed = binary.binary_closing(image)
|
||||
|
||||
footprint = ndi.generate_binary_structure(2, 1)
|
||||
ndimage_opened = ndi.binary_opening(image, structure=footprint)
|
||||
ndimage_closed = ndi.binary_closing(image, structure=footprint)
|
||||
|
||||
assert_array_equal(bin_opened, ndimage_opened)
|
||||
assert_array_equal(bin_closed, ndimage_closed)
|
||||
|
||||
|
||||
def test_binary_output_2d():
|
||||
image = np.zeros((9, 9), np.uint16)
|
||||
image[2:-2, 2:-2] = 2**14
|
||||
image[3:-3, 3:-3] = 2**15
|
||||
image[4, 4] = 2**16-1
|
||||
|
||||
bin_opened = binary.binary_opening(image)
|
||||
bin_closed = binary.binary_closing(image)
|
||||
|
||||
int_opened = np.empty_like(image, dtype=np.uint8)
|
||||
int_closed = np.empty_like(image, dtype=np.uint8)
|
||||
binary.binary_opening(image, out=int_opened)
|
||||
binary.binary_closing(image, out=int_closed)
|
||||
|
||||
assert_equal(bin_opened.dtype, bool)
|
||||
assert_equal(bin_closed.dtype, bool)
|
||||
|
||||
assert_equal(int_opened.dtype, np.uint8)
|
||||
assert_equal(int_closed.dtype, np.uint8)
|
||||
|
||||
|
||||
def test_binary_output_3d():
|
||||
image = np.zeros((9, 9, 9), np.uint16)
|
||||
image[2:-2, 2:-2, 2:-2] = 2**14
|
||||
image[3:-3, 3:-3, 3:-3] = 2**15
|
||||
image[4, 4, 4] = 2**16-1
|
||||
|
||||
bin_opened = binary.binary_opening(image)
|
||||
bin_closed = binary.binary_closing(image)
|
||||
|
||||
int_opened = np.empty_like(image, dtype=np.uint8)
|
||||
int_closed = np.empty_like(image, dtype=np.uint8)
|
||||
binary.binary_opening(image, out=int_opened)
|
||||
binary.binary_closing(image, out=int_closed)
|
||||
|
||||
assert_equal(bin_opened.dtype, bool)
|
||||
assert_equal(bin_closed.dtype, bool)
|
||||
|
||||
assert_equal(int_opened.dtype, np.uint8)
|
||||
assert_equal(int_closed.dtype, np.uint8)
|
||||
197
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_convex_hull.py
vendored
Normal file
197
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_convex_hull.py
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
import numpy as np
|
||||
from skimage.morphology import convex_hull_image, convex_hull_object
|
||||
from skimage.morphology._convex_hull import possible_hull
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
def test_basic():
|
||||
image = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool)
|
||||
|
||||
expected = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool)
|
||||
|
||||
assert_array_equal(convex_hull_image(image), expected)
|
||||
|
||||
|
||||
def test_empty_image():
|
||||
image = np.zeros((6, 6), dtype=bool)
|
||||
with expected_warnings(['entirely zero']):
|
||||
assert_array_equal(convex_hull_image(image), image)
|
||||
|
||||
|
||||
def test_qhull_offset_example():
|
||||
nonzeros = (([1367, 1368, 1368, 1368, 1369, 1369, 1369, 1369, 1369, 1370,
|
||||
1370, 1370, 1370, 1370, 1370, 1370, 1371, 1371, 1371, 1371,
|
||||
1371, 1371, 1371, 1371, 1371, 1372, 1372, 1372, 1372, 1372,
|
||||
1372, 1372, 1372, 1372, 1373, 1373, 1373, 1373, 1373, 1373,
|
||||
1373, 1373, 1373, 1374, 1374, 1374, 1374, 1374, 1374, 1374,
|
||||
1375, 1375, 1375, 1375, 1375, 1376, 1376, 1376, 1377, 1372]),
|
||||
([151, 150, 151, 152, 149, 150, 151, 152, 153, 148, 149, 150,
|
||||
151, 152, 153, 154, 147, 148, 149, 150, 151, 152, 153, 154,
|
||||
155, 146, 147, 148, 149, 150, 151, 152, 153, 154, 146, 147,
|
||||
148, 149, 150, 151, 152, 153, 154, 147, 148, 149, 150, 151,
|
||||
152, 153, 148, 149, 150, 151, 152, 149, 150, 151, 150, 155]))
|
||||
image = np.zeros((1392, 1040), dtype=bool)
|
||||
image[nonzeros] = True
|
||||
expected = image.copy()
|
||||
assert_array_equal(convex_hull_image(image), expected)
|
||||
|
||||
|
||||
def test_pathological_qhull_example():
|
||||
image = np.array(
|
||||
[[0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 0, 0, 0, 0]], dtype=bool)
|
||||
expected = np.array(
|
||||
[[0, 0, 0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 0, 0]], dtype=bool)
|
||||
assert_array_equal(convex_hull_image(image), expected)
|
||||
|
||||
|
||||
def test_pathological_qhull_labels():
|
||||
image = np.array([[0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 0, 0, 0, 0]], dtype=bool)
|
||||
|
||||
expected = np.array([[0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 0, 0, 0]], dtype=bool)
|
||||
|
||||
actual = convex_hull_image(image, include_borders=False)
|
||||
assert_array_equal(actual, expected)
|
||||
|
||||
|
||||
def test_possible_hull():
|
||||
image = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
|
||||
expected = np.array([[1, 4],
|
||||
[2, 3],
|
||||
[3, 2],
|
||||
[4, 1],
|
||||
[4, 1],
|
||||
[3, 2],
|
||||
[2, 3],
|
||||
[1, 4],
|
||||
[2, 5],
|
||||
[3, 6],
|
||||
[4, 7],
|
||||
[2, 5],
|
||||
[3, 6],
|
||||
[4, 7],
|
||||
[4, 2],
|
||||
[4, 3],
|
||||
[4, 4],
|
||||
[4, 5],
|
||||
[4, 6]])
|
||||
|
||||
ph = possible_hull(image)
|
||||
assert_array_equal(ph, expected)
|
||||
|
||||
|
||||
def test_object():
|
||||
image = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 0, 0, 1, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[1, 0, 0, 0, 0, 0, 1, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool)
|
||||
|
||||
expected_conn_1 = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 0, 0, 1, 0, 1],
|
||||
[1, 1, 1, 0, 0, 0, 0, 1, 0],
|
||||
[1, 1, 0, 0, 0, 0, 1, 0, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool)
|
||||
|
||||
assert_array_equal(convex_hull_object(image, connectivity=1),
|
||||
expected_conn_1)
|
||||
|
||||
expected_conn_2 = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 0, 0, 1, 1, 1],
|
||||
[1, 1, 1, 0, 0, 0, 1, 1, 1],
|
||||
[1, 1, 0, 0, 0, 0, 1, 1, 1],
|
||||
[1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=bool)
|
||||
|
||||
assert_array_equal(convex_hull_object(image, connectivity=2),
|
||||
expected_conn_2)
|
||||
|
||||
with testing.raises(ValueError):
|
||||
convex_hull_object(image, connectivity=3)
|
||||
|
||||
out = convex_hull_object(image, connectivity=1)
|
||||
assert_array_equal(out, expected_conn_1)
|
||||
|
||||
|
||||
def test_non_c_contiguous():
|
||||
# 2D Fortran-contiguous
|
||||
image = np.ones((2, 2), order='F', dtype=bool)
|
||||
assert_array_equal(convex_hull_image(image), image)
|
||||
# 3D Fortran-contiguous
|
||||
image = np.ones((2, 2, 2), order='F', dtype=bool)
|
||||
assert_array_equal(convex_hull_image(image), image)
|
||||
# 3D non-contiguous
|
||||
image = np.transpose(np.ones((2, 2, 2), dtype=bool), [0, 2, 1])
|
||||
assert_array_equal(convex_hull_image(image), image)
|
||||
|
||||
|
||||
@testing.fixture
|
||||
def images2d3d():
|
||||
from ...measure.tests.test_regionprops import SAMPLE as image
|
||||
image3d = np.stack((image, image, image))
|
||||
return image, image3d
|
||||
|
||||
|
||||
def test_consistent_2d_3d_hulls(images2d3d):
|
||||
image, image3d = images2d3d
|
||||
chimage = convex_hull_image(image)
|
||||
chimage[8, 0] = True # correct for single point exactly on hull edge
|
||||
chimage3d = convex_hull_image(image3d)
|
||||
assert_array_equal(chimage3d[1], chimage)
|
||||
|
||||
|
||||
def test_few_points():
|
||||
image = np.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, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
image3d = np.stack([image, image, image])
|
||||
with testing.assert_warns(UserWarning):
|
||||
chimage3d = convex_hull_image(image3d)
|
||||
assert_array_equal(chimage3d, np.zeros(image3d.shape, dtype=bool))
|
||||
637
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_extrema.py
vendored
Normal file
637
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_extrema.py
vendored
Normal file
@@ -0,0 +1,637 @@
|
||||
import math
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_equal
|
||||
from pytest import raises, warns
|
||||
|
||||
from skimage._shared.testing import expected_warnings
|
||||
from skimage.morphology import extrema
|
||||
|
||||
|
||||
eps = 1e-12
|
||||
|
||||
|
||||
def diff(a, b):
|
||||
a = np.asarray(a, dtype=np.float64)
|
||||
b = np.asarray(b, dtype=np.float64)
|
||||
t = ((a - b) ** 2).sum()
|
||||
return math.sqrt(t)
|
||||
|
||||
|
||||
class TestExtrema():
|
||||
|
||||
def test_saturated_arithmetic(self):
|
||||
"""Adding/subtracting a constant and clipping"""
|
||||
# Test for unsigned integer
|
||||
data = np.array([[250, 251, 5, 5],
|
||||
[100, 200, 253, 252],
|
||||
[4, 10, 1, 3]],
|
||||
dtype=np.uint8)
|
||||
# adding the constant
|
||||
img_constant_added = extrema._add_constant_clip(data, 4)
|
||||
expected = np.array([[254, 255, 9, 9],
|
||||
[104, 204, 255, 255],
|
||||
[8, 14, 5, 7]],
|
||||
dtype=np.uint8)
|
||||
error = diff(img_constant_added, expected)
|
||||
assert error < eps
|
||||
img_constant_subtracted = extrema._subtract_constant_clip(data, 4)
|
||||
expected = np.array([[246, 247, 1, 1],
|
||||
[96, 196, 249, 248],
|
||||
[0, 6, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
error = diff(img_constant_subtracted, expected)
|
||||
assert error < eps
|
||||
|
||||
# Test for signed integer
|
||||
data = np.array([[32767, 32766],
|
||||
[-32768, -32767]],
|
||||
dtype=np.int16)
|
||||
img_constant_added = extrema._add_constant_clip(data, 1)
|
||||
expected = np.array([[32767, 32767],
|
||||
[-32767, -32766]],
|
||||
dtype=np.int16)
|
||||
error = diff(img_constant_added, expected)
|
||||
assert error < eps
|
||||
img_constant_subtracted = extrema._subtract_constant_clip(data, 1)
|
||||
expected = np.array([[32766, 32765],
|
||||
[-32768, -32768]],
|
||||
dtype=np.int16)
|
||||
error = diff(img_constant_subtracted, expected)
|
||||
assert error < eps
|
||||
|
||||
def test_h_maxima(self):
|
||||
"""h-maxima for various data types"""
|
||||
|
||||
data = np.array([[10, 11, 13, 14, 14, 15, 14, 14, 13, 11],
|
||||
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13],
|
||||
[13, 15, 40, 40, 18, 18, 18, 60, 60, 15],
|
||||
[14, 16, 40, 40, 19, 19, 19, 60, 60, 16],
|
||||
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||||
[15, 16, 18, 19, 19, 20, 19, 19, 18, 16],
|
||||
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||||
[14, 16, 80, 80, 19, 19, 19, 100, 100, 16],
|
||||
[13, 15, 80, 80, 18, 18, 18, 100, 100, 15],
|
||||
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13]],
|
||||
dtype=np.uint8)
|
||||
|
||||
expected_result = np.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, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 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, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
for dtype in [np.uint8, np.uint64, np.int8, np.int64]:
|
||||
data = data.astype(dtype)
|
||||
out = extrema.h_maxima(data, 40)
|
||||
|
||||
error = diff(expected_result, out)
|
||||
assert error < eps
|
||||
|
||||
def test_h_minima(self):
|
||||
"""h-minima for various data types"""
|
||||
|
||||
data = np.array([[10, 11, 13, 14, 14, 15, 14, 14, 13, 11],
|
||||
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13],
|
||||
[13, 15, 40, 40, 18, 18, 18, 60, 60, 15],
|
||||
[14, 16, 40, 40, 19, 19, 19, 60, 60, 16],
|
||||
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||||
[15, 16, 18, 19, 19, 20, 19, 19, 18, 16],
|
||||
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||||
[14, 16, 80, 80, 19, 19, 19, 100, 100, 16],
|
||||
[13, 15, 80, 80, 18, 18, 18, 100, 100, 15],
|
||||
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13]],
|
||||
dtype=np.uint8)
|
||||
data = 100 - data
|
||||
expected_result = np.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, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 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, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
for dtype in [np.uint8, np.uint64, np.int8, np.int64]:
|
||||
data = data.astype(dtype)
|
||||
out = extrema.h_minima(data, 40)
|
||||
|
||||
error = diff(expected_result, out)
|
||||
assert error < eps
|
||||
assert out.dtype == expected_result.dtype
|
||||
|
||||
def test_extrema_float(self):
|
||||
"""specific tests for float type"""
|
||||
data = np.array([[0.10, 0.11, 0.13, 0.14, 0.14, 0.15, 0.14,
|
||||
0.14, 0.13, 0.11],
|
||||
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16,
|
||||
0.16, 0.15, 0.13],
|
||||
[0.13, 0.15, 0.40, 0.40, 0.18, 0.18, 0.18,
|
||||
0.60, 0.60, 0.15],
|
||||
[0.14, 0.16, 0.40, 0.40, 0.19, 0.19, 0.19,
|
||||
0.60, 0.60, 0.16],
|
||||
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19,
|
||||
0.19, 0.18, 0.16],
|
||||
[0.15, 0.182, 0.18, 0.19, 0.204, 0.20, 0.19,
|
||||
0.19, 0.18, 0.16],
|
||||
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19,
|
||||
0.19, 0.18, 0.16],
|
||||
[0.14, 0.16, 0.80, 0.80, 0.19, 0.19, 0.19,
|
||||
1.0, 1.0, 0.16],
|
||||
[0.13, 0.15, 0.80, 0.80, 0.18, 0.18, 0.18,
|
||||
1.0, 1.0, 0.15],
|
||||
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16,
|
||||
0.16, 0.15, 0.13]],
|
||||
dtype=np.float32)
|
||||
inverted_data = 1.0 - data
|
||||
|
||||
out = extrema.h_maxima(data, 0.003)
|
||||
expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 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, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
|
||||
error = diff(expected_result, out)
|
||||
assert error < eps
|
||||
|
||||
out = extrema.h_minima(inverted_data, 0.003)
|
||||
error = diff(expected_result, out)
|
||||
assert error < eps
|
||||
|
||||
def test_h_maxima_float_image(self):
|
||||
"""specific tests for h-maxima float image type"""
|
||||
w = 10
|
||||
x, y = np.mgrid[0:w, 0:w]
|
||||
data = 20 - 0.2 * ((x - w / 2) ** 2 + (y - w / 2) ** 2)
|
||||
data[2:4, 2:4] = 40
|
||||
data[2:4, 7:9] = 60
|
||||
data[7:9, 2:4] = 80
|
||||
data[7:9, 7:9] = 100
|
||||
data = data.astype(np.float32)
|
||||
|
||||
expected_result = np.zeros_like(data)
|
||||
expected_result[(data > 19.9)] = 1.0
|
||||
|
||||
for h in [1.0e-12, 1.0e-6, 1.0e-3, 1.0e-2, 1.0e-1, 0.1]:
|
||||
out = extrema.h_maxima(data, h)
|
||||
error = diff(expected_result, out)
|
||||
assert error < eps
|
||||
|
||||
def test_h_maxima_float_h(self):
|
||||
"""specific tests for h-maxima float h parameter"""
|
||||
data = np.array([[0, 0, 0, 0, 0],
|
||||
[0, 3, 3, 3, 0],
|
||||
[0, 3, 4, 3, 0],
|
||||
[0, 3, 3, 3, 0],
|
||||
[0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
|
||||
h_vals = np.linspace(1.0, 2.0, 100)
|
||||
failures = 0
|
||||
|
||||
for h in h_vals:
|
||||
if h % 1 != 0:
|
||||
msgs = ['possible precision loss converting image']
|
||||
else:
|
||||
msgs = []
|
||||
|
||||
with expected_warnings(msgs):
|
||||
maxima = extrema.h_maxima(data, h)
|
||||
|
||||
if (maxima[2, 2] == 0):
|
||||
failures += 1
|
||||
|
||||
assert (failures == 0)
|
||||
|
||||
def test_h_maxima_large_h(self):
|
||||
"""test that h-maxima works correctly for large h"""
|
||||
data = np.array([[10, 10, 10, 10, 10],
|
||||
[10, 13, 13, 13, 10],
|
||||
[10, 13, 14, 13, 10],
|
||||
[10, 13, 13, 13, 10],
|
||||
[10, 10, 10, 10, 10]], dtype=np.uint8)
|
||||
|
||||
maxima = extrema.h_maxima(data, 5)
|
||||
assert (np.sum(maxima) == 0)
|
||||
|
||||
data = np.array([[10, 10, 10, 10, 10],
|
||||
[10, 13, 13, 13, 10],
|
||||
[10, 13, 14, 13, 10],
|
||||
[10, 13, 13, 13, 10],
|
||||
[10, 10, 10, 10, 10]], dtype=np.float32)
|
||||
|
||||
maxima = extrema.h_maxima(data, 5.0)
|
||||
assert (np.sum(maxima) == 0)
|
||||
|
||||
def test_h_minima_float_image(self):
|
||||
"""specific tests for h-minima float image type"""
|
||||
w = 10
|
||||
x, y = np.mgrid[0:w, 0:w]
|
||||
data = 180 + 0.2 * ((x - w / 2) ** 2 + (y - w / 2) ** 2)
|
||||
data[2:4, 2:4] = 160
|
||||
data[2:4, 7:9] = 140
|
||||
data[7:9, 2:4] = 120
|
||||
data[7:9, 7:9] = 100
|
||||
data = data.astype(np.float32)
|
||||
|
||||
expected_result = np.zeros_like(data)
|
||||
expected_result[(data < 180.1)] = 1.0
|
||||
|
||||
for h in [1.0e-12, 1.0e-6, 1.0e-3, 1.0e-2, 1.0e-1, 0.1]:
|
||||
out = extrema.h_minima(data, h)
|
||||
error = diff(expected_result, out)
|
||||
assert error < eps
|
||||
|
||||
def test_h_minima_float_h(self):
|
||||
"""specific tests for h-minima float h parameter"""
|
||||
data = np.array([[4, 4, 4, 4, 4],
|
||||
[4, 1, 1, 1, 4],
|
||||
[4, 1, 0, 1, 4],
|
||||
[4, 1, 1, 1, 4],
|
||||
[4, 4, 4, 4, 4]], dtype=np.uint8)
|
||||
|
||||
h_vals = np.linspace(1.0, 2.0, 100)
|
||||
failures = 0
|
||||
for h in h_vals:
|
||||
if h % 1 != 0:
|
||||
msgs = ['possible precision loss converting image']
|
||||
else:
|
||||
msgs = []
|
||||
|
||||
with expected_warnings(msgs):
|
||||
minima = extrema.h_minima(data, h)
|
||||
|
||||
if (minima[2, 2] == 0):
|
||||
failures += 1
|
||||
|
||||
assert (failures == 0)
|
||||
|
||||
def test_h_minima_large_h(self):
|
||||
"""test that h-minima works correctly for large h"""
|
||||
data = np.array([[14, 14, 14, 14, 14],
|
||||
[14, 11, 11, 11, 14],
|
||||
[14, 11, 10, 11, 14],
|
||||
[14, 11, 11, 11, 14],
|
||||
[14, 14, 14, 14, 14]], dtype=np.uint8)
|
||||
|
||||
maxima = extrema.h_minima(data, 5)
|
||||
assert (np.sum(maxima) == 0)
|
||||
|
||||
data = np.array([[14, 14, 14, 14, 14],
|
||||
[14, 11, 11, 11, 14],
|
||||
[14, 11, 10, 11, 14],
|
||||
[14, 11, 11, 11, 14],
|
||||
[14, 14, 14, 14, 14]], dtype=np.float32)
|
||||
|
||||
maxima = extrema.h_minima(data, 5.0)
|
||||
assert (np.sum(maxima) == 0)
|
||||
|
||||
|
||||
class TestLocalMaxima(unittest.TestCase):
|
||||
"""Some tests for local_minima are included as well."""
|
||||
|
||||
supported_dtypes = [
|
||||
np.uint8, np.uint16, np.uint32, np.uint64,
|
||||
np.int8, np.int16, np.int32, np.int64,
|
||||
np.float32, np.float64
|
||||
]
|
||||
image = np.array(
|
||||
[[1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 2, 0, 0, 3, 3, 0, 0, 4, 0, 2, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 4, 4, 0, 3, 0, 0, 0],
|
||||
[0, 2, 0, 1, 0, 2, 1, 0, 0, 0, 0, 3, 0, 0, 0],
|
||||
[0, 0, 2, 0, 2, 0, 0, 0, 2, 1, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8
|
||||
)
|
||||
# Connectivity 2, maxima can touch border, returned with default values
|
||||
expected_default = np.array(
|
||||
[[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 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, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=bool
|
||||
)
|
||||
# Connectivity 1 (cross), maxima can touch border
|
||||
expected_cross = np.array(
|
||||
[[1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=bool
|
||||
)
|
||||
|
||||
def test_empty(self):
|
||||
"""Test result with empty image."""
|
||||
result = extrema.local_maxima(np.array([[]]), indices=False)
|
||||
assert result.size == 0
|
||||
assert result.dtype == bool
|
||||
assert result.shape == (1, 0)
|
||||
|
||||
result = extrema.local_maxima(np.array([]), indices=True)
|
||||
assert isinstance(result, tuple)
|
||||
assert len(result) == 1
|
||||
assert result[0].size == 0
|
||||
assert result[0].dtype == np.intp
|
||||
|
||||
result = extrema.local_maxima(np.array([[]]), indices=True)
|
||||
assert isinstance(result, tuple)
|
||||
assert len(result) == 2
|
||||
assert result[0].size == 0
|
||||
assert result[0].dtype == np.intp
|
||||
assert result[1].size == 0
|
||||
assert result[1].dtype == np.intp
|
||||
|
||||
def test_dtypes(self):
|
||||
"""Test results with default configuration for all supported dtypes."""
|
||||
for dtype in self.supported_dtypes:
|
||||
result = extrema.local_maxima(self.image.astype(dtype))
|
||||
assert result.dtype == bool
|
||||
assert_equal(result, self.expected_default)
|
||||
|
||||
def test_dtypes_old(self):
|
||||
"""
|
||||
Test results with default configuration and data copied from old unit
|
||||
tests for all supported dtypes.
|
||||
"""
|
||||
data = np.array(
|
||||
[[10, 11, 13, 14, 14, 15, 14, 14, 13, 11],
|
||||
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13],
|
||||
[13, 15, 40, 40, 18, 18, 18, 60, 60, 15],
|
||||
[14, 16, 40, 40, 19, 19, 19, 60, 60, 16],
|
||||
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||||
[15, 16, 18, 19, 19, 20, 19, 19, 18, 16],
|
||||
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||||
[14, 16, 80, 80, 19, 19, 19, 100, 100, 16],
|
||||
[13, 15, 80, 80, 18, 18, 18, 100, 100, 15],
|
||||
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13]],
|
||||
dtype=np.uint8
|
||||
)
|
||||
expected = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=bool
|
||||
)
|
||||
for dtype in self.supported_dtypes:
|
||||
image = data.astype(dtype)
|
||||
result = extrema.local_maxima(image)
|
||||
assert result.dtype == bool
|
||||
assert_equal(result, expected)
|
||||
|
||||
def test_connectivity(self):
|
||||
"""Test results if footprint is a scalar."""
|
||||
# Connectivity 1: generates cross shaped footprint
|
||||
result_conn1 = extrema.local_maxima(self.image, connectivity=1)
|
||||
assert result_conn1.dtype == bool
|
||||
assert_equal(result_conn1, self.expected_cross)
|
||||
|
||||
# Connectivity 2: generates square shaped footprint
|
||||
result_conn2 = extrema.local_maxima(self.image, connectivity=2)
|
||||
assert result_conn2.dtype == bool
|
||||
assert_equal(result_conn2, self.expected_default)
|
||||
|
||||
# Connectivity 3: generates square shaped footprint
|
||||
result_conn3 = extrema.local_maxima(self.image, connectivity=3)
|
||||
assert result_conn3.dtype == bool
|
||||
assert_equal(result_conn3, self.expected_default)
|
||||
|
||||
def test_footprint(self):
|
||||
"""Test results if footprint is given."""
|
||||
footprint_cross = np.array(
|
||||
[[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=bool)
|
||||
result_footprint_cross = extrema.local_maxima(
|
||||
self.image, footprint=footprint_cross)
|
||||
assert result_footprint_cross.dtype == bool
|
||||
assert_equal(result_footprint_cross, self.expected_cross)
|
||||
|
||||
for footprint in [
|
||||
((True,) * 3,) * 3,
|
||||
np.ones((3, 3), dtype=np.float64),
|
||||
np.ones((3, 3), dtype=np.uint8),
|
||||
np.ones((3, 3), dtype=bool),
|
||||
]:
|
||||
# Test different dtypes for footprint which expects a boolean array
|
||||
# but will accept and convert other types if possible
|
||||
result_footprint_square = extrema.local_maxima(
|
||||
self.image, footprint=footprint
|
||||
)
|
||||
assert result_footprint_square.dtype == bool
|
||||
assert_equal(result_footprint_square, self.expected_default)
|
||||
|
||||
footprint_x = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], dtype=bool)
|
||||
expected_footprint_x = np.array(
|
||||
[[1, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0]],
|
||||
dtype=bool
|
||||
)
|
||||
result_footprint_x = extrema.local_maxima(self.image,
|
||||
footprint=footprint_x)
|
||||
assert result_footprint_x.dtype == bool
|
||||
assert_equal(result_footprint_x, expected_footprint_x)
|
||||
|
||||
def test_indices(self):
|
||||
"""Test output if indices of peaks are desired."""
|
||||
# Connectivity 1
|
||||
expected_conn1 = np.nonzero(self.expected_cross)
|
||||
result_conn1 = extrema.local_maxima(self.image, connectivity=1,
|
||||
indices=True)
|
||||
assert_equal(result_conn1, expected_conn1)
|
||||
|
||||
# Connectivity 2
|
||||
expected_conn2 = np.nonzero(self.expected_default)
|
||||
result_conn2 = extrema.local_maxima(self.image, connectivity=2,
|
||||
indices=True)
|
||||
assert_equal(result_conn2, expected_conn2)
|
||||
|
||||
def test_allow_borders(self):
|
||||
"""Test maxima detection at the image border."""
|
||||
# Use connectivity 1 to allow many maxima, only filtering at border is
|
||||
# of interest
|
||||
result_with_boder = extrema.local_maxima(
|
||||
self.image, connectivity=1, allow_borders=True)
|
||||
assert result_with_boder.dtype == bool
|
||||
assert_equal(result_with_boder, self.expected_cross)
|
||||
|
||||
expected_without_border = np.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, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=bool
|
||||
)
|
||||
result_without_border = extrema.local_maxima(
|
||||
self.image, connectivity=1, allow_borders=False)
|
||||
assert result_with_boder.dtype == bool
|
||||
assert_equal(result_without_border, expected_without_border)
|
||||
|
||||
def test_nd(self):
|
||||
"""Test one- and three-dimensional case."""
|
||||
# One-dimension
|
||||
x_1d = np.array([1, 1, 0, 1, 2, 3, 0, 2, 1, 2, 0])
|
||||
expected_1d = np.array([1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0],
|
||||
dtype=bool)
|
||||
result_1d = extrema.local_maxima(x_1d)
|
||||
assert result_1d.dtype == bool
|
||||
assert_equal(result_1d, expected_1d)
|
||||
|
||||
# 3-dimensions (adapted from old unit test)
|
||||
x_3d = np.zeros((8, 8, 8), dtype=np.uint8)
|
||||
expected_3d = np.zeros((8, 8, 8), dtype=bool)
|
||||
# first maximum: only one pixel
|
||||
x_3d[1, 1:3, 1:3] = 100
|
||||
x_3d[2, 2, 2] = 200
|
||||
x_3d[3, 1:3, 1:3] = 100
|
||||
expected_3d[2, 2, 2] = 1
|
||||
# second maximum: three pixels in z-direction
|
||||
x_3d[5:8, 1, 1] = 200
|
||||
expected_3d[5:8, 1, 1] = 1
|
||||
# third: two maxima in 0 and 3.
|
||||
x_3d[0, 5:8, 5:8] = 200
|
||||
x_3d[1, 6, 6] = 100
|
||||
x_3d[2, 5:7, 5:7] = 200
|
||||
x_3d[0:3, 5:8, 5:8] += 50
|
||||
expected_3d[0, 5:8, 5:8] = 1
|
||||
expected_3d[2, 5:7, 5:7] = 1
|
||||
# four : one maximum in the corner of the square
|
||||
x_3d[6:8, 6:8, 6:8] = 200
|
||||
x_3d[7, 7, 7] = 255
|
||||
expected_3d[7, 7, 7] = 1
|
||||
result_3d = extrema.local_maxima(x_3d)
|
||||
assert result_3d.dtype == bool
|
||||
assert_equal(result_3d, expected_3d)
|
||||
|
||||
def test_constant(self):
|
||||
"""Test behaviour for 'flat' images."""
|
||||
const_image = np.full((7, 6), 42, dtype=np.uint8)
|
||||
expected = np.zeros((7, 6), dtype=np.uint8)
|
||||
for dtype in self.supported_dtypes:
|
||||
const_image = const_image.astype(dtype)
|
||||
# test for local maxima
|
||||
result = extrema.local_maxima(const_image)
|
||||
assert result.dtype == bool
|
||||
assert_equal(result, expected)
|
||||
# test for local minima
|
||||
result = extrema.local_minima(const_image)
|
||||
assert result.dtype == bool
|
||||
assert_equal(result, expected)
|
||||
|
||||
def test_extrema_float(self):
|
||||
"""Specific tests for float type."""
|
||||
# Copied from old unit test for local_maxma
|
||||
image = np.array(
|
||||
[[0.10, 0.11, 0.13, 0.14, 0.14, 0.15, 0.14, 0.14, 0.13, 0.11],
|
||||
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16, 0.16, 0.15, 0.13],
|
||||
[0.13, 0.15, 0.40, 0.40, 0.18, 0.18, 0.18, 0.60, 0.60, 0.15],
|
||||
[0.14, 0.16, 0.40, 0.40, 0.19, 0.19, 0.19, 0.60, 0.60, 0.16],
|
||||
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19, 0.19, 0.18, 0.16],
|
||||
[0.15, 0.182, 0.18, 0.19, 0.204, 0.20, 0.19, 0.19, 0.18, 0.16],
|
||||
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19, 0.19, 0.18, 0.16],
|
||||
[0.14, 0.16, 0.80, 0.80, 0.19, 0.19, 0.19, 1.0, 1.0, 0.16],
|
||||
[0.13, 0.15, 0.80, 0.80, 0.18, 0.18, 0.18, 1.0, 1.0, 0.15],
|
||||
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16, 0.16, 0.15, 0.13]],
|
||||
dtype=np.float32
|
||||
)
|
||||
inverted_image = 1.0 - image
|
||||
expected_result = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 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, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=bool
|
||||
)
|
||||
|
||||
# Test for local maxima with automatic step calculation
|
||||
result = extrema.local_maxima(image)
|
||||
assert result.dtype == bool
|
||||
assert_equal(result, expected_result)
|
||||
|
||||
# Test for local minima with automatic step calculation
|
||||
result = extrema.local_minima(inverted_image)
|
||||
assert result.dtype == bool
|
||||
assert_equal(result, expected_result)
|
||||
|
||||
def test_exceptions(self):
|
||||
"""Test if input validation triggers correct exceptions."""
|
||||
# Mismatching number of dimensions
|
||||
with raises(ValueError, match="number of dimensions"):
|
||||
extrema.local_maxima(
|
||||
self.image, footprint=np.ones((3, 3, 3), dtype=bool))
|
||||
with raises(ValueError, match="number of dimensions"):
|
||||
extrema.local_maxima(
|
||||
self.image, footprint=np.ones((3,), dtype=bool))
|
||||
|
||||
# All dimensions in footprint must be of size 3
|
||||
with raises(ValueError, match="dimension size"):
|
||||
extrema.local_maxima(
|
||||
self.image, footprint=np.ones((2, 3), dtype=bool))
|
||||
with raises(ValueError, match="dimension size"):
|
||||
extrema.local_maxima(
|
||||
self.image, footprint=np.ones((5, 5), dtype=bool))
|
||||
|
||||
with raises(TypeError, match="float16 which is not supported"):
|
||||
extrema.local_maxima(np.empty(1, dtype=np.float16))
|
||||
|
||||
def test_small_array(self):
|
||||
"""Test output for arrays with dimension smaller 3.
|
||||
|
||||
If any dimension of an array is smaller than 3 and `allow_borders` is
|
||||
false a footprint, which has at least 3 elements in each
|
||||
dimension, can't be applied. This is an implementation detail so
|
||||
`local_maxima` should still return valid output (see gh-3261).
|
||||
|
||||
If `allow_borders` is true the array is padded internally and there is
|
||||
no problem.
|
||||
"""
|
||||
warning_msg = "maxima can't exist .* any dimension smaller 3 .*"
|
||||
x = np.array([0, 1])
|
||||
extrema.local_maxima(x, allow_borders=True) # no warning
|
||||
with warns(UserWarning, match=warning_msg):
|
||||
result = extrema.local_maxima(x, allow_borders=False)
|
||||
assert_equal(result, [0, 0])
|
||||
assert result.dtype == bool
|
||||
|
||||
x = np.array([[1, 2], [2, 2]])
|
||||
extrema.local_maxima(x, allow_borders=True, indices=True) # no warning
|
||||
with warns(UserWarning, match=warning_msg):
|
||||
result = extrema.local_maxima(x, allow_borders=False, indices=True)
|
||||
assert_equal(result, np.zeros((2, 0), dtype=np.intp))
|
||||
assert result[0].dtype == np.intp
|
||||
assert result[1].dtype == np.intp
|
||||
286
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_flood_fill.py
vendored
Normal file
286
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_flood_fill.py
vendored
Normal file
@@ -0,0 +1,286 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from skimage.morphology import flood, flood_fill
|
||||
|
||||
eps = 1e-12
|
||||
|
||||
|
||||
def test_empty_input():
|
||||
# Test shortcut
|
||||
output = flood_fill(np.empty(0), (), 2)
|
||||
assert output.size == 0
|
||||
|
||||
# Boolean output type
|
||||
assert flood(np.empty(0), ()).dtype == bool
|
||||
|
||||
# Maintain shape, even with zero size present
|
||||
assert flood(np.empty((20, 0, 4)), ()).shape == (20, 0, 4)
|
||||
|
||||
|
||||
def test_float16():
|
||||
image = np.array([9., 0.1, 42], dtype=np.float16)
|
||||
with pytest.raises(TypeError, match="dtype of `image` is float16"):
|
||||
flood_fill(image, 0, 1)
|
||||
|
||||
|
||||
def test_overrange_tolerance_int():
|
||||
image = np.arange(256, dtype=np.uint8).reshape((8, 8, 4))
|
||||
expected = np.zeros_like(image)
|
||||
|
||||
output = flood_fill(image, (7, 7, 3), 0, tolerance=379)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
|
||||
def test_overrange_tolerance_float():
|
||||
max_value = np.finfo(np.float32).max
|
||||
|
||||
image = np.random.uniform(size=(64, 64), low=-1., high=1.).astype(
|
||||
np.float32)
|
||||
image *= max_value
|
||||
|
||||
expected = np.ones_like(image)
|
||||
output = flood_fill(image, (0, 1), 1., tolerance=max_value * 10)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
|
||||
def test_inplace_int():
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[0, 1, 1, 1, 3, 3, 4]])
|
||||
|
||||
flood_fill(image, (0, 0), 5, in_place=True)
|
||||
|
||||
expected = np.array([[5, 5, 5, 5, 5, 5, 5],
|
||||
[5, 1, 1, 5, 2, 2, 5],
|
||||
[5, 1, 1, 5, 2, 2, 5],
|
||||
[1, 5, 5, 5, 5, 5, 3],
|
||||
[5, 1, 1, 1, 3, 3, 4]])
|
||||
|
||||
np.testing.assert_array_equal(image, expected)
|
||||
|
||||
|
||||
def test_inplace_float():
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[0, 1, 1, 1, 3, 3, 4]], dtype=np.float32)
|
||||
|
||||
flood_fill(image, (0, 0), 5, in_place=True)
|
||||
|
||||
expected = np.array([[5., 5., 5., 5., 5., 5., 5.],
|
||||
[5., 1., 1., 5., 2., 2., 5.],
|
||||
[5., 1., 1., 5., 2., 2., 5.],
|
||||
[1., 5., 5., 5., 5., 5., 3.],
|
||||
[5., 1., 1., 1., 3., 3., 4.]], dtype=np.float32)
|
||||
|
||||
np.testing.assert_allclose(image, expected)
|
||||
|
||||
|
||||
def test_inplace_noncontiguous():
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[0, 1, 1, 1, 3, 3, 4]])
|
||||
|
||||
# Transpose is noncontiguous
|
||||
image2 = image[::2, ::2]
|
||||
|
||||
flood_fill(image2, (0, 0), 5, in_place=True)
|
||||
|
||||
# The inplace modified result
|
||||
expected2 = np.array([[5, 5, 5, 5],
|
||||
[5, 1, 2, 5],
|
||||
[5, 1, 3, 4]])
|
||||
|
||||
np.testing.assert_allclose(image2, expected2)
|
||||
|
||||
# Projected back through the view, `image` also modified
|
||||
expected = np.array([[5, 0, 5, 0, 5, 0, 5],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[5, 1, 1, 0, 2, 2, 5],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[5, 1, 1, 1, 3, 3, 4]])
|
||||
|
||||
np.testing.assert_allclose(image, expected)
|
||||
|
||||
|
||||
def test_1d():
|
||||
image = np.arange(11)
|
||||
expected = np.array([0, 1, -20, -20, -20, -20, -20, -20, -20, 9, 10])
|
||||
|
||||
output = flood_fill(image, 5, -20, tolerance=3)
|
||||
output2 = flood_fill(image, (5,), -20, tolerance=3)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
np.testing.assert_equal(output, output2)
|
||||
|
||||
|
||||
def test_wraparound():
|
||||
# If the borders (or neighbors) aren't correctly accounted for, this fails,
|
||||
# because the algorithm uses an ravelled array.
|
||||
test = np.zeros((5, 7), dtype=np.float64)
|
||||
test[:, 3] = 100
|
||||
|
||||
expected = np.array([[-1., -1., -1., 100., 0., 0., 0.],
|
||||
[-1., -1., -1., 100., 0., 0., 0.],
|
||||
[-1., -1., -1., 100., 0., 0., 0.],
|
||||
[-1., -1., -1., 100., 0., 0., 0.],
|
||||
[-1., -1., -1., 100., 0., 0., 0.]])
|
||||
|
||||
np.testing.assert_equal(flood_fill(test, (0, 0), -1), expected)
|
||||
|
||||
|
||||
def test_neighbors():
|
||||
# This test will only pass if the neighbors are exactly correct
|
||||
test = np.zeros((5, 7), dtype=np.float64)
|
||||
test[:, 3] = 100
|
||||
|
||||
expected = np.array([[0, 0, 0, 255, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0, 0]])
|
||||
output = flood_fill(test, (0, 3), 255)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
test[2] = 100
|
||||
expected[2] = 255
|
||||
|
||||
output2 = flood_fill(test, (2, 3), 255)
|
||||
|
||||
np.testing.assert_equal(output2, expected)
|
||||
|
||||
|
||||
def test_footprint():
|
||||
# Basic tests for nonstandard footprints
|
||||
footprint = np.array([[0, 1, 1],
|
||||
[0, 1, 1],
|
||||
[0, 0, 0]]) # Cannot grow left or down
|
||||
|
||||
output = flood_fill(np.zeros((5, 6), dtype=np.uint8), (3, 1), 255,
|
||||
footprint=footprint)
|
||||
|
||||
expected = np.array([[0, 255, 255, 255, 255, 255],
|
||||
[0, 255, 255, 255, 255, 255],
|
||||
[0, 255, 255, 255, 255, 255],
|
||||
[0, 255, 255, 255, 255, 255],
|
||||
[0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
footprint = np.array([[0, 0, 0],
|
||||
[1, 1, 0],
|
||||
[1, 1, 0]]) # Cannot grow right or up
|
||||
|
||||
output = flood_fill(np.zeros((5, 6), dtype=np.uint8), (1, 4), 255,
|
||||
footprint=footprint)
|
||||
|
||||
expected = np.array([[ 0, 0, 0, 0, 0, 0],
|
||||
[255, 255, 255, 255, 255, 0],
|
||||
[255, 255, 255, 255, 255, 0],
|
||||
[255, 255, 255, 255, 255, 0],
|
||||
[255, 255, 255, 255, 255, 0]], dtype=np.uint8)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
|
||||
def test_basic_nd():
|
||||
for dimension in (3, 4, 5):
|
||||
shape = (5,) * dimension
|
||||
hypercube = np.zeros(shape)
|
||||
slice_mid = tuple(slice(1, -1, None) for dim in range(dimension))
|
||||
hypercube[slice_mid] = 1 # sum is 3**dimension
|
||||
filled = flood_fill(hypercube, (2,) * dimension, 2)
|
||||
|
||||
# Test that the middle sum is correct
|
||||
assert filled.sum() == 3**dimension * 2
|
||||
|
||||
# Test that the entire array is as expected
|
||||
np.testing.assert_equal(
|
||||
filled, np.pad(np.ones((3,) * dimension) * 2, 1, 'constant'))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tolerance", [None, 0])
|
||||
def test_f_order(tolerance):
|
||||
image = np.array([
|
||||
[0, 0, 0, 0],
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
], order="F")
|
||||
expected = np.array([
|
||||
[0, 0, 0, 0],
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
], dtype=bool)
|
||||
|
||||
mask = flood(image, seed_point=(1, 0), tolerance=tolerance)
|
||||
np.testing.assert_array_equal(expected, mask)
|
||||
|
||||
mask = flood(image, seed_point=(2, 1), tolerance=tolerance)
|
||||
np.testing.assert_array_equal(expected, mask)
|
||||
|
||||
|
||||
def test_negative_indexing_seed_point():
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[0, 1, 1, 1, 3, 3, 4]], dtype=np.float32)
|
||||
|
||||
expected = np.array([[5., 5., 5., 5., 5., 5., 5.],
|
||||
[5., 1., 1., 5., 2., 2., 5.],
|
||||
[5., 1., 1., 5., 2., 2., 5.],
|
||||
[1., 5., 5., 5., 5., 5., 3.],
|
||||
[5., 1., 1., 1., 3., 3., 4.]], dtype=np.float32)
|
||||
|
||||
image = flood_fill(image, (0, -1), 5)
|
||||
|
||||
np.testing.assert_allclose(image, expected)
|
||||
|
||||
|
||||
def test_non_adjacent_footprint():
|
||||
# Basic tests for non-adjacent footprints
|
||||
footprint = np.array([[1, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 0, 0, 0, 1]])
|
||||
|
||||
output = flood_fill(np.zeros((5, 6), dtype=np.uint8), (2, 3), 255,
|
||||
footprint=footprint)
|
||||
|
||||
expected = np.array([[0, 255, 0, 0, 0, 255],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 255, 0, 0, 0, 255]], dtype=np.uint8)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
footprint = np.array([[1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1]])
|
||||
|
||||
image = np.zeros((5, 10), dtype=np.uint8)
|
||||
image[:, (3, 7, 8)] = 100
|
||||
|
||||
output = flood_fill(image, (0, 0), 255, footprint=footprint)
|
||||
|
||||
expected = np.array([[255, 255, 255, 100, 255, 255, 255, 100, 100, 0],
|
||||
[255, 255, 255, 100, 255, 255, 255, 100, 100, 0],
|
||||
[255, 255, 255, 100, 255, 255, 255, 100, 100, 0],
|
||||
[255, 255, 255, 100, 255, 255, 255, 100, 100, 0],
|
||||
[255, 255, 255, 100, 255, 255, 255, 100, 100, 0]],
|
||||
dtype=np.uint8)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
238
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_footprints.py
vendored
Normal file
238
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_footprints.py
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
"""
|
||||
Tests for Morphological footprints
|
||||
(skimage.morphology.footprint)
|
||||
|
||||
Author: Damian Eads
|
||||
"""
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_equal
|
||||
|
||||
from skimage._shared.testing import fetch
|
||||
from skimage.morphology import footprints
|
||||
|
||||
|
||||
class TestFootprints:
|
||||
|
||||
def test_square_footprint(self):
|
||||
"""Test square footprints"""
|
||||
for k in range(0, 5):
|
||||
actual_mask = footprints.square(k)
|
||||
expected_mask = np.ones((k, k), dtype='uint8')
|
||||
assert_equal(expected_mask, actual_mask)
|
||||
|
||||
def test_rectangle_footprint(self):
|
||||
"""Test rectangle footprints"""
|
||||
for i in range(0, 5):
|
||||
for j in range(0, 5):
|
||||
actual_mask = footprints.rectangle(i, j)
|
||||
expected_mask = np.ones((i, j), dtype='uint8')
|
||||
assert_equal(expected_mask, actual_mask)
|
||||
|
||||
def test_cube_footprint(self):
|
||||
"""Test cube footprints"""
|
||||
for k in range(0, 5):
|
||||
actual_mask = footprints.cube(k)
|
||||
expected_mask = np.ones((k, k, k), dtype='uint8')
|
||||
assert_equal(expected_mask, actual_mask)
|
||||
|
||||
def strel_worker(self, fn, func):
|
||||
matlab_masks = np.load(fetch(fn))
|
||||
k = 0
|
||||
for arrname in sorted(matlab_masks):
|
||||
expected_mask = matlab_masks[arrname]
|
||||
actual_mask = func(k)
|
||||
if expected_mask.shape == (1,):
|
||||
expected_mask = expected_mask[:, np.newaxis]
|
||||
assert_equal(expected_mask, actual_mask)
|
||||
k = k + 1
|
||||
|
||||
def strel_worker_3d(self, fn, func):
|
||||
matlab_masks = np.load(fetch(fn))
|
||||
k = 0
|
||||
for arrname in sorted(matlab_masks):
|
||||
expected_mask = matlab_masks[arrname]
|
||||
actual_mask = func(k)
|
||||
if expected_mask.shape == (1,):
|
||||
expected_mask = expected_mask[:, np.newaxis]
|
||||
# Test center slice for each dimension. This gives a good
|
||||
# indication of validity without the need for a 3D reference
|
||||
# mask.
|
||||
c = int(expected_mask.shape[0]/2)
|
||||
assert_equal(expected_mask, actual_mask[c, :, :])
|
||||
assert_equal(expected_mask, actual_mask[:, c, :])
|
||||
assert_equal(expected_mask, actual_mask[:, :, c])
|
||||
k = k + 1
|
||||
|
||||
def test_footprint_disk(self):
|
||||
"""Test disk footprints"""
|
||||
self.strel_worker("data/disk-matlab-output.npz", footprints.disk)
|
||||
|
||||
def test_footprint_diamond(self):
|
||||
"""Test diamond footprints"""
|
||||
self.strel_worker("data/diamond-matlab-output.npz", footprints.diamond)
|
||||
|
||||
def test_footprint_ball(self):
|
||||
"""Test ball footprints"""
|
||||
self.strel_worker_3d("data/disk-matlab-output.npz", footprints.ball)
|
||||
|
||||
def test_footprint_octahedron(self):
|
||||
"""Test octahedron footprints"""
|
||||
self.strel_worker_3d("data/diamond-matlab-output.npz",
|
||||
footprints.octahedron)
|
||||
|
||||
def test_footprint_octagon(self):
|
||||
"""Test octagon footprints"""
|
||||
expected_mask1 = np.array([[0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
actual_mask1 = footprints.octagon(5, 3)
|
||||
expected_mask2 = np.array([[0, 1, 0],
|
||||
[1, 1, 1],
|
||||
[0, 1, 0]], dtype=np.uint8)
|
||||
actual_mask2 = footprints.octagon(1, 1)
|
||||
assert_equal(expected_mask1, actual_mask1)
|
||||
assert_equal(expected_mask2, actual_mask2)
|
||||
|
||||
def test_footprint_ellipse(self):
|
||||
"""Test ellipse footprints"""
|
||||
expected_mask1 = np.array([[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
actual_mask1 = footprints.ellipse(5, 3)
|
||||
expected_mask2 = np.array([[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]], dtype=np.uint8)
|
||||
actual_mask2 = footprints.ellipse(1, 1)
|
||||
assert_equal(expected_mask1, actual_mask1)
|
||||
assert_equal(expected_mask2, actual_mask2)
|
||||
assert_equal(expected_mask1, footprints.ellipse(3, 5).T)
|
||||
assert_equal(expected_mask2, footprints.ellipse(1, 1).T)
|
||||
|
||||
def test_footprint_star(self):
|
||||
"""Test star footprints"""
|
||||
expected_mask1 = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
actual_mask1 = footprints.star(4)
|
||||
expected_mask2 = np.array([[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]], dtype=np.uint8)
|
||||
actual_mask2 = footprints.star(1)
|
||||
assert_equal(expected_mask1, actual_mask1)
|
||||
assert_equal(expected_mask2, actual_mask2)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'function, args, supports_sequence_decomposition',
|
||||
[
|
||||
(footprints.disk, (3,), True),
|
||||
(footprints.ball, (3,), True),
|
||||
(footprints.square, (3,), True),
|
||||
(footprints.cube, (3,), True),
|
||||
(footprints.diamond, (3,), True),
|
||||
(footprints.octahedron, (3,), True),
|
||||
(footprints.rectangle, (3, 4), True),
|
||||
(footprints.ellipse, (3, 4), False),
|
||||
(footprints.octagon, (3, 4), True),
|
||||
(footprints.star, (3,), False),
|
||||
]
|
||||
)
|
||||
@pytest.mark.parametrize("dtype", [np.uint8, np.float64])
|
||||
def test_footprint_dtype(function, args, supports_sequence_decomposition,
|
||||
dtype):
|
||||
# make sure footprint dtype matches what was requested
|
||||
footprint = function(*args, dtype=dtype)
|
||||
assert footprint.dtype == dtype
|
||||
|
||||
if supports_sequence_decomposition:
|
||||
sequence = function(*args, dtype=dtype, decomposition='sequence')
|
||||
assert all([fp_tuple[0].dtype == dtype for fp_tuple in sequence])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("function", ["disk", "ball"])
|
||||
@pytest.mark.parametrize("radius", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 50, 75,
|
||||
100])
|
||||
def test_nsphere_series_approximation(function, radius):
|
||||
fp_func = getattr(footprints, function)
|
||||
expected = fp_func(radius, strict_radius=False, decomposition=None)
|
||||
footprint_sequence = fp_func(radius, strict_radius=False,
|
||||
decomposition="sequence")
|
||||
approximate = footprints.footprint_from_sequence(footprint_sequence)
|
||||
assert approximate.shape == expected.shape
|
||||
|
||||
# verify that maximum error does not exceed some fraction of the size
|
||||
error = np.sum(np.abs(expected.astype(int) - approximate.astype(int)))
|
||||
if radius == 1:
|
||||
assert error == 0
|
||||
else:
|
||||
max_error = 0.1 if function == "disk" else 0.15
|
||||
assert error / expected.size <= max_error
|
||||
|
||||
|
||||
@pytest.mark.parametrize("radius", [1, 2, 3, 4, 5, 10, 20, 50, 75])
|
||||
@pytest.mark.parametrize("strict_radius", [False, True])
|
||||
def test_disk_crosses_approximation(radius, strict_radius):
|
||||
fp_func = footprints.disk
|
||||
expected = fp_func(radius, strict_radius=strict_radius, decomposition=None)
|
||||
footprint_sequence = fp_func(radius, strict_radius=strict_radius,
|
||||
decomposition="crosses")
|
||||
approximate = footprints.footprint_from_sequence(footprint_sequence)
|
||||
assert approximate.shape == expected.shape
|
||||
|
||||
# verify that maximum error does not exceed some fraction of the size
|
||||
error = np.sum(np.abs(expected.astype(int) - approximate.astype(int)))
|
||||
max_error = 0.05
|
||||
assert error / expected.size <= max_error
|
||||
|
||||
|
||||
@pytest.mark.parametrize("width", [3, 8, 20, 50])
|
||||
@pytest.mark.parametrize("height", [3, 8, 20, 50])
|
||||
def test_ellipse_crosses_approximation(width, height):
|
||||
fp_func = footprints.ellipse
|
||||
expected = fp_func(width, height, decomposition=None)
|
||||
footprint_sequence = fp_func(width, height, decomposition="crosses")
|
||||
approximate = footprints.footprint_from_sequence(footprint_sequence)
|
||||
assert approximate.shape == expected.shape
|
||||
|
||||
# verify that maximum error does not exceed some fraction of the size
|
||||
error = np.sum(np.abs(expected.astype(int) - approximate.astype(int)))
|
||||
max_error = 0.05
|
||||
assert error / expected.size <= max_error
|
||||
|
||||
|
||||
def test_disk_series_approximation_unavailable():
|
||||
# ValueError if radius is too large (only precomputed up to radius=250)
|
||||
with pytest.raises(ValueError):
|
||||
footprints.disk(radius=10000, decomposition="sequence")
|
||||
|
||||
|
||||
def test_ball_series_approximation_unavailable():
|
||||
# ValueError if radius is too large (only precomputed up to radius=100)
|
||||
with pytest.raises(ValueError):
|
||||
footprints.ball(radius=10000, decomposition="sequence")
|
||||
410
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_gray.py
vendored
Normal file
410
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_gray.py
vendored
Normal file
@@ -0,0 +1,410 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from scipy import ndimage as ndi
|
||||
from numpy.testing import assert_allclose, assert_array_equal, assert_equal
|
||||
|
||||
from skimage import color, data, transform
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage._shared.testing import fetch
|
||||
from skimage.morphology import gray, footprints
|
||||
from skimage.util import img_as_uint, img_as_ubyte
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cam_image():
|
||||
from skimage import data
|
||||
return np.ascontiguousarray(data.camera()[64:112, 64:96])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cell3d_image():
|
||||
from skimage import data
|
||||
return np.ascontiguousarray(data.cells3d()[30:48, 0, 20:36, 20:32])
|
||||
|
||||
|
||||
class TestMorphology():
|
||||
|
||||
# These expected outputs were generated with skimage v0.12.1
|
||||
# using:
|
||||
#
|
||||
# from skimage.morphology.tests.test_gray import TestMorphology
|
||||
# import numpy as np
|
||||
# output = TestMorphology()._build_expected_output()
|
||||
# np.savez_compressed('gray_morph_output.npz', **output)
|
||||
|
||||
def _build_expected_output(self):
|
||||
funcs = (gray.erosion, gray.dilation, gray.opening, gray.closing,
|
||||
gray.white_tophat, gray.black_tophat)
|
||||
footprints_2D = (footprints.square, footprints.diamond,
|
||||
footprints.disk, footprints.star)
|
||||
|
||||
image = img_as_ubyte(transform.downscale_local_mean(
|
||||
color.rgb2gray(data.coffee()), (20, 20)))
|
||||
|
||||
output = {}
|
||||
for n in range(1, 4):
|
||||
for strel in footprints_2D:
|
||||
for func in funcs:
|
||||
key = f'{strel.__name__}_{n}_{func.__name__}'
|
||||
output[key] = func(image, strel(n))
|
||||
|
||||
return output
|
||||
|
||||
def test_gray_morphology(self):
|
||||
expected = dict(np.load(fetch('data/gray_morph_output.npz')))
|
||||
calculated = self._build_expected_output()
|
||||
assert_equal(expected, calculated)
|
||||
|
||||
|
||||
class TestEccentricStructuringElements():
|
||||
def setup_class(self):
|
||||
self.black_pixel = 255 * np.ones((4, 4), dtype=np.uint8)
|
||||
self.black_pixel[1, 1] = 0
|
||||
self.white_pixel = 255 - self.black_pixel
|
||||
self.footprints = [footprints.square(2), footprints.rectangle(2, 2),
|
||||
footprints.rectangle(2, 1),
|
||||
footprints.rectangle(1, 2)]
|
||||
|
||||
def test_dilate_erode_symmetry(self):
|
||||
for s in self.footprints:
|
||||
c = gray.erosion(self.black_pixel, s)
|
||||
d = gray.dilation(self.white_pixel, s)
|
||||
assert np.all(c == (255 - d))
|
||||
|
||||
def test_open_black_pixel(self):
|
||||
for s in self.footprints:
|
||||
gray_open = gray.opening(self.black_pixel, s)
|
||||
assert np.all(gray_open == self.black_pixel)
|
||||
|
||||
def test_close_white_pixel(self):
|
||||
for s in self.footprints:
|
||||
gray_close = gray.closing(self.white_pixel, s)
|
||||
assert np.all(gray_close == self.white_pixel)
|
||||
|
||||
def test_open_white_pixel(self):
|
||||
for s in self.footprints:
|
||||
assert np.all(gray.opening(self.white_pixel, s) == 0)
|
||||
|
||||
def test_close_black_pixel(self):
|
||||
for s in self.footprints:
|
||||
assert np.all(gray.closing(self.black_pixel, s) == 255)
|
||||
|
||||
def test_white_tophat_white_pixel(self):
|
||||
for s in self.footprints:
|
||||
tophat = gray.white_tophat(self.white_pixel, s)
|
||||
assert np.all(tophat == self.white_pixel)
|
||||
|
||||
def test_black_tophat_black_pixel(self):
|
||||
for s in self.footprints:
|
||||
tophat = gray.black_tophat(self.black_pixel, s)
|
||||
assert np.all(tophat == (255 - self.black_pixel))
|
||||
|
||||
def test_white_tophat_black_pixel(self):
|
||||
for s in self.footprints:
|
||||
tophat = gray.white_tophat(self.black_pixel, s)
|
||||
assert np.all(tophat == 0)
|
||||
|
||||
def test_black_tophat_white_pixel(self):
|
||||
for s in self.footprints:
|
||||
tophat = gray.black_tophat(self.white_pixel, s)
|
||||
assert np.all(tophat == 0)
|
||||
|
||||
|
||||
gray_functions = [gray.erosion, gray.dilation,
|
||||
gray.opening, gray.closing,
|
||||
gray.white_tophat, gray.black_tophat]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("function", gray_functions)
|
||||
def test_default_footprint(function):
|
||||
strel = footprints.diamond(radius=1)
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 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]], np.uint8)
|
||||
im_expected = function(image, strel)
|
||||
im_test = function(image)
|
||||
assert_array_equal(im_expected, im_test)
|
||||
|
||||
|
||||
def test_3d_fallback_default_footprint():
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
opened = gray.opening(image)
|
||||
|
||||
# expect a "hyper-cross" centered in the 5x5x5:
|
||||
image_expected = np.zeros((7, 7, 7), dtype=bool)
|
||||
image_expected[2:5, 2:5, 2:5] = ndi.generate_binary_structure(3, 1)
|
||||
assert_array_equal(opened, image_expected)
|
||||
|
||||
|
||||
gray_3d_fallback_functions = [gray.closing, gray.opening]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("function", gray_3d_fallback_functions)
|
||||
def test_3d_fallback_cube_footprint(function):
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
cube = np.ones((3, 3, 3), dtype=np.uint8)
|
||||
|
||||
new_image = function(image, cube)
|
||||
assert_array_equal(new_image, image)
|
||||
|
||||
|
||||
def test_3d_fallback_white_tophat():
|
||||
image = np.zeros((7, 7, 7), dtype=bool)
|
||||
image[2, 2:4, 2:4] = 1
|
||||
image[3, 2:5, 2:5] = 1
|
||||
image[4, 3:5, 3:5] = 1
|
||||
|
||||
with expected_warnings([r'operator.*deprecated|\A\Z']):
|
||||
new_image = gray.white_tophat(image)
|
||||
footprint = ndi.generate_binary_structure(3, 1)
|
||||
with expected_warnings([r'operator.*deprecated|\A\Z']):
|
||||
image_expected = ndi.white_tophat(
|
||||
image.view(dtype=np.uint8), footprint=footprint)
|
||||
assert_array_equal(new_image, image_expected)
|
||||
|
||||
|
||||
def test_3d_fallback_black_tophat():
|
||||
image = np.ones((7, 7, 7), dtype=bool)
|
||||
image[2, 2:4, 2:4] = 0
|
||||
image[3, 2:5, 2:5] = 0
|
||||
image[4, 3:5, 3:5] = 0
|
||||
|
||||
with expected_warnings([r'operator.*deprecated|\A\Z']):
|
||||
new_image = gray.black_tophat(image)
|
||||
footprint = ndi.generate_binary_structure(3, 1)
|
||||
with expected_warnings([r'operator.*deprecated|\A\Z']):
|
||||
image_expected = ndi.black_tophat(
|
||||
image.view(dtype=np.uint8), footprint=footprint)
|
||||
assert_array_equal(new_image, image_expected)
|
||||
|
||||
|
||||
def test_2d_ndimage_equivalence():
|
||||
image = np.zeros((9, 9), np.uint8)
|
||||
image[2:-2, 2:-2] = 128
|
||||
image[3:-3, 3:-3] = 196
|
||||
image[4, 4] = 255
|
||||
|
||||
opened = gray.opening(image)
|
||||
closed = gray.closing(image)
|
||||
|
||||
footprint = ndi.generate_binary_structure(2, 1)
|
||||
ndimage_opened = ndi.grey_opening(image, footprint=footprint)
|
||||
ndimage_closed = ndi.grey_closing(image, footprint=footprint)
|
||||
|
||||
assert_array_equal(opened, ndimage_opened)
|
||||
assert_array_equal(closed, ndimage_closed)
|
||||
|
||||
|
||||
# float test images
|
||||
im = np.array([[ 0.55, 0.72, 0.6 , 0.54, 0.42],
|
||||
[ 0.65, 0.44, 0.89, 0.96, 0.38],
|
||||
[ 0.79, 0.53, 0.57, 0.93, 0.07],
|
||||
[ 0.09, 0.02, 0.83, 0.78, 0.87],
|
||||
[ 0.98, 0.8 , 0.46, 0.78, 0.12]])
|
||||
|
||||
eroded = np.array([[ 0.55, 0.44, 0.54, 0.42, 0.38],
|
||||
[ 0.44, 0.44, 0.44, 0.38, 0.07],
|
||||
[ 0.09, 0.02, 0.53, 0.07, 0.07],
|
||||
[ 0.02, 0.02, 0.02, 0.78, 0.07],
|
||||
[ 0.09, 0.02, 0.46, 0.12, 0.12]])
|
||||
|
||||
dilated = np.array([[ 0.72, 0.72, 0.89, 0.96, 0.54],
|
||||
[ 0.79, 0.89, 0.96, 0.96, 0.96],
|
||||
[ 0.79, 0.79, 0.93, 0.96, 0.93],
|
||||
[ 0.98, 0.83, 0.83, 0.93, 0.87],
|
||||
[ 0.98, 0.98, 0.83, 0.78, 0.87]])
|
||||
|
||||
opened = np.array([[ 0.55, 0.55, 0.54, 0.54, 0.42],
|
||||
[ 0.55, 0.44, 0.54, 0.44, 0.38],
|
||||
[ 0.44, 0.53, 0.53, 0.78, 0.07],
|
||||
[ 0.09, 0.02, 0.78, 0.78, 0.78],
|
||||
[ 0.09, 0.46, 0.46, 0.78, 0.12]])
|
||||
|
||||
closed = np.array([[ 0.72, 0.72, 0.72, 0.54, 0.54],
|
||||
[ 0.72, 0.72, 0.89, 0.96, 0.54],
|
||||
[ 0.79, 0.79, 0.79, 0.93, 0.87],
|
||||
[ 0.79, 0.79, 0.83, 0.78, 0.87],
|
||||
[ 0.98, 0.83, 0.78, 0.78, 0.78]])
|
||||
|
||||
|
||||
def test_float():
|
||||
assert_allclose(gray.erosion(im), eroded)
|
||||
assert_allclose(gray.dilation(im), dilated)
|
||||
assert_allclose(gray.opening(im), opened)
|
||||
assert_allclose(gray.closing(im), closed)
|
||||
|
||||
|
||||
def test_uint16():
|
||||
im16, eroded16, dilated16, opened16, closed16 = (
|
||||
map(img_as_uint, [im, eroded, dilated, opened, closed]))
|
||||
assert_allclose(gray.erosion(im16), eroded16)
|
||||
assert_allclose(gray.dilation(im16), dilated16)
|
||||
assert_allclose(gray.opening(im16), opened16)
|
||||
assert_allclose(gray.closing(im16), closed16)
|
||||
|
||||
|
||||
def test_discontiguous_out_array():
|
||||
image = np.array([[5, 6, 2],
|
||||
[7, 2, 2],
|
||||
[3, 5, 1]], np.uint8)
|
||||
out_array_big = np.zeros((5, 5), np.uint8)
|
||||
out_array = out_array_big[::2, ::2]
|
||||
expected_dilation = np.array([[7, 0, 6, 0, 6],
|
||||
[0, 0, 0, 0, 0],
|
||||
[7, 0, 7, 0, 2],
|
||||
[0, 0, 0, 0, 0],
|
||||
[7, 0, 5, 0, 5]], np.uint8)
|
||||
expected_erosion = np.array([[5, 0, 2, 0, 2],
|
||||
[0, 0, 0, 0, 0],
|
||||
[2, 0, 2, 0, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[3, 0, 1, 0, 1]], np.uint8)
|
||||
gray.dilation(image, out=out_array)
|
||||
assert_array_equal(out_array_big, expected_dilation)
|
||||
gray.erosion(image, out=out_array)
|
||||
assert_array_equal(out_array_big, expected_erosion)
|
||||
|
||||
|
||||
def test_1d_erosion():
|
||||
image = np.array([1, 2, 3, 2, 1])
|
||||
expected = np.array([1, 1, 2, 1, 1])
|
||||
eroded = gray.erosion(image)
|
||||
assert_array_equal(eroded, expected)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function", ["erosion", "dilation", "closing", "opening", "white_tophat",
|
||||
"black_tophat"],
|
||||
)
|
||||
@pytest.mark.parametrize("size", (7,))
|
||||
@pytest.mark.parametrize("decomposition", ['separable', 'sequence'])
|
||||
def test_square_decomposition(cam_image, function, size, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.square(size, decomposition=None)
|
||||
footprint = footprints.square(size, decomposition=decomposition)
|
||||
func = getattr(gray, function)
|
||||
expected = func(cam_image, footprint=footprint_ndarray)
|
||||
out = func(cam_image, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function", ["erosion", "dilation", "closing", "opening", "white_tophat",
|
||||
"black_tophat"],
|
||||
)
|
||||
@pytest.mark.parametrize("nrows", (3, 11))
|
||||
@pytest.mark.parametrize("ncols", (3, 11))
|
||||
@pytest.mark.parametrize("decomposition", ['separable', 'sequence'])
|
||||
def test_rectangle_decomposition(cam_image, function, nrows, ncols,
|
||||
decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.rectangle(nrows, ncols, decomposition=None)
|
||||
footprint = footprints.rectangle(nrows, ncols, decomposition=decomposition)
|
||||
func = getattr(gray, function)
|
||||
expected = func(cam_image, footprint=footprint_ndarray)
|
||||
out = func(cam_image, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function", ["erosion", "dilation", "closing", "opening", "white_tophat",
|
||||
"black_tophat"],
|
||||
)
|
||||
@pytest.mark.parametrize("radius", (2, 3))
|
||||
@pytest.mark.parametrize("decomposition", ['separable', 'sequence'])
|
||||
def test_diamond_decomposition(cam_image, function, radius, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.square(radius, decomposition=None)
|
||||
footprint = footprints.square(radius, decomposition=decomposition)
|
||||
func = getattr(gray, function)
|
||||
expected = func(cam_image, footprint=footprint_ndarray)
|
||||
out = func(cam_image, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function", ["erosion", "dilation", "closing", "opening", "white_tophat",
|
||||
"black_tophat"],
|
||||
)
|
||||
@pytest.mark.parametrize("m", (0, 1, 3, 5))
|
||||
@pytest.mark.parametrize("n", (0, 1, 2, 3))
|
||||
@pytest.mark.parametrize("decomposition", ['sequence'])
|
||||
def test_octagon_decomposition(cam_image, function, m, n, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
if m == 0 and n == 0:
|
||||
with pytest.raises(ValueError):
|
||||
footprints.octagon(m, n, decomposition=decomposition)
|
||||
else:
|
||||
footprint_ndarray = footprints.octagon(m, n, decomposition=None)
|
||||
footprint = footprints.octagon(m, n, decomposition=decomposition)
|
||||
func = getattr(gray, function)
|
||||
expected = func(cam_image, footprint=footprint_ndarray)
|
||||
out = func(cam_image, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function", ["erosion", "dilation", "closing", "opening", "white_tophat",
|
||||
"black_tophat"],
|
||||
)
|
||||
@pytest.mark.parametrize("size", (5,))
|
||||
@pytest.mark.parametrize("decomposition", ['separable', 'sequence'])
|
||||
def test_cube_decomposition(cell3d_image, function, size, decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.cube(size, decomposition=None)
|
||||
footprint = footprints.cube(size, decomposition=decomposition)
|
||||
func = getattr(gray, function)
|
||||
expected = func(cell3d_image, footprint=footprint_ndarray)
|
||||
out = func(cell3d_image, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"function", ["erosion", "dilation", "closing", "opening", "white_tophat",
|
||||
"black_tophat"],
|
||||
)
|
||||
@pytest.mark.parametrize("radius", (3,))
|
||||
@pytest.mark.parametrize("decomposition", ['sequence'])
|
||||
def test_octahedron_decomposition(cell3d_image, function, radius,
|
||||
decomposition):
|
||||
"""Validate footprint decomposition for various shapes.
|
||||
|
||||
comparison is made to the case without decomposition.
|
||||
"""
|
||||
footprint_ndarray = footprints.octahedron(radius, decomposition=None)
|
||||
footprint = footprints.octahedron(radius, decomposition=decomposition)
|
||||
func = getattr(gray, function)
|
||||
expected = func(cell3d_image, footprint=footprint_ndarray)
|
||||
out = func(cell3d_image, footprint=footprint)
|
||||
assert_array_equal(expected, out)
|
||||
82
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_isotropic.py
vendored
Normal file
82
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_isotropic.py
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
from skimage import color, data, morphology
|
||||
from skimage.morphology import binary, isotropic
|
||||
from skimage.util import img_as_bool
|
||||
|
||||
img = color.rgb2gray(data.astronaut())
|
||||
bw_img = img > 100 / 255.
|
||||
|
||||
|
||||
def test_non_square_image():
|
||||
isotropic_res = isotropic.isotropic_erosion(bw_img[:100, :200], 3)
|
||||
binary_res = img_as_bool(binary.binary_erosion(
|
||||
bw_img[:100, :200], morphology.disk(3)))
|
||||
assert_array_equal(isotropic_res, binary_res)
|
||||
|
||||
|
||||
def test_isotropic_erosion():
|
||||
isotropic_res = isotropic.isotropic_erosion(bw_img, 3)
|
||||
binary_res = img_as_bool(binary.binary_erosion(bw_img, morphology.disk(3)))
|
||||
assert_array_equal(isotropic_res, binary_res)
|
||||
|
||||
|
||||
def _disk_with_spacing(radius, dtype=np.uint8, *, strict_radius=True, spacing=None):
|
||||
# Identical to morphology.disk, but with a spacing parameter and without decomposition.
|
||||
# This is different from morphology.ellipse which produces a slightly different footprint.
|
||||
L = np.arange(-radius, radius + 1)
|
||||
X, Y = np.meshgrid(L, L)
|
||||
|
||||
if spacing is not None:
|
||||
X *= spacing[1]
|
||||
Y *= spacing[0]
|
||||
|
||||
if not strict_radius:
|
||||
radius += 0.5
|
||||
return np.array((X ** 2 + Y ** 2) <= radius ** 2, dtype=dtype)
|
||||
|
||||
|
||||
def test_isotropic_erosion_spacing():
|
||||
isotropic_res = isotropic.isotropic_dilation(bw_img, 6, spacing=(1,2))
|
||||
binary_res = img_as_bool(binary.binary_dilation(bw_img, _disk_with_spacing(6, spacing=(1,2))))
|
||||
assert_array_equal(isotropic_res, binary_res)
|
||||
|
||||
|
||||
def test_isotropic_dilation():
|
||||
isotropic_res = isotropic.isotropic_dilation(bw_img, 3)
|
||||
binary_res = img_as_bool(
|
||||
binary.binary_dilation(
|
||||
bw_img, morphology.disk(3)))
|
||||
assert_array_equal(isotropic_res, binary_res)
|
||||
|
||||
|
||||
def test_isotropic_closing():
|
||||
isotropic_res = isotropic.isotropic_closing(bw_img, 3)
|
||||
binary_res = img_as_bool(binary.binary_closing(bw_img, morphology.disk(3)))
|
||||
assert_array_equal(isotropic_res, binary_res)
|
||||
|
||||
|
||||
def test_isotropic_opening():
|
||||
isotropic_res = isotropic.isotropic_opening(bw_img, 3)
|
||||
binary_res = img_as_bool(binary.binary_opening(bw_img, morphology.disk(3)))
|
||||
assert_array_equal(isotropic_res, binary_res)
|
||||
|
||||
|
||||
def test_footprint_overflow():
|
||||
img = np.zeros((20, 20), dtype=bool)
|
||||
img[2:19, 2:19] = True
|
||||
isotropic_res = isotropic.isotropic_erosion(img, 9)
|
||||
binary_res = img_as_bool(binary.binary_erosion(img, morphology.disk(9)))
|
||||
assert_array_equal(isotropic_res, binary_res)
|
||||
|
||||
|
||||
def test_out_argument():
|
||||
for func in (isotropic.isotropic_erosion, isotropic.isotropic_dilation):
|
||||
radius = 3
|
||||
img = np.ones((10, 10))
|
||||
out = np.zeros_like(img)
|
||||
out_saved = out.copy()
|
||||
func(img, radius, out=out)
|
||||
assert np.any(out != out_saved)
|
||||
assert_array_equal(out, func(img, radius))
|
||||
453
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_max_tree.py
vendored
Normal file
453
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_max_tree.py
vendored
Normal file
@@ -0,0 +1,453 @@
|
||||
import numpy as np
|
||||
from skimage.morphology import max_tree, area_closing, area_opening
|
||||
from skimage.morphology import max_tree_local_maxima, diameter_opening
|
||||
from skimage.morphology import diameter_closing
|
||||
from skimage.util import invert
|
||||
|
||||
from skimage._shared.testing import assert_array_equal, TestCase
|
||||
|
||||
eps = 1e-12
|
||||
|
||||
|
||||
def _full_type_test(img, param, expected, func, param_scale=False,
|
||||
**keywords):
|
||||
|
||||
# images as they are
|
||||
out = func(img, param, **keywords)
|
||||
assert_array_equal(out, expected)
|
||||
|
||||
# unsigned int
|
||||
for dt in [np.uint32, np.uint64]:
|
||||
img_cast = img.astype(dt)
|
||||
out = func(img_cast, param, **keywords)
|
||||
exp_cast = expected.astype(dt)
|
||||
assert_array_equal(out, exp_cast)
|
||||
|
||||
# float
|
||||
data_float = img.astype(np.float64)
|
||||
data_float = data_float / 255.0
|
||||
expected_float = expected.astype(np.float64)
|
||||
expected_float = expected_float / 255.0
|
||||
if param_scale:
|
||||
param_cast = param / 255.0
|
||||
else:
|
||||
param_cast = param
|
||||
for dt in [np.float32, np.float64]:
|
||||
data_cast = data_float.astype(dt)
|
||||
out = func(data_cast, param_cast, **keywords)
|
||||
exp_cast = expected_float.astype(dt)
|
||||
error_img = 255.0 * exp_cast - 255.0 * out
|
||||
error = (error_img >= 1.0).sum()
|
||||
assert error < eps
|
||||
|
||||
# signed images
|
||||
img_signed = img.astype(np.int16)
|
||||
img_signed = img_signed - 128
|
||||
exp_signed = expected.astype(np.int16)
|
||||
exp_signed = exp_signed - 128
|
||||
for dt in [np.int8, np.int16, np.int32, np.int64]:
|
||||
img_s = img_signed.astype(dt)
|
||||
out = func(img_s, param, **keywords)
|
||||
exp_s = exp_signed.astype(dt)
|
||||
assert_array_equal(out, exp_s)
|
||||
|
||||
|
||||
class TestMaxtree(TestCase):
|
||||
|
||||
def test_max_tree(self):
|
||||
"Test for max tree"
|
||||
img_type = np.uint8
|
||||
img = np.array([[10, 8, 8, 9],
|
||||
[7, 7, 9, 9],
|
||||
[8, 7, 10, 10],
|
||||
[9, 9, 10, 10]], dtype=img_type)
|
||||
|
||||
P_exp = np.array([[1, 4, 1, 1],
|
||||
[4, 4, 3, 3],
|
||||
[1, 4, 3, 10],
|
||||
[3, 3, 10, 10]], dtype=np.int64)
|
||||
|
||||
S_exp = np.array([4, 5, 9, 1, 2, 8, 3, 6, 7,
|
||||
12, 13, 0, 10, 11, 14, 15],
|
||||
dtype=np.int64)
|
||||
|
||||
for img_type in [np.uint8, np.uint16, np.uint32, np.uint64]:
|
||||
img = img.astype(img_type)
|
||||
P, S = max_tree(img, connectivity=2)
|
||||
assert_array_equal(P, P_exp)
|
||||
assert_array_equal(S, S_exp)
|
||||
|
||||
for img_type in [np.int8, np.int16, np.int32, np.int64]:
|
||||
img = img.astype(img_type)
|
||||
img_shifted = img - 9
|
||||
P, S = max_tree(img_shifted, connectivity=2)
|
||||
assert_array_equal(P, P_exp)
|
||||
assert_array_equal(S, S_exp)
|
||||
|
||||
img_float = img.astype(float)
|
||||
img_float = (img_float - 8) / 2.0
|
||||
for img_type in [np.float32, np.float64]:
|
||||
img_float = img_float.astype(img_type)
|
||||
P, S = max_tree(img_float, connectivity=2)
|
||||
assert_array_equal(P, P_exp)
|
||||
assert_array_equal(S, S_exp)
|
||||
|
||||
return
|
||||
|
||||
def test_area_closing(self):
|
||||
"Test for Area Closing (2 thresholds, all types)"
|
||||
|
||||
# original image
|
||||
img = np.array(
|
||||
[[240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[240, 200, 200, 240, 200, 240, 200, 200, 240, 240, 200, 240],
|
||||
[240, 200, 40, 240, 240, 240, 240, 240, 240, 240, 40, 240],
|
||||
[240, 240, 240, 240, 100, 240, 100, 100, 240, 240, 200, 240],
|
||||
[240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[200, 200, 200, 200, 200, 200, 200, 240, 200, 200, 255, 255],
|
||||
[200, 255, 200, 200, 200, 255, 200, 240, 255, 255, 255, 40],
|
||||
[200, 200, 200, 100, 200, 200, 200, 240, 255, 255, 255, 255],
|
||||
[200, 200, 200, 100, 200, 200, 200, 240, 200, 200, 255, 255],
|
||||
[200, 200, 200, 200, 200, 40, 200, 240, 240, 100, 255, 255],
|
||||
[200, 40, 255, 255, 255, 40, 200, 255, 200, 200, 255, 255],
|
||||
[200, 200, 200, 200, 200, 200, 200, 255, 255, 255, 255, 255]],
|
||||
dtype=np.uint8)
|
||||
|
||||
# expected area closing with area 2
|
||||
expected_2 = np.array(
|
||||
[[240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[240, 200, 200, 240, 240, 240, 200, 200, 240, 240, 200, 240],
|
||||
[240, 200, 200, 240, 240, 240, 240, 240, 240, 240, 200, 240],
|
||||
[240, 240, 240, 240, 240, 240, 100, 100, 240, 240, 200, 240],
|
||||
[240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[200, 200, 200, 200, 200, 200, 200, 240, 200, 200, 255, 255],
|
||||
[200, 255, 200, 200, 200, 255, 200, 240, 255, 255, 255, 255],
|
||||
[200, 200, 200, 100, 200, 200, 200, 240, 255, 255, 255, 255],
|
||||
[200, 200, 200, 100, 200, 200, 200, 240, 200, 200, 255, 255],
|
||||
[200, 200, 200, 200, 200, 40, 200, 240, 240, 200, 255, 255],
|
||||
[200, 200, 255, 255, 255, 40, 200, 255, 200, 200, 255, 255],
|
||||
[200, 200, 200, 200, 200, 200, 200, 255, 255, 255, 255, 255]],
|
||||
dtype=np.uint8)
|
||||
|
||||
# expected diameter closing with diameter 4
|
||||
expected_4 = np.array(
|
||||
[[240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[240, 200, 200, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[240, 200, 200, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240, 240],
|
||||
[200, 200, 200, 200, 200, 200, 200, 240, 240, 240, 255, 255],
|
||||
[200, 255, 200, 200, 200, 255, 200, 240, 255, 255, 255, 255],
|
||||
[200, 200, 200, 200, 200, 200, 200, 240, 255, 255, 255, 255],
|
||||
[200, 200, 200, 200, 200, 200, 200, 240, 200, 200, 255, 255],
|
||||
[200, 200, 200, 200, 200, 200, 200, 240, 240, 200, 255, 255],
|
||||
[200, 200, 255, 255, 255, 200, 200, 255, 200, 200, 255, 255],
|
||||
[200, 200, 200, 200, 200, 200, 200, 255, 255, 255, 255, 255]],
|
||||
dtype=np.uint8)
|
||||
|
||||
# _full_type_test makes a test with many image types.
|
||||
_full_type_test(img, 2, expected_2, area_closing, connectivity=2)
|
||||
_full_type_test(img, 4, expected_4, area_closing, connectivity=2)
|
||||
|
||||
P, S = max_tree(invert(img), connectivity=2)
|
||||
_full_type_test(img, 4, expected_4, area_closing,
|
||||
parent=P, tree_traverser=S)
|
||||
|
||||
def test_area_opening(self):
|
||||
"Test for Area Opening (2 thresholds, all types)"
|
||||
|
||||
# original image
|
||||
img = np.array([[15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15],
|
||||
[15, 55, 55, 15, 55, 15, 55, 55, 15, 15, 55, 15],
|
||||
[15, 55, 215, 15, 15, 15, 15, 15, 15, 15, 215, 15],
|
||||
[15, 15, 15, 15, 155, 15, 155, 155, 15, 15, 55, 15],
|
||||
[15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15],
|
||||
[55, 55, 55, 55, 55, 55, 55, 15, 55, 55, 0, 0],
|
||||
[55, 0, 55, 55, 55, 0, 55, 15, 0, 0, 0, 215],
|
||||
[55, 55, 55, 155, 55, 55, 55, 15, 0, 0, 0, 0],
|
||||
[55, 55, 55, 155, 55, 55, 55, 15, 55, 55, 0, 0],
|
||||
[55, 55, 55, 55, 55, 215, 55, 15, 15, 155, 0, 0],
|
||||
[55, 215, 0, 0, 0, 215, 55, 0, 55, 55, 0, 0],
|
||||
[55, 55, 55, 55, 55, 55, 55, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
|
||||
# expected area closing with area 2
|
||||
expected_2 = np.array([[15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15],
|
||||
[15, 55, 55, 15, 15, 15, 55, 55, 15,
|
||||
15, 55, 15],
|
||||
[15, 55, 55, 15, 15, 15, 15, 15, 15,
|
||||
15, 55, 15],
|
||||
[15, 15, 15, 15, 15, 15, 155, 155, 15,
|
||||
15, 55, 15],
|
||||
[15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15],
|
||||
[55, 55, 55, 55, 55, 55, 55, 15, 55,
|
||||
55, 0, 0],
|
||||
[55, 0, 55, 55, 55, 0, 55, 15, 0,
|
||||
0, 0, 0],
|
||||
[55, 55, 55, 155, 55, 55, 55, 15, 0,
|
||||
0, 0, 0],
|
||||
[55, 55, 55, 155, 55, 55, 55, 15, 55,
|
||||
55, 0, 0],
|
||||
[55, 55, 55, 55, 55, 215, 55, 15, 15,
|
||||
55, 0, 0],
|
||||
[55, 55, 0, 0, 0, 215, 55, 0, 55,
|
||||
55, 0, 0],
|
||||
[55, 55, 55, 55, 55, 55, 55, 0, 0,
|
||||
0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
|
||||
# expected diameter closing with diameter 4
|
||||
expected_4 = np.array([[15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15],
|
||||
[15, 55, 55, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15],
|
||||
[15, 55, 55, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15],
|
||||
[15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15],
|
||||
[15, 15, 15, 15, 15, 15, 15, 15, 15,
|
||||
15, 15, 15],
|
||||
[55, 55, 55, 55, 55, 55, 55, 15, 15,
|
||||
15, 0, 0],
|
||||
[55, 0, 55, 55, 55, 0, 55, 15, 0,
|
||||
0, 0, 0],
|
||||
[55, 55, 55, 55, 55, 55, 55, 15, 0,
|
||||
0, 0, 0],
|
||||
[55, 55, 55, 55, 55, 55, 55, 15, 55,
|
||||
55, 0, 0],
|
||||
[55, 55, 55, 55, 55, 55, 55, 15, 15,
|
||||
55, 0, 0],
|
||||
[55, 55, 0, 0, 0, 55, 55, 0, 55,
|
||||
55, 0, 0],
|
||||
[55, 55, 55, 55, 55, 55, 55, 0, 0,
|
||||
0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
|
||||
# _full_type_test makes a test with many image types.
|
||||
_full_type_test(img, 2, expected_2, area_opening, connectivity=2)
|
||||
_full_type_test(img, 4, expected_4, area_opening, connectivity=2)
|
||||
|
||||
P, S = max_tree(img, connectivity=2)
|
||||
_full_type_test(img, 4, expected_4, area_opening,
|
||||
parent=P, tree_traverser=S)
|
||||
|
||||
def test_diameter_closing(self):
|
||||
"Test for Diameter Opening (2 thresholds, all types)"
|
||||
img = np.array([[97, 95, 93, 92, 91, 90, 90, 90, 91, 92, 93, 95],
|
||||
[95, 93, 91, 89, 88, 88, 88, 88, 88, 89, 91, 93],
|
||||
[93, 63, 63, 63, 63, 86, 86, 86, 87, 43, 43, 91],
|
||||
[92, 89, 88, 86, 85, 85, 84, 85, 85, 43, 43, 89],
|
||||
[91, 88, 87, 85, 84, 84, 83, 84, 84, 85, 87, 88],
|
||||
[90, 88, 86, 85, 84, 83, 83, 83, 84, 85, 86, 88],
|
||||
[90, 88, 86, 84, 83, 83, 82, 83, 83, 84, 86, 88],
|
||||
[90, 88, 86, 85, 84, 83, 83, 83, 84, 85, 86, 88],
|
||||
[91, 88, 87, 85, 84, 84, 83, 84, 84, 85, 87, 88],
|
||||
[92, 89, 23, 23, 85, 85, 84, 85, 85, 3, 3, 89],
|
||||
[93, 91, 23, 23, 87, 86, 86, 86, 87, 88, 3, 91],
|
||||
[95, 93, 91, 89, 88, 88, 88, 88, 88, 89, 91, 93]],
|
||||
dtype=np.uint8)
|
||||
|
||||
ex2 = np.array([[97, 95, 93, 92, 91, 90, 90, 90, 91, 92, 93, 95],
|
||||
[95, 93, 91, 89, 88, 88, 88, 88, 88, 89, 91, 93],
|
||||
[93, 63, 63, 63, 63, 86, 86, 86, 87, 43, 43, 91],
|
||||
[92, 89, 88, 86, 85, 85, 84, 85, 85, 43, 43, 89],
|
||||
[91, 88, 87, 85, 84, 84, 83, 84, 84, 85, 87, 88],
|
||||
[90, 88, 86, 85, 84, 83, 83, 83, 84, 85, 86, 88],
|
||||
[90, 88, 86, 84, 83, 83, 83, 83, 83, 84, 86, 88],
|
||||
[90, 88, 86, 85, 84, 83, 83, 83, 84, 85, 86, 88],
|
||||
[91, 88, 87, 85, 84, 84, 83, 84, 84, 85, 87, 88],
|
||||
[92, 89, 23, 23, 85, 85, 84, 85, 85, 3, 3, 89],
|
||||
[93, 91, 23, 23, 87, 86, 86, 86, 87, 88, 3, 91],
|
||||
[95, 93, 91, 89, 88, 88, 88, 88, 88, 89, 91, 93]],
|
||||
dtype=np.uint8)
|
||||
|
||||
ex4 = np.array([[97, 95, 93, 92, 91, 90, 90, 90, 91, 92, 93, 95],
|
||||
[95, 93, 91, 89, 88, 88, 88, 88, 88, 89, 91, 93],
|
||||
[93, 63, 63, 63, 63, 86, 86, 86, 87, 84, 84, 91],
|
||||
[92, 89, 88, 86, 85, 85, 84, 85, 85, 84, 84, 89],
|
||||
[91, 88, 87, 85, 84, 84, 83, 84, 84, 85, 87, 88],
|
||||
[90, 88, 86, 85, 84, 83, 83, 83, 84, 85, 86, 88],
|
||||
[90, 88, 86, 84, 83, 83, 83, 83, 83, 84, 86, 88],
|
||||
[90, 88, 86, 85, 84, 83, 83, 83, 84, 85, 86, 88],
|
||||
[91, 88, 87, 85, 84, 84, 83, 84, 84, 85, 87, 88],
|
||||
[92, 89, 84, 84, 85, 85, 84, 85, 85, 84, 84, 89],
|
||||
[93, 91, 84, 84, 87, 86, 86, 86, 87, 88, 84, 91],
|
||||
[95, 93, 91, 89, 88, 88, 88, 88, 88, 89, 91, 93]],
|
||||
dtype=np.uint8)
|
||||
|
||||
# _full_type_test makes a test with many image types.
|
||||
_full_type_test(img, 2, ex2, diameter_closing, connectivity=2)
|
||||
_full_type_test(img, 4, ex4, diameter_closing, connectivity=2)
|
||||
|
||||
P, S = max_tree(invert(img), connectivity=2)
|
||||
_full_type_test(img, 4, ex4, diameter_opening,
|
||||
parent=P, tree_traverser=S)
|
||||
|
||||
def test_diameter_opening(self):
|
||||
"Test for Diameter Opening (2 thresholds, all types)"
|
||||
img = np.array([[5, 7, 9, 11, 12, 12, 12, 12, 12, 11, 9, 7],
|
||||
[7, 10, 11, 13, 14, 14, 15, 14, 14, 13, 11, 10],
|
||||
[9, 40, 40, 40, 40, 16, 16, 16, 16, 60, 60, 11],
|
||||
[11, 13, 15, 16, 17, 18, 18, 18, 17, 60, 60, 13],
|
||||
[12, 14, 16, 17, 18, 19, 19, 19, 18, 17, 16, 14],
|
||||
[12, 14, 16, 18, 19, 19, 19, 19, 19, 18, 16, 14],
|
||||
[12, 15, 16, 18, 19, 19, 20, 19, 19, 18, 16, 15],
|
||||
[12, 14, 16, 18, 19, 19, 19, 19, 19, 18, 16, 14],
|
||||
[12, 14, 16, 17, 18, 19, 19, 19, 18, 17, 16, 14],
|
||||
[11, 13, 80, 80, 17, 18, 18, 18, 17, 100, 100, 13],
|
||||
[9, 11, 80, 80, 16, 16, 16, 16, 16, 15, 100, 11],
|
||||
[7, 10, 11, 13, 14, 14, 15, 14, 14, 13, 11, 10]])
|
||||
|
||||
ex2 = np.array([[5, 7, 9, 11, 12, 12, 12, 12, 12, 11, 9, 7],
|
||||
[7, 10, 11, 13, 14, 14, 15, 14, 14, 13, 11, 10],
|
||||
[9, 40, 40, 40, 40, 16, 16, 16, 16, 60, 60, 11],
|
||||
[11, 13, 15, 16, 17, 18, 18, 18, 17, 60, 60, 13],
|
||||
[12, 14, 16, 17, 18, 19, 19, 19, 18, 17, 16, 14],
|
||||
[12, 14, 16, 18, 19, 19, 19, 19, 19, 18, 16, 14],
|
||||
[12, 15, 16, 18, 19, 19, 19, 19, 19, 18, 16, 15],
|
||||
[12, 14, 16, 18, 19, 19, 19, 19, 19, 18, 16, 14],
|
||||
[12, 14, 16, 17, 18, 19, 19, 19, 18, 17, 16, 14],
|
||||
[11, 13, 80, 80, 17, 18, 18, 18, 17, 100, 100, 13],
|
||||
[9, 11, 80, 80, 16, 16, 16, 16, 16, 15, 100, 11],
|
||||
[7, 10, 11, 13, 14, 14, 15, 14, 14, 13, 11, 10]])
|
||||
|
||||
ex4 = np.array([[5, 7, 9, 11, 12, 12, 12, 12, 12, 11, 9, 7],
|
||||
[7, 10, 11, 13, 14, 14, 15, 14, 14, 13, 11, 10],
|
||||
[9, 40, 40, 40, 40, 16, 16, 16, 16, 18, 18, 11],
|
||||
[11, 13, 15, 16, 17, 18, 18, 18, 17, 18, 18, 13],
|
||||
[12, 14, 16, 17, 18, 19, 19, 19, 18, 17, 16, 14],
|
||||
[12, 14, 16, 18, 19, 19, 19, 19, 19, 18, 16, 14],
|
||||
[12, 15, 16, 18, 19, 19, 19, 19, 19, 18, 16, 15],
|
||||
[12, 14, 16, 18, 19, 19, 19, 19, 19, 18, 16, 14],
|
||||
[12, 14, 16, 17, 18, 19, 19, 19, 18, 17, 16, 14],
|
||||
[11, 13, 18, 18, 17, 18, 18, 18, 17, 18, 18, 13],
|
||||
[9, 11, 18, 18, 16, 16, 16, 16, 16, 15, 18, 11],
|
||||
[7, 10, 11, 13, 14, 14, 15, 14, 14, 13, 11, 10]])
|
||||
|
||||
# _full_type_test makes a test with many image types.
|
||||
_full_type_test(img, 2, ex2, diameter_opening, connectivity=2)
|
||||
_full_type_test(img, 4, ex4, diameter_opening, connectivity=2)
|
||||
|
||||
P, S = max_tree(img, connectivity=2)
|
||||
_full_type_test(img, 4, ex4, diameter_opening,
|
||||
parent=P, tree_traverser=S)
|
||||
|
||||
def test_local_maxima(self):
|
||||
"local maxima for various data types"
|
||||
data = np.array([[10, 11, 13, 14, 14, 15, 14, 14, 13, 11],
|
||||
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13],
|
||||
[13, 15, 40, 40, 18, 18, 18, 60, 60, 15],
|
||||
[14, 16, 40, 40, 19, 19, 19, 60, 60, 16],
|
||||
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||||
[15, 16, 18, 19, 19, 20, 19, 19, 18, 16],
|
||||
[14, 16, 18, 19, 19, 19, 19, 19, 18, 16],
|
||||
[14, 16, 80, 80, 19, 19, 19, 100, 100, 16],
|
||||
[13, 15, 80, 80, 18, 18, 18, 100, 100, 15],
|
||||
[11, 13, 15, 16, 16, 16, 16, 16, 15, 13]],
|
||||
dtype=np.uint8)
|
||||
expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 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, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint64)
|
||||
for dtype in [np.uint8, np.uint64, np.int8, np.int64]:
|
||||
|
||||
test_data = data.astype(dtype)
|
||||
out = max_tree_local_maxima(test_data, connectivity=1)
|
||||
out_bin = out > 0
|
||||
assert_array_equal(expected_result, out_bin)
|
||||
assert out.dtype == expected_result.dtype
|
||||
assert np.max(out) == 5
|
||||
|
||||
P, S = max_tree(test_data)
|
||||
out = max_tree_local_maxima(test_data,
|
||||
parent=P,
|
||||
tree_traverser=S)
|
||||
|
||||
assert_array_equal(expected_result, out_bin)
|
||||
|
||||
assert out.dtype == expected_result.dtype
|
||||
assert np.max(out) == 5
|
||||
|
||||
def test_extrema_float(self):
|
||||
"specific tests for float type"
|
||||
data = np.array([[0.10, 0.11, 0.13, 0.14, 0.14, 0.15, 0.14,
|
||||
0.14, 0.13, 0.11],
|
||||
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16,
|
||||
0.16, 0.15, 0.13],
|
||||
[0.13, 0.15, 0.40, 0.40, 0.18, 0.18, 0.18,
|
||||
0.60, 0.60, 0.15],
|
||||
[0.14, 0.16, 0.40, 0.40, 0.19, 0.19, 0.19,
|
||||
0.60, 0.60, 0.16],
|
||||
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19,
|
||||
0.19, 0.18, 0.16],
|
||||
[0.15, 0.182, 0.18, 0.19, 0.204, 0.20, 0.19,
|
||||
0.19, 0.18, 0.16],
|
||||
[0.14, 0.16, 0.18, 0.19, 0.19, 0.19, 0.19,
|
||||
0.19, 0.18, 0.16],
|
||||
[0.14, 0.16, 0.80, 0.80, 0.19, 0.19, 0.19,
|
||||
4.0, 1.0, 0.16],
|
||||
[0.13, 0.15, 0.80, 0.80, 0.18, 0.18, 0.18,
|
||||
1.0, 1.0, 0.15],
|
||||
[0.11, 0.13, 0.15, 0.16, 0.16, 0.16, 0.16,
|
||||
0.16, 0.15, 0.13]],
|
||||
dtype=np.float32)
|
||||
|
||||
expected_result = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 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, 1, 0, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
|
||||
# test for local maxima
|
||||
out = max_tree_local_maxima(data, connectivity=1)
|
||||
out_bin = out > 0
|
||||
assert_array_equal(expected_result, out_bin)
|
||||
assert np.max(out) == 6
|
||||
|
||||
def test_3d(self):
|
||||
"""tests the detection of maxima in 3D."""
|
||||
img = np.zeros((8, 8, 8), dtype=np.uint8)
|
||||
local_maxima = np.zeros((8, 8, 8), dtype=np.uint64)
|
||||
|
||||
# first maximum: only one pixel
|
||||
img[1, 1:3, 1:3] = 100
|
||||
img[2, 2, 2] = 200
|
||||
img[3, 1:3, 1:3] = 100
|
||||
local_maxima[2, 2, 2] = 1
|
||||
|
||||
# second maximum: three pixels in z-direction
|
||||
img[5:8, 1, 1] = 200
|
||||
local_maxima[5:8, 1, 1] = 1
|
||||
|
||||
# third: two maxima in 0 and 3.
|
||||
img[0, 5:8, 5:8] = 200
|
||||
img[1, 6, 6] = 100
|
||||
img[2, 5:7, 5:7] = 200
|
||||
img[0:3, 5:8, 5:8] += 50
|
||||
local_maxima[0, 5:8, 5:8] = 1
|
||||
local_maxima[2, 5:7, 5:7] = 1
|
||||
|
||||
# four : one maximum in the corner of the square
|
||||
img[6:8, 6:8, 6:8] = 200
|
||||
img[7, 7, 7] = 255
|
||||
local_maxima[7, 7, 7] = 1
|
||||
|
||||
out = max_tree_local_maxima(img)
|
||||
out_bin = out > 0
|
||||
assert_array_equal(local_maxima, out_bin)
|
||||
assert np.max(out) == 5
|
||||
225
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_misc.py
vendored
Normal file
225
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_misc.py
vendored
Normal file
@@ -0,0 +1,225 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from skimage.morphology import remove_small_objects, remove_small_holes
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal, assert_equal
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
test_image = np.array([[0, 0, 0, 1, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 0, 1]], bool)
|
||||
|
||||
|
||||
def test_one_connectivity():
|
||||
expected = np.array([[0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 0, 0]], bool)
|
||||
observed = remove_small_objects(test_image, min_size=6)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_two_connectivity():
|
||||
expected = np.array([[0, 0, 0, 1, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 0, 0]], bool)
|
||||
observed = remove_small_objects(test_image, min_size=7, connectivity=2)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_in_place():
|
||||
image = test_image.copy()
|
||||
observed = remove_small_objects(image, min_size=6, out=image)
|
||||
assert_equal(observed is image, True,
|
||||
"remove_small_objects in_place argument failed.")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("in_dtype", [bool, int, np.int32])
|
||||
@pytest.mark.parametrize("out_dtype", [bool, int, np.int32])
|
||||
def test_out(in_dtype, out_dtype):
|
||||
image = test_image.astype(in_dtype, copy=True)
|
||||
expected_out = np.empty_like(test_image, dtype=out_dtype)
|
||||
|
||||
if out_dtype != bool:
|
||||
# object with only 1 label will warn on non-bool output dtype
|
||||
exp_warn = ["Only one label was provided"]
|
||||
else:
|
||||
exp_warn = []
|
||||
|
||||
with expected_warnings(exp_warn):
|
||||
out = remove_small_objects(image, min_size=6, out=expected_out)
|
||||
|
||||
assert out is expected_out
|
||||
|
||||
|
||||
def test_labeled_image():
|
||||
labeled_image = np.array([[2, 2, 2, 0, 1],
|
||||
[2, 2, 2, 0, 1],
|
||||
[2, 0, 0, 0, 0],
|
||||
[0, 0, 3, 3, 3]], dtype=int)
|
||||
expected = np.array([[2, 2, 2, 0, 0],
|
||||
[2, 2, 2, 0, 0],
|
||||
[2, 0, 0, 0, 0],
|
||||
[0, 0, 3, 3, 3]], dtype=int)
|
||||
observed = remove_small_objects(labeled_image, min_size=3)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_uint_image():
|
||||
labeled_image = np.array([[2, 2, 2, 0, 1],
|
||||
[2, 2, 2, 0, 1],
|
||||
[2, 0, 0, 0, 0],
|
||||
[0, 0, 3, 3, 3]], dtype=np.uint8)
|
||||
expected = np.array([[2, 2, 2, 0, 0],
|
||||
[2, 2, 2, 0, 0],
|
||||
[2, 0, 0, 0, 0],
|
||||
[0, 0, 3, 3, 3]], dtype=np.uint8)
|
||||
observed = remove_small_objects(labeled_image, min_size=3)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_single_label_warning():
|
||||
image = np.array([[0, 0, 0, 1, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 0, 0]], int)
|
||||
with expected_warnings(['use a boolean array?']):
|
||||
remove_small_objects(image, min_size=6)
|
||||
|
||||
|
||||
def test_float_input():
|
||||
float_test = np.random.rand(5, 5)
|
||||
with testing.raises(TypeError):
|
||||
remove_small_objects(float_test)
|
||||
|
||||
|
||||
def test_negative_input():
|
||||
negative_int = np.random.randint(-4, -1, size=(5, 5))
|
||||
with testing.raises(ValueError):
|
||||
remove_small_objects(negative_int)
|
||||
|
||||
|
||||
test_holes_image = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], bool)
|
||||
|
||||
|
||||
def test_one_connectivity_holes():
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], bool)
|
||||
observed = remove_small_holes(test_holes_image, area_threshold=3)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_two_connectivity_holes():
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], bool)
|
||||
observed = remove_small_holes(test_holes_image, area_threshold=3,
|
||||
connectivity=2)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_in_place_holes():
|
||||
image = test_holes_image.copy()
|
||||
observed = remove_small_holes(image, area_threshold=3, out=image)
|
||||
assert_equal(observed is image, True,
|
||||
"remove_small_holes in_place argument failed.")
|
||||
|
||||
|
||||
def test_out_remove_small_holes():
|
||||
image = test_holes_image.copy()
|
||||
expected_out = np.empty_like(image)
|
||||
out = remove_small_holes(image, area_threshold=3, out=expected_out)
|
||||
|
||||
assert out is expected_out
|
||||
|
||||
|
||||
def test_non_bool_out():
|
||||
image = test_holes_image.copy()
|
||||
expected_out = np.empty_like(image, dtype=int)
|
||||
with testing.raises(TypeError):
|
||||
remove_small_holes(image, area_threshold=3, out=expected_out)
|
||||
|
||||
|
||||
def test_labeled_image_holes():
|
||||
labeled_holes_image = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 0, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 2, 2]],
|
||||
dtype=int)
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], dtype=bool)
|
||||
with expected_warnings(['returned as a boolean array']):
|
||||
observed = remove_small_holes(labeled_holes_image, area_threshold=3)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_uint_image_holes():
|
||||
labeled_holes_image = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 0, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 2, 2]],
|
||||
dtype=np.uint8)
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 1]], dtype=bool)
|
||||
with expected_warnings(['returned as a boolean array']):
|
||||
observed = remove_small_holes(labeled_holes_image, area_threshold=3)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_label_warning_holes():
|
||||
labeled_holes_image = np.array([[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 1, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 0, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 2, 2, 2]],
|
||||
dtype=int)
|
||||
with expected_warnings(['use a boolean array?']):
|
||||
remove_small_holes(labeled_holes_image, area_threshold=3)
|
||||
remove_small_holes(labeled_holes_image.astype(bool), area_threshold=3)
|
||||
|
||||
|
||||
def test_float_input_holes():
|
||||
float_test = np.random.rand(5, 5)
|
||||
with testing.raises(TypeError):
|
||||
remove_small_holes(float_test)
|
||||
152
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_reconstruction.py
vendored
Normal file
152
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_reconstruction.py
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
import math
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.morphology.grayreconstruct import reconstruction
|
||||
|
||||
|
||||
def test_zeros():
|
||||
"""Test reconstruction with image and mask of zeros"""
|
||||
assert_array_almost_equal(
|
||||
reconstruction(np.zeros((5, 7)), np.zeros((5, 7))), 0)
|
||||
|
||||
|
||||
def test_image_equals_mask():
|
||||
"""Test reconstruction where the image and mask are the same"""
|
||||
assert_array_almost_equal(
|
||||
reconstruction(np.ones((7, 5)), np.ones((7, 5))), 1)
|
||||
|
||||
|
||||
def test_image_less_than_mask():
|
||||
"""Test reconstruction where the image is uniform and less than mask"""
|
||||
image = np.ones((5, 5))
|
||||
mask = np.ones((5, 5)) * 2
|
||||
assert_array_almost_equal(reconstruction(image, mask), 1)
|
||||
|
||||
|
||||
def test_one_image_peak():
|
||||
"""Test reconstruction with one peak pixel"""
|
||||
image = np.ones((5, 5))
|
||||
image[2, 2] = 2
|
||||
mask = np.ones((5, 5)) * 3
|
||||
assert_array_almost_equal(reconstruction(image, mask), 2)
|
||||
|
||||
|
||||
# minsize chosen to test sizes covering use of 8, 16 and 32-bit integers
|
||||
# internally
|
||||
@pytest.mark.parametrize('minsize', [None, 200, 20000, 40000, 80000])
|
||||
@pytest.mark.parametrize('dtype', [np.uint8, np.float32])
|
||||
def test_two_image_peaks(minsize, dtype):
|
||||
"""Test reconstruction with two peak pixels isolated by the mask"""
|
||||
image = np.array([[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 2, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 3, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1]], dtype=dtype)
|
||||
|
||||
mask = np.array([[4, 4, 4, 1, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 1, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 4, 4, 4, 1],
|
||||
[1, 1, 1, 1, 1, 4, 4, 4, 1],
|
||||
[1, 1, 1, 1, 1, 4, 4, 4, 1]], dtype=dtype)
|
||||
|
||||
expected = np.array([[2, 2, 2, 1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 1, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 3, 3, 3, 1],
|
||||
[1, 1, 1, 1, 1, 3, 3, 3, 1],
|
||||
[1, 1, 1, 1, 1, 3, 3, 3, 1]], dtype=dtype)
|
||||
if minsize is not None:
|
||||
# increase data size by tiling (done to test various int types)
|
||||
nrow = math.ceil(math.sqrt(minsize / image.size))
|
||||
ncol = math.ceil(minsize / (image.size * nrow))
|
||||
image = np.tile(image, (nrow, ncol))
|
||||
mask = np.tile(mask, (nrow, ncol))
|
||||
expected = np.tile(expected, (nrow, ncol))
|
||||
out = reconstruction(image, mask)
|
||||
assert out.dtype == _supported_float_type(mask.dtype)
|
||||
assert_array_almost_equal(out, expected)
|
||||
|
||||
|
||||
def test_zero_image_one_mask():
|
||||
"""Test reconstruction with an image of all zeros and a mask that's not"""
|
||||
result = reconstruction(np.zeros((10, 10)), np.ones((10, 10)))
|
||||
assert_array_almost_equal(result, 0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.int8, np.uint8, np.int16, np.uint16,
|
||||
np.int32, np.uint32, np.int64, np.uint64,
|
||||
np.float16, np.float32, np.float64])
|
||||
def test_fill_hole(dtype):
|
||||
"""Test reconstruction by erosion, which should fill holes in mask."""
|
||||
seed = np.array([0, 8, 8, 8, 8, 8, 8, 8, 8, 0], dtype=dtype)
|
||||
mask = np.array([0, 3, 6, 2, 1, 1, 1, 4, 2, 0], dtype=dtype)
|
||||
result = reconstruction(seed, mask, method='erosion')
|
||||
assert result.dtype == _supported_float_type(mask.dtype)
|
||||
expected = np.array([0, 3, 6, 4, 4, 4, 4, 4, 2, 0], dtype=dtype)
|
||||
assert_array_almost_equal(result, expected)
|
||||
|
||||
|
||||
def test_invalid_seed():
|
||||
seed = np.ones((5, 5))
|
||||
mask = np.ones((5, 5))
|
||||
with pytest.raises(ValueError):
|
||||
reconstruction(seed * 2, mask,
|
||||
method='dilation')
|
||||
with pytest.raises(ValueError):
|
||||
reconstruction(seed * 0.5, mask,
|
||||
method='erosion')
|
||||
|
||||
|
||||
def test_invalid_footprint():
|
||||
seed = np.ones((5, 5))
|
||||
mask = np.ones((5, 5))
|
||||
with pytest.raises(ValueError):
|
||||
reconstruction(seed, mask,
|
||||
footprint=np.ones((4, 4)))
|
||||
with pytest.raises(ValueError):
|
||||
reconstruction(seed, mask,
|
||||
footprint=np.ones((3, 4)))
|
||||
reconstruction(seed, mask, footprint=np.ones((3, 3)))
|
||||
|
||||
|
||||
def test_invalid_method():
|
||||
seed = np.array([0, 8, 8, 8, 8, 8, 8, 8, 8, 0])
|
||||
mask = np.array([0, 3, 6, 2, 1, 1, 1, 4, 2, 0])
|
||||
with pytest.raises(ValueError):
|
||||
reconstruction(seed, mask, method='foo')
|
||||
|
||||
|
||||
def test_invalid_offset_not_none():
|
||||
"""Test reconstruction with invalid not None offset parameter"""
|
||||
image = np.array([[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 2, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 3, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1]])
|
||||
|
||||
mask = np.array([[4, 4, 4, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 1, 1, 1, 1, 1],
|
||||
[4, 4, 4, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 4, 4, 4],
|
||||
[1, 1, 1, 1, 1, 4, 4, 4]])
|
||||
with pytest.raises(ValueError):
|
||||
reconstruction(image, mask, method='dilation',
|
||||
footprint=np.ones((3, 3)), offset=np.array([3, 0]))
|
||||
|
||||
|
||||
def test_offset_not_none():
|
||||
"""Test reconstruction with valid offset parameter"""
|
||||
seed = np.array([0, 3, 6, 2, 1, 1, 1, 4, 2, 0])
|
||||
mask = np.array([0, 8, 6, 8, 8, 8, 8, 4, 4, 0])
|
||||
expected = np.array([0, 3, 6, 6, 6, 6, 6, 4, 4, 0])
|
||||
|
||||
assert_array_almost_equal(
|
||||
reconstruction(seed, mask, method='dilation',
|
||||
footprint=np.ones(3), offset=np.array([0])), expected)
|
||||
229
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_skeletonize.py
vendored
Normal file
229
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_skeletonize.py
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_equal
|
||||
from scipy.ndimage import correlate
|
||||
|
||||
from skimage import draw
|
||||
from skimage._shared.testing import fetch
|
||||
from skimage.io import imread
|
||||
from skimage.morphology import medial_axis, skeletonize, thin
|
||||
from skimage.morphology._skeletonize import (_generate_thin_luts,
|
||||
G123_LUT, G123P_LUT)
|
||||
|
||||
|
||||
class TestSkeletonize():
|
||||
def test_skeletonize_no_foreground(self):
|
||||
im = np.zeros((5, 5))
|
||||
result = skeletonize(im)
|
||||
assert_array_equal(result, np.zeros((5, 5)))
|
||||
|
||||
def test_skeletonize_wrong_dim1(self):
|
||||
im = np.zeros(5)
|
||||
with pytest.raises(ValueError):
|
||||
skeletonize(im)
|
||||
|
||||
def test_skeletonize_wrong_dim2(self):
|
||||
im = np.zeros((5, 5, 5))
|
||||
with pytest.raises(ValueError):
|
||||
skeletonize(im, method='zhang')
|
||||
|
||||
def test_skeletonize_all_foreground(self):
|
||||
im = np.ones((3, 4))
|
||||
skeletonize(im)
|
||||
|
||||
def test_skeletonize_single_point(self):
|
||||
im = np.zeros((5, 5), np.uint8)
|
||||
im[3, 3] = 1
|
||||
result = skeletonize(im)
|
||||
assert_array_equal(result, im)
|
||||
|
||||
def test_skeletonize_already_thinned(self):
|
||||
im = np.zeros((5, 5), np.uint8)
|
||||
im[3, 1:-1] = 1
|
||||
im[2, -1] = 1
|
||||
im[4, 0] = 1
|
||||
result = skeletonize(im)
|
||||
assert_array_equal(result, im)
|
||||
|
||||
def test_skeletonize_output(self):
|
||||
im = imread(fetch("data/bw_text.png"), as_gray=True)
|
||||
|
||||
# make black the foreground
|
||||
im = (im == 0)
|
||||
result = skeletonize(im)
|
||||
|
||||
expected = np.load(fetch("data/bw_text_skeleton.npy"))
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_skeletonize_num_neighbors(self):
|
||||
# an empty image
|
||||
image = np.zeros((300, 300))
|
||||
|
||||
# foreground object 1
|
||||
image[10:-10, 10:100] = 1
|
||||
image[-100:-10, 10:-10] = 1
|
||||
image[10:-10, -100:-10] = 1
|
||||
|
||||
# foreground object 2
|
||||
rs, cs = draw.line(250, 150, 10, 280)
|
||||
for i in range(10):
|
||||
image[rs + i, cs] = 1
|
||||
rs, cs = draw.line(10, 150, 250, 280)
|
||||
for i in range(20):
|
||||
image[rs + i, cs] = 1
|
||||
|
||||
# foreground object 3
|
||||
ir, ic = np.indices(image.shape)
|
||||
circle1 = (ic - 135)**2 + (ir - 150)**2 < 30**2
|
||||
circle2 = (ic - 135)**2 + (ir - 150)**2 < 20**2
|
||||
image[circle1] = 1
|
||||
image[circle2] = 0
|
||||
result = skeletonize(image)
|
||||
|
||||
# there should never be a 2x2 block of foreground pixels in a skeleton
|
||||
mask = np.array([[1, 1],
|
||||
[1, 1]], np.uint8)
|
||||
blocks = correlate(result, mask, mode='constant')
|
||||
assert not np.any(blocks == 4)
|
||||
|
||||
def test_lut_fix(self):
|
||||
im = np.zeros((6, 6), np.uint8)
|
||||
im[1, 2] = 1
|
||||
im[2, 2] = 1
|
||||
im[2, 3] = 1
|
||||
im[3, 3] = 1
|
||||
im[3, 4] = 1
|
||||
im[4, 4] = 1
|
||||
im[4, 5] = 1
|
||||
result = skeletonize(im)
|
||||
expected = np.array([[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, 1],
|
||||
[0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
assert np.all(result == expected)
|
||||
|
||||
|
||||
class TestThin():
|
||||
@property
|
||||
def input_image(self):
|
||||
"""image to test thinning with"""
|
||||
ii = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
return ii
|
||||
|
||||
def test_zeros(self):
|
||||
assert np.all(thin(np.zeros((10, 10))) == False)
|
||||
|
||||
def test_iter_1(self):
|
||||
result = thin(self.input_image, 1).astype(np.uint8)
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 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],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_noiter(self):
|
||||
result = thin(self.input_image).astype(np.uint8)
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 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]], dtype=np.uint8)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_baddim(self):
|
||||
for ii in [np.zeros(3), np.zeros((3, 3, 3))]:
|
||||
with pytest.raises(ValueError):
|
||||
thin(ii)
|
||||
|
||||
def test_lut_generation(self):
|
||||
g123, g123p = _generate_thin_luts()
|
||||
|
||||
assert_array_equal(g123, G123_LUT)
|
||||
assert_array_equal(g123p, G123P_LUT)
|
||||
|
||||
|
||||
class TestMedialAxis():
|
||||
def test_00_00_zeros(self):
|
||||
'''Test skeletonize on an array of all zeros'''
|
||||
result = medial_axis(np.zeros((10, 10), bool))
|
||||
assert np.all(result == False)
|
||||
|
||||
def test_00_01_zeros_masked(self):
|
||||
'''Test skeletonize on an array that is completely masked'''
|
||||
result = medial_axis(np.zeros((10, 10), bool),
|
||||
np.zeros((10, 10), bool))
|
||||
assert np.all(result == False)
|
||||
|
||||
def test_vertical_line(self):
|
||||
'''Test a thick vertical line, issue #3861'''
|
||||
img = np.zeros((9, 9))
|
||||
img[:, 2] = 1
|
||||
img[:, 3] = 1
|
||||
img[:, 4] = 1
|
||||
|
||||
expected = np.full(img.shape, False)
|
||||
expected[:, 3] = True
|
||||
|
||||
result = medial_axis(img)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_01_01_rectangle(self):
|
||||
'''Test skeletonize on a rectangle'''
|
||||
image = np.zeros((9, 15), bool)
|
||||
image[1:-1, 1:-1] = True
|
||||
#
|
||||
# The result should be four diagonals from the
|
||||
# corners, meeting in a horizontal line
|
||||
#
|
||||
expected = np.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, 1, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 1, 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]],
|
||||
dtype=bool)
|
||||
result = medial_axis(image)
|
||||
assert np.all(result == expected)
|
||||
result, distance = medial_axis(image, return_distance=True)
|
||||
assert distance.max() == 4
|
||||
|
||||
def test_01_02_hole(self):
|
||||
'''Test skeletonize on a rectangle with a hole in the middle'''
|
||||
image = np.zeros((9, 15), bool)
|
||||
image[1:-1, 1:-1] = True
|
||||
image[4, 4:-4] = False
|
||||
expected = np.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, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 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]],
|
||||
dtype=bool)
|
||||
result = medial_axis(image)
|
||||
assert np.all(result == expected)
|
||||
|
||||
def test_narrow_image(self):
|
||||
"""Test skeletonize on a 1-pixel thin strip"""
|
||||
image = np.zeros((1, 5), bool)
|
||||
image[:, 1:-1] = True
|
||||
result = medial_axis(image)
|
||||
assert np.all(result == image)
|
||||
185
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_skeletonize_3d.py
vendored
Normal file
185
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_skeletonize_3d.py
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
|
||||
from skimage import io, draw
|
||||
from skimage.data import binary_blobs
|
||||
from skimage.util import img_as_ubyte
|
||||
from skimage.morphology import skeletonize, skeletonize_3d
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_equal, assert_, parametrize, fetch
|
||||
|
||||
# basic behavior tests (mostly copied over from 2D skeletonize)
|
||||
|
||||
def test_skeletonize_wrong_dim():
|
||||
im = np.zeros(5, dtype=np.uint8)
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im, method='lee')
|
||||
|
||||
im = np.zeros((5, 5, 5, 5), dtype=np.uint8)
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im, method='lee')
|
||||
|
||||
|
||||
def test_skeletonize_1D_old_api():
|
||||
# a corner case of an image of a shape(1, N)
|
||||
im = np.ones((5, 1), dtype=np.uint8)
|
||||
res = skeletonize_3d(im)
|
||||
assert_equal(res, im)
|
||||
|
||||
|
||||
def test_skeletonize_1D():
|
||||
# a corner case of an image of a shape(1, N)
|
||||
im = np.ones((5, 1), dtype=np.uint8)
|
||||
res = skeletonize(im, method='lee')
|
||||
assert_equal(res, im)
|
||||
|
||||
|
||||
def test_skeletonize_no_foreground():
|
||||
im = np.zeros((5, 5), dtype=np.uint8)
|
||||
result = skeletonize(im, method='lee')
|
||||
assert_equal(result, im)
|
||||
|
||||
|
||||
def test_skeletonize_all_foreground():
|
||||
im = np.ones((3, 4), dtype=np.uint8)
|
||||
assert_equal(skeletonize(im, method='lee'),
|
||||
np.array([[0, 0, 0, 0],
|
||||
[1, 1, 1, 1],
|
||||
[0, 0, 0, 0]], dtype=np.uint8))
|
||||
|
||||
|
||||
def test_skeletonize_single_point():
|
||||
im = np.zeros((5, 5), dtype=np.uint8)
|
||||
im[3, 3] = 1
|
||||
result = skeletonize(im, method='lee')
|
||||
assert_equal(result, im)
|
||||
|
||||
|
||||
def test_skeletonize_already_thinned():
|
||||
im = np.zeros((5, 5), dtype=np.uint8)
|
||||
im[3, 1:-1] = 1
|
||||
im[2, -1] = 1
|
||||
im[4, 0] = 1
|
||||
result = skeletonize(im, method='lee')
|
||||
assert_equal(result, im)
|
||||
|
||||
|
||||
def test_dtype_conv():
|
||||
# check that the operation does the right thing with floats etc
|
||||
# also check non-contiguous input
|
||||
img = np.random.random((16, 16))[::2, ::2]
|
||||
img[img < 0.5] = 0
|
||||
|
||||
orig = img.copy()
|
||||
res = skeletonize(img, method='lee')
|
||||
img_max = img_as_ubyte(img).max()
|
||||
|
||||
assert_equal(res.dtype, np.uint8)
|
||||
assert_equal(img, orig) # operation does not clobber the original
|
||||
assert_equal(res.max(), img_max) # the intensity range is preserved
|
||||
|
||||
|
||||
@parametrize("img", [
|
||||
np.ones((8, 8), dtype=float), np.ones((4, 8, 8), dtype=float)
|
||||
])
|
||||
def test_input_with_warning(img):
|
||||
# check that the input is not clobbered
|
||||
# for 2D and 3D images of varying dtypes
|
||||
check_input(img)
|
||||
|
||||
|
||||
@parametrize("img", [
|
||||
np.ones((8, 8), dtype=np.uint8), np.ones((4, 8, 8), dtype=np.uint8),
|
||||
np.ones((8, 8), dtype=bool), np.ones((4, 8, 8), dtype=bool)
|
||||
])
|
||||
def test_input_without_warning(img):
|
||||
# check that the input is not clobbered
|
||||
# for 2D and 3D images of varying dtypes
|
||||
check_input(img)
|
||||
|
||||
|
||||
def check_input(img):
|
||||
orig = img.copy()
|
||||
skeletonize(img, method='lee')
|
||||
assert_equal(img, orig)
|
||||
|
||||
|
||||
def test_skeletonize_num_neighbors():
|
||||
# an empty image
|
||||
image = np.zeros((300, 300))
|
||||
|
||||
# foreground object 1
|
||||
image[10:-10, 10:100] = 1
|
||||
image[-100:-10, 10:-10] = 1
|
||||
image[10:-10, -100:-10] = 1
|
||||
|
||||
# foreground object 2
|
||||
rs, cs = draw.line(250, 150, 10, 280)
|
||||
for i in range(10):
|
||||
image[rs + i, cs] = 1
|
||||
rs, cs = draw.line(10, 150, 250, 280)
|
||||
for i in range(20):
|
||||
image[rs + i, cs] = 1
|
||||
|
||||
# foreground object 3
|
||||
ir, ic = np.indices(image.shape)
|
||||
circle1 = (ic - 135)**2 + (ir - 150)**2 < 30**2
|
||||
circle2 = (ic - 135)**2 + (ir - 150)**2 < 20**2
|
||||
image[circle1] = 1
|
||||
image[circle2] = 0
|
||||
result = skeletonize(image, method='lee')
|
||||
|
||||
# there should never be a 2x2 block of foreground pixels in a skeleton
|
||||
mask = np.array([[1, 1],
|
||||
[1, 1]], np.uint8)
|
||||
blocks = ndi.correlate(result, mask, mode='constant')
|
||||
assert_(not np.any(blocks == 4))
|
||||
|
||||
|
||||
def test_two_hole_image():
|
||||
# test a simple 2D image against FIJI
|
||||
img_o = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
img_f = np.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, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 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]],
|
||||
dtype=np.uint8)
|
||||
res = skeletonize(img_o, method='lee')
|
||||
assert_equal(res, img_f)
|
||||
|
||||
|
||||
def test_3d_vs_fiji():
|
||||
# generate an image with blobs and compate its skeleton to
|
||||
# the skeleton generated by FIJI (Plugins>Skeleton->Skeletonize)
|
||||
img = binary_blobs(32, 0.05, n_dim=3, seed=1234)
|
||||
img = img[:-2, ...]
|
||||
img = img.astype(np.uint8)*255
|
||||
|
||||
img_s = skeletonize(img)
|
||||
img_f = io.imread(fetch("data/_blobs_3d_fiji_skeleton.tif"))
|
||||
assert_equal(img_s, img_f)
|
||||
126
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_util.py
vendored
Normal file
126
.CondaPkg/env/lib/python3.11/site-packages/skimage/morphology/tests/test_util.py
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
"""Tests for `_util`."""
|
||||
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
from skimage.morphology import _util
|
||||
|
||||
|
||||
@pytest.mark.parametrize("image_shape", [
|
||||
(111,), (33, 44), (22, 55, 11), (6, 5, 4, 3)
|
||||
])
|
||||
@pytest.mark.parametrize("order", ["C", "F"])
|
||||
def test_offsets_to_raveled_neighbors_highest_connectivity(image_shape, order):
|
||||
"""
|
||||
Check scenarios where footprint is always of the highest connectivity
|
||||
and all dimensions are > 2.
|
||||
"""
|
||||
footprint = np.ones((3,) * len(image_shape), dtype=bool)
|
||||
center = (1,) * len(image_shape)
|
||||
offsets = _util._offsets_to_raveled_neighbors(
|
||||
image_shape, footprint, center, order
|
||||
)
|
||||
|
||||
# Assert only neighbors are present, center was removed
|
||||
assert len(offsets) == footprint.sum() - 1
|
||||
assert 0 not in offsets
|
||||
# Assert uniqueness
|
||||
assert len(set(offsets)) == offsets.size
|
||||
# offsets form pairs of with same value but different signs
|
||||
# if footprint is symmetric around center
|
||||
assert all(-x in offsets for x in offsets)
|
||||
|
||||
# Construct image whose values are the Manhattan distance to its center
|
||||
image_center = tuple(s // 2 for s in image_shape)
|
||||
coords = [
|
||||
np.abs(np.arange(s, dtype=np.intp) - c)
|
||||
for s, c in zip(image_shape, image_center)
|
||||
]
|
||||
grid = np.meshgrid(*coords, indexing="ij")
|
||||
image = np.sum(grid, axis=0)
|
||||
|
||||
image_raveled = image.ravel(order)
|
||||
image_center_raveled = np.ravel_multi_index(
|
||||
image_center, image_shape, order=order
|
||||
)
|
||||
|
||||
# Sample raveled image around its center
|
||||
samples = []
|
||||
for offset in offsets:
|
||||
index = image_center_raveled + offset
|
||||
samples.append(image_raveled[index])
|
||||
|
||||
# Assert that center with value 0 wasn't selected
|
||||
assert np.min(samples) == 1
|
||||
# Assert that only neighbors where selected
|
||||
# (highest value == connectivity)
|
||||
assert np.max(samples) == len(image_shape)
|
||||
# Assert that nearest neighbors are selected first
|
||||
assert list(sorted(samples)) == samples
|
||||
|
||||
|
||||
@pytest.mark.parametrize("image_shape", [
|
||||
(2,), (2, 2), (2, 1, 2), (2, 2, 1, 2), (0, 2, 1, 2)
|
||||
])
|
||||
@pytest.mark.parametrize("order", ["C", "F"])
|
||||
def test_offsets_to_raveled_neighbors_footprint_smaller_image(image_shape,
|
||||
order):
|
||||
"""
|
||||
Test if a dimension indicated by `image_shape` is smaller than in
|
||||
`footprint`.
|
||||
"""
|
||||
footprint = np.ones((3,) * len(image_shape), dtype=bool)
|
||||
center = (1,) * len(image_shape)
|
||||
offsets = _util._offsets_to_raveled_neighbors(
|
||||
image_shape, footprint, center, order
|
||||
)
|
||||
|
||||
# Assert only neighbors are present, center and duplicates (possible
|
||||
# for this scenario) where removed
|
||||
assert len(offsets) <= footprint.sum() - 1
|
||||
assert 0 not in offsets
|
||||
# Assert uniqueness
|
||||
assert len(set(offsets)) == offsets.size
|
||||
# offsets form pairs of with same value but different signs
|
||||
# if footprint is symmetric around center
|
||||
assert all(-x in offsets for x in offsets)
|
||||
|
||||
|
||||
def test_offsets_to_raveled_neighbors_explicit_0():
|
||||
"""Check reviewed example."""
|
||||
image_shape = (100, 200, 3)
|
||||
footprint = np.ones((3, 3, 3), dtype=bool)
|
||||
center = (1, 1, 1)
|
||||
offsets = _util._offsets_to_raveled_neighbors(
|
||||
image_shape, footprint, center
|
||||
)
|
||||
|
||||
desired = np.array([
|
||||
3, -600, 1, -1, 600, -3, 4, 2, 603, -2, -4,
|
||||
-597, 601, -599, -601, -603, 599, 597, 602, -604, 596, -596,
|
||||
-598, -602, 598, 604
|
||||
])
|
||||
assert_array_equal(offsets, desired)
|
||||
|
||||
|
||||
def test_offsets_to_raveled_neighbors_explicit_1():
|
||||
"""Check reviewed example where footprint is larger in last dimension."""
|
||||
image_shape = (10, 9, 8, 3)
|
||||
footprint = np.ones((3, 3, 3, 4), dtype=bool)
|
||||
center = (1, 1, 1, 1)
|
||||
offsets = _util._offsets_to_raveled_neighbors(
|
||||
image_shape, footprint, center
|
||||
)
|
||||
|
||||
desired = np.array([
|
||||
216, 24, -24, 3, -216, 1, -1, -3, 215, -27, -25, -23, -21, -2,
|
||||
-192, 192, 2, 4, 21, 23, 25, 27, -4, 217, 213, -219, 219, -217,
|
||||
-213, -215, 240, -240, 193, 239, -237, 241, -239, 218, -220, 22,
|
||||
-241, 243, 189, 26, -243, 191, 20, -218, 195, -193, 220, -191,
|
||||
-212, -189, 214, 28, -195, -214, -28, 212, -22, 237, -20, -26, 236,
|
||||
196, 190, 242, 238, 194, 188, -244, -188, -196, -194, -190, -238,
|
||||
-236, 244, -242, 5, 221, -211, -19, 29, -235, -187, 197, 245
|
||||
])
|
||||
assert_array_equal(offsets, desired)
|
||||
Reference in New Issue
Block a user