update
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user