Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
60
venv/Lib/site-packages/skimage/morphology/__init__.py
Normal file
60
venv/Lib/site-packages/skimage/morphology/__init__.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
from .binary import (binary_erosion, binary_dilation, binary_opening,
|
||||
binary_closing)
|
||||
from .grey import (erosion, dilation, opening, closing, white_tophat,
|
||||
black_tophat)
|
||||
from .selem import (square, rectangle, diamond, disk, cube, octahedron, ball,
|
||||
octagon, star)
|
||||
from ..measure._label import label
|
||||
from ._skeletonize import skeletonize, medial_axis, thin, skeletonize_3d
|
||||
from .convex_hull import convex_hull_image, convex_hull_object
|
||||
from .greyreconstruct import reconstruction
|
||||
from .misc import remove_small_objects, remove_small_holes
|
||||
from .extrema import h_minima, h_maxima, local_maxima, local_minima
|
||||
from ._flood_fill import flood, flood_fill
|
||||
from .max_tree import (max_tree, area_opening, area_closing,
|
||||
diameter_opening, diameter_closing,
|
||||
max_tree_local_maxima)
|
||||
from ._deprecated import watershed
|
||||
|
||||
__all__ = ['binary_erosion',
|
||||
'binary_dilation',
|
||||
'binary_opening',
|
||||
'binary_closing',
|
||||
'erosion',
|
||||
'dilation',
|
||||
'opening',
|
||||
'closing',
|
||||
'white_tophat',
|
||||
'black_tophat',
|
||||
'square',
|
||||
'rectangle',
|
||||
'diamond',
|
||||
'disk',
|
||||
'cube',
|
||||
'octahedron',
|
||||
'ball',
|
||||
'octagon',
|
||||
'star',
|
||||
'label',
|
||||
'watershed',
|
||||
'skeletonize',
|
||||
'skeletonize_3d',
|
||||
'thin',
|
||||
'medial_axis',
|
||||
'convex_hull_image',
|
||||
'convex_hull_object',
|
||||
'reconstruction',
|
||||
'remove_small_objects',
|
||||
'remove_small_holes',
|
||||
'h_minima',
|
||||
'h_maxima',
|
||||
'local_maxima',
|
||||
'local_minima',
|
||||
'flood',
|
||||
'flood_fill',
|
||||
'max_tree',
|
||||
'area_opening',
|
||||
'area_closing',
|
||||
'diameter_opening',
|
||||
'diameter_closing',
|
||||
'max_tree_local_maxima']
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
111
venv/Lib/site-packages/skimage/morphology/_deprecated.py
Normal file
111
venv/Lib/site-packages/skimage/morphology/_deprecated.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
from .._shared.utils import deprecated
|
||||
|
||||
|
||||
@deprecated('skimage.segmentation.watershed', removed_version='0.19')
|
||||
def watershed(image, markers=None, connectivity=1, offset=None, mask=None,
|
||||
compactness=0, watershed_line=False):
|
||||
"""Find watershed basins in `image` flooded from given `markers`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray (2-D, 3-D, ...) of integers
|
||||
Data array where the lowest value points are labeled first.
|
||||
markers : int, or ndarray of int, same shape as `image`, optional
|
||||
The desired number of markers, or an array marking the basins with the
|
||||
values to be assigned in the label matrix. Zero means not a marker. If
|
||||
``None`` (no markers given), the local minima of the image are used as
|
||||
markers.
|
||||
connectivity : ndarray, optional
|
||||
An array with the same number of dimensions as `image` whose
|
||||
non-zero elements indicate neighbors for connection.
|
||||
Following the scipy convention, default is a one-connected array of
|
||||
the dimension of the image.
|
||||
offset : array_like of shape image.ndim, optional
|
||||
offset of the connectivity (one offset per dimension)
|
||||
mask : ndarray of bools or 0s and 1s, optional
|
||||
Array of same shape as `image`. Only points at which mask == True
|
||||
will be labeled.
|
||||
compactness : float, optional
|
||||
Use compact watershed [3]_ with given compactness parameter.
|
||||
Higher values result in more regularly-shaped watershed basins.
|
||||
watershed_line : bool, optional
|
||||
If watershed_line is True, a one-pixel wide line separates the regions
|
||||
obtained by the watershed algorithm. The line has the label 0.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out: ndarray
|
||||
A labeled matrix of the same type and shape as markers
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.segmentation.random_walker: random walker segmentation
|
||||
A segmentation algorithm based on anisotropic diffusion, usually
|
||||
slower than the watershed but with good results on noisy data and
|
||||
boundaries with holes.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function implements a watershed algorithm [1]_ [2]_ that apportions
|
||||
pixels into marked basins. The algorithm uses a priority queue to hold
|
||||
the pixels with the metric for the priority queue being pixel value, then
|
||||
the time of entry into the queue - this settles ties in favor of the
|
||||
closest marker.
|
||||
Some ideas taken from
|
||||
Soille, "Automated Basin Delineation from Digital Elevation Models Using
|
||||
Mathematical Morphology", Signal Processing 20 (1990) 171-182
|
||||
The most important insight in the paper is that entry time onto the queue
|
||||
solves two problems: a pixel should be assigned to the neighbor with the
|
||||
largest gradient or, if there is no gradient, pixels on a plateau should
|
||||
be split between markers on opposite sides.
|
||||
This implementation converts all arguments to specific, lowest common
|
||||
denominator types, then passes these to a C algorithm.
|
||||
Markers can be determined manually, or automatically using for example
|
||||
the local minima of the gradient of the image, or the local maxima of the
|
||||
distance function to the background for separating overlapping objects
|
||||
(see example).
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Watershed_%28image_processing%29
|
||||
.. [2] http://cmm.ensmp.fr/~beucher/wtshed.html
|
||||
.. [3] Peer Neubert & Peter Protzel (2014). Compact Watershed and
|
||||
Preemptive SLIC: On Improving Trade-offs of Superpixel Segmentation
|
||||
Algorithms. ICPR 2014, pp 996-1001. :DOI:`10.1109/ICPR.2014.181`
|
||||
https://www.tu-chemnitz.de/etit/proaut/publications/cws_pSLIC_ICPR.pdf
|
||||
|
||||
Examples
|
||||
--------
|
||||
The watershed algorithm is useful to separate overlapping objects.
|
||||
|
||||
We first generate an initial image with two overlapping circles:
|
||||
|
||||
>>> import numpy as np
|
||||
>>> x, y = np.indices((80, 80))
|
||||
>>> x1, y1, x2, y2 = 28, 28, 44, 52
|
||||
>>> r1, r2 = 16, 20
|
||||
>>> mask_circle1 = (x - x1)**2 + (y - y1)**2 < r1**2
|
||||
>>> mask_circle2 = (x - x2)**2 + (y - y2)**2 < r2**2
|
||||
>>> image = np.logical_or(mask_circle1, mask_circle2)
|
||||
|
||||
Next, we want to separate the two circles. We generate markers at the
|
||||
maxima of the distance to the background:
|
||||
|
||||
>>> from scipy import ndimage as ndi
|
||||
>>> distance = ndi.distance_transform_edt(image)
|
||||
>>> from skimage.feature import peak_local_max
|
||||
>>> local_maxi = peak_local_max(distance, labels=image,
|
||||
... footprint=np.ones((3, 3)),
|
||||
... indices=False)
|
||||
>>> markers = ndi.label(local_maxi)[0]
|
||||
|
||||
Finally, we run the watershed on the image and markers:
|
||||
|
||||
>>> labels = watershed(-distance, markers, mask=image) # doctest: +SKIP
|
||||
|
||||
The algorithm works also for 3-D images, and can be used for example to
|
||||
separate overlapping spheres.
|
||||
"""
|
||||
from ..segmentation import watershed as _watershed
|
||||
return _watershed(image, markers, connectivity, offset, mask,
|
||||
compactness, watershed_line)
|
Binary file not shown.
289
venv/Lib/site-packages/skimage/morphology/_flood_fill.py
Normal file
289
venv/Lib/site-packages/skimage/morphology/_flood_fill.py
Normal file
|
@ -0,0 +1,289 @@
|
|||
"""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 warnings import warn
|
||||
|
||||
from ._util import (_resolve_neighborhood, _set_border_values,
|
||||
_fast_pad, _offsets_to_raveled_neighbors)
|
||||
from ._flood_fill_cy import _flood_fill_equal, _flood_fill_tolerance
|
||||
|
||||
|
||||
def flood_fill(image, seed_point, new_value, *, selem=None, connectivity=None,
|
||||
tolerance=None, in_place=False, inplace=None):
|
||||
"""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`.
|
||||
selem : ndarray, optional
|
||||
A 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 `selem` 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).
|
||||
inplace : bool, optional
|
||||
This parameter is deprecated and will be removed in version 0.19.0
|
||||
in favor of in_place. If True, flood filling is applied to `image`
|
||||
inplace. 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]])
|
||||
"""
|
||||
if inplace is not None:
|
||||
warn('The `inplace` parameter is depreciated and will be removed '
|
||||
'in version 0.19.0. Use `in_place` instead.',
|
||||
stacklevel=2,
|
||||
category=FutureWarning)
|
||||
in_place = inplace
|
||||
|
||||
mask = flood(image, seed_point, selem=selem, connectivity=connectivity,
|
||||
tolerance=tolerance)
|
||||
|
||||
if not in_place:
|
||||
image = image.copy()
|
||||
|
||||
image[mask] = new_value
|
||||
return image
|
||||
|
||||
|
||||
def flood(image, seed_point, *, selem=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.
|
||||
selem : ndarray, optional
|
||||
A 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 larger or
|
||||
equal to `connectivity` are considered neighbors. Ignored if
|
||||
`selem` 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'
|
||||
|
||||
seed_value = image[seed_point]
|
||||
|
||||
# Shortcut for rank zero
|
||||
if 0 in image.shape:
|
||||
return np.zeros(image.shape, dtype=np.bool)
|
||||
|
||||
# Convenience for 1d input
|
||||
try:
|
||||
iter(seed_point)
|
||||
except TypeError:
|
||||
seed_point = (seed_point,)
|
||||
|
||||
selem = _resolve_neighborhood(selem, connectivity, image.ndim)
|
||||
|
||||
# Must annotate borders
|
||||
working_image = _fast_pad(image, image.min(), order=order)
|
||||
|
||||
# Stride-aware neighbors - works for both C- and Fortran-contiguity
|
||||
ravelled_seed_idx = np.ravel_multi_index([i+1 for i in seed_point],
|
||||
working_image.shape, order=order)
|
||||
neighbor_offsets = _offsets_to_raveled_neighbors(
|
||||
working_image.shape, selem, center=((1,) * image.ndim), 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)
|
||||
|
||||
try:
|
||||
if tolerance is not None:
|
||||
# Check if tolerance could create overflow problems
|
||||
try:
|
||||
max_value = np.finfo(working_image.dtype).max
|
||||
min_value = np.finfo(working_image.dtype).min
|
||||
except ValueError:
|
||||
max_value = np.iinfo(working_image.dtype).max
|
||||
min_value = np.iinfo(working_image.dtype).min
|
||||
|
||||
high_tol = min(max_value, seed_value + tolerance)
|
||||
low_tol = max(min_value, seed_value - tolerance)
|
||||
|
||||
_flood_fill_tolerance(working_image.ravel(order),
|
||||
flags.ravel(order),
|
||||
neighbor_offsets,
|
||||
ravelled_seed_idx,
|
||||
seed_value,
|
||||
low_tol,
|
||||
high_tol)
|
||||
else:
|
||||
_flood_fill_equal(working_image.ravel(order),
|
||||
flags.ravel(order),
|
||||
neighbor_offsets,
|
||||
ravelled_seed_idx,
|
||||
seed_value)
|
||||
except TypeError:
|
||||
if working_image.dtype == np.float16:
|
||||
# Provide the user with clearer error message
|
||||
raise TypeError("dtype of `image` is float16 which is not "
|
||||
"supported, try upcasting to float32")
|
||||
else:
|
||||
raise
|
||||
|
||||
# Output what the user requested; view does not create a new copy.
|
||||
return flags[(slice(1, -1),) * image.ndim].view(np.bool)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
647
venv/Lib/site-packages/skimage/morphology/_skeletonize.py
Normal file
647
venv/Lib/site-packages/skimage/morphology/_skeletonize.py
Normal file
|
@ -0,0 +1,647 @@
|
|||
"""
|
||||
Algorithms for computing the skeleton of a binary image
|
||||
"""
|
||||
|
||||
|
||||
import numpy as np
|
||||
from ..util import img_as_ubyte, crop
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .._shared.utils import check_nD, warn
|
||||
from ._skeletonize_cy import (_fast_skeletonize, _skeletonize_loop,
|
||||
_table_lookup_index)
|
||||
from ._skeletonize_3d_cy import _compute_thin_image
|
||||
|
||||
|
||||
def skeletonize(image, *, method=None):
|
||||
"""Compute the skeleton of a binary image.
|
||||
|
||||
Thinning is used to reduce each connected component in a binary image
|
||||
to a single-pixel wide skeleton.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray, 2D or 3D
|
||||
A binary image containing the objects to be skeletonized. Zeros
|
||||
represent background, nonzero values are foreground.
|
||||
method : {'zhang', 'lee'}, optional
|
||||
Which algorithm to use. Zhang's algorithm [Zha84]_ only works for
|
||||
2D images, and is the default for 2D. Lee's algorithm [Lee94]_
|
||||
works for 2D or 3D images and is the default for 3D.
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray
|
||||
The thinned image.
|
||||
|
||||
See also
|
||||
--------
|
||||
medial_axis
|
||||
|
||||
References
|
||||
----------
|
||||
.. [Lee94] T.-C. Lee, R.L. Kashyap and C.-N. Chu, Building skeleton models
|
||||
via 3-D medial surface/axis thinning algorithms.
|
||||
Computer Vision, Graphics, and Image Processing, 56(6):462-478, 1994.
|
||||
|
||||
.. [Zha84] A fast parallel algorithm for thinning digital patterns,
|
||||
T. Y. Zhang and C. Y. Suen, Communications of the ACM,
|
||||
March 1984, Volume 27, Number 3.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> X, Y = np.ogrid[0:9, 0:9]
|
||||
>>> ellipse = (1./3 * (X - 4)**2 + (Y - 4)**2 < 3**2).astype(np.uint8)
|
||||
>>> ellipse
|
||||
array([[0, 0, 0, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
|
||||
>>> skel = skeletonize(ellipse)
|
||||
>>> skel.astype(np.uint8)
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
|
||||
if image.ndim == 2 and (method is None or method == 'zhang'):
|
||||
skeleton = skeletonize_2d(image)
|
||||
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('skeletonize requires a 2D or 3D image as input, '
|
||||
'got {}D.'.format(image.ndim))
|
||||
return skeleton
|
||||
|
||||
|
||||
def skeletonize_2d(image):
|
||||
"""Return the skeleton of a 2D binary image.
|
||||
|
||||
Thinning is used to reduce each connected component in a binary image
|
||||
to a single-pixel wide skeleton.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : numpy.ndarray
|
||||
A binary image containing the objects to be skeletonized. '1'
|
||||
represents foreground, and '0' represents background. It
|
||||
also accepts arrays of boolean values where True is foreground.
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray
|
||||
A matrix containing the thinned image.
|
||||
|
||||
See also
|
||||
--------
|
||||
medial_axis
|
||||
|
||||
Notes
|
||||
-----
|
||||
The algorithm [Zha84]_ works by making successive passes of the image,
|
||||
removing pixels on object borders. This continues until no
|
||||
more pixels can be removed. The image is correlated with a
|
||||
mask that assigns each pixel a number in the range [0...255]
|
||||
corresponding to each possible pattern of its 8 neighbouring
|
||||
pixels. A look up table is then used to assign the pixels a
|
||||
value of 0, 1, 2 or 3, which are selectively removed during
|
||||
the iterations.
|
||||
|
||||
Note that this algorithm will give different results than a
|
||||
medial axis transform, which is also often referred to as
|
||||
"skeletonization".
|
||||
|
||||
References
|
||||
----------
|
||||
.. [Zha84] A fast parallel algorithm for thinning digital patterns,
|
||||
T. Y. Zhang and C. Y. Suen, Communications of the ACM,
|
||||
March 1984, Volume 27, Number 3.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> X, Y = np.ogrid[0:9, 0:9]
|
||||
>>> ellipse = (1./3 * (X - 4)**2 + (Y - 4)**2 < 3**2).astype(np.uint8)
|
||||
>>> ellipse
|
||||
array([[0, 0, 0, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0, 0, 0]], dtype=uint8)
|
||||
>>> skel = skeletonize(ellipse)
|
||||
>>> skel.astype(np.uint8)
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
|
||||
# convert to unsigned int (this should work for boolean values)
|
||||
image = image.astype(np.uint8)
|
||||
|
||||
# check some properties of the input image:
|
||||
# - 2D
|
||||
# - binary image with only 0's and 1's
|
||||
if image.ndim != 2:
|
||||
raise ValueError('Skeletonize requires a 2D array')
|
||||
if not np.all(np.in1d(image.flat, (0, 1))):
|
||||
raise ValueError('Image contains values other than 0 and 1')
|
||||
|
||||
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(np.bool)
|
||||
|
||||
def G1(n):
|
||||
s = 0
|
||||
bits = nabe(n)
|
||||
for i in (0, 2, 4, 6):
|
||||
if not(bits[i]) and (bits[i + 1] or bits[(i + 2) % 8]):
|
||||
s += 1
|
||||
return s == 1
|
||||
|
||||
g1_lut = np.array([G1(n) for n in range(256)])
|
||||
|
||||
def G2(n):
|
||||
n1, n2 = 0, 0
|
||||
bits = nabe(n)
|
||||
for k in (1, 3, 5, 7):
|
||||
if bits[k] or bits[k - 1]:
|
||||
n1 += 1
|
||||
if bits[k] or bits[(k + 1) % 8]:
|
||||
n2 += 1
|
||||
return min(n1, n2) in [2, 3]
|
||||
|
||||
g2_lut = np.array([G2(n) for n in range(256)])
|
||||
|
||||
g12_lut = g1_lut & g2_lut
|
||||
|
||||
def G3(n):
|
||||
bits = nabe(n)
|
||||
return not((bits[1] or bits[2] or not(bits[7])) and bits[0])
|
||||
|
||||
def G3p(n):
|
||||
bits = nabe(n)
|
||||
return not((bits[5] or bits[6] or not(bits[3])) and bits[4])
|
||||
|
||||
g3_lut = np.array([G3(n) for n in range(256)])
|
||||
g3p_lut = np.array([G3p(n) for n in range(256)])
|
||||
|
||||
g123_lut = g12_lut & g3_lut
|
||||
g123p_lut = g12_lut & g3p_lut
|
||||
|
||||
return g123_lut, g123p_lut
|
||||
|
||||
|
||||
G123_LUT = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
|
||||
0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1,
|
||||
0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
|
||||
0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
|
||||
0, 1, 1, 0, 0, 1, 0, 0, 0], dtype=np.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=np.bool)
|
||||
|
||||
|
||||
def thin(image, max_iter=None):
|
||||
"""
|
||||
Perform morphological thinning of a binary image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : binary (M, N) ndarray
|
||||
The image to be thinned.
|
||||
|
||||
max_iter : int, number of iterations, optional
|
||||
Regardless of the value of this parameter, the thinned image
|
||||
is returned immediately if an iteration produces no change.
|
||||
If this parameter is specified it thus sets an upper bound on
|
||||
the number of iterations performed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray of bool
|
||||
Thinned image.
|
||||
|
||||
See also
|
||||
--------
|
||||
skeletonize, medial_axis
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm [1]_ works by making multiple passes over the image,
|
||||
removing pixels matching a set of criteria designed to thin
|
||||
connected regions while preserving eight-connected components and
|
||||
2 x 2 squares [2]_. In each of the two sub-iterations the algorithm
|
||||
correlates the intermediate skeleton image with a neighborhood mask,
|
||||
then looks up each neighborhood in a lookup table indicating whether
|
||||
the central pixel should be deleted in that sub-iteration.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Z. Guo and R. W. Hall, "Parallel thinning with
|
||||
two-subiteration algorithms," Comm. ACM, vol. 32, no. 3,
|
||||
pp. 359-373, 1989. :DOI:`10.1145/62065.62074`
|
||||
.. [2] Lam, L., Seong-Whan Lee, and Ching Y. Suen, "Thinning
|
||||
Methodologies-A Comprehensive Survey," IEEE Transactions on
|
||||
Pattern Analysis and Machine Intelligence, Vol 14, No. 9,
|
||||
p. 879, 1992. :DOI:`10.1109/34.161346`
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> square = np.zeros((7, 7), dtype=np.uint8)
|
||||
>>> square[1:-1, 2:-2] = 1
|
||||
>>> square[0, 1] = 1
|
||||
>>> square
|
||||
array([[0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
>>> skel = thin(square)
|
||||
>>> skel.astype(np.uint8)
|
||||
array([[0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
"""
|
||||
# check that image is 2d
|
||||
check_nD(image, 2)
|
||||
|
||||
# convert image to uint8 with values in {0, 1}
|
||||
skel = np.asanyarray(image, dtype=bool).astype(np.uint8)
|
||||
|
||||
# neighborhood mask
|
||||
mask = np.array([[ 8, 4, 2],
|
||||
[16, 0, 1],
|
||||
[32, 64, 128]], dtype=np.uint8)
|
||||
|
||||
# iterate until convergence, up to the iteration limit
|
||||
max_iter = max_iter or np.inf
|
||||
n_iter = 0
|
||||
n_pts_old, n_pts_new = np.inf, np.sum(skel)
|
||||
while n_pts_old != n_pts_new and n_iter < max_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
|
||||
n_iter += 1
|
||||
|
||||
return skel.astype(np.bool)
|
||||
|
||||
|
||||
# --------- Skeletonization by medial axis transform --------
|
||||
|
||||
_eight_connect = ndi.generate_binary_structure(2, 2)
|
||||
|
||||
|
||||
def medial_axis(image, mask=None, return_distance=False):
|
||||
"""
|
||||
Compute the medial axis transform of a binary image
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : binary ndarray, shape (M, N)
|
||||
The image of the shape to be skeletonized.
|
||||
mask : binary ndarray, shape (M, N), optional
|
||||
If a mask is given, only those elements in `image` with a true
|
||||
value in `mask` are used for computing the medial axis.
|
||||
return_distance : bool, optional
|
||||
If true, the distance transform is returned as well as the skeleton.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray of bools
|
||||
Medial axis transform of the image
|
||||
dist : ndarray of ints, optional
|
||||
Distance transform of the image (only returned if `return_distance`
|
||||
is True)
|
||||
|
||||
See also
|
||||
--------
|
||||
skeletonize
|
||||
|
||||
Notes
|
||||
-----
|
||||
This algorithm computes the medial axis transform of an image
|
||||
as the ridges of its distance transform.
|
||||
|
||||
The different steps of the algorithm are as follows
|
||||
* A lookup table is used, that assigns 0 or 1 to each configuration of
|
||||
the 3x3 binary square, whether the central pixel should be removed
|
||||
or kept. We want a point to be removed if it has more than one neighbor
|
||||
and if removing it does not change the number of connected components.
|
||||
|
||||
* The distance transform to the background is computed, as well as
|
||||
the cornerness of the pixel.
|
||||
|
||||
* The foreground (value of 1) points are ordered by
|
||||
the distance transform, then the cornerness.
|
||||
|
||||
* A cython function is called to reduce the image to its skeleton. It
|
||||
processes pixels in the order determined at the previous step, and
|
||||
removes or maintains a pixel according to the lookup table. Because
|
||||
of the ordering, it is possible to process all pixels in only one
|
||||
pass.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> square = np.zeros((7, 7), dtype=np.uint8)
|
||||
>>> square[1:-1, 2:-2] = 1
|
||||
>>> square
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
>>> medial_axis(square).astype(np.uint8)
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
global _eight_connect
|
||||
if mask is None:
|
||||
masked_image = image.astype(np.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 neighbourhood 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 neighbourhood
|
||||
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.RandomState(0)
|
||||
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.
|
||||
border_value : bool
|
||||
The value of pixels beyond the border of the image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
result : ndarray of same shape as `image`
|
||||
Transformed image
|
||||
|
||||
Notes
|
||||
-----
|
||||
The pixels are numbered like this::
|
||||
|
||||
|
||||
0 1 2
|
||||
3 4 5
|
||||
6 7 8
|
||||
|
||||
The index at a pixel is the sum of 2**<pixel-number> for pixels
|
||||
that evaluate to true.
|
||||
"""
|
||||
#
|
||||
# We accumulate into the indexer to get the index into the table
|
||||
# at each point in the image
|
||||
#
|
||||
if image.shape[0] < 3 or image.shape[1] < 3:
|
||||
image = image.astype(bool)
|
||||
indexer = np.zeros(image.shape, int)
|
||||
indexer[1:, 1:] += image[:-1, :-1] * 2**0
|
||||
indexer[1:, :] += image[:-1, :] * 2**1
|
||||
indexer[1:, :-1] += image[:-1, 1:] * 2**2
|
||||
|
||||
indexer[:, 1:] += image[:, :-1] * 2**3
|
||||
indexer[:, :] += image[:, :] * 2**4
|
||||
indexer[:, :-1] += image[:, 1:] * 2**5
|
||||
|
||||
indexer[:-1, 1:] += image[1:, :-1] * 2**6
|
||||
indexer[:-1, :] += image[1:, :] * 2**7
|
||||
indexer[:-1, :-1] += image[1:, 1:] * 2**8
|
||||
else:
|
||||
indexer = _table_lookup_index(np.ascontiguousarray(image, np.uint8))
|
||||
image = table[indexer]
|
||||
return image
|
||||
|
||||
|
||||
def skeletonize_3d(image):
|
||||
"""Compute the skeleton of a binary image.
|
||||
|
||||
Thinning is used to reduce each connected component in a binary image
|
||||
to a single-pixel wide skeleton.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray, 2D or 3D
|
||||
A binary image containing the objects to be skeletonized. Zeros
|
||||
represent background, nonzero values are foreground.
|
||||
|
||||
Returns
|
||||
-------
|
||||
skeleton : ndarray
|
||||
The thinned image.
|
||||
|
||||
See also
|
||||
--------
|
||||
skeletonize, medial_axis
|
||||
|
||||
Notes
|
||||
-----
|
||||
The method of [Lee94]_ uses an octree data structure to examine a 3x3x3
|
||||
neighborhood of a pixel. The algorithm proceeds by iteratively sweeping
|
||||
over the image, and removing pixels at each iteration until the image
|
||||
stops changing. Each iteration consists of two steps: first, a list of
|
||||
candidates for removal is assembled; then pixels from this list are
|
||||
rechecked sequentially, to better preserve connectivity of the image.
|
||||
|
||||
The algorithm this function implements is different from the algorithms
|
||||
used by either `skeletonize` or `medial_axis`, thus for 2D images the
|
||||
results produced by this function are generally different.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [Lee94] T.-C. Lee, R.L. Kashyap and C.-N. Chu, Building skeleton models
|
||||
via 3-D medial surface/axis thinning algorithms.
|
||||
Computer Vision, Graphics, and Image Processing, 56(6):462-478, 1994.
|
||||
|
||||
"""
|
||||
# make sure the image is 3D or 2D
|
||||
if image.ndim < 2 or image.ndim > 3:
|
||||
raise ValueError("skeletonize_3d can only handle 2D or 3D images; "
|
||||
"got image.ndim = %s instead." % image.ndim)
|
||||
image = np.ascontiguousarray(image)
|
||||
image = img_as_ubyte(image, force_copy=False)
|
||||
|
||||
# make an in image 3D and pad it w/ zeros to simplify dealing w/ boundaries
|
||||
# NB: careful here to not clobber the original *and* minimize copying
|
||||
image_o = image
|
||||
if image.ndim == 2:
|
||||
image_o = image[np.newaxis, ...]
|
||||
image_o = np.pad(image_o, pad_width=1, mode='constant')
|
||||
|
||||
# normalize to binary
|
||||
maxval = image_o.max()
|
||||
image_o[image_o != 0] = 1
|
||||
|
||||
# do the computation
|
||||
image_o = np.asarray(_compute_thin_image(image_o))
|
||||
|
||||
# crop it back and restore the original intensity range
|
||||
image_o = crop(image_o, crop_width=1)
|
||||
if image.ndim == 2:
|
||||
image_o = image_o[0]
|
||||
image_o *= maxval
|
||||
|
||||
return image_o
|
Binary file not shown.
Binary file not shown.
261
venv/Lib/site-packages/skimage/morphology/_util.py
Normal file
261
venv/Lib/site-packages/skimage/morphology/_util.py
Normal file
|
@ -0,0 +1,261 @@
|
|||
"""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 structuring element 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 structuring element 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 structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
c_connectivity : array of bool
|
||||
The structuring element corresponding to the input `connectivity`.
|
||||
offset : array of int
|
||||
The offset corresponding to the center of the structuring element.
|
||||
|
||||
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 _offsets_to_raveled_neighbors(image_shape, selem, 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.
|
||||
selem : ndarray
|
||||
A 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 `selem`.
|
||||
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 `selem`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> _offsets_to_raveled_neighbors((4, 5), np.ones((4, 3)), (1, 1))
|
||||
array([-5, -1, 1, 5, -6, -4, 4, 6, 10, 9, 11])
|
||||
>>> _offsets_to_raveled_neighbors((2, 3, 2), np.ones((3, 3, 3)), (1, 1, 1))
|
||||
array([ 2, -6, 1, -1, 6, -2, 3, 8, -3, -4, 7, -5, -7, -8, 5, 4, -9,
|
||||
9])
|
||||
"""
|
||||
if not selem.ndim == len(image_shape) == len(center):
|
||||
raise ValueError(
|
||||
"number of dimensions in image shape, structuring element and its"
|
||||
"center index does not match"
|
||||
)
|
||||
|
||||
selem_indices = np.array(np.nonzero(selem)).T
|
||||
offsets = selem_indices - center
|
||||
|
||||
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
|
||||
distances = np.abs(offsets).sum(axis=1)
|
||||
raveled_offsets = raveled_offsets[np.argsort(distances)]
|
||||
|
||||
# If any dimension in image_shape is smaller than selem.shape
|
||||
# duplicates might occur, remove them
|
||||
if any(x < y for x, y in zip(image_shape, selem.shape)):
|
||||
# np.unique reorders, which we don't want
|
||||
_, indices = np.unique(raveled_offsets, return_index=True)
|
||||
raveled_offsets = raveled_offsets[np.sort(indices)]
|
||||
|
||||
# Remove "offset to center"
|
||||
raveled_offsets = raveled_offsets[1:]
|
||||
|
||||
return raveled_offsets
|
||||
|
||||
|
||||
def _resolve_neighborhood(selem, connectivity, ndim):
|
||||
"""Validate or create structuring element.
|
||||
|
||||
Depending on the values of `connectivity` and `selem` this function
|
||||
either creates a new structuring element (`selem` is None) using
|
||||
`connectivity` or validates the given structuring element (`selem` is not
|
||||
None).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
selem : ndarray
|
||||
A 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 `selem` 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
|
||||
`selem` is not None.
|
||||
ndim : int
|
||||
Number of dimensions `selem` ought to have.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
Validated or new structuring element 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 selem is None:
|
||||
if connectivity is None:
|
||||
connectivity = ndim
|
||||
selem = ndi.generate_binary_structure(ndim, connectivity)
|
||||
else:
|
||||
# Validate custom structured element
|
||||
selem = np.asarray(selem, dtype=np.bool)
|
||||
# Must specify neighbors for all dimensions
|
||||
if selem.ndim != ndim:
|
||||
raise ValueError(
|
||||
"number of dimensions in image and structuring element do not"
|
||||
"match"
|
||||
)
|
||||
# Must only specify direct neighbors
|
||||
if any(s != 3 for s in selem.shape):
|
||||
raise ValueError("dimension size in structuring element is not 3")
|
||||
|
||||
return selem
|
||||
|
||||
|
||||
def _set_border_values(image, value):
|
||||
"""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.
|
||||
|
||||
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]])
|
||||
"""
|
||||
for axis in range(image.ndim):
|
||||
# Index first and last element in each dimension
|
||||
sl = (slice(None),) * axis + ((0, -1),) + (...,)
|
||||
image[sl] = value
|
||||
|
||||
|
||||
def _fast_pad(image, value, *, order="C"):
|
||||
"""Pad an array on all axes by one with a value.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image to pad.
|
||||
value : scalar
|
||||
The value to use. Should be compatible with `image`'s dtype.
|
||||
order : "C" or "F"
|
||||
Specify the memory layout of the padded image (C or Fortran style).
|
||||
|
||||
Returns
|
||||
-------
|
||||
padded_image : ndarray
|
||||
The new image.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The output of this function is equivalent to::
|
||||
|
||||
np.pad(image, 1, mode="constant", constant_values=value)
|
||||
|
||||
Up to versions < 1.17 `numpy.pad` uses concatenation to create padded
|
||||
arrays while this method needs to only allocate and copy once.
|
||||
This can result in significant speed gains if `image` has a large number of
|
||||
dimensions.
|
||||
Thus this function may be safely removed once that version is the minimum
|
||||
required by scikit-image.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> _fast_pad(np.zeros((2, 3), dtype=int), 4)
|
||||
array([[4, 4, 4, 4, 4],
|
||||
[4, 0, 0, 0, 4],
|
||||
[4, 0, 0, 0, 4],
|
||||
[4, 4, 4, 4, 4]])
|
||||
"""
|
||||
# Allocate padded image
|
||||
new_shape = np.array(image.shape) + 2
|
||||
new_image = np.empty(new_shape, dtype=image.dtype, order=order)
|
||||
|
||||
# Copy old image into new space
|
||||
sl = (slice(1, -1),) * image.ndim
|
||||
new_image[sl] = image
|
||||
# and set the edge values
|
||||
_set_border_values(new_image, value)
|
||||
|
||||
return new_image
|
146
venv/Lib/site-packages/skimage/morphology/binary.py
Normal file
146
venv/Lib/site-packages/skimage/morphology/binary.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
"""
|
||||
Binary morphological operations
|
||||
"""
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
from .misc import default_selem
|
||||
|
||||
|
||||
# The default_selem decorator provides a diamond structuring element as default
|
||||
# with the same dimension as the input image and size 3 along each axis.
|
||||
@default_selem
|
||||
def binary_erosion(image, selem=None, out=None):
|
||||
"""Return fast binary morphological erosion of an image.
|
||||
|
||||
This function returns the same result as greyscale 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.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None is
|
||||
passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
eroded : ndarray of bool or uint
|
||||
The result of the morphological erosion taking values in
|
||||
``[False, True]``.
|
||||
|
||||
"""
|
||||
if out is None:
|
||||
out = np.empty(image.shape, dtype=np.bool)
|
||||
ndi.binary_erosion(image, structure=selem, output=out, border_value=True)
|
||||
return out
|
||||
|
||||
|
||||
@default_selem
|
||||
def binary_dilation(image, selem=None, out=None):
|
||||
"""Return fast binary morphological dilation of an image.
|
||||
|
||||
This function returns the same result as greyscale 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.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None is
|
||||
passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dilated : ndarray of bool or uint
|
||||
The result of the morphological dilation with values in
|
||||
``[False, True]``.
|
||||
"""
|
||||
if out is None:
|
||||
out = np.empty(image.shape, dtype=np.bool)
|
||||
ndi.binary_dilation(image, structure=selem, output=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_selem
|
||||
def binary_opening(image, selem=None, out=None):
|
||||
"""Return fast binary morphological opening of an image.
|
||||
|
||||
This function returns the same result as greyscale 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.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
opening : ndarray of bool
|
||||
The result of the morphological opening.
|
||||
|
||||
"""
|
||||
eroded = binary_erosion(image, selem)
|
||||
out = binary_dilation(eroded, selem, out=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_selem
|
||||
def binary_closing(image, selem=None, out=None):
|
||||
"""Return fast binary morphological closing of an image.
|
||||
|
||||
This function returns the same result as greyscale 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.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use a cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray of bool, optional
|
||||
The array to store the result of the morphology. If None,
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
closing : ndarray of bool
|
||||
The result of the morphological closing.
|
||||
|
||||
"""
|
||||
dilated = binary_dilation(image, selem)
|
||||
out = binary_erosion(dilated, selem, out=out)
|
||||
return out
|
164
venv/Lib/site-packages/skimage/morphology/convex_hull.py
Normal file
164
venv/Lib/site-packages/skimage/morphology/convex_hull.py
Normal file
|
@ -0,0 +1,164 @@
|
|||
"""Convex Hull."""
|
||||
from itertools import product
|
||||
import numpy as np
|
||||
from scipy.spatial import ConvexHull
|
||||
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 convex_hull_image(image, offset_coordinates=True, tolerance=1e-10):
|
||||
"""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.
|
||||
|
||||
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=np.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.
|
||||
hull0 = ConvexHull(coords)
|
||||
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
|
||||
hull = ConvexHull(coords)
|
||||
vertices = hull.points[hull.vertices]
|
||||
|
||||
# If 2D, use fast Cython function to locate convex hull pixels
|
||||
if ndim == 2:
|
||||
mask = grid_points_in_poly(image.shape, vertices)
|
||||
else:
|
||||
gridcoords = np.reshape(np.mgrid[tuple(map(slice, image.shape))],
|
||||
(ndim, -1))
|
||||
# A point is in the hull if it satisfies all of the hull's inequalities
|
||||
coords_in_hull = np.all(hull.equations[:, :ndim].dot(gridcoords) +
|
||||
hull.equations[:, ndim:] < tolerance, axis=0)
|
||||
mask = np.reshape(coords_in_hull, image.shape)
|
||||
|
||||
return mask
|
||||
|
||||
|
||||
def convex_hull_object(image, neighbors=None, *, connectivity=None):
|
||||
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.
|
||||
neighbors : {4, 8}, int, optional
|
||||
Whether to use 4 or 8 adjacent pixels as neighbors.
|
||||
If ``None``, set to 8. **Deprecated, use** ``connectivity`` **instead.**
|
||||
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. If ``None``, set to 2::
|
||||
|
||||
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 neighbors is None and connectivity is None:
|
||||
connectivity = 2
|
||||
elif neighbors is not None:
|
||||
# Backward-compatibility
|
||||
if neighbors == 4:
|
||||
connectivity = 1
|
||||
elif neighbors == 8:
|
||||
connectivity = 2
|
||||
else:
|
||||
raise ValueError('`neighbors` must be either 4 or 8.')
|
||||
warn("The argument `neighbors` is deprecated and will be removed in "
|
||||
"scikit-image 0.18, use `connectivity` instead. "
|
||||
"For neighbors={neighbors}, use connectivity={connectivity}"
|
||||
"".format(neighbors=neighbors, connectivity=connectivity),
|
||||
stacklevel=2)
|
||||
else:
|
||||
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
|
536
venv/Lib/site-packages/skimage/morphology/extrema.py
Normal file
536
venv/Lib/site-packages/skimage/morphology/extrema.py
Normal file
|
@ -0,0 +1,536 @@
|
|||
"""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 ..util import dtype_limits, invert, crop
|
||||
from .._shared.utils import warn
|
||||
from . import greyreconstruct, _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, selem=None):
|
||||
"""Determine all maxima of the image with height >= h.
|
||||
|
||||
The local maxima are defined as connected sets of pixels with equal
|
||||
grey level strictly greater than the grey 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 a higher 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 for which the minimal value is greater.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the maxima are to be calculated.
|
||||
h : unsigned integer
|
||||
The minimal height of all extracted maxima.
|
||||
selem : 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 maxima of height >= h. The resulting image is a binary image, where
|
||||
pixels belonging to the selected maxima take value 1, the others
|
||||
take value 0.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.extrema.h_minima
|
||||
skimage.morphology.extrema.local_maxima
|
||||
skimage.morphology.extrema.local_minima
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Soille, P., "Morphological Image Analysis: Principles and
|
||||
Applications" (Chapter 6), 2nd edition (2003), ISBN 3540429883.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import extrema
|
||||
|
||||
We create an image (quadratic function with a maximum in the center and
|
||||
4 additional constant maxima.
|
||||
The heights of the maxima are: 1, 21, 41, 61, 81
|
||||
|
||||
>>> w = 10
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 20 - 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:4,2:4] = 40; f[2:4,7:9] = 60; f[7:9,2:4] = 80; f[7:9,7:9] = 100
|
||||
>>> f = f.astype(np.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(np.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 = greyreconstruct.reconstruction(shifted_img, image,
|
||||
method='dilation', selem=selem)
|
||||
residue_img = image - rec_img
|
||||
return (residue_img >= h).astype(np.uint8)
|
||||
|
||||
|
||||
def h_minima(image, h, selem=None):
|
||||
"""Determine all minima of the image with depth >= h.
|
||||
|
||||
The local minima are defined as connected sets of pixels with equal
|
||||
grey level strictly smaller than the grey 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 a deeper 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 for which the maximal value is smaller.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the minima are to be calculated.
|
||||
h : unsigned integer
|
||||
The minimal depth of all extracted minima.
|
||||
selem : 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 minima of depth >= h. The resulting image is a binary image, where
|
||||
pixels belonging to the selected minima take value 1, the other pixels
|
||||
take value 0.
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.morphology.extrema.h_maxima
|
||||
skimage.morphology.extrema.local_maxima
|
||||
skimage.morphology.extrema.local_minima
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Soille, P., "Morphological Image Analysis: Principles and
|
||||
Applications" (Chapter 6), 2nd edition (2003), ISBN 3540429883.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import extrema
|
||||
|
||||
We create an image (quadratic function with a minimum in the center and
|
||||
4 additional constant maxima.
|
||||
The depth of the minima are: 1, 21, 41, 61, 81
|
||||
|
||||
>>> w = 10
|
||||
>>> x, y = np.mgrid[0:w,0:w]
|
||||
>>> f = 180 + 0.2*((x - w/2)**2 + (y-w/2)**2)
|
||||
>>> f[2:4,2:4] = 160; f[2:4,7:9] = 140; f[7:9,2:4] = 120; f[7:9,7:9] = 100
|
||||
>>> f = f.astype(np.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(np.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 = greyreconstruct.reconstruction(shifted_img, image,
|
||||
method='erosion', selem=selem)
|
||||
residue_img = rec_img - image
|
||||
return (residue_img >= h).astype(np.uint8)
|
||||
|
||||
|
||||
def local_maxima(image, selem=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.
|
||||
selem : ndarray, optional
|
||||
A 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 `selem` 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
|
||||
`selem` 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=np.bool)
|
||||
|
||||
if allow_borders:
|
||||
# Ensure that local maxima are always at least one smaller sample away
|
||||
# from the image border
|
||||
image = _util._fast_pad(image, 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 & structuring element 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:
|
||||
selem = _util._resolve_neighborhood(selem, connectivity, image.ndim)
|
||||
neighbor_offsets = _util._offsets_to_raveled_neighbors(
|
||||
image.shape, selem, 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(np.bool)
|
||||
|
||||
|
||||
def local_minima(image, selem=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.
|
||||
selem : ndarray, optional
|
||||
A 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 `selem` 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
|
||||
`selem` 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),
|
||||
selem=selem,
|
||||
connectivity=connectivity,
|
||||
indices=indices,
|
||||
allow_borders=allow_borders
|
||||
)
|
489
venv/Lib/site-packages/skimage/morphology/grey.py
Normal file
489
venv/Lib/site-packages/skimage/morphology/grey.py
Normal file
|
@ -0,0 +1,489 @@
|
|||
"""
|
||||
Grayscale morphological operations
|
||||
"""
|
||||
import functools
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
from .misc import default_selem
|
||||
from ..util import crop
|
||||
|
||||
__all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat',
|
||||
'black_tophat']
|
||||
|
||||
|
||||
def _shift_selem(selem, shift_x, shift_y):
|
||||
"""Shift the binary image `selem` in the left and/or up.
|
||||
|
||||
This only affects 2D structuring elements with even number of rows
|
||||
or columns.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
selem : 2D array, shape (M, N)
|
||||
The input structuring element.
|
||||
shift_x, shift_y : bool
|
||||
Whether to move `selem` along each axis.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : 2D array, shape (M + int(shift_x), N + int(shift_y))
|
||||
The shifted structuring element.
|
||||
"""
|
||||
if selem.ndim != 2:
|
||||
# do nothing for 1D or 3D or higher structuring elements
|
||||
return selem
|
||||
m, n = selem.shape
|
||||
if m % 2 == 0:
|
||||
extra_row = np.zeros((1, n), selem.dtype)
|
||||
if shift_x:
|
||||
selem = np.vstack((selem, extra_row))
|
||||
else:
|
||||
selem = np.vstack((extra_row, selem))
|
||||
m += 1
|
||||
if n % 2 == 0:
|
||||
extra_col = np.zeros((m, 1), selem.dtype)
|
||||
if shift_y:
|
||||
selem = np.hstack((selem, extra_col))
|
||||
else:
|
||||
selem = np.hstack((extra_col, selem))
|
||||
return selem
|
||||
|
||||
|
||||
def _invert_selem(selem):
|
||||
"""Change the order of the values in `selem`.
|
||||
|
||||
This is a patch for the *weird* footprint inversion in
|
||||
`ndi.grey_morphology` [1]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
selem : array
|
||||
The input structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
inverted : array, same shape and type as `selem`
|
||||
The structuring element, in opposite order.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> selem = np.array([[0, 0, 0], [0, 1, 1], [0, 1, 1]], np.uint8)
|
||||
>>> _invert_selem(selem)
|
||||
array([[1, 1, 0],
|
||||
[1, 1, 0],
|
||||
[0, 0, 0]], dtype=uint8)
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://github.com/scipy/scipy/blob/ec20ababa400e39ac3ffc9148c01ef86d5349332/scipy/ndimage/morphology.py#L1285
|
||||
"""
|
||||
inverted = selem[(slice(None, None, -1),) * selem.ndim]
|
||||
return inverted
|
||||
|
||||
|
||||
def pad_for_eccentric_selems(func):
|
||||
"""Pad input images for certain morphological operations.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : callable
|
||||
A morphological function, either opening or closing, that
|
||||
supports eccentric structuring elements. Its parameters must
|
||||
include at least `image`, `selem`, and `out`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func_out : callable
|
||||
The same function, but correctly padding the input image before
|
||||
applying the input function.
|
||||
|
||||
See Also
|
||||
--------
|
||||
opening, closing.
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def func_out(image, selem, out=None, *args, **kwargs):
|
||||
pad_widths = []
|
||||
padding = False
|
||||
if out is None:
|
||||
out = np.empty_like(image)
|
||||
for axis_len in selem.shape:
|
||||
if axis_len % 2 == 0:
|
||||
axis_pad_width = axis_len - 1
|
||||
padding = True
|
||||
else:
|
||||
axis_pad_width = 0
|
||||
pad_widths.append((axis_pad_width,) * 2)
|
||||
if padding:
|
||||
image = np.pad(image, pad_widths, mode='edge')
|
||||
out_temp = np.empty_like(image)
|
||||
else:
|
||||
out_temp = out
|
||||
out_temp = func(image, selem, out=out_temp, *args, **kwargs)
|
||||
if padding:
|
||||
out[:] = crop(out_temp, pad_widths)
|
||||
else:
|
||||
out = out_temp
|
||||
return out
|
||||
return func_out
|
||||
|
||||
@default_selem
|
||||
def erosion(image, selem=None, out=None, shift_x=False, shift_y=False):
|
||||
"""Return greyscale 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.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as an array of 1's and 0's.
|
||||
If None, use cross-shaped structuring element (connectivity=1).
|
||||
out : ndarrays, optional
|
||||
The array to store the result of the morphology. If None is
|
||||
passed, a new array will be allocated.
|
||||
shift_x, shift_y : bool, optional
|
||||
shift structuring element about center point. This only affects
|
||||
eccentric structuring elements (i.e. selem with even numbered sides).
|
||||
|
||||
Returns
|
||||
-------
|
||||
eroded : array, same shape as `image`
|
||||
The result of the morphological erosion.
|
||||
|
||||
Notes
|
||||
-----
|
||||
For ``uint8`` (and ``uint16`` up to a certain bit-depth) data, the
|
||||
lower algorithm complexity makes the `skimage.filters.rank.minimum`
|
||||
function more efficient for larger images and structuring elements.
|
||||
|
||||
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)
|
||||
|
||||
"""
|
||||
selem = np.array(selem)
|
||||
selem = _shift_selem(selem, shift_x, shift_y)
|
||||
if out is None:
|
||||
out = np.empty_like(image)
|
||||
ndi.grey_erosion(image, footprint=selem, output=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_selem
|
||||
def dilation(image, selem=None, out=None, shift_x=False, shift_y=False):
|
||||
"""Return greyscale morphological dilation of an image.
|
||||
|
||||
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
|
||||
Image array.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None, is
|
||||
passed, a new array will be allocated.
|
||||
shift_x, shift_y : bool, optional
|
||||
shift structuring element about center point. This only affects
|
||||
eccentric structuring elements (i.e. selem with even numbered sides).
|
||||
|
||||
Returns
|
||||
-------
|
||||
dilated : uint8 array, same shape and type as `image`
|
||||
The result of the morphological dilation.
|
||||
|
||||
Notes
|
||||
-----
|
||||
For `uint8` (and `uint16` up to a certain bit-depth) data, the lower
|
||||
algorithm complexity makes the `skimage.filters.rank.maximum` function more
|
||||
efficient for larger images and structuring elements.
|
||||
|
||||
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)
|
||||
|
||||
"""
|
||||
selem = np.array(selem)
|
||||
selem = _shift_selem(selem, shift_x, shift_y)
|
||||
# Inside ndimage.grey_dilation, the structuring element is inverted,
|
||||
# eg. `selem = selem[::-1, ::-1]` for 2D [1]_, for reasons unknown to
|
||||
# this author (@jni). To "patch" this behaviour, we invert our own
|
||||
# selem before passing it to `ndi.grey_dilation`.
|
||||
# [1] https://github.com/scipy/scipy/blob/ec20ababa400e39ac3ffc9148c01ef86d5349332/scipy/ndimage/morphology.py#L1285
|
||||
selem = _invert_selem(selem)
|
||||
if out is None:
|
||||
out = np.empty_like(image)
|
||||
ndi.grey_dilation(image, footprint=selem, output=out)
|
||||
return out
|
||||
|
||||
|
||||
@default_selem
|
||||
@pad_for_eccentric_selems
|
||||
def opening(image, selem=None, out=None):
|
||||
"""Return greyscale morphological opening of an image.
|
||||
|
||||
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
|
||||
Image array.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as an array of 1's and 0's.
|
||||
If None, use cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
opening : array, same shape and type as `image`
|
||||
The result of the morphological opening.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Open up gap between two bright regions (but also shrink regions)
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> bad_connection = np.array([[1, 0, 0, 0, 1],
|
||||
... [1, 1, 0, 1, 1],
|
||||
... [1, 1, 1, 1, 1],
|
||||
... [1, 1, 0, 1, 1],
|
||||
... [1, 0, 0, 0, 1]], dtype=np.uint8)
|
||||
>>> opening(bad_connection, square(3))
|
||||
array([[0, 0, 0, 0, 0],
|
||||
[1, 1, 0, 1, 1],
|
||||
[1, 1, 0, 1, 1],
|
||||
[1, 1, 0, 1, 1],
|
||||
[0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
eroded = erosion(image, selem)
|
||||
# note: shift_x, shift_y do nothing if selem side length is odd
|
||||
out = dilation(eroded, selem, out=out, shift_x=True, shift_y=True)
|
||||
return out
|
||||
|
||||
|
||||
@default_selem
|
||||
@pad_for_eccentric_selems
|
||||
def closing(image, selem=None, out=None):
|
||||
"""Return greyscale morphological closing of an image.
|
||||
|
||||
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
|
||||
Image array.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as an array of 1's and 0's.
|
||||
If None, use cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None,
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
closing : array, same shape and type as `image`
|
||||
The result of the morphological closing.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Close a gap between two bright lines
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> broken_line = np.array([[0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0],
|
||||
... [1, 1, 0, 1, 1],
|
||||
... [0, 0, 0, 0, 0],
|
||||
... [0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
>>> closing(broken_line, square(3))
|
||||
array([[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
dilated = dilation(image, selem)
|
||||
# note: shift_x, shift_y do nothing if selem side length is odd
|
||||
out = erosion(dilated, selem, out=out, shift_x=True, shift_y=True)
|
||||
return out
|
||||
|
||||
|
||||
@default_selem
|
||||
def white_tophat(image, selem=None, out=None):
|
||||
"""Return white top hat of an image.
|
||||
|
||||
The white top hat of an image is defined as the image minus its
|
||||
morphological opening. This operation returns the bright spots of the image
|
||||
that are smaller than the structuring element.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image array.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as an array of 1's and 0's.
|
||||
If None, use cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : array, same shape and type as `image`
|
||||
The result of the morphological white top hat.
|
||||
|
||||
See also
|
||||
--------
|
||||
black_tophat
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Top-hat_transform
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> # Subtract grey background from bright peak
|
||||
>>> import numpy as np
|
||||
>>> from skimage.morphology import square
|
||||
>>> bright_on_grey = 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_grey, 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)
|
||||
|
||||
"""
|
||||
selem = np.array(selem)
|
||||
if out is image:
|
||||
opened = opening(image, selem)
|
||||
if np.issubdtype(opened.dtype, np.bool_):
|
||||
np.logical_xor(out, opened, out=out)
|
||||
else:
|
||||
out -= opened
|
||||
return out
|
||||
elif out is None:
|
||||
out = np.empty_like(image)
|
||||
# work-around for NumPy deprecation warning for arithmetic
|
||||
# operations on bool arrays
|
||||
if isinstance(image, np.ndarray) and image.dtype == np.bool:
|
||||
image_ = image.view(dtype=np.uint8)
|
||||
else:
|
||||
image_ = image
|
||||
if isinstance(out, np.ndarray) and out.dtype == np.bool:
|
||||
out_ = out.view(dtype=np.uint8)
|
||||
else:
|
||||
out_ = out
|
||||
out_ = ndi.white_tophat(image_, footprint=selem, output=out_)
|
||||
return out
|
||||
|
||||
|
||||
@default_selem
|
||||
def black_tophat(image, selem=None, out=None):
|
||||
"""Return black top hat of an image.
|
||||
|
||||
The black top hat of an image is defined as its morphological closing minus
|
||||
the original image. This operation returns the dark spots of the image that
|
||||
are smaller than the structuring element. Note that dark spots in the
|
||||
original image are bright spots after the black top hat.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
Image array.
|
||||
selem : ndarray, optional
|
||||
The neighborhood expressed as a 2-D array of 1's and 0's.
|
||||
If None, use cross-shaped structuring element (connectivity=1).
|
||||
out : ndarray, optional
|
||||
The array to store the result of the morphology. If None
|
||||
is passed, a new array will be allocated.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : array, same shape and type as `image`
|
||||
The result of the morphological black top hat.
|
||||
|
||||
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_grey = 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_grey, square(3))
|
||||
array([[0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 1, 5, 1, 0],
|
||||
[0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
if out is image:
|
||||
original = image.copy()
|
||||
else:
|
||||
original = image
|
||||
out = closing(image, selem, out=out)
|
||||
if np.issubdtype(out.dtype, np.bool_):
|
||||
np.logical_xor(out, original, out=out)
|
||||
else:
|
||||
out -= original
|
||||
return out
|
211
venv/Lib/site-packages/skimage/morphology/greyreconstruct.py
Normal file
211
venv/Lib/site-packages/skimage/morphology/greyreconstruct.py
Normal file
|
@ -0,0 +1,211 @@
|
|||
"""
|
||||
This morphological reconstruction routine was adapted from CellProfiler, code
|
||||
licensed under both GPL and BSD licenses.
|
||||
|
||||
Website: http://www.cellprofiler.org
|
||||
Copyright (c) 2003-2009 Massachusetts Institute of Technology
|
||||
Copyright (c) 2009-2011 Broad Institute
|
||||
All rights reserved.
|
||||
Original author: Lee Kamentsky
|
||||
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from ..filters._rank_order import rank_order
|
||||
|
||||
|
||||
def reconstruction(seed, mask, method='dilation', selem=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 structuring element 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 structuring element, 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'.
|
||||
selem : 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 structuring element.
|
||||
Default is located on the geometrical center of the selem, in that case
|
||||
selem 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 greyscale 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.")
|
||||
try:
|
||||
from ._greyreconstruct import reconstruction_loop
|
||||
except ImportError:
|
||||
raise ImportError("_greyreconstruct extension not available.")
|
||||
|
||||
if selem is None:
|
||||
selem = np.ones([3] * seed.ndim, dtype=bool)
|
||||
else:
|
||||
selem = selem.astype(bool)
|
||||
|
||||
if offset is None:
|
||||
if not all([d % 2 == 1 for d in selem.shape]):
|
||||
raise ValueError("Footprint dimensions must all be odd")
|
||||
offset = np.array([d // 2 for d in selem.shape])
|
||||
else:
|
||||
if offset.ndim != selem.ndim:
|
||||
raise ValueError("Offset and selem ndims must be equal.")
|
||||
if not all([(0 <= o < d) for o, d in zip(offset, selem.shape)]):
|
||||
raise ValueError("Offset must be included inside selem")
|
||||
|
||||
# Cross out the center of the selem
|
||||
selem[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(selem.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' "
|
||||
"or 'dilation'. Got '%s'." % method)
|
||||
images = np.full(dims, pad_value, dtype='float64')
|
||||
images[(0, *inside_slices)] = seed
|
||||
images[(1, *inside_slices)] = mask
|
||||
|
||||
# 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
|
||||
selem_mgrid = np.mgrid[[slice(-o, d - o)
|
||||
for d, o in zip(selem.shape, offset)]]
|
||||
selem_offsets = selem_mgrid[:, selem].transpose()
|
||||
nb_strides = np.array([np.sum(value_stride * selem_offset)
|
||||
for selem_offset in selem_offsets], np.int32)
|
||||
|
||||
images = images.flatten()
|
||||
|
||||
# Erosion goes smallest to largest; dilation goes largest to smallest.
|
||||
index_sorted = np.argsort(images).astype(np.int32)
|
||||
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(len(images), -1, np.int32)
|
||||
next = np.full(len(images), -1, np.int32)
|
||||
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]
|
||||
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(selem.shape) - 1)
|
||||
return rec_img[inside_slices]
|
670
venv/Lib/site-packages/skimage/morphology/max_tree.py
Normal file
670
venv/Lib/site-packages/skimage/morphology/max_tree.py
Normal file
|
@ -0,0 +1,670 @@
|
|||
"""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 structuring element, but rather act with a
|
||||
flexible structuring element that meets a certain criterion.
|
||||
|
||||
This implementation provides functions for:
|
||||
1. max-tree generation
|
||||
2. area openings / closings
|
||||
3. diameter openings / closings
|
||||
4. local maxima
|
||||
|
||||
References:
|
||||
.. [1] Salembier, P., Oliveras, A., & Garrido, L. (1998). Antiextensive
|
||||
Connected Operators for Image and Sequence Processing.
|
||||
IEEE Transactions on Image Processing, 7(4), 555-570.
|
||||
:DOI:10.1109/83.663500
|
||||
.. [2] Berger, C., Geraud, T., Levillain, R., Widynski, N., Baillard, A.,
|
||||
Bertin, E. (2007). Effective Component Tree Computation with
|
||||
Application to Pattern Recognition in Astronomical Imaging.
|
||||
In International Conference on Image Processing (ICIP) (pp. 41-44).
|
||||
:DOI:10.1109/ICIP.2007.4379949
|
||||
.. [3] Najman, L., & Couprie, M. (2006). Building the component tree in
|
||||
quasi-linear time. IEEE Transactions on Image Processing, 15(11),
|
||||
3531-3539.
|
||||
:DOI:10.1109/TIP.2006.877518
|
||||
.. [4] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:10.1109/TIP.2014.2336551
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ._util import _validate_connectivity, _offsets_to_raveled_neighbors
|
||||
from ..util import invert
|
||||
|
||||
from . import _max_tree
|
||||
|
||||
unsigned_int_types = [np.uint8, np.uint16, np.uint32, np.uint64]
|
||||
signed_int_types = [np.int8, np.int16, np.int32, np.int64]
|
||||
signed_float_types = [np.float16, np.float32, np.float64]
|
||||
|
||||
|
||||
# building the max tree.
|
||||
def max_tree(image, connectivity=1):
|
||||
"""Build the max tree from an image.
|
||||
|
||||
Component trees represent the hierarchical structure of the connected
|
||||
components resulting from sequential thresholding operations applied to an
|
||||
image. A connected component at one level is parent of a component at a
|
||||
higher level if the latter is included in the first. A max-tree is an
|
||||
efficient representation of a component tree. A connected component at
|
||||
one level is represented by one reference pixel at this level, which is
|
||||
parent to all other pixels at that level and to the reference pixel at the
|
||||
level above. The max-tree is the basis for many morphological operators,
|
||||
namely connected operators.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
The input image for which the max-tree is to be calculated.
|
||||
This image can be of any type.
|
||||
connectivity : unsigned int, optional
|
||||
The neighborhood connectivity. The integer represents the maximum
|
||||
number of orthogonal steps to reach a neighbor. In 2D, it is 1 for
|
||||
a 4-neighborhood and 2 for a 8-neighborhood. Default value is 1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
parent : ndarray, int64
|
||||
Array of same shape as image. The value of each pixel is the index of
|
||||
its parent in the ravelled array.
|
||||
|
||||
tree_traverser : 1D array, int64
|
||||
The ordered pixel indices (referring to the ravelled array). The pixels
|
||||
are ordered such that every pixel is preceded by its parent (except for
|
||||
the root which has no parent).
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Salembier, P., Oliveras, A., & Garrido, L. (1998). Antiextensive
|
||||
Connected Operators for Image and Sequence Processing.
|
||||
IEEE Transactions on Image Processing, 7(4), 555-570.
|
||||
:DOI:`10.1109/83.663500`
|
||||
.. [2] Berger, C., Geraud, T., Levillain, R., Widynski, N., Baillard, A.,
|
||||
Bertin, E. (2007). Effective Component Tree Computation with
|
||||
Application to Pattern Recognition in Astronomical Imaging.
|
||||
In International Conference on Image Processing (ICIP) (pp. 41-44).
|
||||
:DOI:`10.1109/ICIP.2007.4379949`
|
||||
.. [3] Najman, L., & Couprie, M. (2006). Building the component tree in
|
||||
quasi-linear time. IEEE Transactions on Image Processing, 15(11),
|
||||
3531-3539.
|
||||
:DOI:`10.1109/TIP.2006.877518`
|
||||
.. [4] Carlinet, E., & Geraud, T. (2014). A Comparative Review of
|
||||
Component Tree Computation Algorithms. IEEE Transactions on Image
|
||||
Processing, 23(9), 3885-3895.
|
||||
:DOI:`10.1109/TIP.2014.2336551`
|
||||
|
||||
Examples
|
||||
--------
|
||||
We create a small sample image (Figure 1 from [4]) and build the max-tree.
|
||||
|
||||
>>> image = np.array([[15, 13, 16], [12, 12, 10], [16, 12, 14]])
|
||||
>>> P, S = max_tree(image, connectivity=2)
|
||||
"""
|
||||
# User defined masks are not allowed, as there might be more than one
|
||||
# connected component in the mask (and therefore not a single tree that
|
||||
# represents the image). Mask here is an image that is 0 on the border
|
||||
# and 1 everywhere else.
|
||||
mask = np.ones(image.shape)
|
||||
for k in range(len(image.shape)):
|
||||
np.moveaxis(mask, k, 0)[0] = 0
|
||||
np.moveaxis(mask, k, 0)[-1] = 0
|
||||
|
||||
neighbors, offset = _validate_connectivity(image.ndim, connectivity,
|
||||
offset=None)
|
||||
|
||||
# initialization of the parent image
|
||||
parent = np.zeros(image.shape, dtype=np.int64)
|
||||
|
||||
# flat_neighborhood contains a list of offsets allowing one to find the
|
||||
# neighbors in the ravelled image.
|
||||
flat_neighborhood = _offsets_to_raveled_neighbors(image.shape, neighbors,
|
||||
offset).astype(np.int32)
|
||||
|
||||
# pixels need to be sorted according to their gray level.
|
||||
tree_traverser = np.argsort(image.ravel()).astype(np.int64)
|
||||
|
||||
# call of cython function.
|
||||
_max_tree._max_tree(image.ravel(), mask.ravel().astype(np.uint8),
|
||||
flat_neighborhood, offset.astype(np.int32),
|
||||
np.array(image.shape, dtype=np.int32),
|
||||
parent.ravel(), tree_traverser)
|
||||
|
||||
return parent, tree_traverser
|
||||
|
||||
|
||||
def area_opening(image, area_threshold=64, connectivity=1,
|
||||
parent=None, tree_traverser=None):
|
||||
"""Perform an area opening of the image.
|
||||
|
||||
Area opening removes all bright structures of an image with
|
||||
a surface smaller than area_threshold.
|
||||
The output image is thus the largest image smaller than the input
|
||||
for which all local maxima have at least a surface of
|
||||
area_threshold pixels.
|
||||
|
||||
Area openings are similar to morphological openings, but
|
||||
they do not use a fixed structuring element, 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(np.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(np.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 structuring element, 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(np.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(np.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(np.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
|
227
venv/Lib/site-packages/skimage/morphology/misc.py
Normal file
227
venv/Lib/site-packages/skimage/morphology/misc.py
Normal file
|
@ -0,0 +1,227 @@
|
|||
"""Miscellaneous morphology functions."""
|
||||
import numpy as np
|
||||
import functools
|
||||
from scipy import ndimage as ndi
|
||||
from .._shared.utils import warn
|
||||
from .selem import _default_selem
|
||||
|
||||
# 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_selem(func):
|
||||
"""Decorator to add a default structuring element 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 structuring element of same dimension
|
||||
as the input image with connectivity 1.
|
||||
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def func_out(image, selem=None, *args, **kwargs):
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
return func(image, selem=selem, *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. "
|
||||
"Got %s." % ar.dtype)
|
||||
|
||||
|
||||
def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False):
|
||||
"""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.
|
||||
in_place : bool, optional (default: False)
|
||||
If ``True``, remove the objects in the input array itself.
|
||||
Otherwise, make a copy.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
If the input array is of an invalid type, such as float or string.
|
||||
ValueError
|
||||
If the input array contains negative values.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray, same shape and type as input `ar`
|
||||
The input array with small connected components removed.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage import morphology
|
||||
>>> a = np.array([[0, 0, 0, 1, 0],
|
||||
... [1, 1, 1, 0, 0],
|
||||
... [1, 1, 1, 0, 1]], bool)
|
||||
>>> b = morphology.remove_small_objects(a, 6)
|
||||
>>> b
|
||||
array([[False, False, False, False, False],
|
||||
[ True, True, True, False, False],
|
||||
[ True, True, True, False, False]])
|
||||
>>> c = morphology.remove_small_objects(a, 7, connectivity=2)
|
||||
>>> c
|
||||
array([[False, False, False, True, False],
|
||||
[ True, True, True, False, False],
|
||||
[ True, True, True, False, False]])
|
||||
>>> d = morphology.remove_small_objects(a, 6, in_place=True)
|
||||
>>> d is a
|
||||
True
|
||||
|
||||
"""
|
||||
# Raising type error if not int or bool
|
||||
_check_dtype_supported(ar)
|
||||
|
||||
if in_place:
|
||||
out = ar
|
||||
else:
|
||||
out = ar.copy()
|
||||
|
||||
if min_size == 0: # shortcut for efficiency
|
||||
return out
|
||||
|
||||
if out.dtype == bool:
|
||||
selem = ndi.generate_binary_structure(ar.ndim, connectivity)
|
||||
ccs = np.zeros_like(ar, dtype=np.int32)
|
||||
ndi.label(ar, selem, 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, in_place=False):
|
||||
"""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.
|
||||
in_place : bool, optional (default: False)
|
||||
If `True`, remove the connected components in the input array itself.
|
||||
Otherwise, make a copy.
|
||||
|
||||
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, in_place=True)
|
||||
>>> 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 in_place:
|
||||
out = ar
|
||||
else:
|
||||
out = ar.copy()
|
||||
|
||||
# Creating the inverse of ar
|
||||
if in_place:
|
||||
np.logical_not(out, out=out)
|
||||
else:
|
||||
out = np.logical_not(out)
|
||||
|
||||
# removing small objects from the inverse of ar
|
||||
out = remove_small_objects(out, area_threshold, connectivity, in_place)
|
||||
|
||||
if in_place:
|
||||
np.logical_not(out, out=out)
|
||||
else:
|
||||
out = np.logical_not(out)
|
||||
|
||||
return out
|
359
venv/Lib/site-packages/skimage/morphology/selem.py
Normal file
359
venv/Lib/site-packages/skimage/morphology/selem.py
Normal file
|
@ -0,0 +1,359 @@
|
|||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
from .. import draw
|
||||
|
||||
|
||||
def square(width, dtype=np.uint8):
|
||||
"""Generates a flat, square-shaped structuring element.
|
||||
|
||||
Every pixel along the perimeter has a chessboard distance
|
||||
no greater than radius (radius=floor(width/2)) pixels.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : int
|
||||
The width and height of the square.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
A structuring element consisting only of ones, i.e. every
|
||||
pixel belongs to the neighborhood.
|
||||
|
||||
"""
|
||||
return np.ones((width, width), dtype=dtype)
|
||||
|
||||
|
||||
def rectangle(width, height, dtype=np.uint8):
|
||||
"""Generates a flat, rectangular-shaped structuring element.
|
||||
|
||||
Every pixel in the rectangle generated for a given width and given height
|
||||
belongs to the neighborhood.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : int
|
||||
The width of the rectangle.
|
||||
height : int
|
||||
The height of the rectangle.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
A structuring element consisting only of ones, i.e. every
|
||||
pixel belongs to the neighborhood.
|
||||
|
||||
"""
|
||||
return np.ones((width, height), dtype=dtype)
|
||||
|
||||
|
||||
def diamond(radius, dtype=np.uint8):
|
||||
"""Generates a flat, diamond-shaped structuring element.
|
||||
|
||||
A pixel is part of the neighborhood (i.e. labeled 1) if
|
||||
the city block/Manhattan distance between it and the center of
|
||||
the neighborhood is no greater than radius.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
The radius of the diamond-shaped structuring element.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
"""
|
||||
L = np.arange(0, radius * 2 + 1)
|
||||
I, J = np.meshgrid(L, L)
|
||||
return np.array(np.abs(I - radius) + np.abs(J - radius) <= radius,
|
||||
dtype=dtype)
|
||||
|
||||
|
||||
def disk(radius, dtype=np.uint8):
|
||||
"""Generates a flat, disk-shaped structuring element.
|
||||
|
||||
A pixel is within the neighborhood if the Euclidean distance between
|
||||
it and the origin is no greater than radius.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
The radius of the disk-shaped structuring element.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
"""
|
||||
L = np.arange(-radius, radius + 1)
|
||||
X, Y = np.meshgrid(L, L)
|
||||
return np.array((X ** 2 + Y ** 2) <= radius ** 2, dtype=dtype)
|
||||
|
||||
|
||||
def ellipse(width, height, dtype=np.uint8):
|
||||
"""Generates a flat, ellipse-shaped structuring element.
|
||||
|
||||
Every pixel along the perimeter of ellipse satisfies
|
||||
the equation ``(x/width+1)**2 + (y/height+1)**2 = 1``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : int
|
||||
The width of the ellipse-shaped structuring element.
|
||||
height : int
|
||||
The height of the ellipse-shaped structuring element.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.morphology import selem
|
||||
>>> selem.ellipse(5, 3)
|
||||
array([[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0]], dtype=uint8)
|
||||
|
||||
"""
|
||||
selem = np.zeros((2 * height + 1, 2 * width + 1), dtype=dtype)
|
||||
rows, cols = draw.ellipse(height, width, height + 1, width + 1)
|
||||
selem[rows, cols] = 1
|
||||
return selem
|
||||
|
||||
|
||||
def cube(width, dtype=np.uint8):
|
||||
""" Generates a cube-shaped structuring element.
|
||||
|
||||
This is the 3D equivalent of a square.
|
||||
Every pixel along the perimeter has a chessboard distance
|
||||
no greater than radius (radius=floor(width/2)) pixels.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
width : int
|
||||
The width, height and depth of the cube.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
A structuring element consisting only of ones, i.e. every
|
||||
pixel belongs to the neighborhood.
|
||||
|
||||
"""
|
||||
return np.ones((width, width, width), dtype=dtype)
|
||||
|
||||
|
||||
def octahedron(radius, dtype=np.uint8):
|
||||
"""Generates a octahedron-shaped structuring element.
|
||||
|
||||
This is the 3D equivalent of a diamond.
|
||||
A pixel is part of the neighborhood (i.e. labeled 1) if
|
||||
the city block/Manhattan distance between it and the center of
|
||||
the neighborhood is no greater than radius.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
The radius of the octahedron-shaped structuring element.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
"""
|
||||
# note that in contrast to diamond(), this method allows non-integer radii
|
||||
n = 2 * radius + 1
|
||||
Z, Y, X = np.mgrid[-radius:radius:n * 1j,
|
||||
-radius:radius:n * 1j,
|
||||
-radius:radius:n * 1j]
|
||||
s = np.abs(X) + np.abs(Y) + np.abs(Z)
|
||||
return np.array(s <= radius, dtype=dtype)
|
||||
|
||||
|
||||
def ball(radius, dtype=np.uint8):
|
||||
"""Generates a ball-shaped structuring element.
|
||||
|
||||
This is the 3D equivalent of a disk.
|
||||
A pixel is within the neighborhood if the Euclidean distance between
|
||||
it and the origin is no greater than radius.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
radius : int
|
||||
The radius of the ball-shaped structuring element.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
"""
|
||||
n = 2 * radius + 1
|
||||
Z, Y, X = np.mgrid[-radius:radius:n * 1j,
|
||||
-radius:radius:n * 1j,
|
||||
-radius:radius:n * 1j]
|
||||
s = X ** 2 + Y ** 2 + Z ** 2
|
||||
return np.array(s <= radius * radius, dtype=dtype)
|
||||
|
||||
|
||||
def octagon(m, n, dtype=np.uint8):
|
||||
"""Generates an octagon shaped structuring element.
|
||||
|
||||
For a given size of (m) horizontal and vertical sides
|
||||
and a given (n) height or width of slanted sides octagon is generated.
|
||||
The slanted sides are 45 or 135 degrees to the horizontal axis
|
||||
and hence the widths and heights are equal.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
m : int
|
||||
The size of the horizontal and vertical sides.
|
||||
n : int
|
||||
The height or width of the slanted sides.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
|
||||
"""
|
||||
from . import convex_hull_image
|
||||
selem = np.zeros((m + 2 * n, m + 2 * n))
|
||||
selem[0, n] = 1
|
||||
selem[n, 0] = 1
|
||||
selem[0, m + n - 1] = 1
|
||||
selem[m + n - 1, 0] = 1
|
||||
selem[-1, n] = 1
|
||||
selem[n, -1] = 1
|
||||
selem[-1, m + n - 1] = 1
|
||||
selem[m + n - 1, -1] = 1
|
||||
selem = convex_hull_image(selem).astype(dtype)
|
||||
return selem
|
||||
|
||||
|
||||
def star(a, dtype=np.uint8):
|
||||
"""Generates a star shaped structuring element.
|
||||
|
||||
Start has 8 vertices and is an overlap of square of size `2*a + 1`
|
||||
with its 45 degree rotated version.
|
||||
The slanted sides are 45 or 135 degrees to the horizontal axis.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : int
|
||||
Parameter deciding the size of the star structural element. The side
|
||||
of the square array returned is `2*a + 1 + 2*floor(a / 2)`.
|
||||
|
||||
Other Parameters
|
||||
----------------
|
||||
dtype : data-type
|
||||
The data type of the structuring element.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
|
||||
"""
|
||||
from . import convex_hull_image
|
||||
|
||||
if a == 1:
|
||||
bfilter = np.zeros((3, 3), dtype)
|
||||
bfilter[:] = 1
|
||||
return bfilter
|
||||
|
||||
m = 2 * a + 1
|
||||
n = a // 2
|
||||
selem_square = np.zeros((m + 2 * n, m + 2 * n))
|
||||
selem_square[n: m + n, n: m + n] = 1
|
||||
|
||||
c = (m + 2 * n - 1) // 2
|
||||
selem_rotated = np.zeros((m + 2 * n, m + 2 * n))
|
||||
selem_rotated[0, c] = selem_rotated[-1, c] = 1
|
||||
selem_rotated[c, 0] = selem_rotated[c, -1] = 1
|
||||
selem_rotated = convex_hull_image(selem_rotated).astype(int)
|
||||
|
||||
selem = selem_square + selem_rotated
|
||||
selem[selem > 0] = 1
|
||||
|
||||
return selem.astype(dtype)
|
||||
|
||||
|
||||
def _default_selem(ndim):
|
||||
"""Generates a cross-shaped structuring element (connectivity=1).
|
||||
|
||||
This is the default structuring element (selem) if no selem was specified.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ndim : int
|
||||
Number of dimensions of the image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
selem : ndarray
|
||||
The structuring element where elements of the neighborhood
|
||||
are 1 and 0 otherwise.
|
||||
|
||||
"""
|
||||
return ndi.morphology.generate_binary_structure(ndim, 1)
|
52
venv/Lib/site-packages/skimage/morphology/setup.py
Normal file
52
venv/Lib/site-packages/skimage/morphology/setup.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from skimage._build import cython
|
||||
|
||||
base_path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def configuration(parent_package='', top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration, get_numpy_include_dirs
|
||||
|
||||
config = Configuration('morphology', parent_package, top_path)
|
||||
|
||||
cython(['_skeletonize_cy.pyx',
|
||||
'_convex_hull.pyx',
|
||||
'_greyreconstruct.pyx',
|
||||
'_extrema_cy.pyx'], working_path=base_path)
|
||||
# _skeletonize_3d uses c++, so it must be cythonized separately
|
||||
cython(['_skeletonize_3d_cy.pyx.in'], working_path=base_path)
|
||||
cython(['_extrema_cy.pyx'], working_path=base_path)
|
||||
cython(['_flood_fill_cy.pyx'], working_path=base_path)
|
||||
cython(['_max_tree.pyx'], working_path=base_path)
|
||||
|
||||
config.add_extension('_skeletonize_cy', sources=['_skeletonize_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_convex_hull', sources=['_convex_hull.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_greyreconstruct', sources=['_greyreconstruct.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_max_tree', sources=['_max_tree.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_skeletonize_3d_cy',
|
||||
sources=['_skeletonize_3d_cy.cpp'],
|
||||
include_dirs=[get_numpy_include_dirs()],
|
||||
language='c++')
|
||||
config.add_extension('_extrema_cy', sources=['_extrema_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_flood_fill_cy', sources=['_flood_fill_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
|
||||
return config
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer='scikit-image Developers',
|
||||
author='Damian Eads',
|
||||
maintainer_email='scikit-image@python.org',
|
||||
description='Morphology Wrapper',
|
||||
url='https://github.com/scikit-image/scikit-image',
|
||||
license='SciPy License (BSD Style)',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
from ..._shared.testing import setup_test, teardown_test
|
||||
|
||||
|
||||
def setup():
|
||||
setup_test()
|
||||
|
||||
|
||||
def teardown():
|
||||
teardown_test()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
178
venv/Lib/site-packages/skimage/morphology/tests/test_binary.py
Normal file
178
venv/Lib/site-packages/skimage/morphology/tests/test_binary.py
Normal file
|
@ -0,0 +1,178 @@
|
|||
import numpy as np
|
||||
from numpy import testing
|
||||
|
||||
from skimage import data, color
|
||||
from skimage.util import img_as_bool
|
||||
from skimage.morphology import binary, grey, selem
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
import pytest
|
||||
|
||||
img = color.rgb2gray(data.astronaut())
|
||||
bw_img = img > 100 / 255.
|
||||
|
||||
|
||||
def test_non_square_image():
|
||||
strel = selem.square(3)
|
||||
binary_res = binary.binary_erosion(bw_img[:100, :200], strel)
|
||||
grey_res = img_as_bool(grey.erosion(bw_img[:100, :200], strel))
|
||||
testing.assert_array_equal(binary_res, grey_res)
|
||||
|
||||
|
||||
def test_binary_erosion():
|
||||
strel = selem.square(3)
|
||||
binary_res = binary.binary_erosion(bw_img, strel)
|
||||
grey_res = img_as_bool(grey.erosion(bw_img, strel))
|
||||
testing.assert_array_equal(binary_res, grey_res)
|
||||
|
||||
|
||||
def test_binary_dilation():
|
||||
strel = selem.square(3)
|
||||
binary_res = binary.binary_dilation(bw_img, strel)
|
||||
grey_res = img_as_bool(grey.dilation(bw_img, strel))
|
||||
testing.assert_array_equal(binary_res, grey_res)
|
||||
|
||||
|
||||
def test_binary_closing():
|
||||
strel = selem.square(3)
|
||||
binary_res = binary.binary_closing(bw_img, strel)
|
||||
grey_res = img_as_bool(grey.closing(bw_img, strel))
|
||||
testing.assert_array_equal(binary_res, grey_res)
|
||||
|
||||
|
||||
def test_binary_opening():
|
||||
strel = selem.square(3)
|
||||
binary_res = binary.binary_opening(bw_img, strel)
|
||||
grey_res = img_as_bool(grey.opening(bw_img, strel))
|
||||
testing.assert_array_equal(binary_res, grey_res)
|
||||
|
||||
|
||||
def test_selem_overflow():
|
||||
strel = 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, strel)
|
||||
grey_res = img_as_bool(grey.erosion(img, strel))
|
||||
testing.assert_array_equal(binary_res, grey_res)
|
||||
|
||||
|
||||
def test_out_argument():
|
||||
for func in (binary.binary_erosion, binary.binary_dilation):
|
||||
strel = np.ones((3, 3), dtype=np.uint8)
|
||||
img = np.ones((10, 10))
|
||||
out = np.zeros_like(img)
|
||||
out_saved = out.copy()
|
||||
func(img, strel, out=out)
|
||||
testing.assert_(np.any(out != out_saved))
|
||||
testing.assert_array_equal(out, func(img, strel))
|
||||
|
||||
|
||||
binary_functions = [binary.binary_erosion, binary.binary_dilation,
|
||||
binary.binary_opening, binary.binary_closing]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("function", binary_functions)
|
||||
def test_default_selem(function):
|
||||
strel = selem.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)
|
||||
testing.assert_array_equal(im_expected, im_test)
|
||||
|
||||
def test_3d_fallback_default_selem():
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), np.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)
|
||||
testing.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_selem(function):
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), np.bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
cube = np.ones((3, 3, 3), dtype=np.uint8)
|
||||
|
||||
new_image = function(image, cube)
|
||||
testing.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)
|
||||
|
||||
selem = ndi.generate_binary_structure(2, 1)
|
||||
ndimage_opened = ndi.binary_opening(image, structure=selem)
|
||||
ndimage_closed = ndi.binary_closing(image, structure=selem)
|
||||
|
||||
testing.assert_array_equal(bin_opened, ndimage_opened)
|
||||
testing.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)
|
||||
|
||||
testing.assert_equal(bin_opened.dtype, np.bool)
|
||||
testing.assert_equal(bin_closed.dtype, np.bool)
|
||||
|
||||
testing.assert_equal(int_opened.dtype, np.uint8)
|
||||
testing.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)
|
||||
|
||||
testing.assert_equal(bin_opened.dtype, np.bool)
|
||||
testing.assert_equal(bin_closed.dtype, np.bool)
|
||||
|
||||
testing.assert_equal(int_opened.dtype, np.uint8)
|
||||
testing.assert_equal(int_closed.dtype, np.uint8)
|
||||
|
||||
if __name__ == '__main__':
|
||||
testing.run_module_suite()
|
313
venv/Lib/site-packages/skimage/morphology/tests/test_ccomp.py
Normal file
313
venv/Lib/site-packages/skimage/morphology/tests/test_ccomp.py
Normal file
|
@ -0,0 +1,313 @@
|
|||
import numpy as np
|
||||
|
||||
from skimage.measure import label
|
||||
import skimage.measure._ccomp as ccomp
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
BG = 0 # background value
|
||||
|
||||
|
||||
class TestConnectedComponents:
|
||||
def setup(self):
|
||||
self.x = np.array([
|
||||
[0, 0, 3, 2, 1, 9],
|
||||
[0, 1, 1, 9, 2, 9],
|
||||
[0, 0, 1, 9, 9, 9],
|
||||
[3, 1, 1, 5, 3, 0]])
|
||||
|
||||
self.labels = np.array([
|
||||
[0, 0, 1, 2, 3, 4],
|
||||
[0, 5, 5, 4, 2, 4],
|
||||
[0, 0, 5, 4, 4, 4],
|
||||
[6, 5, 5, 7, 8, 0]])
|
||||
|
||||
# No background - there is no label 0, instead, labelling starts with 1
|
||||
# and all labels are incremented by 1.
|
||||
self.labels_nobg = self.labels + 1
|
||||
# The 0 at lower right corner is isolated, so it should get a new label
|
||||
self.labels_nobg[-1, -1] = 10
|
||||
|
||||
# We say that background value is 9 (and bg label is 0)
|
||||
self.labels_bg_9 = self.labels_nobg.copy()
|
||||
self.labels_bg_9[self.x == 9] = 0
|
||||
# Then, where there was the label 5, we now expect 4 etc.
|
||||
# (we assume that the label of value 9 would normally be 5)
|
||||
self.labels_bg_9[self.labels_bg_9 > 5] -= 1
|
||||
|
||||
def test_basic(self):
|
||||
assert_array_equal(label(self.x), self.labels)
|
||||
|
||||
# Make sure data wasn't modified
|
||||
assert self.x[0, 2] == 3
|
||||
|
||||
# Check that everything works if there is no background
|
||||
assert_array_equal(label(self.x, background=99), self.labels_nobg)
|
||||
# Check that everything works if background value != 0
|
||||
assert_array_equal(label(self.x, background=9), self.labels_bg_9)
|
||||
|
||||
def test_random(self):
|
||||
x = (np.random.rand(20, 30) * 5).astype(np.int)
|
||||
labels = label(x)
|
||||
|
||||
n = labels.max()
|
||||
for i in range(n):
|
||||
values = x[labels == i]
|
||||
assert np.all(values == values[0])
|
||||
|
||||
def test_diag(self):
|
||||
x = np.array([[0, 0, 1],
|
||||
[0, 1, 0],
|
||||
[1, 0, 0]])
|
||||
assert_array_equal(label(x), x)
|
||||
|
||||
def test_4_vs_8(self):
|
||||
x = np.array([[0, 1],
|
||||
[1, 0]], dtype=int)
|
||||
with expected_warnings(["use 'connectivity'"]):
|
||||
assert_array_equal(label(x, 4),
|
||||
[[0, 1],
|
||||
[2, 0]])
|
||||
assert_array_equal(label(x, 8),
|
||||
[[0, 1],
|
||||
[1, 0]])
|
||||
|
||||
assert_array_equal(label(x, connectivity=1),
|
||||
[[0, 1],
|
||||
[2, 0]])
|
||||
assert_array_equal(label(x, connectivity=2),
|
||||
[[0, 1],
|
||||
[1, 0]])
|
||||
|
||||
def test_background(self):
|
||||
x = np.array([[1, 0, 0],
|
||||
[1, 1, 5],
|
||||
[0, 0, 0]])
|
||||
|
||||
assert_array_equal(label(x), [[1, 0, 0],
|
||||
[1, 1, 2],
|
||||
[0, 0, 0]])
|
||||
|
||||
assert_array_equal(label(x, background=0),
|
||||
[[1, 0, 0],
|
||||
[1, 1, 2],
|
||||
[0, 0, 0]])
|
||||
|
||||
def test_background_two_regions(self):
|
||||
x = np.array([[0, 0, 6],
|
||||
[0, 0, 6],
|
||||
[5, 5, 5]])
|
||||
|
||||
res = label(x, background=0)
|
||||
assert_array_equal(res,
|
||||
[[0, 0, 1],
|
||||
[0, 0, 1],
|
||||
[2, 2, 2]])
|
||||
|
||||
def test_background_one_region_center(self):
|
||||
x = np.array([[0, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 0]])
|
||||
|
||||
with expected_warnings(["use 'connectivity'"]):
|
||||
assert_array_equal(label(x, neighbors=4, background=0),
|
||||
[[0, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 0]])
|
||||
assert_array_equal(label(x, connectivity=1, background=0),
|
||||
[[0, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 0]])
|
||||
|
||||
|
||||
def test_return_num(self):
|
||||
x = np.array([[1, 0, 6],
|
||||
[0, 0, 6],
|
||||
[5, 5, 5]])
|
||||
|
||||
assert_array_equal(label(x, return_num=True)[1], 3)
|
||||
|
||||
assert_array_equal(label(x, background=-1, return_num=True)[1], 4)
|
||||
|
||||
|
||||
class TestConnectedComponents3d:
|
||||
def setup(self):
|
||||
self.x = np.zeros((3, 4, 5), int)
|
||||
self.x[0] = np.array([[0, 3, 2, 1, 9],
|
||||
[0, 1, 9, 2, 9],
|
||||
[0, 1, 9, 9, 9],
|
||||
[3, 1, 5, 3, 0]])
|
||||
|
||||
self.x[1] = np.array([[3, 3, 2, 1, 9],
|
||||
[0, 3, 9, 2, 1],
|
||||
[0, 3, 3, 1, 1],
|
||||
[3, 1, 3, 3, 0]])
|
||||
|
||||
self.x[2] = np.array([[3, 3, 8, 8, 0],
|
||||
[2, 3, 9, 8, 8],
|
||||
[2, 3, 0, 8, 0],
|
||||
[2, 1, 0, 0, 0]])
|
||||
|
||||
self.labels = np.zeros((3, 4, 5), int)
|
||||
|
||||
self.labels[0] = np.array([[0, 1, 2, 3, 4],
|
||||
[0, 5, 4, 2, 4],
|
||||
[0, 5, 4, 4, 4],
|
||||
[1, 5, 6, 1, 0]])
|
||||
|
||||
self.labels[1] = np.array([[1, 1, 2, 3, 4],
|
||||
[0, 1, 4, 2, 3],
|
||||
[0, 1, 1, 3, 3],
|
||||
[1, 5, 1, 1, 0]])
|
||||
|
||||
self.labels[2] = np.array([[1, 1, 7, 7, 0],
|
||||
[8, 1, 4, 7, 7],
|
||||
[8, 1, 0, 7, 0],
|
||||
[8, 5, 0, 0, 0]])
|
||||
|
||||
def test_basic(self):
|
||||
labels = label(self.x)
|
||||
assert_array_equal(labels, self.labels)
|
||||
|
||||
assert self.x[0, 0, 2] == 2, \
|
||||
"Data was modified!"
|
||||
|
||||
def test_random(self):
|
||||
x = (np.random.rand(20, 30) * 5).astype(np.int)
|
||||
labels = label(x)
|
||||
|
||||
n = labels.max()
|
||||
for i in range(n):
|
||||
values = x[labels == i]
|
||||
assert np.all(values == values[0])
|
||||
|
||||
def test_diag(self):
|
||||
x = np.zeros((3, 3, 3), int)
|
||||
x[0, 2, 2] = 1
|
||||
x[1, 1, 1] = 1
|
||||
x[2, 0, 0] = 1
|
||||
assert_array_equal(label(x), x)
|
||||
|
||||
def test_4_vs_8(self):
|
||||
x = np.zeros((2, 2, 2), int)
|
||||
x[0, 1, 1] = 1
|
||||
x[1, 0, 0] = 1
|
||||
label4 = x.copy()
|
||||
label4[1, 0, 0] = 2
|
||||
with expected_warnings(["use 'connectivity'"]):
|
||||
assert_array_equal(label(x, 4), label4)
|
||||
assert_array_equal(label(x, 8), x)
|
||||
|
||||
def test_connectivity_1_vs_2(self):
|
||||
x = np.zeros((2, 2, 2), int)
|
||||
x[0, 1, 1] = 1
|
||||
x[1, 0, 0] = 1
|
||||
label1 = x.copy()
|
||||
label1[1, 0, 0] = 2
|
||||
assert_array_equal(label(x, connectivity=1), label1)
|
||||
assert_array_equal(label(x, connectivity=3), x)
|
||||
|
||||
def test_background(self):
|
||||
x = np.zeros((2, 3, 3), int)
|
||||
x[0] = np.array([[1, 0, 0],
|
||||
[1, 0, 0],
|
||||
[0, 0, 0]])
|
||||
x[1] = np.array([[0, 0, 0],
|
||||
[0, 1, 5],
|
||||
[0, 0, 0]])
|
||||
|
||||
lnb = x.copy()
|
||||
lnb[0] = np.array([[1, 2, 2],
|
||||
[1, 2, 2],
|
||||
[2, 2, 2]])
|
||||
lnb[1] = np.array([[2, 2, 2],
|
||||
[2, 1, 3],
|
||||
[2, 2, 2]])
|
||||
lb = x.copy()
|
||||
lb[0] = np.array([[1, BG, BG],
|
||||
[1, BG, BG],
|
||||
[BG, BG, BG]])
|
||||
lb[1] = np.array([[BG, BG, BG],
|
||||
[BG, 1, 2],
|
||||
[BG, BG, BG]])
|
||||
|
||||
assert_array_equal(label(x), lb)
|
||||
assert_array_equal(label(x, background=-1), lnb)
|
||||
|
||||
def test_background_two_regions(self):
|
||||
x = np.zeros((2, 3, 3), int)
|
||||
x[0] = np.array([[0, 0, 6],
|
||||
[0, 0, 6],
|
||||
[5, 5, 5]])
|
||||
x[1] = np.array([[6, 6, 0],
|
||||
[5, 0, 0],
|
||||
[0, 0, 0]])
|
||||
lb = x.copy()
|
||||
lb[0] = np.array([[BG, BG, 1],
|
||||
[BG, BG, 1],
|
||||
[2, 2, 2]])
|
||||
lb[1] = np.array([[1, 1, BG],
|
||||
[2, BG, BG],
|
||||
[BG, BG, BG]])
|
||||
|
||||
res = label(x, background=0)
|
||||
assert_array_equal(res, lb)
|
||||
|
||||
def test_background_one_region_center(self):
|
||||
x = np.zeros((3, 3, 3), int)
|
||||
x[1, 1, 1] = 1
|
||||
|
||||
lb = np.ones_like(x) * BG
|
||||
lb[1, 1, 1] = 1
|
||||
|
||||
with expected_warnings(["use 'connectivity'"]):
|
||||
assert_array_equal(label(x, neighbors=4, background=0), lb)
|
||||
|
||||
assert_array_equal(label(x, connectivity=1, background=0), lb)
|
||||
|
||||
def test_return_num(self):
|
||||
x = np.array([[1, 0, 6],
|
||||
[0, 0, 6],
|
||||
[5, 5, 5]])
|
||||
|
||||
assert_array_equal(label(x, return_num=True)[1], 3)
|
||||
assert_array_equal(label(x, background=-1, return_num=True)[1], 4)
|
||||
|
||||
def test_1D(self):
|
||||
x = np.array((0, 1, 2, 2, 1, 1, 0, 0))
|
||||
xlen = len(x)
|
||||
y = np.array((0, 1, 2, 2, 3, 3, 0, 0))
|
||||
reshapes = ((xlen,),
|
||||
(1, xlen), (xlen, 1),
|
||||
(1, xlen, 1), (xlen, 1, 1), (1, 1, xlen))
|
||||
for reshape in reshapes:
|
||||
x2 = x.reshape(reshape)
|
||||
labelled = label(x2)
|
||||
assert_array_equal(y, labelled.flatten())
|
||||
|
||||
def test_nd(self):
|
||||
x = np.ones((1, 2, 3, 4))
|
||||
with testing.raises(NotImplementedError):
|
||||
label(x)
|
||||
|
||||
|
||||
class TestSupport:
|
||||
def test_reshape(self):
|
||||
shapes_in = ((3, 1, 2), (1, 4, 5), (3, 1, 1), (2, 1), (1,))
|
||||
for shape in shapes_in:
|
||||
shape = np.array(shape)
|
||||
numones = sum(shape == 1)
|
||||
inp = np.random.random(shape)
|
||||
|
||||
fixed, swaps = ccomp.reshape_array(inp)
|
||||
shape2 = fixed.shape
|
||||
# now check that all ones are at the beginning
|
||||
for i in range(numones):
|
||||
assert shape2[i] == 1
|
||||
|
||||
back = ccomp.undo_reshape_array(fixed, swaps)
|
||||
# check that the undo works as expected
|
||||
assert_array_equal(inp, back)
|
|
@ -0,0 +1,171 @@
|
|||
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]),
|
||||
([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]))
|
||||
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, 0, 0, 0]], dtype=bool)
|
||||
assert_array_equal(convex_hull_image(image), 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)
|
||||
|
||||
with expected_warnings(['`neighbors` is deprecated']):
|
||||
out = convex_hull_object(image, neighbors=4)
|
||||
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)
|
623
venv/Lib/site-packages/skimage/morphology/tests/test_extrema.py
Normal file
623
venv/Lib/site-packages/skimage/morphology/tests/test_extrema.py
Normal file
|
@ -0,0 +1,623 @@
|
|||
import math
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from numpy.testing import assert_equal
|
||||
from pytest import raises, warns
|
||||
|
||||
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(unittest.TestCase):
|
||||
|
||||
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 i in range(h_vals.size):
|
||||
maxima = extrema.h_maxima(data, h_vals[i])
|
||||
|
||||
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 i in range(h_vals.size):
|
||||
minima = extrema.h_minima(data, h_vals[i])
|
||||
|
||||
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=np.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=np.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 == np.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 == np.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=np.bool
|
||||
)
|
||||
for dtype in self.supported_dtypes:
|
||||
image = data.astype(dtype)
|
||||
result = extrema.local_maxima(image)
|
||||
assert result.dtype == np.bool
|
||||
assert_equal(result, expected)
|
||||
|
||||
def test_connectivity(self):
|
||||
"""Test results if selem is a scalar."""
|
||||
# Connectivity 1: generates cross shaped structuring element
|
||||
result_conn1 = extrema.local_maxima(self.image, connectivity=1)
|
||||
assert result_conn1.dtype == np.bool
|
||||
assert_equal(result_conn1, self.expected_cross)
|
||||
|
||||
# Connectivity 2: generates square shaped structuring element
|
||||
result_conn2 = extrema.local_maxima(self.image, connectivity=2)
|
||||
assert result_conn2.dtype == np.bool
|
||||
assert_equal(result_conn2, self.expected_default)
|
||||
|
||||
# Connectivity 3: generates square shaped structuring element
|
||||
result_conn3 = extrema.local_maxima(self.image, connectivity=3)
|
||||
assert result_conn3.dtype == np.bool
|
||||
assert_equal(result_conn3, self.expected_default)
|
||||
|
||||
def test_selem(self):
|
||||
"""Test results if selem is given."""
|
||||
selem_cross = np.array(
|
||||
[[0, 1, 0], [1, 1, 1], [0, 1, 0]], dtype=np.bool)
|
||||
result_selem_cross = extrema.local_maxima(
|
||||
self.image, selem=selem_cross)
|
||||
assert result_selem_cross.dtype == np.bool
|
||||
assert_equal(result_selem_cross, self.expected_cross)
|
||||
|
||||
for selem in [
|
||||
((True,) * 3,) * 3,
|
||||
np.ones((3, 3), dtype=np.float64),
|
||||
np.ones((3, 3), dtype=np.uint8),
|
||||
np.ones((3, 3), dtype=np.bool),
|
||||
]:
|
||||
# Test different dtypes for selem which expects a boolean array but
|
||||
# will accept and convert other types if possible
|
||||
result_selem_square = extrema.local_maxima(self.image, selem=selem)
|
||||
assert result_selem_square.dtype == np.bool
|
||||
assert_equal(result_selem_square, self.expected_default)
|
||||
|
||||
selem_x = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], dtype=np.bool)
|
||||
expected_selem_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=np.bool
|
||||
)
|
||||
result_selem_x = extrema.local_maxima(self.image, selem=selem_x)
|
||||
assert result_selem_x.dtype == np.bool
|
||||
assert_equal(result_selem_x, expected_selem_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 == np.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=np.bool
|
||||
)
|
||||
result_without_border = extrema.local_maxima(
|
||||
self.image, connectivity=1, allow_borders=False)
|
||||
assert result_with_boder.dtype == np.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=np.bool)
|
||||
result_1d = extrema.local_maxima(x_1d)
|
||||
assert result_1d.dtype == np.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=np.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 == np.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 == np.bool
|
||||
assert_equal(result, expected)
|
||||
# test for local minima
|
||||
result = extrema.local_minima(const_image)
|
||||
assert result.dtype == np.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=np.bool
|
||||
)
|
||||
|
||||
# Test for local maxima with automatic step calculation
|
||||
result = extrema.local_maxima(image)
|
||||
assert result.dtype == np.bool
|
||||
assert_equal(result, expected_result)
|
||||
|
||||
# Test for local minima with automatic step calculation
|
||||
result = extrema.local_minima(inverted_image)
|
||||
assert result.dtype == np.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, selem=np.ones((3, 3, 3), dtype=np.bool))
|
||||
with raises(ValueError, match="number of dimensions"):
|
||||
extrema.local_maxima(
|
||||
self.image, selem=np.ones((3,), dtype=np.bool))
|
||||
|
||||
# All dimensions in selem must be of size 3
|
||||
with raises(ValueError, match="dimension size"):
|
||||
extrema.local_maxima(
|
||||
self.image, selem=np.ones((2, 3), dtype=np.bool))
|
||||
with raises(ValueError, match="dimension size"):
|
||||
extrema.local_maxima(
|
||||
self.image, selem=np.ones((5, 5), dtype=np.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 structuring element, 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 == np.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
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
np.testing.run_module_suite()
|
|
@ -0,0 +1,279 @@
|
|||
import numpy as np
|
||||
import pytest
|
||||
from pytest import raises
|
||||
|
||||
from skimage.morphology import flood, flood_fill
|
||||
from skimage._shared.testing import expected_warnings
|
||||
|
||||
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 == np.bool
|
||||
|
||||
# Maintain shape, even with zero size present
|
||||
assert flood(np.empty((20, 0, 4)), ()).shape == (20, 0, 4)
|
||||
|
||||
|
||||
def test_float16():
|
||||
image = np.array([9., 0.1, 42], dtype=np.float16)
|
||||
with raises(TypeError, match="dtype of `image` is float16"):
|
||||
flood_fill(image, 0, 1)
|
||||
|
||||
|
||||
def test_overrange_tolerance_int():
|
||||
image = np.arange(256, dtype=np.uint8).reshape((8, 8, 4))
|
||||
expected = np.zeros_like(image)
|
||||
|
||||
output = flood_fill(image, (7, 7, 3), 0, tolerance=379)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
|
||||
def test_overrange_tolerance_float():
|
||||
max_value = np.finfo(np.float32).max
|
||||
min_value = np.finfo(np.float32).min
|
||||
|
||||
image = np.random.uniform(size=(64, 64), low=-1., high=1.).astype(
|
||||
np.float32)
|
||||
image *= max_value
|
||||
|
||||
expected = np.ones_like(image)
|
||||
output = flood_fill(image, (0, 1), 1., tolerance=max_value * 10)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
|
||||
def test_inplace_int():
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[0, 1, 1, 1, 3, 3, 4]])
|
||||
|
||||
flood_fill(image, (0, 0), 5, in_place=True)
|
||||
|
||||
expected = np.array([[5, 5, 5, 5, 5, 5, 5],
|
||||
[5, 1, 1, 5, 2, 2, 5],
|
||||
[5, 1, 1, 5, 2, 2, 5],
|
||||
[1, 5, 5, 5, 5, 5, 3],
|
||||
[5, 1, 1, 1, 3, 3, 4]])
|
||||
|
||||
np.testing.assert_array_equal(image, expected)
|
||||
|
||||
|
||||
def test_inplace_float():
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[0, 1, 1, 1, 3, 3, 4]], dtype=np.float32)
|
||||
|
||||
flood_fill(image, (0, 0), 5, in_place=True)
|
||||
|
||||
expected = np.array([[5., 5., 5., 5., 5., 5., 5.],
|
||||
[5., 1., 1., 5., 2., 2., 5.],
|
||||
[5., 1., 1., 5., 2., 2., 5.],
|
||||
[1., 5., 5., 5., 5., 5., 3.],
|
||||
[5., 1., 1., 1., 3., 3., 4.]], dtype=np.float32)
|
||||
|
||||
np.testing.assert_allclose(image, expected)
|
||||
|
||||
|
||||
def test_inplace_noncontiguous():
|
||||
image = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[0, 1, 1, 1, 3, 3, 4]])
|
||||
|
||||
# Transpose is noncontiguous
|
||||
image2 = image[::2, ::2]
|
||||
|
||||
flood_fill(image2, (0, 0), 5, in_place=True)
|
||||
|
||||
# The inplace modified result
|
||||
expected2 = np.array([[5, 5, 5, 5],
|
||||
[5, 1, 2, 5],
|
||||
[5, 1, 3, 4]])
|
||||
|
||||
np.testing.assert_allclose(image2, expected2)
|
||||
|
||||
# Projected back through the view, `image` also modified
|
||||
expected = np.array([[5, 0, 5, 0, 5, 0, 5],
|
||||
[0, 1, 1, 0, 2, 2, 0],
|
||||
[5, 1, 1, 0, 2, 2, 5],
|
||||
[1, 0, 0, 0, 0, 0, 3],
|
||||
[5, 1, 1, 1, 3, 3, 4]])
|
||||
|
||||
np.testing.assert_allclose(image, expected)
|
||||
|
||||
|
||||
def test_inplace_int_deprecated():
|
||||
"""This test is deprecated and will be removed in
|
||||
version 0.19.0. See #4248.
|
||||
"""
|
||||
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]])
|
||||
|
||||
with expected_warnings(['The `inplace`']):
|
||||
flood_fill(image, (0, 0), 5, inplace=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_deprecated():
|
||||
"""This test is deprecated and will be removed in
|
||||
version 0.19.0. See #4248.
|
||||
"""
|
||||
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)
|
||||
|
||||
with expected_warnings(['The `inplace`']):
|
||||
flood_fill(image, (0, 0), 5, inplace=True)
|
||||
|
||||
expected = np.array([[5., 5., 5., 5., 5., 5., 5.],
|
||||
[5., 1., 1., 5., 2., 2., 5.],
|
||||
[5., 1., 1., 5., 2., 2., 5.],
|
||||
[1., 5., 5., 5., 5., 5., 3.],
|
||||
[5., 1., 1., 1., 3., 3., 4.]], dtype=np.float32)
|
||||
|
||||
np.testing.assert_allclose(image, expected)
|
||||
|
||||
|
||||
def test_1d():
|
||||
image = np.arange(11)
|
||||
expected = np.array([0, 1, -20, -20, -20, -20, -20, -20, -20, 9, 10])
|
||||
|
||||
output = flood_fill(image, 5, -20, tolerance=3)
|
||||
output2 = flood_fill(image, (5,), -20, tolerance=3)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
np.testing.assert_equal(output, output2)
|
||||
|
||||
|
||||
def test_wraparound():
|
||||
# If the borders (or neighbors) aren't correctly accounted for, this fails,
|
||||
# because the algorithm uses an ravelled array.
|
||||
test = np.zeros((5, 7), dtype=np.float64)
|
||||
test[:, 3] = 100
|
||||
|
||||
expected = np.array([[-1., -1., -1., 100., 0., 0., 0.],
|
||||
[-1., -1., -1., 100., 0., 0., 0.],
|
||||
[-1., -1., -1., 100., 0., 0., 0.],
|
||||
[-1., -1., -1., 100., 0., 0., 0.],
|
||||
[-1., -1., -1., 100., 0., 0., 0.]])
|
||||
|
||||
np.testing.assert_equal(flood_fill(test, (0, 0), -1), expected)
|
||||
|
||||
|
||||
def test_neighbors():
|
||||
# This test will only pass if the neighbors are exactly correct
|
||||
test = np.zeros((5, 7), dtype=np.float64)
|
||||
test[:, 3] = 100
|
||||
|
||||
expected = np.array([[0, 0, 0, 255, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0, 0],
|
||||
[0, 0, 0, 255, 0, 0, 0]])
|
||||
output = flood_fill(test, (0, 3), 255)
|
||||
|
||||
np.testing.assert_equal(output, expected)
|
||||
|
||||
test[2] = 100
|
||||
expected[2] = 255
|
||||
|
||||
output2 = flood_fill(test, (2, 3), 255)
|
||||
|
||||
np.testing.assert_equal(output2, expected)
|
||||
|
||||
|
||||
def test_selem():
|
||||
# Basic tests for nonstandard structuring elements
|
||||
selem = 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,
|
||||
selem=selem)
|
||||
|
||||
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)
|
||||
|
||||
selem = 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,
|
||||
selem=selem)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
np.testing.run_module_suite()
|
276
venv/Lib/site-packages/skimage/morphology/tests/test_grey.py
Normal file
276
venv/Lib/site-packages/skimage/morphology/tests/test_grey.py
Normal file
|
@ -0,0 +1,276 @@
|
|||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from skimage import color, data, transform
|
||||
from skimage.util import img_as_uint, img_as_ubyte
|
||||
from skimage.morphology import grey, selem
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import (assert_array_equal, assert_equal,
|
||||
TestCase, parametrize, fetch)
|
||||
|
||||
|
||||
class TestMorphology(TestCase):
|
||||
|
||||
# These expected outputs were generated with skimage v0.12.1
|
||||
# using:
|
||||
#
|
||||
# from skimage.morphology.tests.test_grey import TestMorphology
|
||||
# import numpy as np
|
||||
# output = TestMorphology()._build_expected_output()
|
||||
# np.savez_compressed('gray_morph_output.npz', **output)
|
||||
|
||||
def _build_expected_output(self):
|
||||
funcs = (grey.erosion, grey.dilation, grey.opening, grey.closing,
|
||||
grey.white_tophat, grey.black_tophat)
|
||||
selems_2D = (selem.square, selem.diamond,
|
||||
selem.disk, selem.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 selems_2D:
|
||||
for func in funcs:
|
||||
key = '{0}_{1}_{2}'.format(
|
||||
strel.__name__, n, func.__name__)
|
||||
output[key] = func(image, strel(n))
|
||||
|
||||
return output
|
||||
|
||||
def test_gray_morphology(self):
|
||||
expected = dict(np.load(fetch('data/gray_morph_output.npz')))
|
||||
calculated = self._build_expected_output()
|
||||
assert_equal(expected, calculated)
|
||||
|
||||
|
||||
class TestEccentricStructuringElements(TestCase):
|
||||
def setUp(self):
|
||||
self.black_pixel = 255 * np.ones((4, 4), dtype=np.uint8)
|
||||
self.black_pixel[1, 1] = 0
|
||||
self.white_pixel = 255 - self.black_pixel
|
||||
self.selems = [selem.square(2), selem.rectangle(2, 2),
|
||||
selem.rectangle(2, 1), selem.rectangle(1, 2)]
|
||||
|
||||
def test_dilate_erode_symmetry(self):
|
||||
for s in self.selems:
|
||||
c = grey.erosion(self.black_pixel, s)
|
||||
d = grey.dilation(self.white_pixel, s)
|
||||
assert np.all(c == (255 - d))
|
||||
|
||||
def test_open_black_pixel(self):
|
||||
for s in self.selems:
|
||||
grey_open = grey.opening(self.black_pixel, s)
|
||||
assert np.all(grey_open == self.black_pixel)
|
||||
|
||||
def test_close_white_pixel(self):
|
||||
for s in self.selems:
|
||||
grey_close = grey.closing(self.white_pixel, s)
|
||||
assert np.all(grey_close == self.white_pixel)
|
||||
|
||||
def test_open_white_pixel(self):
|
||||
for s in self.selems:
|
||||
assert np.all(grey.opening(self.white_pixel, s) == 0)
|
||||
|
||||
def test_close_black_pixel(self):
|
||||
for s in self.selems:
|
||||
assert np.all(grey.closing(self.black_pixel, s) == 255)
|
||||
|
||||
def test_white_tophat_white_pixel(self):
|
||||
for s in self.selems:
|
||||
tophat = grey.white_tophat(self.white_pixel, s)
|
||||
assert np.all(tophat == self.white_pixel)
|
||||
|
||||
def test_black_tophat_black_pixel(self):
|
||||
for s in self.selems:
|
||||
tophat = grey.black_tophat(self.black_pixel, s)
|
||||
assert np.all(tophat == (255 - self.black_pixel))
|
||||
|
||||
def test_white_tophat_black_pixel(self):
|
||||
for s in self.selems:
|
||||
tophat = grey.white_tophat(self.black_pixel, s)
|
||||
assert np.all(tophat == 0)
|
||||
|
||||
def test_black_tophat_white_pixel(self):
|
||||
for s in self.selems:
|
||||
tophat = grey.black_tophat(self.white_pixel, s)
|
||||
assert np.all(tophat == 0)
|
||||
|
||||
|
||||
grey_functions = [grey.erosion, grey.dilation,
|
||||
grey.opening, grey.closing,
|
||||
grey.white_tophat, grey.black_tophat]
|
||||
|
||||
|
||||
@parametrize("function", grey_functions)
|
||||
def test_default_selem(function):
|
||||
strel = selem.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)
|
||||
testing.assert_array_equal(im_expected, im_test)
|
||||
|
||||
|
||||
def test_3d_fallback_default_selem():
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), np.bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
opened = grey.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)
|
||||
|
||||
|
||||
grey_3d_fallback_functions = [grey.closing, grey.opening]
|
||||
|
||||
|
||||
@parametrize("function", grey_3d_fallback_functions)
|
||||
def test_3d_fallback_cube_selem(function):
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), np.bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
cube = np.ones((3, 3, 3), dtype=np.uint8)
|
||||
|
||||
new_image = function(image, cube)
|
||||
testing.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 = grey.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 = grey.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 = grey.opening(image)
|
||||
closed = grey.closing(image)
|
||||
|
||||
selem = ndi.generate_binary_structure(2, 1)
|
||||
ndimage_opened = ndi.grey_opening(image, footprint=selem)
|
||||
ndimage_closed = ndi.grey_closing(image, footprint=selem)
|
||||
|
||||
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():
|
||||
np.testing.assert_allclose(grey.erosion(im), eroded)
|
||||
np.testing.assert_allclose(grey.dilation(im), dilated)
|
||||
np.testing.assert_allclose(grey.opening(im), opened)
|
||||
np.testing.assert_allclose(grey.closing(im), closed)
|
||||
|
||||
|
||||
def test_uint16():
|
||||
im16, eroded16, dilated16, opened16, closed16 = (
|
||||
map(img_as_uint, [im, eroded, dilated, opened, closed]))
|
||||
np.testing.assert_allclose(grey.erosion(im16), eroded16)
|
||||
np.testing.assert_allclose(grey.dilation(im16), dilated16)
|
||||
np.testing.assert_allclose(grey.opening(im16), opened16)
|
||||
np.testing.assert_allclose(grey.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)
|
||||
grey.dilation(image, out=out_array)
|
||||
assert_array_equal(out_array_big, expected_dilation)
|
||||
grey.erosion(image, out=out_array)
|
||||
testing.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 = grey.erosion(image)
|
||||
testing.assert_array_equal(eroded, expected)
|
457
venv/Lib/site-packages/skimage/morphology/tests/test_max_tree.py
Normal file
457
venv/Lib/site-packages/skimage/morphology/tests/test_max_tree.py
Normal file
|
@ -0,0 +1,457 @@
|
|||
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 import testing
|
||||
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(np.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
|
||||
|
||||
if __name__ == "__main__":
|
||||
np.testing.run_module_suite()
|
191
venv/Lib/site-packages/skimage/morphology/tests/test_misc.py
Normal file
191
venv/Lib/site-packages/skimage/morphology/tests/test_misc.py
Normal file
|
@ -0,0 +1,191 @@
|
|||
import numpy as np
|
||||
from skimage.morphology import remove_small_objects, remove_small_holes
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal, assert_equal
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
test_image = np.array([[0, 0, 0, 1, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 0, 1]], bool)
|
||||
|
||||
|
||||
def test_one_connectivity():
|
||||
expected = np.array([[0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 0, 0]], bool)
|
||||
observed = remove_small_objects(test_image, min_size=6)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_two_connectivity():
|
||||
expected = np.array([[0, 0, 0, 1, 0],
|
||||
[1, 1, 1, 0, 0],
|
||||
[1, 1, 1, 0, 0]], bool)
|
||||
observed = remove_small_objects(test_image, min_size=7, connectivity=2)
|
||||
assert_array_equal(observed, expected)
|
||||
|
||||
|
||||
def test_in_place():
|
||||
image = test_image.copy()
|
||||
observed = remove_small_objects(image, min_size=6, in_place=True)
|
||||
assert_equal(observed is image, True,
|
||||
"remove_small_objects in_place argument failed.")
|
||||
|
||||
|
||||
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]], np.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]], np.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]], np.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, in_place=True)
|
||||
assert_equal(observed is image, True,
|
||||
"remove_small_holes in_place argument failed.")
|
||||
|
||||
|
||||
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=np.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=np.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=np.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=np.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)
|
|
@ -0,0 +1,141 @@
|
|||
"""
|
||||
These tests are originally part of CellProfiler, code licensed under both GPL and BSD licenses.
|
||||
|
||||
Website: http://www.cellprofiler.org
|
||||
Copyright (c) 2003-2009 Massachusetts Institute of Technology
|
||||
Copyright (c) 2009-2011 Broad Institute
|
||||
All rights reserved.
|
||||
Original author: Lee Kamentsky
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from skimage.morphology.greyreconstruct import reconstruction
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_almost_equal
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_two_image_peaks():
|
||||
"""Test reconstruction with two peak pixels isolated by the mask"""
|
||||
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]])
|
||||
|
||||
expected = np.array([[2, 2, 2, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 1, 1, 1, 1, 1],
|
||||
[2, 2, 2, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 3, 3, 3],
|
||||
[1, 1, 1, 1, 1, 3, 3, 3],
|
||||
[1, 1, 1, 1, 1, 3, 3, 3]])
|
||||
assert_array_almost_equal(reconstruction(image, mask), 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)
|
||||
|
||||
|
||||
def test_fill_hole():
|
||||
"""Test reconstruction by erosion, which should fill holes in mask."""
|
||||
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])
|
||||
result = reconstruction(seed, mask, method='erosion')
|
||||
assert_array_almost_equal(result, np.array([0, 3, 6, 4, 4, 4, 4, 4, 2, 0]))
|
||||
|
||||
|
||||
def test_invalid_seed():
|
||||
seed = np.ones((5, 5))
|
||||
mask = np.ones((5, 5))
|
||||
with testing.raises(ValueError):
|
||||
reconstruction(seed * 2, mask,
|
||||
method='dilation')
|
||||
with testing.raises(ValueError):
|
||||
reconstruction(seed * 0.5, mask,
|
||||
method='erosion')
|
||||
|
||||
|
||||
def test_invalid_selem():
|
||||
seed = np.ones((5, 5))
|
||||
mask = np.ones((5, 5))
|
||||
with testing.raises(ValueError):
|
||||
reconstruction(seed, mask,
|
||||
selem=np.ones((4, 4)))
|
||||
with testing.raises(ValueError):
|
||||
reconstruction(seed, mask,
|
||||
selem=np.ones((3, 4)))
|
||||
reconstruction(seed, mask, selem=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 testing.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 testing.raises(ValueError):
|
||||
reconstruction(image, mask, method='dilation',
|
||||
selem=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',
|
||||
selem=np.ones(3), offset=np.array([0])), expected)
|
148
venv/Lib/site-packages/skimage/morphology/tests/test_selem.py
Normal file
148
venv/Lib/site-packages/skimage/morphology/tests/test_selem.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
"""
|
||||
Tests for Morphological structuring elements
|
||||
(skimage.morphology.selem)
|
||||
|
||||
Author: Damian Eads
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from skimage import data
|
||||
from skimage.morphology import selem
|
||||
|
||||
from skimage._shared.testing import assert_equal, fetch
|
||||
from skimage._shared import testing
|
||||
|
||||
|
||||
class TestSElem():
|
||||
|
||||
def test_square_selem(self):
|
||||
"""Test square structuring elements"""
|
||||
for k in range(0, 5):
|
||||
actual_mask = selem.square(k)
|
||||
expected_mask = np.ones((k, k), dtype='uint8')
|
||||
assert_equal(expected_mask, actual_mask)
|
||||
|
||||
def test_rectangle_selem(self):
|
||||
"""Test rectangle structuring elements"""
|
||||
for i in range(0, 5):
|
||||
for j in range(0, 5):
|
||||
actual_mask = selem.rectangle(i, j)
|
||||
expected_mask = np.ones((i, j), dtype='uint8')
|
||||
assert_equal(expected_mask, actual_mask)
|
||||
|
||||
def test_cube_selem(self):
|
||||
"""Test cube structuring elements"""
|
||||
for k in range(0, 5):
|
||||
actual_mask = selem.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_selem_disk(self):
|
||||
"""Test disk structuring elements"""
|
||||
self.strel_worker("data/disk-matlab-output.npz", selem.disk)
|
||||
|
||||
def test_selem_diamond(self):
|
||||
"""Test diamond structuring elements"""
|
||||
self.strel_worker("data/diamond-matlab-output.npz", selem.diamond)
|
||||
|
||||
def test_selem_ball(self):
|
||||
"""Test ball structuring elements"""
|
||||
self.strel_worker_3d("data/disk-matlab-output.npz", selem.ball)
|
||||
|
||||
def test_selem_octahedron(self):
|
||||
"""Test octahedron structuring elements"""
|
||||
self.strel_worker_3d("data/diamond-matlab-output.npz",
|
||||
selem.octahedron)
|
||||
|
||||
def test_selem_octagon(self):
|
||||
"""Test octagon structuring elements"""
|
||||
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 = selem.octagon(5, 3)
|
||||
expected_mask2 = np.array([[0, 1, 0],
|
||||
[1, 1, 1],
|
||||
[0, 1, 0]], dtype=np.uint8)
|
||||
actual_mask2 = selem.octagon(1, 1)
|
||||
assert_equal(expected_mask1, actual_mask1)
|
||||
assert_equal(expected_mask2, actual_mask2)
|
||||
|
||||
def test_selem_ellipse(self):
|
||||
"""Test ellipse structuring elements"""
|
||||
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 = selem.ellipse(5, 3)
|
||||
expected_mask2 = np.array([[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]], dtype=np.uint8)
|
||||
actual_mask2 = selem.ellipse(1, 1)
|
||||
assert_equal(expected_mask1, actual_mask1)
|
||||
assert_equal(expected_mask2, actual_mask2)
|
||||
assert_equal(expected_mask1, selem.ellipse(3, 5).T)
|
||||
assert_equal(expected_mask2, selem.ellipse(1, 1).T)
|
||||
|
||||
def test_selem_star(self):
|
||||
"""Test star structuring elements"""
|
||||
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 = selem.star(4)
|
||||
expected_mask2 = np.array([[1, 1, 1],
|
||||
[1, 1, 1],
|
||||
[1, 1, 1]], dtype=np.uint8)
|
||||
actual_mask2 = selem.star(1)
|
||||
assert_equal(expected_mask1, actual_mask1)
|
||||
assert_equal(expected_mask2, actual_mask2)
|
|
@ -0,0 +1,242 @@
|
|||
import numpy as np
|
||||
from skimage.morphology import skeletonize, medial_axis, thin
|
||||
from skimage.morphology._skeletonize import (_generate_thin_luts,
|
||||
G123_LUT, G123P_LUT)
|
||||
from skimage import draw
|
||||
from scipy.ndimage import correlate
|
||||
from skimage.io import imread
|
||||
from skimage import data
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal, fetch
|
||||
|
||||
|
||||
class TestSkeletonize():
|
||||
def test_skeletonize_no_foreground(self):
|
||||
im = np.zeros((5, 5))
|
||||
result = skeletonize(im)
|
||||
assert_array_equal(result, np.zeros((5, 5)))
|
||||
|
||||
def test_skeletonize_wrong_dim1(self):
|
||||
im = np.zeros((5))
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im)
|
||||
|
||||
def test_skeletonize_wrong_dim2(self):
|
||||
im = np.zeros((5, 5, 5))
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im, method='zhang')
|
||||
|
||||
def test_skeletonize_not_binary(self):
|
||||
im = np.zeros((5, 5))
|
||||
im[0, 0] = 1
|
||||
im[0, 1] = 2
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im)
|
||||
|
||||
def test_skeletonize_unexpected_value(self):
|
||||
im = np.zeros((5, 5))
|
||||
im[0, 0] = 2
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im)
|
||||
|
||||
def test_skeletonize_all_foreground(self):
|
||||
im = np.ones((3, 4))
|
||||
skeletonize(im)
|
||||
|
||||
def test_skeletonize_single_point(self):
|
||||
im = np.zeros((5, 5), np.uint8)
|
||||
im[3, 3] = 1
|
||||
result = skeletonize(im)
|
||||
assert_array_equal(result, im)
|
||||
|
||||
def test_skeletonize_already_thinned(self):
|
||||
im = np.zeros((5, 5), np.uint8)
|
||||
im[3, 1:-1] = 1
|
||||
im[2, -1] = 1
|
||||
im[4, 0] = 1
|
||||
result = skeletonize(im)
|
||||
assert_array_equal(result, im)
|
||||
|
||||
def test_skeletonize_output(self):
|
||||
im = imread(fetch("data/bw_text.png"), as_gray=True)
|
||||
|
||||
# make black the foreground
|
||||
im = (im == 0)
|
||||
result = skeletonize(im)
|
||||
|
||||
expected = np.load(fetch("data/bw_text_skeleton.npy"))
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_skeletonize_num_neighbours(self):
|
||||
# an empty image
|
||||
image = np.zeros((300, 300))
|
||||
|
||||
# foreground object 1
|
||||
image[10:-10, 10:100] = 1
|
||||
image[-100:-10, 10:-10] = 1
|
||||
image[10:-10, -100:-10] = 1
|
||||
|
||||
# foreground object 2
|
||||
rs, cs = draw.line(250, 150, 10, 280)
|
||||
for i in range(10):
|
||||
image[rs + i, cs] = 1
|
||||
rs, cs = draw.line(10, 150, 250, 280)
|
||||
for i in range(20):
|
||||
image[rs + i, cs] = 1
|
||||
|
||||
# foreground object 3
|
||||
ir, ic = np.indices(image.shape)
|
||||
circle1 = (ic - 135)**2 + (ir - 150)**2 < 30**2
|
||||
circle2 = (ic - 135)**2 + (ir - 150)**2 < 20**2
|
||||
image[circle1] = 1
|
||||
image[circle2] = 0
|
||||
result = skeletonize(image)
|
||||
|
||||
# there should never be a 2x2 block of foreground pixels in a skeleton
|
||||
mask = np.array([[1, 1],
|
||||
[1, 1]], np.uint8)
|
||||
blocks = correlate(result, mask, mode='constant')
|
||||
assert not np.any(blocks == 4)
|
||||
|
||||
def test_lut_fix(self):
|
||||
im = np.zeros((6, 6), np.uint8)
|
||||
im[1, 2] = 1
|
||||
im[2, 2] = 1
|
||||
im[2, 3] = 1
|
||||
im[3, 3] = 1
|
||||
im[3, 4] = 1
|
||||
im[4, 4] = 1
|
||||
im[4, 5] = 1
|
||||
result = skeletonize(im)
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
assert np.all(result == expected)
|
||||
|
||||
|
||||
class TestThin():
|
||||
@property
|
||||
def input_image(self):
|
||||
"""image to test thinning with"""
|
||||
ii = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
return ii
|
||||
|
||||
def test_zeros(self):
|
||||
assert np.all(thin(np.zeros((10, 10))) == False)
|
||||
|
||||
def test_iter_1(self):
|
||||
result = thin(self.input_image, 1).astype(np.uint8)
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_noiter(self):
|
||||
result = thin(self.input_image).astype(np.uint8)
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=np.uint8)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_baddim(self):
|
||||
for ii in [np.zeros((3)), np.zeros((3, 3, 3))]:
|
||||
with testing.raises(ValueError):
|
||||
thin(ii)
|
||||
|
||||
def test_lut_generation(self):
|
||||
g123, g123p = _generate_thin_luts()
|
||||
|
||||
assert_array_equal(g123, G123_LUT)
|
||||
assert_array_equal(g123p, G123P_LUT)
|
||||
|
||||
|
||||
class TestMedialAxis():
|
||||
def test_00_00_zeros(self):
|
||||
'''Test skeletonize on an array of all zeros'''
|
||||
result = medial_axis(np.zeros((10, 10), bool))
|
||||
assert np.all(result == False)
|
||||
|
||||
def test_00_01_zeros_masked(self):
|
||||
'''Test skeletonize on an array that is completely masked'''
|
||||
result = medial_axis(np.zeros((10, 10), bool),
|
||||
np.zeros((10, 10), bool))
|
||||
assert np.all(result == False)
|
||||
|
||||
def test_vertical_line(self):
|
||||
'''Test a thick vertical line, issue #3861'''
|
||||
img = np.zeros((9, 9))
|
||||
img[:, 2] = 1
|
||||
img[:, 3] = 1
|
||||
img[:, 4] = 1
|
||||
|
||||
expected = np.full(img.shape, False)
|
||||
expected[:, 3] = True
|
||||
|
||||
result = medial_axis(img)
|
||||
assert_array_equal(result, expected)
|
||||
|
||||
def test_01_01_rectangle(self):
|
||||
'''Test skeletonize on a rectangle'''
|
||||
image = np.zeros((9, 15), bool)
|
||||
image[1:-1, 1:-1] = True
|
||||
#
|
||||
# The result should be four diagonals from the
|
||||
# corners, meeting in a horizontal line
|
||||
#
|
||||
expected = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.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=np.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)
|
|
@ -0,0 +1,188 @@
|
|||
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import scipy.ndimage as ndi
|
||||
|
||||
from skimage import io, draw, data
|
||||
from skimage.data import binary_blobs
|
||||
from skimage.util import img_as_ubyte
|
||||
from skimage.morphology import skeletonize, skeletonize_3d
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_equal, assert_, parametrize, fetch
|
||||
|
||||
# basic behavior tests (mostly copied over from 2D skeletonize)
|
||||
|
||||
def test_skeletonize_wrong_dim():
|
||||
im = np.zeros(5, dtype=np.uint8)
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im, method='lee')
|
||||
|
||||
im = np.zeros((5, 5, 5, 5), dtype=np.uint8)
|
||||
with testing.raises(ValueError):
|
||||
skeletonize(im, method='lee')
|
||||
|
||||
|
||||
def test_skeletonize_1D_old_api():
|
||||
# a corner case of an image of a shape(1, N)
|
||||
im = np.ones((5, 1), dtype=np.uint8)
|
||||
res = skeletonize_3d(im)
|
||||
assert_equal(res, im)
|
||||
|
||||
|
||||
def test_skeletonize_1D():
|
||||
# a corner case of an image of a shape(1, N)
|
||||
im = np.ones((5, 1), dtype=np.uint8)
|
||||
res = skeletonize(im, method='lee')
|
||||
assert_equal(res, im)
|
||||
|
||||
|
||||
def test_skeletonize_no_foreground():
|
||||
im = np.zeros((5, 5), dtype=np.uint8)
|
||||
result = skeletonize(im, method='lee')
|
||||
assert_equal(result, im)
|
||||
|
||||
|
||||
def test_skeletonize_all_foreground():
|
||||
im = np.ones((3, 4), dtype=np.uint8)
|
||||
assert_equal(skeletonize(im, method='lee'),
|
||||
np.array([[0, 0, 0, 0],
|
||||
[1, 1, 1, 1],
|
||||
[0, 0, 0, 0]], dtype=np.uint8))
|
||||
|
||||
|
||||
def test_skeletonize_single_point():
|
||||
im = np.zeros((5, 5), dtype=np.uint8)
|
||||
im[3, 3] = 1
|
||||
result = skeletonize(im, method='lee')
|
||||
assert_equal(result, im)
|
||||
|
||||
|
||||
def test_skeletonize_already_thinned():
|
||||
im = np.zeros((5, 5), dtype=np.uint8)
|
||||
im[3, 1:-1] = 1
|
||||
im[2, -1] = 1
|
||||
im[4, 0] = 1
|
||||
result = skeletonize(im, method='lee')
|
||||
assert_equal(result, im)
|
||||
|
||||
|
||||
def test_dtype_conv():
|
||||
# check that the operation does the right thing with floats etc
|
||||
# also check non-contiguous input
|
||||
img = np.random.random((16, 16))[::2, ::2]
|
||||
img[img < 0.5] = 0
|
||||
|
||||
orig = img.copy()
|
||||
res = skeletonize(img, method='lee')
|
||||
img_max = img_as_ubyte(img).max()
|
||||
|
||||
assert_equal(res.dtype, np.uint8)
|
||||
assert_equal(img, orig) # operation does not clobber the original
|
||||
assert_equal(res.max(), img_max) # the intensity range is preserved
|
||||
|
||||
|
||||
@parametrize("img", [
|
||||
np.ones((8, 8), dtype=float), np.ones((4, 8, 8), dtype=float)
|
||||
])
|
||||
def test_input_with_warning(img):
|
||||
# check that the input is not clobbered
|
||||
# for 2D and 3D images of varying dtypes
|
||||
check_input(img)
|
||||
|
||||
|
||||
@parametrize("img", [
|
||||
np.ones((8, 8), dtype=np.uint8), np.ones((4, 8, 8), dtype=np.uint8),
|
||||
np.ones((8, 8), dtype=bool), np.ones((4, 8, 8), dtype=bool)
|
||||
])
|
||||
def test_input_without_warning(img):
|
||||
# check that the input is not clobbered
|
||||
# for 2D and 3D images of varying dtypes
|
||||
check_input(img)
|
||||
|
||||
|
||||
def check_input(img):
|
||||
orig = img.copy()
|
||||
skeletonize(img, method='lee')
|
||||
assert_equal(img, orig)
|
||||
|
||||
|
||||
def test_skeletonize_num_neighbours():
|
||||
# an empty image
|
||||
image = np.zeros((300, 300))
|
||||
|
||||
# foreground object 1
|
||||
image[10:-10, 10:100] = 1
|
||||
image[-100:-10, 10:-10] = 1
|
||||
image[10:-10, -100:-10] = 1
|
||||
|
||||
# foreground object 2
|
||||
rs, cs = draw.line(250, 150, 10, 280)
|
||||
for i in range(10):
|
||||
image[rs + i, cs] = 1
|
||||
rs, cs = draw.line(10, 150, 250, 280)
|
||||
for i in range(20):
|
||||
image[rs + i, cs] = 1
|
||||
|
||||
# foreground object 3
|
||||
ir, ic = np.indices(image.shape)
|
||||
circle1 = (ic - 135)**2 + (ir - 150)**2 < 30**2
|
||||
circle2 = (ic - 135)**2 + (ir - 150)**2 < 20**2
|
||||
image[circle1] = 1
|
||||
image[circle2] = 0
|
||||
result = skeletonize(image, method='lee')
|
||||
|
||||
# there should never be a 2x2 block of foreground pixels in a skeleton
|
||||
mask = np.array([[1, 1],
|
||||
[1, 1]], np.uint8)
|
||||
blocks = ndi.correlate(result, mask, mode='constant')
|
||||
assert_(not np.any(blocks == 4))
|
||||
|
||||
|
||||
def test_two_hole_image():
|
||||
# test a simple 2D image against FIJI
|
||||
img_o = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
img_f = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.uint8)
|
||||
res = skeletonize(img_o, method='lee')
|
||||
assert_equal(res, img_f)
|
||||
|
||||
|
||||
def test_3d_vs_fiji():
|
||||
# generate an image with blobs and compate its skeleton to
|
||||
# the skeleton generated by FIJI
|
||||
img = binary_blobs(32, 0.05, n_dim=3, seed=1234)
|
||||
img = img[:-2, ...]
|
||||
img = img.astype(np.uint8)*255
|
||||
|
||||
img_s = skeletonize(img)
|
||||
img_f = io.imread(fetch("data/_blobs_3d_fiji_skeleton.tif"))
|
||||
assert_equal(img_s, img_f)
|
127
venv/Lib/site-packages/skimage/morphology/tests/test_util.py
Normal file
127
venv/Lib/site-packages/skimage/morphology/tests/test_util.py
Normal file
|
@ -0,0 +1,127 @@
|
|||
"""Tests for `_util`."""
|
||||
|
||||
|
||||
import pytest
|
||||
import numpy as np
|
||||
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 selem is always of the highest connectivity
|
||||
and all dimensions are > 2.
|
||||
"""
|
||||
selem = np.ones((3,) * len(image_shape), dtype=bool)
|
||||
center = (1,) * len(image_shape)
|
||||
offsets = _util._offsets_to_raveled_neighbors(
|
||||
image_shape, selem, center, order
|
||||
)
|
||||
|
||||
# Assert only neighbors are present, center was removed
|
||||
assert len(offsets) == selem.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 selem 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_selem_smaller_image(image_shape, order):
|
||||
"""
|
||||
Test if a dimension indicated by `image_shape` is smaller than in
|
||||
`selem`.
|
||||
"""
|
||||
selem = np.ones((3,) * len(image_shape), dtype=bool)
|
||||
center = (1,) * len(image_shape)
|
||||
offsets = _util._offsets_to_raveled_neighbors(
|
||||
image_shape, selem, center, order
|
||||
)
|
||||
|
||||
# Assert only neighbors are present, center and duplicates (possible
|
||||
# for this scenario) where removed
|
||||
assert len(offsets) <= selem.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 selem 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)
|
||||
selem = np.ones((3, 3, 3), dtype=bool)
|
||||
center = (1, 1, 1)
|
||||
offsets = _util._offsets_to_raveled_neighbors(
|
||||
image_shape, selem, center
|
||||
)
|
||||
|
||||
desired = np.array([
|
||||
3, -600, 1, -1, 600, -3, 4, 2, 603, -2, -4,
|
||||
-597, 601, -599, -601, -603, 599, 597, 602, -604, 596, -596,
|
||||
-598, -602, 598, 604
|
||||
])
|
||||
assert_array_equal(offsets, desired)
|
||||
|
||||
|
||||
def test_offsets_to_raveled_neighbors_explicit_1():
|
||||
"""Check reviewed example where selem is larger in last dimension."""
|
||||
image_shape = (10, 9, 8, 3)
|
||||
selem = np.ones((3, 3, 3, 4), dtype=bool)
|
||||
center = (1, 1, 1, 1)
|
||||
offsets = _util._offsets_to_raveled_neighbors(
|
||||
image_shape, selem, center
|
||||
)
|
||||
|
||||
desired = np.array([
|
||||
24, 3, 1, -1, -3, -24, -216, 216, -192, 215, -2,
|
||||
-21, -23, 2, -25, -27, 4, 217, 21, 219, -4, 23,
|
||||
25, -240, 240, 192, 27, -213, -219, 213, -215, -217, -243,
|
||||
191, -241, 195, 189, 212, 26, 5, 20, 28, 22, 214,
|
||||
243, -237, -22, 241, -214, -212, 237, -218, -195, -20, 220,
|
||||
-193, -191, 218, -189, -28, -26, 193, -239, -220, 239, 196,
|
||||
221, 242, 236, 238, 194, -244, -188, -238, -211, -196, -194,
|
||||
-190, -236, -19, 244, 29, 188, -242, 190, -187, 197, -235,
|
||||
245
|
||||
])
|
||||
assert_array_equal(offsets, desired)
|
Loading…
Add table
Add a link
Reference in a new issue