update
This commit is contained in:
98
.CondaPkg/env/Lib/site-packages/skimage/morphology/__init__.py
vendored
Normal file
98
.CondaPkg/env/Lib/site-packages/skimage/morphology/__init__.py
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
"""Utilities that operate on shapes in images.
|
||||
|
||||
These operations are particularly suited for binary images,
|
||||
although some may be useful for images of other types as well.
|
||||
|
||||
Basic morphological operations include dilation and erosion.
|
||||
"""
|
||||
|
||||
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,
|
||||
mirror_footprint,
|
||||
octagon,
|
||||
octahedron,
|
||||
pad_footprint,
|
||||
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, remove_objects_by_distance
|
||||
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',
|
||||
'mirror_footprint',
|
||||
'octagon',
|
||||
'octahedron',
|
||||
'opening',
|
||||
'pad_footprint',
|
||||
'reconstruction',
|
||||
'rectangle',
|
||||
'remove_small_holes',
|
||||
'remove_small_objects',
|
||||
'remove_objects_by_distance',
|
||||
'skeletonize',
|
||||
'square',
|
||||
'star',
|
||||
'thin',
|
||||
'white_tophat',
|
||||
]
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/_flood_fill.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/_flood_fill.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/_skeletonize.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/_skeletonize.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/_util.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/_util.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/binary.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/binary.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/convex_hull.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/convex_hull.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/extrema.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/extrema.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/footprints.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/footprints.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/gray.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/gray.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/grayreconstruct.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/grayreconstruct.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/isotropic.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/isotropic.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/max_tree.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/max_tree.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/misc.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/__pycache__/misc.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_convex_hull.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_convex_hull.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_convex_hull.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_convex_hull.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_extrema_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_extrema_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_extrema_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_extrema_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
310
.CondaPkg/env/Lib/site-packages/skimage/morphology/_flood_fill.py
vendored
Normal file
310
.CondaPkg/env/Lib/site-packages/skimage/morphology/_flood_fill.py
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
"""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,
|
||||
)
|
||||
from .._shared.dtype import numeric_dtype_min_max
|
||||
|
||||
|
||||
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:
|
||||
tolerance = abs(tolerance)
|
||||
# Account for over- & underflow problems with seed_value ± tolerance
|
||||
# in a way that works with NumPy 1 & 2
|
||||
min_value, max_value = numeric_dtype_min_max(seed_value.dtype)
|
||||
low_tol = max(min_value.item(), seed_value.item() - tolerance)
|
||||
high_tol = min(max_value.item(), seed_value.item() + 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/site-packages/skimage/morphology/_flood_fill_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_flood_fill_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_flood_fill_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_flood_fill_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_grayreconstruct.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_grayreconstruct.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_grayreconstruct.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_grayreconstruct.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_max_tree.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_max_tree.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_max_tree.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_max_tree.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_misc_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_misc_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_misc_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_misc_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
663
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize.py
vendored
Normal file
663
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize.py
vendored
Normal file
@@ -0,0 +1,663 @@
|
||||
"""
|
||||
Algorithms for computing the skeleton of a binary image
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .._shared.utils import check_nD, deprecate_func
|
||||
from ..util import crop
|
||||
from ._skeletonize_3d_cy import _compute_thin_image
|
||||
from ._skeletonize_cy import _fast_skeletonize, _skeletonize_loop, _table_lookup_index
|
||||
|
||||
|
||||
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 or ``False``
|
||||
represent background, nonzero values or ``True`` 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 of bool
|
||||
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(bool)
|
||||
>>> ellipse.view(np.uint8)
|
||||
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.view(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)
|
||||
|
||||
"""
|
||||
image = image.astype(bool, order="C", copy=False)
|
||||
|
||||
if method not in {'zhang', 'lee', None}:
|
||||
raise ValueError(
|
||||
f'skeletonize method should be either "lee" or "zhang", ' f'got {method}.'
|
||||
)
|
||||
if image.ndim == 2 and (method is None or method == 'zhang'):
|
||||
skeleton = _skeletonize_2d(image)
|
||||
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
|
||||
An image containing the objects to be skeletonized. Zeros or ``False``
|
||||
represent background, nonzero values or ``True`` are foreground.
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray
|
||||
A matrix containing the thinned image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
medial_axis, skeletonize, skeletonize_3d, thin
|
||||
|
||||
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(bool)
|
||||
>>> ellipse.view(np.uint8)
|
||||
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.view(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
|
||||
|
||||
|
||||
# fmt: off
|
||||
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)
|
||||
# fmt: on
|
||||
|
||||
|
||||
def thin(image, max_num_iter=None):
|
||||
"""
|
||||
Perform morphological thinning of a binary image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : binary (M, N) ndarray
|
||||
The image to thin. If this input isn't already a binary image,
|
||||
it gets converted into one: In this case, zero values are considered
|
||||
background (False), nonzero values are considered foreground (True).
|
||||
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=bool)
|
||||
>>> square[1:-1, 2:-2] = 1
|
||||
>>> square[0, 1] = 1
|
||||
>>> square.view(np.uint8)
|
||||
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.view(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).view(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, *, rng=None):
|
||||
"""Compute the medial axis transform of a binary image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : binary ndarray, shape (M, N)
|
||||
The image of the shape to skeletonize. If this input isn't already a
|
||||
binary image, it gets converted into one: In this case, zero values are
|
||||
considered background (False), nonzero values are considered
|
||||
foreground (True).
|
||||
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.
|
||||
rng : {`numpy.random.Generator`, int}, optional
|
||||
Pseudo-random number generator.
|
||||
By default, a PCG64 generator is used (see :func:`numpy.random.default_rng`).
|
||||
If `rng` is an int, it is used to seed the generator.
|
||||
|
||||
The PRNG determines the order in which pixels are processed for
|
||||
tiebreaking.
|
||||
|
||||
.. 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, thin
|
||||
|
||||
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=bool)
|
||||
>>> square[1:-1, 2:-2] = 1
|
||||
>>> square.view(np.uint8)
|
||||
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).view(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(rng)
|
||||
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
|
||||
An image containing the objects to be skeletonized. Zeros or ``False``
|
||||
represent background, nonzero values or ``True`` are foreground.
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray of bool
|
||||
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_o = image.astype(bool, order="C", copy=False)
|
||||
|
||||
# make a 2D input image 3D and pad it w/ zeros to simplify dealing w/ boundaries
|
||||
# NB: careful here to not clobber the original *and* minimize copying
|
||||
if image.ndim == 2:
|
||||
image_o = image_o[np.newaxis, ...]
|
||||
image_o = np.pad(image_o, pad_width=1, mode='constant') # copies
|
||||
|
||||
# do the computation
|
||||
image_o = _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]
|
||||
|
||||
return image_o
|
||||
|
||||
|
||||
def skeletonize_3d(image):
|
||||
return _skeletonize_3d(image)
|
||||
|
||||
|
||||
skeletonize_3d.__doc__ = _skeletonize_3d.__doc__
|
||||
skeletonize_3d = deprecate_func(
|
||||
deprecated_version="0.23",
|
||||
removed_version="0.25",
|
||||
hint="Use `skimage.morphology.skeletonize` instead.",
|
||||
)(skeletonize_3d)
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize_3d_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize_3d_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize_3d_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize_3d_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize_cy.cp312-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize_cy.cp312-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize_cy.cp312-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/_skeletonize_cy.cp312-win_amd64.pyd
vendored
Normal file
Binary file not shown.
328
.CondaPkg/env/Lib/site-packages/skimage/morphology/_util.py
vendored
Normal file
328
.CondaPkg/env/Lib/site-packages/skimage/morphology/_util.py
vendored
Normal file
@@ -0,0 +1,328 @@
|
||||
"""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, kind="stable")]
|
||||
sorted_distances = np.sort(distances, kind="stable")
|
||||
|
||||
# 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)
|
||||
indices = np.sort(indices, kind="stable")
|
||||
sorted_raveled_offsets = sorted_raveled_offsets[indices]
|
||||
sorted_distances = sorted_distances[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([-6, -2, -1, 1, 2, 6, -8, -7, -5, -4, -3, 3, 4, 5, 7, 8, -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/site-packages/skimage/morphology/ball_decompositions.npy
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/ball_decompositions.npy
vendored
Normal file
Binary file not shown.
320
.CondaPkg/env/Lib/site-packages/skimage/morphology/binary.py
vendored
Normal file
320
.CondaPkg/env/Lib/site-packages/skimage/morphology/binary.py
vendored
Normal file
@@ -0,0 +1,320 @@
|
||||
"""
|
||||
Binary morphological operations
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .footprints import _footprint_is_sequence, pad_footprint
|
||||
from .misc import default_footprint
|
||||
|
||||
|
||||
def _iterate_binary_func(binary_func, image, footprint, out, border_value):
|
||||
"""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, border_value=border_value
|
||||
)
|
||||
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,
|
||||
border_value=border_value,
|
||||
)
|
||||
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, *, mode='ignore'):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'max', 'min', 'ignore'.
|
||||
If 'max' or 'ignore', pixels outside the image domain are assumed
|
||||
to be `True`, which causes them to not influence the result.
|
||||
Default is 'ignore'.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` was added in 0.23.
|
||||
|
||||
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
|
||||
:func:`skimage.morphology.disk` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
For even-sized footprints, :func:`skimage.morphology.erosion` and
|
||||
this function produce an output that differs: one is shifted by one pixel
|
||||
compared to the other.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.isotropic_erosion
|
||||
|
||||
"""
|
||||
if out is None:
|
||||
out = np.empty(image.shape, dtype=bool)
|
||||
|
||||
if mode not in {"max", "min", "ignore"}:
|
||||
raise ValueError(f"unsupported mode, got {mode!r}")
|
||||
border_value = False if mode == 'min' else True
|
||||
|
||||
footprint = pad_footprint(footprint, pad_end=True)
|
||||
if not _footprint_is_sequence(footprint):
|
||||
footprint = [(footprint, 1)]
|
||||
|
||||
out = _iterate_binary_func(
|
||||
binary_func=ndi.binary_erosion,
|
||||
image=image,
|
||||
footprint=footprint,
|
||||
out=out,
|
||||
border_value=border_value,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def binary_dilation(image, footprint=None, out=None, *, mode='ignore'):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'max', 'min', 'ignore'.
|
||||
If 'min' or 'ignore', pixels outside the image domain are assumed
|
||||
to be `False`, which causes them to not influence the result.
|
||||
Default is 'ignore'.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` was added in 0.23.
|
||||
|
||||
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
|
||||
:func:`skimage.morphology.disk` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
For non-symmetric footprints, :func:`skimage.morphology.binary_dilation`
|
||||
and :func:`skimage.morphology.dilation` produce an output that differs:
|
||||
`binary_dilation` mirrors the footprint, whereas `dilation` does not.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.isotropic_dilation
|
||||
|
||||
"""
|
||||
if out is None:
|
||||
out = np.empty(image.shape, dtype=bool)
|
||||
|
||||
if mode not in {"max", "min", "ignore"}:
|
||||
raise ValueError(f"unsupported mode, got {mode!r}")
|
||||
border_value = True if mode == 'max' else False
|
||||
|
||||
footprint = pad_footprint(footprint, pad_end=True)
|
||||
if not _footprint_is_sequence(footprint):
|
||||
footprint = [(footprint, 1)]
|
||||
|
||||
out = _iterate_binary_func(
|
||||
binary_func=ndi.binary_dilation,
|
||||
image=image,
|
||||
footprint=footprint,
|
||||
out=out,
|
||||
border_value=border_value,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def binary_opening(image, footprint=None, out=None, *, mode='ignore'):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'max', 'min', 'ignore'.
|
||||
If 'ignore', pixels outside the image domain are assumed to be `True`
|
||||
for the erosion and `False` for the dilation, which causes them to not
|
||||
influence the result. Default is 'ignore'.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` was added in 0.23.
|
||||
|
||||
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
|
||||
:func:`skimage.morphology.disk` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.isotropic_opening
|
||||
|
||||
"""
|
||||
tmp = binary_erosion(image, footprint, mode=mode)
|
||||
out = binary_dilation(tmp, footprint, out=out, mode=mode)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def binary_closing(image, footprint=None, out=None, *, mode='ignore'):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'max', 'min', 'ignore'.
|
||||
If 'ignore', pixels outside the image domain are assumed to be `True`
|
||||
for the erosion and `False` for the dilation, which causes them to not
|
||||
influence the result. Default is 'ignore'.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` was added in 0.23.
|
||||
|
||||
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
|
||||
:func:`skimage.morphology.disk` provide an option to automatically generate a
|
||||
footprint sequence of this type.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.isotropic_closing
|
||||
|
||||
"""
|
||||
tmp = binary_dilation(image, footprint, mode=mode)
|
||||
out = binary_erosion(tmp, footprint, out=out, mode=mode)
|
||||
return out
|
||||
222
.CondaPkg/env/Lib/site-packages/skimage/morphology/convex_hull.py
vendored
Normal file
222
.CondaPkg/env/Lib/site-packages/skimage/morphology/convex_hull.py
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
"""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/site-packages/skimage/morphology/disk_decompositions.npy
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/disk_decompositions.npy
vendored
Normal file
Binary file not shown.
549
.CondaPkg/env/Lib/site-packages/skimage/morphology/extrema.py
vendored
Normal file
549
.CondaPkg/env/Lib/site-packages/skimage/morphology/extrema.py
vendored
Normal file
@@ -0,0 +1,549 @@
|
||||
"""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.h_minima
|
||||
skimage.morphology.local_maxima
|
||||
skimage.morphology.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.h_maxima
|
||||
skimage.morphology.local_maxima
|
||||
skimage.morphology.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,
|
||||
)
|
||||
1030
.CondaPkg/env/Lib/site-packages/skimage/morphology/footprints.py
vendored
Normal file
1030
.CondaPkg/env/Lib/site-packages/skimage/morphology/footprints.py
vendored
Normal file
File diff suppressed because it is too large
Load Diff
703
.CondaPkg/env/Lib/site-packages/skimage/morphology/gray.py
vendored
Normal file
703
.CondaPkg/env/Lib/site-packages/skimage/morphology/gray.py
vendored
Normal file
@@ -0,0 +1,703 @@
|
||||
"""
|
||||
Grayscale morphological operations
|
||||
"""
|
||||
|
||||
import warnings
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .footprints import _footprint_is_sequence, mirror_footprint, pad_footprint
|
||||
from .misc import default_footprint
|
||||
from .._shared.utils import DEPRECATED
|
||||
|
||||
|
||||
__all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat', 'black_tophat']
|
||||
|
||||
|
||||
def _iterate_gray_func(gray_func, image, footprints, out, mode, cval):
|
||||
"""Helper to call `gray_func` for each footprint in a sequence.
|
||||
|
||||
`gray_func` is a morphology function that accepts `footprint`, `output`,
|
||||
`mode` and `cval` keyword arguments (e.g. `scipy.ndimage.grey_erosion`).
|
||||
"""
|
||||
fp, num_iter = footprints[0]
|
||||
gray_func(image, footprint=fp, output=out, mode=mode, cval=cval)
|
||||
for _ in range(1, num_iter):
|
||||
gray_func(out.copy(), footprint=fp, output=out, mode=mode, cval=cval)
|
||||
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, mode=mode, cval=cval)
|
||||
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 or None
|
||||
Whether to move `footprint` along each axis. If ``None``, the
|
||||
array is not modified along that dimension.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : 2D array, shape (M + int(shift_x), N + int(shift_y))
|
||||
The shifted footprint.
|
||||
"""
|
||||
footprint = np.asarray(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 _shift_footprints(footprint, shift_x, shift_y):
|
||||
"""Shifts the footprints, whether it's a single array or a sequence.
|
||||
|
||||
See `_shift_footprint`, which is called for each array in the sequence.
|
||||
"""
|
||||
if shift_x is DEPRECATED and shift_y is DEPRECATED:
|
||||
return footprint
|
||||
|
||||
warning_msg = (
|
||||
"The parameters `shift_x` and `shift_y` are deprecated since v0.23 and "
|
||||
"will be removed in v0.26. Use `pad_footprint` or modify the footprint"
|
||||
"manually instead."
|
||||
)
|
||||
warnings.warn(warning_msg, FutureWarning, stacklevel=4)
|
||||
|
||||
if _footprint_is_sequence(footprint):
|
||||
return tuple((_shift_footprint(fp, shift_x, shift_y), n) for fp, n in footprint)
|
||||
return _shift_footprint(footprint, shift_x, shift_y)
|
||||
|
||||
|
||||
def _min_max_to_constant_mode(dtype, mode, cval):
|
||||
"""Replace 'max' and 'min' with appropriate 'cval' and 'constant' mode."""
|
||||
if mode == "max":
|
||||
mode = "constant"
|
||||
if np.issubdtype(dtype, bool):
|
||||
cval = True
|
||||
elif np.issubdtype(dtype, np.integer):
|
||||
cval = np.iinfo(dtype).max
|
||||
else:
|
||||
cval = np.inf
|
||||
elif mode == "min":
|
||||
mode = "constant"
|
||||
if np.issubdtype(dtype, bool):
|
||||
cval = False
|
||||
elif np.issubdtype(dtype, np.integer):
|
||||
cval = np.iinfo(dtype).min
|
||||
else:
|
||||
cval = -np.inf
|
||||
return mode, cval
|
||||
|
||||
|
||||
_SUPPORTED_MODES = {
|
||||
"reflect",
|
||||
"constant",
|
||||
"nearest",
|
||||
"mirror",
|
||||
"wrap",
|
||||
"max",
|
||||
"min",
|
||||
"ignore",
|
||||
}
|
||||
|
||||
|
||||
@default_footprint
|
||||
def erosion(
|
||||
image,
|
||||
footprint=None,
|
||||
out=None,
|
||||
shift_x=DEPRECATED,
|
||||
shift_y=DEPRECATED,
|
||||
*,
|
||||
mode="reflect",
|
||||
cval=0.0,
|
||||
):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'reflect', 'constant', 'nearest', 'mirror', 'wrap',
|
||||
'max', 'min', or 'ignore'.
|
||||
If 'max' or 'ignore', pixels outside the image domain are assumed
|
||||
to be the maximum for the image's dtype, which causes them to not
|
||||
influence the result. Default is 'reflect'.
|
||||
cval : scalar, optional
|
||||
Value to fill past edges of input if `mode` is 'constant'. Default
|
||||
is 0.0.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` and `cval` were added in 0.23.
|
||||
|
||||
Returns
|
||||
-------
|
||||
eroded : array, same shape as `image`
|
||||
The result of the morphological erosion.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
shift_x, shift_y : DEPRECATED
|
||||
|
||||
.. deprecated:: 0.23
|
||||
|
||||
Notes
|
||||
-----
|
||||
For ``uint8`` (and ``uint16`` up to a certain bit-depth) data, the
|
||||
lower algorithm complexity makes the :func:`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
|
||||
:func:`skimage.morphology.disk` provide an option to automatically generate
|
||||
a footprint sequence of this type.
|
||||
|
||||
For even-sized footprints, :func:`skimage.morphology.binary_erosion` and
|
||||
this function produce an output that differs: one is shifted by one pixel
|
||||
compared to the other.
|
||||
|
||||
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 mode not in _SUPPORTED_MODES:
|
||||
raise ValueError(f"unsupported mode, got {mode!r}")
|
||||
if mode == "ignore":
|
||||
mode = "max"
|
||||
mode, cval = _min_max_to_constant_mode(image.dtype, mode, cval)
|
||||
|
||||
footprint = _shift_footprints(footprint, shift_x, shift_y)
|
||||
footprint = pad_footprint(footprint, pad_end=False)
|
||||
if not _footprint_is_sequence(footprint):
|
||||
footprint = [(footprint, 1)]
|
||||
|
||||
out = _iterate_gray_func(
|
||||
gray_func=ndi.grey_erosion,
|
||||
image=image,
|
||||
footprints=footprint,
|
||||
out=out,
|
||||
mode=mode,
|
||||
cval=cval,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def dilation(
|
||||
image,
|
||||
footprint=None,
|
||||
out=None,
|
||||
shift_x=DEPRECATED,
|
||||
shift_y=DEPRECATED,
|
||||
*,
|
||||
mode="reflect",
|
||||
cval=0.0,
|
||||
):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'reflect', 'constant', 'nearest', 'mirror', 'wrap',
|
||||
'max', 'min', or 'ignore'.
|
||||
If 'min' or 'ignore', pixels outside the image domain are assumed
|
||||
to be the maximum for the image's dtype, which causes them to not
|
||||
influence the result. Default is 'reflect'.
|
||||
cval : scalar, optional
|
||||
Value to fill past edges of input if `mode` is 'constant'. Default
|
||||
is 0.0.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` and `cval` were added in 0.23.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dilated : uint8 array, same shape and type as `image`
|
||||
The result of the morphological dilation.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
shift_x, shift_y : DEPRECATED
|
||||
|
||||
.. deprecated:: 0.23
|
||||
|
||||
Notes
|
||||
-----
|
||||
For ``uint8`` (and ``uint16`` up to a certain bit-depth) data, the lower
|
||||
algorithm complexity makes the :func:`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
|
||||
:func:`skimage.morphology.disk` provide an option to automatically generate
|
||||
a footprint sequence of this type.
|
||||
|
||||
For non-symmetric footprints, :func:`skimage.morphology.binary_dilation`
|
||||
and :func:`skimage.morphology.dilation` produce an output that differs:
|
||||
`binary_dilation` mirrors the footprint, whereas `dilation` does not.
|
||||
|
||||
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 mode not in _SUPPORTED_MODES:
|
||||
raise ValueError(f"unsupported mode, got {mode!r}")
|
||||
if mode == "ignore":
|
||||
mode = "min"
|
||||
mode, cval = _min_max_to_constant_mode(image.dtype, mode, cval)
|
||||
|
||||
footprint = _shift_footprints(footprint, shift_x, shift_y)
|
||||
footprint = pad_footprint(footprint, pad_end=False)
|
||||
# Note that `ndi.grey_dilation` mirrors the footprint and this
|
||||
# additional inversion should be removed in skimage2, see gh-6676.
|
||||
footprint = mirror_footprint(footprint)
|
||||
if not _footprint_is_sequence(footprint):
|
||||
footprint = [(footprint, 1)]
|
||||
|
||||
out = _iterate_gray_func(
|
||||
gray_func=ndi.grey_dilation,
|
||||
image=image,
|
||||
footprints=footprint,
|
||||
out=out,
|
||||
mode=mode,
|
||||
cval=cval,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def opening(image, footprint=None, out=None, *, mode="reflect", cval=0.0):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'reflect', 'constant', 'nearest', 'mirror', 'wrap',
|
||||
'max', 'min', or 'ignore'.
|
||||
If 'ignore', pixels outside the image domain are assumed
|
||||
to be the maximum for the image's dtype in the erosion, and minimum
|
||||
in the dilation, which causes them to not influence the result.
|
||||
Default is 'reflect'.
|
||||
cval : scalar, optional
|
||||
Value to fill past edges of input if `mode` is 'constant'. Default
|
||||
is 0.0.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` and `cval` were added in 0.23.
|
||||
|
||||
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
|
||||
:func:`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)
|
||||
|
||||
"""
|
||||
footprint = pad_footprint(footprint, pad_end=False)
|
||||
eroded = erosion(image, footprint, mode=mode, cval=cval)
|
||||
out = dilation(eroded, mirror_footprint(footprint), out=out, mode=mode, cval=cval)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def closing(image, footprint=None, out=None, *, mode="reflect", cval=0.0):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'reflect', 'constant', 'nearest', 'mirror', 'wrap',
|
||||
'max', 'min', or 'ignore'.
|
||||
If 'ignore', pixels outside the image domain are assumed
|
||||
to be the maximum for the image's dtype in the erosion, and minimum
|
||||
in the dilation, which causes them to not influence the result.
|
||||
Default is 'reflect'.
|
||||
cval : scalar, optional
|
||||
Value to fill past edges of input if `mode` is 'constant'. Default
|
||||
is 0.0.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` and `cval` were added in 0.23.
|
||||
|
||||
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
|
||||
:func:`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)
|
||||
|
||||
"""
|
||||
footprint = pad_footprint(footprint, pad_end=False)
|
||||
dilated = dilation(image, footprint, mode=mode, cval=cval)
|
||||
out = erosion(dilated, mirror_footprint(footprint), out=out, mode=mode, cval=cval)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def white_tophat(image, footprint=None, out=None, *, mode="reflect", cval=0.0):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'reflect', 'constant', 'nearest', 'mirror', 'wrap',
|
||||
'max', 'min', or 'ignore'. See :func:`skimage.morphology.opening`.
|
||||
Default is 'reflect'.
|
||||
cval : scalar, optional
|
||||
Value to fill past edges of input if `mode` is 'constant'. Default
|
||||
is 0.0.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` and `cval` were added in 0.23.
|
||||
|
||||
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
|
||||
:func:`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:
|
||||
# We need a temporary image
|
||||
opened = opening(image, footprint, mode=mode, cval=cval)
|
||||
if np.issubdtype(opened.dtype, bool):
|
||||
np.logical_xor(out, opened, out=out)
|
||||
else:
|
||||
out -= opened
|
||||
return out
|
||||
|
||||
# Else write intermediate result into output image
|
||||
out = opening(image, footprint, out=out, mode=mode, cval=cval)
|
||||
if np.issubdtype(out.dtype, bool):
|
||||
np.logical_xor(image, out, out=out)
|
||||
else:
|
||||
np.subtract(image, out, out=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_footprint
|
||||
def black_tophat(image, footprint=None, out=None, *, mode="reflect", cval=0.0):
|
||||
"""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.
|
||||
mode : str, optional
|
||||
The `mode` parameter determines how the array borders are handled.
|
||||
Valid modes are: 'reflect', 'constant', 'nearest', 'mirror', 'wrap',
|
||||
'max', 'min', or 'ignore'. See :func:`skimage.morphology.closing`.
|
||||
Default is 'reflect'.
|
||||
cval : scalar, optional
|
||||
Value to fill past edges of input if `mode` is 'constant'. Default
|
||||
is 0.0.
|
||||
|
||||
.. versionadded:: 0.23
|
||||
`mode` and `cval` were added in 0.23.
|
||||
|
||||
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
|
||||
:func:`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:
|
||||
# We need a temporary image
|
||||
closed = closing(image, footprint, mode=mode, cval=cval)
|
||||
if np.issubdtype(closed.dtype, bool):
|
||||
np.logical_xor(closed, out, out=out)
|
||||
else:
|
||||
np.subtract(closed, out, out=out)
|
||||
return out
|
||||
|
||||
out = closing(image, footprint, out=out, mode=mode, cval=cval)
|
||||
if np.issubdtype(out.dtype, np.bool_):
|
||||
np.logical_xor(out, image, out=out)
|
||||
else:
|
||||
out -= image
|
||||
return out
|
||||
217
.CondaPkg/env/Lib/site-packages/skimage/morphology/grayreconstruct.py
vendored
Normal file
217
.CondaPkg/env/Lib/site-packages/skimage/morphology/grayreconstruct.py
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
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]
|
||||
194
.CondaPkg/env/Lib/site-packages/skimage/morphology/isotropic.py
vendored
Normal file
194
.CondaPkg/env/Lib/site-packages/skimage/morphology/isotropic.py
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
"""
|
||||
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)
|
||||
700
.CondaPkg/env/Lib/site-packages/skimage/morphology/max_tree.py
vendored
Normal file
700
.CondaPkg/env/Lib/site-packages/skimage/morphology/max_tree.py
vendored
Normal file
@@ -0,0 +1,700 @@
|
||||
"""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(), kind="stable").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
|
||||
455
.CondaPkg/env/Lib/site-packages/skimage/morphology/misc.py
vendored
Normal file
455
.CondaPkg/env/Lib/site-packages/skimage/morphology/misc.py
vendored
Normal file
@@ -0,0 +1,455 @@
|
||||
"""Miscellaneous morphology functions."""
|
||||
|
||||
import numpy as np
|
||||
import functools
|
||||
from scipy import ndimage as ndi
|
||||
from scipy.spatial import cKDTree
|
||||
|
||||
from .._shared.utils import warn
|
||||
from .._shared._dependency_checks import is_wasm
|
||||
from ._misc_cy import _remove_objects_by_distance
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.remove_objects_by_distance
|
||||
|
||||
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
|
||||
|
||||
|
||||
def remove_objects_by_distance(
|
||||
label_image,
|
||||
min_distance,
|
||||
*,
|
||||
priority=None,
|
||||
p_norm=2,
|
||||
spacing=None,
|
||||
out=None,
|
||||
):
|
||||
"""Remove objects, in specified order, until remaining are a minimum distance apart.
|
||||
|
||||
Remove labeled objects from an image until the remaining ones are spaced
|
||||
more than a given distance from one another. By default, smaller objects
|
||||
are removed first.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label_image : ndarray of integers
|
||||
An n-dimensional array containing object labels, e.g. as returned by
|
||||
:func:`~.label`. A value of zero is considered background, all other
|
||||
object IDs must be positive integers.
|
||||
min_distance : int or float
|
||||
Remove objects whose distance to other objects is not greater than this
|
||||
positive value. Objects with a lower `priority` are removed first.
|
||||
priority : ndarray, optional
|
||||
Defines the priority with which objects are removed. Expects a
|
||||
1-dimensional array of length
|
||||
:func:`np.amax(label_image) + 1 <numpy.amax>` that contains the priority
|
||||
for each object's label at the respective index. Objects with a lower value
|
||||
are removed first until all remaining objects fulfill the distance
|
||||
requirement. If not given, priority is given to objects with a higher
|
||||
number of samples and their label value second.
|
||||
p_norm : int or float, optional
|
||||
The Minkowski distance of order p, used to calculate the distance
|
||||
between objects. The default ``2`` corresponds to the Euclidean
|
||||
distance, ``1`` to the "Manhattan" distance, and ``np.inf`` to the
|
||||
Chebyshev distance.
|
||||
spacing : sequence of float, optional
|
||||
The pixel spacing along each axis of `label_image`. If not specified,
|
||||
a grid spacing of unity (1) is implied.
|
||||
out : ndarray, optional
|
||||
Array of the same shape and dtype as `image`, into which the output is
|
||||
placed. By default, a new array is created.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray
|
||||
Array of the same shape as `label_image`, for which objects that violate
|
||||
the `min_distance` condition were removed.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.remove_small_objects
|
||||
Remove objects smaller than the specified size.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The basic steps of this algorithm work as follows:
|
||||
|
||||
1. Find the indices for of all given objects and separate them depending on
|
||||
if they point to an object's border or not.
|
||||
2. Sort indices by their label value, ensuring that indices which point to
|
||||
the same object are next to each other. This optimization allows finding
|
||||
all parts of an object, simply by stepping to the neighboring indices.
|
||||
3. Sort boundary indices by `priority`. Use a stable-sort to preserve the
|
||||
ordering from the previous sorting step. If `priority` is not given,
|
||||
use :func:`numpy.bincount` as a fallback.
|
||||
4. Construct a :class:`scipy.spatial.cKDTree` from the boundary indices.
|
||||
5. Iterate across boundary indices in priority-sorted order, and query the
|
||||
kd-tree for objects that are too close. Remove ones that are and don't
|
||||
take them into account when evaluating other objects later on.
|
||||
|
||||
The performance of this algorithm depends on the number of samples in
|
||||
`label_image` that belong to an object's border.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import skimage as ski
|
||||
>>> ski.morphology.remove_objects_by_distance(np.array([2, 0, 1, 1]), 2)
|
||||
array([0, 0, 1, 1])
|
||||
>>> ski.morphology.remove_objects_by_distance(
|
||||
... np.array([2, 0, 1, 1]), 2, priority=np.array([0, 1, 9])
|
||||
... )
|
||||
array([2, 0, 0, 0])
|
||||
>>> label_image = np.array(
|
||||
... [[8, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9],
|
||||
... [8, 8, 8, 0, 0, 0, 0, 0, 0, 9, 9],
|
||||
... [0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0],
|
||||
... [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
... [0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
... [2, 0, 0, 0, 0, 1, 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, 7, 7]]
|
||||
... )
|
||||
>>> ski.morphology.remove_objects_by_distance(
|
||||
... label_image, min_distance=3
|
||||
... )
|
||||
array([[8, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9],
|
||||
[8, 8, 8, 0, 0, 0, 0, 0, 0, 9, 9],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[2, 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, 7, 7]])
|
||||
"""
|
||||
if min_distance < 0:
|
||||
raise ValueError(f"min_distance must be >= 0, was {min_distance}")
|
||||
if not np.issubdtype(label_image.dtype, np.integer):
|
||||
raise ValueError(
|
||||
f"`label_image` must be of integer dtype, got {label_image.dtype}"
|
||||
)
|
||||
if out is None:
|
||||
out = label_image.copy(order="C")
|
||||
elif out is not label_image:
|
||||
out[:] = label_image
|
||||
# May create a copy if order is not C, account for that later
|
||||
out_raveled = out.ravel(order="C")
|
||||
|
||||
if spacing is not None:
|
||||
spacing = np.array(spacing)
|
||||
if spacing.shape != (out.ndim,) or spacing.min() <= 0:
|
||||
raise ValueError(
|
||||
"`spacing` must contain exactly one positive factor "
|
||||
"for each dimension of `label_image`"
|
||||
)
|
||||
|
||||
indices = np.flatnonzero(out_raveled)
|
||||
# Optimization: Split indices into those on the object boundaries and inner
|
||||
# ones. The KDTree is built only from the boundary indices, which reduces
|
||||
# the size of the critical loop significantly! Remaining indices are only
|
||||
# used to remove the inner parts of objects as well.
|
||||
if (spacing is None or np.all(spacing[0] == spacing)) and p_norm <= 2:
|
||||
# For unity spacing we can make the borders more sparse by using a
|
||||
# lower connectivity
|
||||
footprint = ndi.generate_binary_structure(out.ndim, 1)
|
||||
else:
|
||||
footprint = ndi.generate_binary_structure(out.ndim, out.ndim)
|
||||
border = (
|
||||
ndi.maximum_filter(out, footprint=footprint)
|
||||
!= ndi.minimum_filter(out, footprint=footprint)
|
||||
).ravel()[indices]
|
||||
border_indices = indices[border]
|
||||
inner_indices = indices[~border]
|
||||
|
||||
if border_indices.size == 0:
|
||||
# Image without any or only one object, return early
|
||||
return out
|
||||
|
||||
# Sort by label ID first, so that IDs of the same object are contiguous
|
||||
# in the sorted index. This allows fast discovery of the whole object by
|
||||
# simple iteration up or down the index!
|
||||
border_indices = border_indices[np.argsort(out_raveled[border_indices])]
|
||||
inner_indices = inner_indices[np.argsort(out_raveled[inner_indices])]
|
||||
|
||||
if priority is None:
|
||||
if is_wasm:
|
||||
# bincount expects intp (32-bit) on WASM, so down-cast to that
|
||||
priority = np.bincount(out_raveled.astype(np.intp, copy=False))
|
||||
else:
|
||||
priority = np.bincount(out_raveled)
|
||||
# `priority` can only be indexed by positive object IDs,
|
||||
# `border_indices` contains all unique sorted IDs so check the lowest / first
|
||||
smallest_id = out_raveled[border_indices[0]]
|
||||
if smallest_id < 0:
|
||||
raise ValueError(f"found object with negative ID {smallest_id!r}")
|
||||
|
||||
try:
|
||||
# Sort by priority second using a stable sort to preserve the contiguous
|
||||
# sorting of objects. Because each pixel in an object has the same
|
||||
# priority we don't need to worry about separating objects.
|
||||
border_indices = border_indices[
|
||||
np.argsort(priority[out_raveled[border_indices]], kind="stable")[::-1]
|
||||
]
|
||||
except IndexError as error:
|
||||
# Use np.amax only for the exception path to provide a nicer error message
|
||||
expected_shape = (np.amax(out_raveled) + 1,)
|
||||
if priority.shape != expected_shape:
|
||||
raise ValueError(
|
||||
"shape of `priority` must be (np.amax(label_image) + 1,), "
|
||||
f"expected {expected_shape}, got {priority.shape} instead"
|
||||
) from error
|
||||
else:
|
||||
raise
|
||||
|
||||
# Construct kd-tree from unraveled border indices (optionally scale by `spacing`)
|
||||
unraveled_indices = np.unravel_index(border_indices, out.shape)
|
||||
if spacing is not None:
|
||||
unraveled_indices = tuple(
|
||||
unraveled_indices[dim] * spacing[dim] for dim in range(out.ndim)
|
||||
)
|
||||
kdtree = cKDTree(data=np.asarray(unraveled_indices, dtype=np.float64).T)
|
||||
|
||||
_remove_objects_by_distance(
|
||||
out=out_raveled,
|
||||
border_indices=border_indices,
|
||||
inner_indices=inner_indices,
|
||||
kdtree=kdtree,
|
||||
min_distance=min_distance,
|
||||
p_norm=p_norm,
|
||||
shape=label_image.shape,
|
||||
)
|
||||
|
||||
if out_raveled.base is not out:
|
||||
# `out_raveled` is a copy, re-assign
|
||||
out[:] = out_raveled.reshape(out.shape)
|
||||
return out
|
||||
0
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__init__.py
vendored
Normal file
0
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__init__.py
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/__init__.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_binary.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_binary.cpython-312.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_extrema.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_extrema.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_flood_fill.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_flood_fill.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_footprints.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_footprints.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_gray.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_gray.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_isotropic.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_isotropic.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_max_tree.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_max_tree.cpython-312.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_misc.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_misc.cpython-312.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_util.cpython-312.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/__pycache__/test_util.cpython-312.pyc
vendored
Normal file
Binary file not shown.
363
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_binary.py
vendored
Normal file
363
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_binary.py
vendored
Normal file
@@ -0,0 +1,363 @@
|
||||
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.0
|
||||
|
||||
|
||||
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_closing_extensive():
|
||||
footprint = np.array([[0, 0, 1], [0, 1, 1], [1, 1, 1]])
|
||||
|
||||
result_default = binary.binary_closing(bw_img, footprint=footprint)
|
||||
assert np.all(result_default >= bw_img)
|
||||
|
||||
# mode="min" is expected to be not extensive
|
||||
result_min = binary.binary_closing(img, footprint=footprint, mode="min")
|
||||
assert not np.all(result_min >= bw_img)
|
||||
|
||||
|
||||
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 test_binary_opening_anti_extensive():
|
||||
footprint = np.array([[0, 0, 1], [0, 1, 1], [1, 1, 1]])
|
||||
|
||||
result_default = binary.binary_opening(bw_img, footprint=footprint)
|
||||
assert np.all(result_default <= bw_img)
|
||||
|
||||
# mode="max" is expected to be not extensive
|
||||
result_max = binary.binary_opening(bw_img, footprint=footprint, mode="max")
|
||||
assert not np.all(result_max <= bw_img)
|
||||
|
||||
|
||||
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, rng=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("func", binary_functions)
|
||||
@pytest.mark.parametrize("mode", ['max', 'min', 'ignore'])
|
||||
def test_supported_mode(func, mode):
|
||||
img = np.ones((10, 10), dtype=bool)
|
||||
func(img, mode=mode)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("func", binary_functions)
|
||||
@pytest.mark.parametrize("mode", ["reflect", 3, None])
|
||||
def test_unsupported_mode(func, mode):
|
||||
img = np.ones((10, 10))
|
||||
with pytest.raises(ValueError, match="unsupported mode"):
|
||||
func(img, mode=mode)
|
||||
|
||||
|
||||
@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)
|
||||
349
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_convex_hull.py
vendored
Normal file
349
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_convex_hull.py
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
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))
|
||||
690
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_extrema.py
vendored
Normal file
690
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_extrema.py
vendored
Normal file
@@ -0,0 +1,690 @@
|
||||
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
|
||||
360
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_flood_fill.py
vendored
Normal file
360
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_flood_fill.py
vendored
Normal file
@@ -0,0 +1,360 @@
|
||||
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, 0.1, 42], dtype=np.float16)
|
||||
with pytest.raises(TypeError, match="dtype of `image` is float16"):
|
||||
flood_fill(image, 0, 1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tolerance", [-150, 150, -379, 379])
|
||||
def test_overrange_tolerance_int(tolerance):
|
||||
image = np.arange(256, dtype=np.uint8).reshape((8, 8, 4))
|
||||
seed = (3, 4, 2)
|
||||
expected = np.zeros_like(image)
|
||||
output = flood_fill(image, seed, 0, tolerance=tolerance)
|
||||
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.0, high=1.0).astype(np.float32)
|
||||
image *= max_value
|
||||
|
||||
expected = np.ones_like(image)
|
||||
output = flood_fill(image, (0, 1), 1.0, tolerance=max_value.item() * 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.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
|
||||
[5.0, 1.0, 1.0, 5.0, 2.0, 2.0, 5.0],
|
||||
[5.0, 1.0, 1.0, 5.0, 2.0, 2.0, 5.0],
|
||||
[1.0, 5.0, 5.0, 5.0, 5.0, 5.0, 3.0],
|
||||
[5.0, 1.0, 1.0, 1.0, 3.0, 3.0, 4.0],
|
||||
],
|
||||
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.0, -1.0, -1.0, 100.0, 0.0, 0.0, 0.0],
|
||||
[-1.0, -1.0, -1.0, 100.0, 0.0, 0.0, 0.0],
|
||||
[-1.0, -1.0, -1.0, 100.0, 0.0, 0.0, 0.0],
|
||||
[-1.0, -1.0, -1.0, 100.0, 0.0, 0.0, 0.0],
|
||||
[-1.0, -1.0, -1.0, 100.0, 0.0, 0.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.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0],
|
||||
[5.0, 1.0, 1.0, 5.0, 2.0, 2.0, 5.0],
|
||||
[5.0, 1.0, 1.0, 5.0, 2.0, 2.0, 5.0],
|
||||
[1.0, 5.0, 5.0, 5.0, 5.0, 5.0, 3.0],
|
||||
[5.0, 1.0, 1.0, 1.0, 3.0, 3.0, 4.0],
|
||||
],
|
||||
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)
|
||||
269
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_footprints.py
vendored
Normal file
269
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_footprints.py
vendored
Normal file
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
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")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("as_sequence", [tuple, None])
|
||||
def test_mirror_footprint(as_sequence):
|
||||
footprint = np.array([[0, 0, 0], [0, 1, 1], [0, 1, 1]], np.uint8)
|
||||
expected_res = np.array([[1, 1, 0], [1, 1, 0], [0, 0, 0]], dtype=np.uint8)
|
||||
if as_sequence is not None:
|
||||
footprint = as_sequence([(footprint, 2), (footprint.T, 3)])
|
||||
expected_res = as_sequence([(expected_res, 2), (expected_res.T, 3)])
|
||||
|
||||
actual_res = footprints.mirror_footprint(footprint)
|
||||
assert type(expected_res) is type(actual_res)
|
||||
assert_equal(expected_res, actual_res)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("as_sequence", [tuple, None])
|
||||
@pytest.mark.parametrize("pad_end", [True, False])
|
||||
def test_pad_footprint(as_sequence, pad_end):
|
||||
footprint = np.array([[0, 0], [1, 0], [1, 1]], np.uint8)
|
||||
pad_width = [(0, 0), (0, 1)] if pad_end is True else [(0, 0), (1, 0)]
|
||||
expected_res = np.pad(footprint, pad_width)
|
||||
if as_sequence is not None:
|
||||
footprint = as_sequence([(footprint, 2), (footprint.T, 3)])
|
||||
expected_res = as_sequence([(expected_res, 2), (expected_res.T, 3)])
|
||||
|
||||
actual_res = footprints.pad_footprint(footprint, pad_end=pad_end)
|
||||
assert type(expected_res) is type(actual_res)
|
||||
assert_equal(expected_res, actual_res)
|
||||
514
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_gray.py
vendored
Normal file
514
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_gray.py
vendored
Normal file
@@ -0,0 +1,514 @@
|
||||
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, assert_stacklevel
|
||||
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])
|
||||
|
||||
|
||||
gray_morphology_funcs = (
|
||||
gray.erosion,
|
||||
gray.dilation,
|
||||
gray.opening,
|
||||
gray.closing,
|
||||
gray.white_tophat,
|
||||
gray.black_tophat,
|
||||
)
|
||||
|
||||
|
||||
class TestMorphology:
|
||||
# These expected outputs were generated with skimage v0.22.0 + PR #6695
|
||||
# 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):
|
||||
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 gray_morphology_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)
|
||||
|
||||
def test_gray_closing_extensive(self):
|
||||
img = data.coins()
|
||||
footprint = np.array([[0, 0, 1], [0, 1, 1], [1, 1, 1]])
|
||||
|
||||
# Default mode="reflect" is not extensive for backwards-compatibility
|
||||
result_default = gray.closing(img, footprint=footprint)
|
||||
assert not np.all(result_default >= img)
|
||||
|
||||
result = gray.closing(img, footprint=footprint, mode="ignore")
|
||||
assert np.all(result >= img)
|
||||
|
||||
def test_gray_opening_anti_extensive(self):
|
||||
img = data.coins()
|
||||
footprint = np.array([[0, 0, 1], [0, 1, 1], [1, 1, 1]])
|
||||
|
||||
# Default mode="reflect" is not extensive for backwards-compatibility
|
||||
result_default = gray.opening(img, footprint=footprint)
|
||||
assert not np.all(result_default <= img)
|
||||
|
||||
result_ignore = gray.opening(img, footprint=footprint, mode="ignore")
|
||||
assert np.all(result_ignore <= img)
|
||||
|
||||
@pytest.mark.parametrize("func", gray_morphology_funcs)
|
||||
@pytest.mark.parametrize("mode", gray._SUPPORTED_MODES)
|
||||
def test_supported_mode(self, func, mode):
|
||||
img = np.ones((10, 10))
|
||||
func(img, mode=mode)
|
||||
|
||||
@pytest.mark.parametrize("func", gray_morphology_funcs)
|
||||
@pytest.mark.parametrize("mode", ["", "symmetric", 3, None])
|
||||
def test_unsupported_mode(self, func, mode):
|
||||
img = np.ones((10, 10))
|
||||
with pytest.raises(ValueError, match="unsupported mode"):
|
||||
func(img, mode=mode)
|
||||
|
||||
|
||||
class TestEccentricStructuringElements:
|
||||
def setup_class(self):
|
||||
self.black_pixel = 255 * np.ones((6, 6), dtype=np.uint8)
|
||||
self.black_pixel[2, 2] = 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 == self.white_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", ['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.diamond(radius, decomposition=None)
|
||||
footprint = footprints.diamond(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)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("func", [gray.erosion, gray.dilation])
|
||||
@pytest.mark.parametrize("name", ["shift_x", "shift_y"])
|
||||
@pytest.mark.parametrize("value", [True, False, None])
|
||||
def test_deprecated_shift(func, name, value):
|
||||
img = np.ones(10)
|
||||
func(img) # Shouldn't warn
|
||||
|
||||
regex = "`shift_x` and `shift_y` are deprecated"
|
||||
with pytest.warns(FutureWarning, match=regex) as record:
|
||||
func(img, **{name: value})
|
||||
assert_stacklevel(record)
|
||||
83
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_isotropic.py
vendored
Normal file
83
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_isotropic.py
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
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.0
|
||||
|
||||
|
||||
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))
|
||||
469
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_max_tree.py
vendored
Normal file
469
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_max_tree.py
vendored
Normal file
@@ -0,0 +1,469 @@
|
||||
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
|
||||
522
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_misc.py
vendored
Normal file
522
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_misc.py
vendored
Normal file
@@ -0,0 +1,522 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
import scipy as sp
|
||||
|
||||
from skimage.morphology import (
|
||||
remove_small_objects,
|
||||
remove_small_holes,
|
||||
remove_objects_by_distance,
|
||||
local_maxima,
|
||||
label,
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
# Dtypes supported by the `label_image` parameter in `remove_objects_by_distance`
|
||||
supported_dtypes = [
|
||||
np.uint8,
|
||||
np.uint16,
|
||||
np.uint32,
|
||||
np.int8,
|
||||
np.int16,
|
||||
np.int32,
|
||||
np.int64,
|
||||
]
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class Test_remove_near_objects:
|
||||
|
||||
@pytest.mark.parametrize("min_distance", [2.1, 5, 30.99, 49])
|
||||
@pytest.mark.parametrize("dtype", supported_dtypes)
|
||||
def test_min_distance_1d(self, min_distance, dtype):
|
||||
# First 3 objects are only just to close, last one is just far enough
|
||||
d = int(np.floor(min_distance))
|
||||
labels = np.zeros(d * 3 + 2, dtype=dtype)
|
||||
labels[[0, d, 2 * d, 3 * d + 1]] = 1
|
||||
labels, _ = sp.ndimage.label(labels, output=dtype)
|
||||
desired = labels.copy()
|
||||
desired[d] = 0
|
||||
|
||||
result = remove_objects_by_distance(labels, min_distance)
|
||||
assert result.dtype == desired.dtype
|
||||
assert_array_equal(result, desired)
|
||||
|
||||
@pytest.mark.parametrize("dtype", supported_dtypes)
|
||||
@pytest.mark.parametrize("order", ["C", "F"])
|
||||
def test_handcrafted_2d(self, dtype, order):
|
||||
label = np.array(
|
||||
[
|
||||
[8, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9],
|
||||
[8, 8, 8, 0, 0, 0, 0, 0, 0, 9, 9],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 3, 0, 0, 0, 5, 0, 0, 0, 0],
|
||||
[2, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7],
|
||||
],
|
||||
dtype=dtype,
|
||||
order=order,
|
||||
)
|
||||
priority = np.arange(10)
|
||||
desired = np.array(
|
||||
[
|
||||
[8, 0, 0, 0, 0, 0, 0, 0, 0, 9, 9],
|
||||
[8, 8, 8, 0, 0, 0, 0, 0, 0, 9, 9],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[2, 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, 7, 7],
|
||||
],
|
||||
dtype=dtype,
|
||||
)
|
||||
result = remove_objects_by_distance(label, 3, priority=priority)
|
||||
assert result.flags["C_CONTIGUOUS"]
|
||||
assert_array_equal(result, desired)
|
||||
|
||||
@pytest.mark.parametrize("ndim", [1, 2, 3, 4, 5])
|
||||
def test_large_objects_nd(self, ndim):
|
||||
shape = (5,) * ndim
|
||||
a = np.ones(shape, dtype=np.uint8)
|
||||
a[-2, ...] = 0
|
||||
labels, _ = sp.ndimage.label(a)
|
||||
desired = labels.copy()
|
||||
desired[-2:, ...] = 0
|
||||
|
||||
result = remove_objects_by_distance(labels, 2)
|
||||
assert_array_equal(result, desired)
|
||||
|
||||
@pytest.mark.parametrize("distance", [5, 50, 100])
|
||||
@pytest.mark.parametrize("p_norm", [1, 2, np.inf])
|
||||
def test_random(self, distance, p_norm):
|
||||
rng = np.random.default_rng(1713648513)
|
||||
image = rng.random(size=(400, 400))
|
||||
maxima = local_maxima(image)
|
||||
objects = label(maxima)
|
||||
|
||||
spaced_objects = remove_objects_by_distance(objects, distance, p_norm=p_norm)
|
||||
kdtree = sp.spatial.cKDTree(
|
||||
np.array(np.nonzero(spaced_objects), dtype=np.float64).transpose(),
|
||||
)
|
||||
|
||||
# Compute distance between all objects that are equal or smaller `distance`
|
||||
distances = kdtree.sparse_distance_matrix(
|
||||
kdtree, max_distance=distance, p=p_norm
|
||||
)
|
||||
# There should be no objects left
|
||||
assert distances.count_nonzero() == 0
|
||||
|
||||
# But increasing by 1 should reveal a few objects
|
||||
distances = kdtree.sparse_distance_matrix(
|
||||
kdtree, max_distance=distance + 1, p=p_norm
|
||||
)
|
||||
assert distances.count_nonzero() > 0
|
||||
|
||||
@pytest.mark.parametrize("value", [0, 1])
|
||||
@pytest.mark.parametrize("dtype", supported_dtypes)
|
||||
def test_constant(self, value, dtype):
|
||||
labels = np.empty((10, 10), dtype=dtype)
|
||||
labels.fill(value)
|
||||
|
||||
result = remove_objects_by_distance(labels, 3)
|
||||
assert_array_equal(labels, result)
|
||||
|
||||
def test_empty(self):
|
||||
labels = np.empty((3, 3, 0), dtype=int)
|
||||
result = remove_objects_by_distance(labels, 3)
|
||||
assert_equal(labels, result)
|
||||
|
||||
def test_priority(self):
|
||||
labels = np.array([0, 1, 4, 1])
|
||||
|
||||
# Object with more samples takes precedence
|
||||
result = remove_objects_by_distance(labels, 3)
|
||||
desired = np.array([0, 1, 0, 1])
|
||||
assert_array_equal(result, desired)
|
||||
|
||||
# Assigning priority with equal values, sorts by higher label ID second
|
||||
priority = np.array([0, 1, 1, 1, 1])
|
||||
result = remove_objects_by_distance(labels, 3, priority=priority)
|
||||
desired = np.array([0, 0, 4, 0])
|
||||
assert_array_equal(result, desired)
|
||||
|
||||
# But given a different priority that order can be overruled
|
||||
priority = np.array([0, 1, 1, 1, -1])
|
||||
result = remove_objects_by_distance(labels, 3, priority=priority)
|
||||
desired = np.array([0, 1, 0, 1])
|
||||
assert_array_equal(result, desired)
|
||||
|
||||
@pytest.mark.parametrize("order", ["C", "F"])
|
||||
def test_out(self, order):
|
||||
labels_original = np.array([[1, 0, 2], [1, 0, 2]], order=order)
|
||||
desired = np.array([[0, 0, 2], [0, 0, 2]], order=order)
|
||||
|
||||
# By default, input image is not modified
|
||||
labels = labels_original.copy(order=order)
|
||||
remove_objects_by_distance(labels, 2)
|
||||
assert_array_equal(labels, labels_original)
|
||||
|
||||
# But modified if passed to `out`
|
||||
remove_objects_by_distance(labels, 2, out=labels)
|
||||
assert labels.flags[f"{order}_CONTIGUOUS"]
|
||||
assert_array_equal(labels, desired)
|
||||
|
||||
@pytest.mark.parametrize("min_distance", [-10, -0.1])
|
||||
def test_negative_min_distance(self, min_distance):
|
||||
labels = np.array([1, 0, 2])
|
||||
with pytest.raises(ValueError, match="must be >= 0"):
|
||||
remove_objects_by_distance(labels, min_distance)
|
||||
|
||||
def test_p_norm(self):
|
||||
labels = np.array([[2, 0], [0, 1]])
|
||||
removed = np.array([[2, 0], [0, 0]])
|
||||
|
||||
# p_norm=2, default (Euclidean distance)
|
||||
result = remove_objects_by_distance(labels, 1.4)
|
||||
assert_array_equal(result, labels)
|
||||
result = remove_objects_by_distance(labels, np.sqrt(2))
|
||||
assert_array_equal(result, removed)
|
||||
|
||||
# p_norm=1 (Manhatten distance)
|
||||
result = remove_objects_by_distance(
|
||||
labels,
|
||||
min_distance=1.9,
|
||||
p_norm=1,
|
||||
)
|
||||
assert_array_equal(result, labels)
|
||||
result = remove_objects_by_distance(labels, 2, p_norm=1)
|
||||
assert_array_equal(result, removed)
|
||||
|
||||
# p_norm=np.inf (Chebyshev distance)
|
||||
result = remove_objects_by_distance(labels, 0.9, p_norm=np.inf)
|
||||
assert_array_equal(result, labels)
|
||||
result = remove_objects_by_distance(labels, 1, p_norm=np.inf)
|
||||
assert_array_equal(result, removed)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"shape",
|
||||
[
|
||||
(0,),
|
||||
],
|
||||
)
|
||||
def test_priority_shape(self, shape):
|
||||
remove_objects_by_distance(np.array([0, 0, 0]), 3, priority=np.ones((0,)))
|
||||
remove_objects_by_distance(np.array([0, 0, 0]), 3, priority=np.ones((1,)))
|
||||
|
||||
error_msg = r"shape of `priority` must be \(np\.amax\(label_image\) \+ 1,\)"
|
||||
with pytest.raises(ValueError, match=error_msg):
|
||||
remove_objects_by_distance(np.array([1, 0, 0]), 3, priority=np.ones((0,)))
|
||||
with pytest.raises(ValueError, match=error_msg):
|
||||
remove_objects_by_distance(np.array([1, 0, 0]), 3, priority=np.ones((1,)))
|
||||
with pytest.raises(ValueError, match=error_msg):
|
||||
remove_objects_by_distance(np.array([1, 0, 0]), 3, priority=np.ones((1,)))
|
||||
|
||||
def test_negative_label_ids(self):
|
||||
labels = np.array(
|
||||
[
|
||||
[1, 1, -1, 2, 2, 2],
|
||||
[1, 1, 3, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[3, 3, 3, 3, 3, 3],
|
||||
]
|
||||
)
|
||||
with pytest.raises(ValueError, match=".*object with negative ID"):
|
||||
remove_objects_by_distance(labels, 1, priority=np.ones(4))
|
||||
|
||||
def test_objects_with_inside(self):
|
||||
labels = np.array(
|
||||
[
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[3, 3, 3, 3, 3, 3],
|
||||
]
|
||||
)
|
||||
desired = np.array(
|
||||
[
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0],
|
||||
[3, 3, 3, 3, 3, 3],
|
||||
]
|
||||
)
|
||||
result = remove_objects_by_distance(labels, 1, priority=np.arange(4))
|
||||
assert_array_equal(result, desired)
|
||||
|
||||
def test_spacing(self):
|
||||
labels = np.array(
|
||||
[[1, 0, 0, 2], [0, 0, 0, 0], [0, 0, 0, 0], [3, 0, 0, 4]], dtype=int
|
||||
)
|
||||
|
||||
# Stretch second dimension
|
||||
result = remove_objects_by_distance(labels, 3, spacing=(1, 3))
|
||||
expected = np.array(
|
||||
[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [3, 0, 0, 4]], dtype=int
|
||||
)
|
||||
np.testing.assert_array_equal(result, expected)
|
||||
|
||||
# Compress second dimension
|
||||
result = remove_objects_by_distance(labels, 1, spacing=(1, 1 / 3))
|
||||
expected = np.array(
|
||||
[[0, 0, 0, 2], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 4]], dtype=int
|
||||
)
|
||||
np.testing.assert_array_equal(result, expected)
|
||||
|
||||
@pytest.mark.parametrize("spacing", [(-1, -1), (1,), (1, 1, 1), [[1, 1]], 1])
|
||||
def test_spacing_raises(self, spacing):
|
||||
labels = np.array(
|
||||
[[1, 0, 0, 2], [0, 0, 0, 0], [0, 0, 0, 0], [3, 0, 0, 4]], dtype=int
|
||||
)
|
||||
regex = ".*must contain exactly one positive factor for each dimension"
|
||||
with pytest.raises(ValueError, match=regex):
|
||||
remove_objects_by_distance(labels, 3, spacing=spacing)
|
||||
190
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_reconstruction.py
vendored
Normal file
190
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_reconstruction.py
vendored
Normal file
@@ -0,0 +1,190 @@
|
||||
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,
|
||||
)
|
||||
275
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_skeletonize.py
vendored
Normal file
275
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_skeletonize.py
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
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 G123_LUT, G123P_LUT, _generate_thin_luts
|
||||
|
||||
|
||||
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, dtype=bool)
|
||||
with pytest.raises(ValueError):
|
||||
skeletonize(im)
|
||||
|
||||
def test_skeletonize_wrong_dim2(self):
|
||||
im = np.zeros((5, 5, 5), dtype=bool)
|
||||
with pytest.raises(ValueError):
|
||||
skeletonize(im, method='zhang')
|
||||
|
||||
def test_skeletonize_wrong_method(self):
|
||||
im = np.ones((5, 5), dtype=bool)
|
||||
with pytest.raises(ValueError):
|
||||
skeletonize(im, method='foo')
|
||||
|
||||
def test_skeletonize_all_foreground(self):
|
||||
im = np.ones((3, 4), dtype=bool)
|
||||
skeletonize(im)
|
||||
|
||||
def test_skeletonize_single_point(self):
|
||||
im = np.zeros((5, 5), dtype=bool)
|
||||
im[3, 3] = 1
|
||||
result = skeletonize(im)
|
||||
assert_array_equal(result, im)
|
||||
|
||||
def test_skeletonize_already_thinned(self):
|
||||
im = np.zeros((5, 5), dtype=bool)
|
||||
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)
|
||||
|
||||
@pytest.mark.parametrize("dtype", [bool, float, int])
|
||||
def test_skeletonize_num_neighbors(self, dtype):
|
||||
# an empty image
|
||||
image = np.zeros((300, 300), dtype=dtype)
|
||||
|
||||
# foreground object 1
|
||||
image[10:-10, 10:100] = 2
|
||||
image[-100:-10, 10:-10] = 2
|
||||
image[10:-10, -100:-10] = 2
|
||||
|
||||
# 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] = 3
|
||||
|
||||
# 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), dtype=bool)
|
||||
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=bool,
|
||||
)
|
||||
assert np.all(result == expected)
|
||||
|
||||
@pytest.mark.parametrize("ndim", [2, 3])
|
||||
def test_skeletonize_copies_input(self, ndim):
|
||||
"""Skeletonize mustn't modify the original input image."""
|
||||
image = np.ones((3,) * ndim, dtype=bool)
|
||||
image = np.pad(image, 1)
|
||||
original = image.copy()
|
||||
skeletonize(image)
|
||||
np.testing.assert_array_equal(image, original)
|
||||
|
||||
|
||||
class TestThin:
|
||||
@property
|
||||
def input_image(self):
|
||||
"""image to test thinning with"""
|
||||
ii = np.array(
|
||||
[
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 2, 3, 4, 5, 0],
|
||||
[0, 1, 0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 6, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
],
|
||||
dtype=float,
|
||||
)
|
||||
return ii
|
||||
|
||||
def test_zeros(self):
|
||||
image = np.zeros((10, 10), dtype=bool)
|
||||
assert np.all(thin(image) == False)
|
||||
|
||||
@pytest.mark.parametrize("dtype", [bool, float, int])
|
||||
def test_iter_1(self, dtype):
|
||||
image = self.input_image.astype(dtype)
|
||||
result = thin(image, 1).astype(bool)
|
||||
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=bool,
|
||||
)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
@pytest.mark.parametrize("dtype", [bool, float, int])
|
||||
def test_noiter(self, dtype):
|
||||
image = self.input_image.astype(dtype)
|
||||
result = thin(image).astype(bool)
|
||||
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=bool,
|
||||
)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_baddim(self):
|
||||
for ii in [np.zeros(3, dtype=bool), np.zeros((3, 3, 3), dtype=bool)]:
|
||||
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)
|
||||
|
||||
@pytest.mark.parametrize("dtype", [bool, float, int])
|
||||
def test_vertical_line(self, dtype):
|
||||
'''Test a thick vertical line, issue #3861'''
|
||||
img = np.zeros((9, 9), dtype=dtype)
|
||||
img[:, 2] = 1
|
||||
img[:, 3] = 2
|
||||
img[:, 4] = 3
|
||||
|
||||
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)
|
||||
195
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_skeletonize_3d.py
vendored
Normal file
195
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_skeletonize_3d.py
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
import scipy.ndimage as ndi
|
||||
|
||||
from skimage import io, draw
|
||||
from skimage.data import binary_blobs
|
||||
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=bool)
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im, method='lee')
|
||||
|
||||
im = np.zeros((5, 5, 5, 5), dtype=bool)
|
||||
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=bool)
|
||||
res = skeletonize(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=bool)
|
||||
res = skeletonize(im, method='lee')
|
||||
assert_equal(res, im)
|
||||
|
||||
|
||||
def test_skeletonize_no_foreground():
|
||||
im = np.zeros((5, 5), dtype=bool)
|
||||
result = skeletonize(im, method='lee')
|
||||
assert_equal(result, im)
|
||||
|
||||
|
||||
def test_skeletonize_all_foreground():
|
||||
im = np.ones((3, 4), dtype=bool)
|
||||
assert_equal(
|
||||
skeletonize(im, method='lee'),
|
||||
np.array([[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0]], dtype=bool),
|
||||
)
|
||||
|
||||
|
||||
def test_skeletonize_single_point():
|
||||
im = np.zeros((5, 5), dtype=bool)
|
||||
im[3, 3] = 1
|
||||
result = skeletonize(im, method='lee')
|
||||
assert_equal(result, im)
|
||||
|
||||
|
||||
def test_skeletonize_already_thinned():
|
||||
im = np.zeros((5, 5), dtype=bool)
|
||||
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')
|
||||
|
||||
assert res.dtype == bool
|
||||
assert_equal(img, orig) # operation does not clobber the original
|
||||
|
||||
|
||||
@parametrize("img", [np.ones((8, 8), dtype=bool), np.ones((4, 8, 8), dtype=bool)])
|
||||
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=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)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("dtype", [bool, float, int])
|
||||
def test_skeletonize_num_neighbors(dtype):
|
||||
# an empty image
|
||||
image = np.zeros((300, 300), dtype=dtype)
|
||||
|
||||
# foreground object 1
|
||||
image[10:-10, 10:100] = 1
|
||||
image[-100:-10, 10:-10] = 2
|
||||
image[10:-10, -100:-10] = 3
|
||||
|
||||
# foreground object 2
|
||||
rs, cs = draw.line(250, 150, 10, 280)
|
||||
for i in range(10):
|
||||
image[rs + i, cs] = 4
|
||||
rs, cs = draw.line(10, 150, 250, 280)
|
||||
for i in range(20):
|
||||
image[rs + i, cs] = 5
|
||||
|
||||
# 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').astype(np.uint8)
|
||||
|
||||
# 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=bool,
|
||||
)
|
||||
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=bool,
|
||||
)
|
||||
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, rng=1234)
|
||||
img = img[:-2, ...]
|
||||
|
||||
img_s = skeletonize(img)
|
||||
img_f = io.imread(fetch("data/_blobs_3d_fiji_skeleton.tif")).astype(bool)
|
||||
assert_equal(img_s, img_f)
|
||||
|
||||
|
||||
def test_deprecated_skeletonize_3d():
|
||||
image = np.ones((10, 10), dtype=bool)
|
||||
regex = "Use `skimage\\.morphology\\.skeletonize"
|
||||
with pytest.warns(FutureWarning, match=regex) as record:
|
||||
skeletonize_3d(image)
|
||||
assert len(record) == 1
|
||||
assert record[0].filename == __file__, "warning points at wrong file"
|
||||
221
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_util.py
vendored
Normal file
221
.CondaPkg/env/Lib/site-packages/skimage/morphology/tests/test_util.py
vendored
Normal file
@@ -0,0 +1,221 @@
|
||||
"""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(
|
||||
[
|
||||
-600,
|
||||
-3,
|
||||
-1,
|
||||
1,
|
||||
3,
|
||||
600,
|
||||
-603,
|
||||
-601,
|
||||
-599,
|
||||
-597,
|
||||
-4,
|
||||
-2,
|
||||
2,
|
||||
4,
|
||||
597,
|
||||
599,
|
||||
601,
|
||||
603,
|
||||
-604,
|
||||
-602,
|
||||
-598,
|
||||
-596,
|
||||
596,
|
||||
598,
|
||||
602,
|
||||
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,
|
||||
-3,
|
||||
-1,
|
||||
1,
|
||||
3,
|
||||
24,
|
||||
216,
|
||||
-240,
|
||||
-219,
|
||||
-217,
|
||||
-215,
|
||||
-213,
|
||||
-192,
|
||||
-27,
|
||||
-25,
|
||||
-23,
|
||||
-21,
|
||||
-4,
|
||||
-2,
|
||||
2,
|
||||
4,
|
||||
21,
|
||||
23,
|
||||
25,
|
||||
27,
|
||||
192,
|
||||
213,
|
||||
215,
|
||||
217,
|
||||
219,
|
||||
240,
|
||||
-243,
|
||||
-241,
|
||||
-239,
|
||||
-237,
|
||||
-220,
|
||||
-218,
|
||||
-214,
|
||||
-212,
|
||||
-195,
|
||||
-193,
|
||||
-191,
|
||||
-189,
|
||||
-28,
|
||||
-26,
|
||||
-22,
|
||||
-20,
|
||||
20,
|
||||
22,
|
||||
26,
|
||||
28,
|
||||
189,
|
||||
191,
|
||||
193,
|
||||
195,
|
||||
212,
|
||||
214,
|
||||
218,
|
||||
220,
|
||||
237,
|
||||
239,
|
||||
241,
|
||||
243,
|
||||
-244,
|
||||
-242,
|
||||
-238,
|
||||
-236,
|
||||
-196,
|
||||
-194,
|
||||
-190,
|
||||
-188,
|
||||
188,
|
||||
190,
|
||||
194,
|
||||
196,
|
||||
236,
|
||||
238,
|
||||
242,
|
||||
244,
|
||||
5,
|
||||
-211,
|
||||
-19,
|
||||
29,
|
||||
221,
|
||||
-235,
|
||||
-187,
|
||||
197,
|
||||
245,
|
||||
]
|
||||
)
|
||||
assert_array_equal(offsets, desired)
|
||||
Reference in New Issue
Block a user