using for loop to install conda package
This commit is contained in:
39
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__init__.py
vendored
Normal file
39
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__init__.py
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
from ._expand_labels import expand_labels
|
||||
from .random_walker_segmentation import random_walker
|
||||
from .active_contour_model import active_contour
|
||||
from ._felzenszwalb import felzenszwalb
|
||||
from .slic_superpixels import slic
|
||||
from ._quickshift import quickshift
|
||||
from .boundaries import find_boundaries, mark_boundaries
|
||||
from ._clear_border import clear_border
|
||||
from ._join import join_segmentations, relabel_sequential
|
||||
from ._watershed import watershed
|
||||
from ._chan_vese import chan_vese
|
||||
from .morphsnakes import (morphological_geodesic_active_contour,
|
||||
morphological_chan_vese, inverse_gaussian_gradient,
|
||||
disk_level_set, checkerboard_level_set)
|
||||
from ..morphology import flood, flood_fill
|
||||
|
||||
|
||||
__all__ = [
|
||||
'expand_labels',
|
||||
'random_walker',
|
||||
'active_contour',
|
||||
'felzenszwalb',
|
||||
'slic',
|
||||
'quickshift',
|
||||
'find_boundaries',
|
||||
'mark_boundaries',
|
||||
'clear_border',
|
||||
'join_segmentations',
|
||||
'relabel_sequential',
|
||||
'watershed',
|
||||
'chan_vese',
|
||||
'morphological_geodesic_active_contour',
|
||||
'morphological_chan_vese',
|
||||
'inverse_gaussian_gradient',
|
||||
'disk_level_set',
|
||||
'checkerboard_level_set',
|
||||
'flood',
|
||||
'flood_fill',
|
||||
]
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_chan_vese.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_chan_vese.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_clear_border.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_clear_border.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_expand_labels.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_expand_labels.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_felzenszwalb.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_felzenszwalb.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_join.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_join.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_quickshift.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_quickshift.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_watershed.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/_watershed.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/boundaries.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/boundaries.cpython-311.pyc
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/morphsnakes.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/morphsnakes.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/slic_superpixels.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/__pycache__/slic_superpixels.cpython-311.pyc
vendored
Normal file
Binary file not shown.
345
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_chan_vese.py
vendored
Normal file
345
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_chan_vese.py
vendored
Normal file
@@ -0,0 +1,345 @@
|
||||
import numpy as np
|
||||
from scipy.ndimage import distance_transform_edt as distance
|
||||
|
||||
from .._shared.utils import _supported_float_type
|
||||
|
||||
|
||||
def _cv_curvature(phi):
|
||||
"""Returns the 'curvature' of a level set 'phi'.
|
||||
"""
|
||||
P = np.pad(phi, 1, mode='edge')
|
||||
fy = (P[2:, 1:-1] - P[:-2, 1:-1]) / 2.0
|
||||
fx = (P[1:-1, 2:] - P[1:-1, :-2]) / 2.0
|
||||
fyy = P[2:, 1:-1] + P[:-2, 1:-1] - 2*phi
|
||||
fxx = P[1:-1, 2:] + P[1:-1, :-2] - 2*phi
|
||||
fxy = .25 * (P[2:, 2:] + P[:-2, :-2] - P[:-2, 2:] - P[2:, :-2])
|
||||
grad2 = fx**2 + fy**2
|
||||
K = ((fxx*fy**2 - 2*fxy*fx*fy + fyy*fx**2) /
|
||||
(grad2*np.sqrt(grad2) + 1e-8))
|
||||
return K
|
||||
|
||||
|
||||
def _cv_calculate_variation(image, phi, mu, lambda1, lambda2, dt):
|
||||
"""Returns the variation of level set 'phi' based on algorithm parameters.
|
||||
"""
|
||||
eta = 1e-16
|
||||
P = np.pad(phi, 1, mode='edge')
|
||||
|
||||
phixp = P[1:-1, 2:] - P[1:-1, 1:-1]
|
||||
phixn = P[1:-1, 1:-1] - P[1:-1, :-2]
|
||||
phix0 = (P[1:-1, 2:] - P[1:-1, :-2]) / 2.0
|
||||
|
||||
phiyp = P[2:, 1:-1] - P[1:-1, 1:-1]
|
||||
phiyn = P[1:-1, 1:-1] - P[:-2, 1:-1]
|
||||
phiy0 = (P[2:, 1:-1] - P[:-2, 1:-1]) / 2.0
|
||||
|
||||
C1 = 1. / np.sqrt(eta + phixp**2 + phiy0**2)
|
||||
C2 = 1. / np.sqrt(eta + phixn**2 + phiy0**2)
|
||||
C3 = 1. / np.sqrt(eta + phix0**2 + phiyp**2)
|
||||
C4 = 1. / np.sqrt(eta + phix0**2 + phiyn**2)
|
||||
|
||||
K = (P[1:-1, 2:] * C1 + P[1:-1, :-2] * C2 +
|
||||
P[2:, 1:-1] * C3 + P[:-2, 1:-1] * C4)
|
||||
|
||||
Hphi = 1 * (phi > 0)
|
||||
(c1, c2) = _cv_calculate_averages(image, Hphi)
|
||||
|
||||
difference_from_average_term = (- lambda1 * (image-c1)**2 +
|
||||
lambda2 * (image-c2)**2)
|
||||
new_phi = (phi + (dt*_cv_delta(phi)) *
|
||||
(mu*K + difference_from_average_term))
|
||||
return new_phi / (1 + mu * dt * _cv_delta(phi) * (C1+C2+C3+C4))
|
||||
|
||||
|
||||
def _cv_heavyside(x, eps=1.):
|
||||
"""Returns the result of a regularised heavyside function of the
|
||||
input value(s).
|
||||
"""
|
||||
return 0.5 * (1. + (2./np.pi) * np.arctan(x/eps))
|
||||
|
||||
|
||||
def _cv_delta(x, eps=1.):
|
||||
"""Returns the result of a regularised dirac function of the
|
||||
input value(s).
|
||||
"""
|
||||
return eps / (eps**2 + x**2)
|
||||
|
||||
|
||||
def _cv_calculate_averages(image, Hphi):
|
||||
"""Returns the average values 'inside' and 'outside'.
|
||||
"""
|
||||
H = Hphi
|
||||
Hinv = 1. - H
|
||||
Hsum = np.sum(H)
|
||||
Hinvsum = np.sum(Hinv)
|
||||
avg_inside = np.sum(image * H)
|
||||
avg_oustide = np.sum(image * Hinv)
|
||||
if Hsum != 0:
|
||||
avg_inside /= Hsum
|
||||
if Hinvsum != 0:
|
||||
avg_oustide /= Hinvsum
|
||||
return (avg_inside, avg_oustide)
|
||||
|
||||
|
||||
def _cv_difference_from_average_term(image, Hphi, lambda_pos, lambda_neg):
|
||||
"""Returns the 'energy' contribution due to the difference from
|
||||
the average value within a region at each point.
|
||||
"""
|
||||
(c1, c2) = _cv_calculate_averages(image, Hphi)
|
||||
Hinv = 1. - Hphi
|
||||
return (lambda_pos * (image-c1)**2 * Hphi +
|
||||
lambda_neg * (image-c2)**2 * Hinv)
|
||||
|
||||
|
||||
def _cv_edge_length_term(phi, mu):
|
||||
"""Returns the 'energy' contribution due to the length of the
|
||||
edge between regions at each point, multiplied by a factor 'mu'.
|
||||
"""
|
||||
toret = _cv_curvature(phi)
|
||||
return mu * toret
|
||||
|
||||
|
||||
def _cv_energy(image, phi, mu, lambda1, lambda2):
|
||||
"""Returns the total 'energy' of the current level set function.
|
||||
"""
|
||||
H = _cv_heavyside(phi)
|
||||
avgenergy = _cv_difference_from_average_term(image, H, lambda1, lambda2)
|
||||
lenenergy = _cv_edge_length_term(phi, mu)
|
||||
return np.sum(avgenergy) + np.sum(lenenergy)
|
||||
|
||||
|
||||
def _cv_reset_level_set(phi):
|
||||
"""This is a placeholder function as resetting the level set is not
|
||||
strictly necessary, and has not been done for this implementation.
|
||||
"""
|
||||
return phi
|
||||
|
||||
|
||||
def _cv_checkerboard(image_size, square_size, dtype=np.float64):
|
||||
"""Generates a checkerboard level set function.
|
||||
|
||||
According to Pascal Getreuer, such a level set function has fast
|
||||
convergence.
|
||||
"""
|
||||
yv = np.arange(image_size[0], dtype=dtype).reshape(image_size[0], 1)
|
||||
xv = np.arange(image_size[1], dtype=dtype)
|
||||
sf = np.pi / square_size
|
||||
xv *= sf
|
||||
yv *= sf
|
||||
return np.sin(yv) * np.sin(xv)
|
||||
|
||||
|
||||
def _cv_large_disk(image_size):
|
||||
"""Generates a disk level set function.
|
||||
|
||||
The disk covers the whole image along its smallest dimension.
|
||||
"""
|
||||
res = np.ones(image_size)
|
||||
centerY = int((image_size[0]-1) / 2)
|
||||
centerX = int((image_size[1]-1) / 2)
|
||||
res[centerY, centerX] = 0.
|
||||
radius = float(min(centerX, centerY))
|
||||
return (radius - distance(res)) / radius
|
||||
|
||||
|
||||
def _cv_small_disk(image_size):
|
||||
"""Generates a disk level set function.
|
||||
|
||||
The disk covers half of the image along its smallest dimension.
|
||||
"""
|
||||
res = np.ones(image_size)
|
||||
centerY = int((image_size[0]-1) / 2)
|
||||
centerX = int((image_size[1]-1) / 2)
|
||||
res[centerY, centerX] = 0.
|
||||
radius = float(min(centerX, centerY)) / 2.0
|
||||
return (radius - distance(res)) / (radius * 3)
|
||||
|
||||
|
||||
def _cv_init_level_set(init_level_set, image_shape, dtype=np.float64):
|
||||
"""Generates an initial level set function conditional on input arguments.
|
||||
"""
|
||||
if type(init_level_set) == str:
|
||||
if init_level_set == 'checkerboard':
|
||||
res = _cv_checkerboard(image_shape, 5, dtype)
|
||||
elif init_level_set == 'disk':
|
||||
res = _cv_large_disk(image_shape)
|
||||
elif init_level_set == 'small disk':
|
||||
res = _cv_small_disk(image_shape)
|
||||
else:
|
||||
raise ValueError("Incorrect name for starting level set preset.")
|
||||
else:
|
||||
res = init_level_set
|
||||
return res.astype(dtype, copy=False)
|
||||
|
||||
|
||||
def chan_vese(image, mu=0.25, lambda1=1.0, lambda2=1.0, tol=1e-3,
|
||||
max_num_iter=500, dt=0.5, init_level_set='checkerboard',
|
||||
extended_output=False):
|
||||
"""Chan-Vese segmentation algorithm.
|
||||
|
||||
Active contour model by evolving a level set. Can be used to
|
||||
segment objects without clearly defined boundaries.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N) ndarray
|
||||
Grayscale image to be segmented.
|
||||
mu : float, optional
|
||||
'edge length' weight parameter. Higher `mu` values will
|
||||
produce a 'round' edge, while values closer to zero will
|
||||
detect smaller objects.
|
||||
lambda1 : float, optional
|
||||
'difference from average' weight parameter for the output
|
||||
region with value 'True'. If it is lower than `lambda2`, this
|
||||
region will have a larger range of values than the other.
|
||||
lambda2 : float, optional
|
||||
'difference from average' weight parameter for the output
|
||||
region with value 'False'. If it is lower than `lambda1`, this
|
||||
region will have a larger range of values than the other.
|
||||
tol : float, positive, optional
|
||||
Level set variation tolerance between iterations. If the
|
||||
L2 norm difference between the level sets of successive
|
||||
iterations normalized by the area of the image is below this
|
||||
value, the algorithm will assume that the solution was
|
||||
reached.
|
||||
max_num_iter : uint, optional
|
||||
Maximum number of iterations allowed before the algorithm
|
||||
interrupts itself.
|
||||
dt : float, optional
|
||||
A multiplication factor applied at calculations for each step,
|
||||
serves to accelerate the algorithm. While higher values may
|
||||
speed up the algorithm, they may also lead to convergence
|
||||
problems.
|
||||
init_level_set : str or (M, N) ndarray, optional
|
||||
Defines the starting level set used by the algorithm.
|
||||
If a string is inputted, a level set that matches the image
|
||||
size will automatically be generated. Alternatively, it is
|
||||
possible to define a custom level set, which should be an
|
||||
array of float values, with the same shape as 'image'.
|
||||
Accepted string values are as follows.
|
||||
|
||||
'checkerboard'
|
||||
the starting level set is defined as
|
||||
sin(x/5*pi)*sin(y/5*pi), where x and y are pixel
|
||||
coordinates. This level set has fast convergence, but may
|
||||
fail to detect implicit edges.
|
||||
'disk'
|
||||
the starting level set is defined as the opposite
|
||||
of the distance from the center of the image minus half of
|
||||
the minimum value between image width and image height.
|
||||
This is somewhat slower, but is more likely to properly
|
||||
detect implicit edges.
|
||||
'small disk'
|
||||
the starting level set is defined as the
|
||||
opposite of the distance from the center of the image
|
||||
minus a quarter of the minimum value between image width
|
||||
and image height.
|
||||
extended_output : bool, optional
|
||||
If set to True, the return value will be a tuple containing
|
||||
the three return values (see below). If set to False which
|
||||
is the default value, only the 'segmentation' array will be
|
||||
returned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
segmentation : (M, N) ndarray, bool
|
||||
Segmentation produced by the algorithm.
|
||||
phi : (M, N) ndarray of floats
|
||||
Final level set computed by the algorithm.
|
||||
energies : list of floats
|
||||
Shows the evolution of the 'energy' for each step of the
|
||||
algorithm. This should allow to check whether the algorithm
|
||||
converged.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The Chan-Vese Algorithm is designed to segment objects without
|
||||
clearly defined boundaries. This algorithm is based on level sets
|
||||
that are evolved iteratively to minimize an energy, which is
|
||||
defined by weighted values corresponding to the sum of differences
|
||||
intensity from the average value outside the segmented region, the
|
||||
sum of differences from the average value inside the segmented
|
||||
region, and a term which is dependent on the length of the
|
||||
boundary of the segmented region.
|
||||
|
||||
This algorithm was first proposed by Tony Chan and Luminita Vese,
|
||||
in a publication entitled "An Active Contour Model Without Edges"
|
||||
[1]_.
|
||||
|
||||
This implementation of the algorithm is somewhat simplified in the
|
||||
sense that the area factor 'nu' described in the original paper is
|
||||
not implemented, and is only suitable for grayscale images.
|
||||
|
||||
Typical values for `lambda1` and `lambda2` are 1. If the
|
||||
'background' is very different from the segmented object in terms
|
||||
of distribution (for example, a uniform black image with figures
|
||||
of varying intensity), then these values should be different from
|
||||
each other.
|
||||
|
||||
Typical values for mu are between 0 and 1, though higher values
|
||||
can be used when dealing with shapes with very ill-defined
|
||||
contours.
|
||||
|
||||
The 'energy' which this algorithm tries to minimize is defined
|
||||
as the sum of the differences from the average within the region
|
||||
squared and weighed by the 'lambda' factors to which is added the
|
||||
length of the contour multiplied by the 'mu' factor.
|
||||
|
||||
Supports 2D grayscale images only, and does not implement the area
|
||||
term described in the original article.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] An Active Contour Model without Edges, Tony Chan and
|
||||
Luminita Vese, Scale-Space Theories in Computer Vision,
|
||||
1999, :DOI:`10.1007/3-540-48236-9_13`
|
||||
.. [2] Chan-Vese Segmentation, Pascal Getreuer Image Processing On
|
||||
Line, 2 (2012), pp. 214-224,
|
||||
:DOI:`10.5201/ipol.2012.g-cv`
|
||||
.. [3] The Chan-Vese Algorithm - Project Report, Rami Cohen, 2011
|
||||
:arXiv:`1107.2782`
|
||||
"""
|
||||
if len(image.shape) != 2:
|
||||
raise ValueError("Input image should be a 2D array.")
|
||||
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
phi = _cv_init_level_set(init_level_set, image.shape, dtype=float_dtype)
|
||||
|
||||
if type(phi) != np.ndarray or phi.shape != image.shape:
|
||||
raise ValueError("The dimensions of initial level set do not "
|
||||
"match the dimensions of image.")
|
||||
|
||||
image = image.astype(float_dtype, copy=False)
|
||||
image = image - np.min(image)
|
||||
if np.max(image) != 0:
|
||||
image = image / np.max(image)
|
||||
|
||||
i = 0
|
||||
old_energy = _cv_energy(image, phi, mu, lambda1, lambda2)
|
||||
energies = []
|
||||
phivar = tol + 1
|
||||
segmentation = phi > 0
|
||||
|
||||
while(phivar > tol and i < max_num_iter):
|
||||
# Save old level set values
|
||||
oldphi = phi
|
||||
|
||||
# Calculate new level set
|
||||
phi = _cv_calculate_variation(image, phi, mu, lambda1, lambda2, dt)
|
||||
phi = _cv_reset_level_set(phi)
|
||||
phivar = np.sqrt(((phi-oldphi)**2).mean())
|
||||
|
||||
# Extract energy and compare to previous level set and
|
||||
# segmentation to see if continuing is necessary
|
||||
segmentation = phi > 0
|
||||
new_energy = _cv_energy(image, phi, mu, lambda1, lambda2)
|
||||
|
||||
# Save old energy values
|
||||
energies.append(old_energy)
|
||||
old_energy = new_energy
|
||||
i += 1
|
||||
|
||||
if extended_output:
|
||||
return (segmentation, phi, energies)
|
||||
else:
|
||||
return segmentation
|
||||
108
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_clear_border.py
vendored
Normal file
108
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_clear_border.py
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
import numpy as np
|
||||
|
||||
from ..measure import label
|
||||
|
||||
|
||||
def clear_border(labels, buffer_size=0, bgval=0, mask=None,
|
||||
*, out=None):
|
||||
"""Clear objects connected to the label image border.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
labels : (M[, N[, ..., P]]) array of int or bool
|
||||
Imaging data labels.
|
||||
buffer_size : int, optional
|
||||
The width of the border examined. By default, only objects
|
||||
that touch the outside of the image are removed.
|
||||
bgval : float or int, optional
|
||||
Cleared objects are set to this value.
|
||||
mask : ndarray of bool, same shape as `image`, optional.
|
||||
Image data mask. Objects in labels image overlapping with
|
||||
False pixels of mask will be removed. If defined, the
|
||||
argument buffer_size will be ignored.
|
||||
out : ndarray
|
||||
Array of the same shape as `labels`, into which the
|
||||
output is placed. By default, a new array is created.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : (M[, N[, ..., P]]) array
|
||||
Imaging data labels with cleared borders
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from skimage.segmentation import clear_border
|
||||
>>> labels = np.array([[0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
... [1, 1, 0, 0, 1, 0, 0, 1, 0],
|
||||
... [1, 1, 0, 1, 0, 1, 0, 0, 0],
|
||||
... [0, 0, 0, 1, 1, 1, 1, 0, 0],
|
||||
... [0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
... [0, 0, 0, 0, 0, 0, 0, 0, 0]])
|
||||
>>> clear_border(labels)
|
||||
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, 0, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]])
|
||||
>>> mask = 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]]).astype(bool)
|
||||
>>> clear_border(labels, mask=mask)
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 1, 0, 0, 1, 0],
|
||||
[0, 0, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]])
|
||||
|
||||
"""
|
||||
if any(buffer_size >= s for s in labels.shape) and mask is None:
|
||||
# ignore buffer_size if mask
|
||||
raise ValueError("buffer size may not be greater than labels size")
|
||||
|
||||
if out is None:
|
||||
out = labels.copy()
|
||||
|
||||
if mask is not None:
|
||||
err_msg = (f'labels and mask should have the same shape but '
|
||||
f'are {out.shape} and {mask.shape}')
|
||||
if out.shape != mask.shape:
|
||||
raise(ValueError, err_msg)
|
||||
if mask.dtype != bool:
|
||||
raise TypeError("mask should be of type bool.")
|
||||
borders = ~mask
|
||||
else:
|
||||
# create borders with buffer_size
|
||||
borders = np.zeros_like(out, dtype=bool)
|
||||
ext = buffer_size + 1
|
||||
slstart = slice(ext)
|
||||
slend = slice(-ext, None)
|
||||
slices = [slice(None) for _ in out.shape]
|
||||
for d in range(out.ndim):
|
||||
slices[d] = slstart
|
||||
borders[tuple(slices)] = True
|
||||
slices[d] = slend
|
||||
borders[tuple(slices)] = True
|
||||
slices[d] = slice(None)
|
||||
|
||||
# Re-label, in case we are dealing with a binary out
|
||||
# and to get consistent labeling
|
||||
labels, number = label(out, background=0, return_num=True)
|
||||
|
||||
# determine all objects that are connected to borders
|
||||
borders_indices = np.unique(labels[borders])
|
||||
indices = np.arange(number + 1)
|
||||
# mask all label indices that are connected to borders
|
||||
label_mask = np.in1d(indices, borders_indices)
|
||||
# create mask for pixels to clear
|
||||
mask = label_mask[labels.reshape(-1)].reshape(labels.shape)
|
||||
|
||||
# clear border pixels
|
||||
out[mask] = bgval
|
||||
|
||||
return out
|
||||
95
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_expand_labels.py
vendored
Normal file
95
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_expand_labels.py
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
import numpy as np
|
||||
from scipy.ndimage import distance_transform_edt
|
||||
|
||||
|
||||
def expand_labels(label_image, distance=1):
|
||||
"""Expand labels in label image by ``distance`` pixels without overlapping.
|
||||
|
||||
Given a label image, ``expand_labels`` grows label regions (connected components)
|
||||
outwards by up to ``distance`` pixels without overflowing into neighboring regions.
|
||||
More specifically, each background pixel that is within Euclidean distance
|
||||
of <= ``distance`` pixels of a connected component is assigned the label of that
|
||||
connected component.
|
||||
Where multiple connected components are within ``distance`` pixels of a background
|
||||
pixel, the label value of the closest connected component will be assigned (see
|
||||
Notes for the case of multiple labels at equal distance).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label_image : ndarray of dtype int
|
||||
label image
|
||||
distance : float
|
||||
Euclidean distance in pixels by which to grow the labels. Default is one.
|
||||
|
||||
Returns
|
||||
-------
|
||||
enlarged_labels : ndarray of dtype int
|
||||
Labeled array, where all connected regions have been enlarged
|
||||
|
||||
Notes
|
||||
-----
|
||||
Where labels are spaced more than ``distance`` pixels are apart, this is
|
||||
equivalent to a morphological dilation with a disc or hyperball of radius ``distance``.
|
||||
However, in contrast to a morphological dilation, ``expand_labels`` will
|
||||
not expand a label region into a neighboring region.
|
||||
|
||||
This implementation of ``expand_labels`` is derived from CellProfiler [1]_, where
|
||||
it is known as module "IdentifySecondaryObjects (Distance-N)" [2]_.
|
||||
|
||||
There is an important edge case when a pixel has the same distance to
|
||||
multiple regions, as it is not defined which region expands into that
|
||||
space. Here, the exact behavior depends on the upstream implementation
|
||||
of ``scipy.ndimage.distance_transform_edt``.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:func:`skimage.measure.label`, :func:`skimage.segmentation.watershed`, :func:`skimage.morphology.dilation`
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://cellprofiler.org
|
||||
.. [2] https://github.com/CellProfiler/CellProfiler/blob/082930ea95add7b72243a4fa3d39ae5145995e9c/cellprofiler/modules/identifysecondaryobjects.py#L559
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> labels = np.array([0, 1, 0, 0, 0, 0, 2])
|
||||
>>> expand_labels(labels, distance=1)
|
||||
array([1, 1, 1, 0, 0, 2, 2])
|
||||
|
||||
Labels will not overwrite each other:
|
||||
|
||||
>>> expand_labels(labels, distance=3)
|
||||
array([1, 1, 1, 1, 2, 2, 2])
|
||||
|
||||
In case of ties, behavior is undefined, but currently resolves to the
|
||||
label closest to ``(0,) * ndim`` in lexicographical order.
|
||||
|
||||
>>> labels_tied = np.array([0, 1, 0, 2, 0])
|
||||
>>> expand_labels(labels_tied, 1)
|
||||
array([1, 1, 1, 2, 2])
|
||||
>>> labels2d = np.array(
|
||||
... [[0, 1, 0, 0],
|
||||
... [2, 0, 0, 0],
|
||||
... [0, 3, 0, 0]]
|
||||
... )
|
||||
>>> expand_labels(labels2d, 1)
|
||||
array([[2, 1, 1, 0],
|
||||
[2, 2, 0, 0],
|
||||
[2, 3, 3, 0]])
|
||||
"""
|
||||
|
||||
distances, nearest_label_coords = distance_transform_edt(
|
||||
label_image == 0, return_indices=True
|
||||
)
|
||||
labels_out = np.zeros_like(label_image)
|
||||
dilate_mask = distances <= distance
|
||||
# build the coordinates to find nearest labels,
|
||||
# in contrast to [1] this implementation supports label arrays
|
||||
# of any dimension
|
||||
masked_nearest_label_coords = [
|
||||
dimension_indices[dilate_mask]
|
||||
for dimension_indices in nearest_label_coords
|
||||
]
|
||||
nearest_labels = label_image[tuple(masked_nearest_label_coords)]
|
||||
labels_out[dilate_mask] = nearest_labels
|
||||
return labels_out
|
||||
70
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_felzenszwalb.py
vendored
Normal file
70
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_felzenszwalb.py
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
import numpy as np
|
||||
|
||||
from ._felzenszwalb_cy import _felzenszwalb_cython
|
||||
from .._shared import utils
|
||||
|
||||
|
||||
@utils.channel_as_last_axis(multichannel_output=False)
|
||||
def felzenszwalb(image, scale=1, sigma=0.8, min_size=20, *,
|
||||
channel_axis=-1):
|
||||
"""Computes Felsenszwalb's efficient graph based image segmentation.
|
||||
|
||||
Produces an oversegmentation of a multichannel (i.e. RGB) image
|
||||
using a fast, minimum spanning tree based clustering on the image grid.
|
||||
The parameter ``scale`` sets an observation level. Higher scale means
|
||||
less and larger segments. ``sigma`` is the diameter of a Gaussian kernel,
|
||||
used for smoothing the image prior to segmentation.
|
||||
|
||||
The number of produced segments as well as their size can only be
|
||||
controlled indirectly through ``scale``. Segment size within an image can
|
||||
vary greatly depending on local contrast.
|
||||
|
||||
For RGB images, the algorithm uses the euclidean distance between pixels in
|
||||
color space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (width, height, 3) or (width, height) ndarray
|
||||
Input image.
|
||||
scale : float
|
||||
Free parameter. Higher means larger clusters.
|
||||
sigma : float
|
||||
Width (standard deviation) of Gaussian kernel used in preprocessing.
|
||||
min_size : int
|
||||
Minimum component size. Enforced using postprocessing.
|
||||
channel_axis : int or None, optional
|
||||
If None, the image is assumed to be a grayscale (single channel) image.
|
||||
Otherwise, this parameter indicates which axis of the array corresponds
|
||||
to channels.
|
||||
|
||||
.. versionadded:: 0.19
|
||||
``channel_axis`` was added in 0.19.
|
||||
|
||||
Returns
|
||||
-------
|
||||
segment_mask : (width, height) ndarray
|
||||
Integer mask indicating segment labels.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Efficient graph-based image segmentation, Felzenszwalb, P.F. and
|
||||
Huttenlocher, D.P. International Journal of Computer Vision, 2004
|
||||
|
||||
Notes
|
||||
-----
|
||||
The `k` parameter used in the original paper renamed to `scale` here.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.segmentation import felzenszwalb
|
||||
>>> from skimage.data import coffee
|
||||
>>> img = coffee()
|
||||
>>> segments = felzenszwalb(img, scale=3.0, sigma=0.95, min_size=5)
|
||||
"""
|
||||
if channel_axis is None and image.ndim > 2:
|
||||
raise ValueError("This algorithm works only on single or "
|
||||
"multi-channel 2d images. ")
|
||||
|
||||
image = np.atleast_3d(image)
|
||||
return _felzenszwalb_cython(image, scale=scale, sigma=sigma,
|
||||
min_size=min_size)
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_felzenszwalb_cy.cp311-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_felzenszwalb_cy.cp311-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_felzenszwalb_cy.cp311-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_felzenszwalb_cy.cp311-win_amd64.pyd
vendored
Normal file
Binary file not shown.
157
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_join.py
vendored
Normal file
157
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_join.py
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
import numpy as np
|
||||
|
||||
from ..util._map_array import map_array, ArrayMap
|
||||
|
||||
|
||||
def join_segmentations(s1, s2):
|
||||
"""Return the join of the two input segmentations.
|
||||
|
||||
The join J of S1 and S2 is defined as the segmentation in which two
|
||||
voxels are in the same segment if and only if they are in the same
|
||||
segment in *both* S1 and S2.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
s1, s2 : numpy arrays
|
||||
s1 and s2 are label fields of the same shape.
|
||||
|
||||
Returns
|
||||
-------
|
||||
j : numpy array
|
||||
The join segmentation of s1 and s2.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.segmentation import join_segmentations
|
||||
>>> s1 = np.array([[0, 0, 1, 1],
|
||||
... [0, 2, 1, 1],
|
||||
... [2, 2, 2, 1]])
|
||||
>>> s2 = np.array([[0, 1, 1, 0],
|
||||
... [0, 1, 1, 0],
|
||||
... [0, 1, 1, 1]])
|
||||
>>> join_segmentations(s1, s2)
|
||||
array([[0, 1, 3, 2],
|
||||
[0, 5, 3, 2],
|
||||
[4, 5, 5, 3]])
|
||||
"""
|
||||
if s1.shape != s2.shape:
|
||||
raise ValueError("Cannot join segmentations of different shape. "
|
||||
f"s1.shape: {s1.shape}, s2.shape: {s2.shape}")
|
||||
s1 = relabel_sequential(s1)[0]
|
||||
s2 = relabel_sequential(s2)[0]
|
||||
j = (s2.max() + 1) * s1 + s2
|
||||
j = relabel_sequential(j)[0]
|
||||
return j
|
||||
|
||||
|
||||
def relabel_sequential(label_field, offset=1):
|
||||
"""Relabel arbitrary labels to {`offset`, ... `offset` + number_of_labels}.
|
||||
|
||||
This function also returns the forward map (mapping the original labels to
|
||||
the reduced labels) and the inverse map (mapping the reduced labels back
|
||||
to the original ones).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label_field : numpy array of int, arbitrary shape
|
||||
An array of labels, which must be non-negative integers.
|
||||
offset : int, optional
|
||||
The return labels will start at `offset`, which should be
|
||||
strictly positive.
|
||||
|
||||
Returns
|
||||
-------
|
||||
relabeled : numpy array of int, same shape as `label_field`
|
||||
The input label field with labels mapped to
|
||||
{offset, ..., number_of_labels + offset - 1}.
|
||||
The data type will be the same as `label_field`, except when
|
||||
offset + number_of_labels causes overflow of the current data type.
|
||||
forward_map : ArrayMap
|
||||
The map from the original label space to the returned label
|
||||
space. Can be used to re-apply the same mapping. See examples
|
||||
for usage. The output data type will be the same as `relabeled`.
|
||||
inverse_map : ArrayMap
|
||||
The map from the new label space to the original space. This
|
||||
can be used to reconstruct the original label field from the
|
||||
relabeled one. The output data type will be the same as `label_field`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The label 0 is assumed to denote the background and is never remapped.
|
||||
|
||||
The forward map can be extremely big for some inputs, since its
|
||||
length is given by the maximum of the label field. However, in most
|
||||
situations, ``label_field.max()`` is much smaller than
|
||||
``label_field.size``, and in these cases the forward map is
|
||||
guaranteed to be smaller than either the input or output images.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.segmentation import relabel_sequential
|
||||
>>> label_field = np.array([1, 1, 5, 5, 8, 99, 42])
|
||||
>>> relab, fw, inv = relabel_sequential(label_field)
|
||||
>>> relab
|
||||
array([1, 1, 2, 2, 3, 5, 4])
|
||||
>>> print(fw)
|
||||
ArrayMap:
|
||||
1 → 1
|
||||
5 → 2
|
||||
8 → 3
|
||||
42 → 4
|
||||
99 → 5
|
||||
>>> np.array(fw)
|
||||
array([0, 1, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5])
|
||||
>>> np.array(inv)
|
||||
array([ 0, 1, 5, 8, 42, 99])
|
||||
>>> (fw[label_field] == relab).all()
|
||||
True
|
||||
>>> (inv[relab] == label_field).all()
|
||||
True
|
||||
>>> relab, fw, inv = relabel_sequential(label_field, offset=5)
|
||||
>>> relab
|
||||
array([5, 5, 6, 6, 7, 9, 8])
|
||||
"""
|
||||
if offset <= 0:
|
||||
raise ValueError("Offset must be strictly positive.")
|
||||
if np.min(label_field) < 0:
|
||||
raise ValueError("Cannot relabel array that contains negative values.")
|
||||
offset = int(offset)
|
||||
in_vals = np.unique(label_field)
|
||||
if in_vals[0] == 0:
|
||||
# always map 0 to 0
|
||||
out_vals = np.concatenate(
|
||||
[[0], np.arange(offset, offset+len(in_vals)-1)]
|
||||
)
|
||||
else:
|
||||
out_vals = np.arange(offset, offset+len(in_vals))
|
||||
input_type = label_field.dtype
|
||||
if input_type.kind not in "iu":
|
||||
raise TypeError("label_field must have an integer dtype")
|
||||
|
||||
# Some logic to determine the output type:
|
||||
# - we don't want to return a smaller output type than the input type,
|
||||
# ie if we get uint32 as labels input, don't return a uint8 array.
|
||||
# - but, in some cases, using the input type could result in overflow. The
|
||||
# input type could be a signed integer (e.g. int32) but
|
||||
# `np.min_scalar_type` will always return an unsigned type. We check for
|
||||
# that by casting the largest output value to the input type. If it is
|
||||
# unchanged, we use the input type, else we use the unsigned minimum
|
||||
# required type
|
||||
required_type = np.min_scalar_type(out_vals[-1])
|
||||
if input_type.itemsize < required_type.itemsize:
|
||||
output_type = required_type
|
||||
else:
|
||||
if out_vals[-1] < np.iinfo(input_type).max:
|
||||
output_type = input_type
|
||||
else:
|
||||
output_type = required_type
|
||||
out_array = np.empty(label_field.shape, dtype=output_type)
|
||||
out_vals = out_vals.astype(output_type)
|
||||
map_array(label_field, in_vals, out_vals, out=out_array)
|
||||
fw_map = ArrayMap(in_vals, out_vals)
|
||||
inv_map = ArrayMap(out_vals, in_vals)
|
||||
return out_array, fw_map, inv_map
|
||||
87
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_quickshift.py
vendored
Normal file
87
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_quickshift.py
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
import numpy as np
|
||||
|
||||
from .._shared.filters import gaussian
|
||||
from .._shared.utils import _supported_float_type
|
||||
from ..color import rgb2lab
|
||||
from ..util import img_as_float
|
||||
from ._quickshift_cy import _quickshift_cython
|
||||
|
||||
|
||||
def quickshift(image, ratio=1.0, kernel_size=5, max_dist=10,
|
||||
return_tree=False, sigma=0, convert2lab=True, random_seed=42,
|
||||
*, channel_axis=-1):
|
||||
"""Segments image using quickshift clustering in Color-(x,y) space.
|
||||
|
||||
Produces an oversegmentation of the image using the quickshift mode-seeking
|
||||
algorithm.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (width, height, channels) ndarray
|
||||
Input image. The axis corresponding to color channels can be specified
|
||||
via the `channel_axis` argument.
|
||||
ratio : float, optional, between 0 and 1
|
||||
Balances color-space proximity and image-space proximity.
|
||||
Higher values give more weight to color-space.
|
||||
kernel_size : float, optional
|
||||
Width of Gaussian kernel used in smoothing the
|
||||
sample density. Higher means fewer clusters.
|
||||
max_dist : float, optional
|
||||
Cut-off point for data distances.
|
||||
Higher means fewer clusters.
|
||||
return_tree : bool, optional
|
||||
Whether to return the full segmentation hierarchy tree and distances.
|
||||
sigma : float, optional
|
||||
Width for Gaussian smoothing as preprocessing. Zero means no smoothing.
|
||||
convert2lab : bool, optional
|
||||
Whether the input should be converted to Lab colorspace prior to
|
||||
segmentation. For this purpose, the input is assumed to be RGB.
|
||||
random_seed : int, optional
|
||||
Random seed used for breaking ties.
|
||||
channel_axis : int, optional
|
||||
The axis of `image` corresponding to color channels. Defaults to the
|
||||
last axis.
|
||||
|
||||
Returns
|
||||
-------
|
||||
segment_mask : (width, height) ndarray
|
||||
Integer mask indicating segment labels.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The authors advocate to convert the image to Lab color space prior to
|
||||
segmentation, though this is not strictly necessary. For this to work, the
|
||||
image must be given in RGB format.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Quick shift and kernel methods for mode seeking,
|
||||
Vedaldi, A. and Soatto, S.
|
||||
European Conference on Computer Vision, 2008
|
||||
"""
|
||||
|
||||
image = img_as_float(np.atleast_3d(image))
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
image = image.astype(float_dtype, copy=False)
|
||||
|
||||
if image.ndim > 3:
|
||||
raise ValueError("only 2D color images are supported")
|
||||
|
||||
# move channels to last position as expected by the Cython code
|
||||
image = np.moveaxis(image, source=channel_axis, destination=-1)
|
||||
|
||||
if convert2lab:
|
||||
if image.shape[-1] != 3:
|
||||
ValueError("Only RGB images can be converted to Lab space.")
|
||||
image = rgb2lab(image)
|
||||
|
||||
if kernel_size < 1:
|
||||
raise ValueError("`kernel_size` should be >= 1.")
|
||||
|
||||
image = gaussian(image, [sigma, sigma, 0], mode='reflect', channel_axis=-1)
|
||||
image = np.ascontiguousarray(image * ratio)
|
||||
|
||||
segment_mask = _quickshift_cython(
|
||||
image, kernel_size=kernel_size, max_dist=max_dist,
|
||||
return_tree=return_tree, random_seed=random_seed)
|
||||
return segment_mask
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_quickshift_cy.cp311-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_quickshift_cy.cp311-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_quickshift_cy.cp311-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_quickshift_cy.cp311-win_amd64.pyd
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_slic.cp311-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_slic.cp311-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_slic.cp311-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_slic.cp311-win_amd64.pyd
vendored
Normal file
Binary file not shown.
223
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_watershed.py
vendored
Normal file
223
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_watershed.py
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
"""watershed.py - watershed algorithm
|
||||
|
||||
This module implements a watershed algorithm 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.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from . import _watershed_cy
|
||||
from ..morphology.extrema import local_minima
|
||||
from ..morphology._util import (_validate_connectivity,
|
||||
_offsets_to_raveled_neighbors)
|
||||
from ..util import crop, regular_seeds
|
||||
|
||||
|
||||
def _validate_inputs(image, markers, mask, connectivity):
|
||||
"""Ensure that all inputs to watershed have matching shapes and types.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : array
|
||||
The input image.
|
||||
markers : int or array of int
|
||||
The marker image.
|
||||
mask : array, or None
|
||||
A boolean mask, True where we want to compute the watershed.
|
||||
connectivity : int in {1, ..., image.ndim}
|
||||
The connectivity of the neighborhood of a pixel.
|
||||
|
||||
Returns
|
||||
-------
|
||||
image, markers, mask : arrays
|
||||
The validated and formatted arrays. Image will have dtype float64,
|
||||
markers int32, and mask int8. If ``None`` was given for the mask,
|
||||
it is a volume of all 1s.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the shapes of the given arrays don't match.
|
||||
"""
|
||||
n_pixels = image.size
|
||||
if mask is None:
|
||||
# Use a complete `True` mask if none is provided
|
||||
mask = np.ones(image.shape, bool)
|
||||
else:
|
||||
mask = np.asanyarray(mask, dtype=bool)
|
||||
n_pixels = np.sum(mask)
|
||||
if mask.shape != image.shape:
|
||||
message = (f'`mask` (shape {mask.shape}) must have same shape '
|
||||
f'as `image` (shape {image.shape})')
|
||||
raise ValueError(message)
|
||||
if markers is None:
|
||||
markers_bool = local_minima(image, connectivity=connectivity) * mask
|
||||
footprint = ndi.generate_binary_structure(markers_bool.ndim, connectivity)
|
||||
markers = ndi.label(markers_bool, structure=footprint)[0]
|
||||
elif not isinstance(markers, (np.ndarray, list, tuple)):
|
||||
# not array-like, assume int
|
||||
# given int, assume that number of markers *within mask*.
|
||||
markers = regular_seeds(image.shape,
|
||||
int(markers / (n_pixels / image.size)))
|
||||
markers *= mask
|
||||
else:
|
||||
markers = np.asanyarray(markers) * mask
|
||||
if markers.shape != image.shape:
|
||||
message = (f'`markers` (shape {markers.shape}) must have same '
|
||||
f'shape as `image` (shape {image.shape})')
|
||||
raise ValueError(message)
|
||||
return (image.astype(np.float64),
|
||||
markers.astype(np.int32),
|
||||
mask.astype(np.int8))
|
||||
|
||||
|
||||
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, ...)
|
||||
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.
|
||||
Note that the method used for adding this line expects that
|
||||
marker regions are not adjacent; the watershed line may not catch
|
||||
borders between adjacent marker regions.
|
||||
|
||||
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:
|
||||
|
||||
>>> 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
|
||||
>>> max_coords = peak_local_max(distance, labels=image,
|
||||
... footprint=np.ones((3, 3)))
|
||||
>>> local_maxima = np.zeros_like(image, dtype=bool)
|
||||
>>> local_maxima[tuple(max_coords.T)] = True
|
||||
>>> markers = ndi.label(local_maxima)[0]
|
||||
|
||||
Finally, we run the watershed on the image and markers:
|
||||
|
||||
>>> labels = watershed(-distance, markers, mask=image)
|
||||
|
||||
The algorithm works also for 3-D images, and can be used for example to
|
||||
separate overlapping spheres.
|
||||
"""
|
||||
image, markers, mask = _validate_inputs(image, markers, mask, connectivity)
|
||||
connectivity, offset = _validate_connectivity(image.ndim, connectivity,
|
||||
offset)
|
||||
|
||||
# pad the image, markers, and mask so that we can use the mask to
|
||||
# keep from running off the edges
|
||||
pad_width = [(p, p) for p in offset]
|
||||
image = np.pad(image, pad_width, mode='constant')
|
||||
mask = np.pad(mask, pad_width, mode='constant').ravel()
|
||||
output = np.pad(markers, pad_width, mode='constant')
|
||||
|
||||
flat_neighborhood = _offsets_to_raveled_neighbors(
|
||||
image.shape, connectivity, center=offset)
|
||||
marker_locations = np.flatnonzero(output)
|
||||
image_strides = np.array(image.strides, dtype=np.intp) // image.itemsize
|
||||
|
||||
_watershed_cy.watershed_raveled(image.ravel(),
|
||||
marker_locations, flat_neighborhood,
|
||||
mask, image_strides, compactness,
|
||||
output.ravel(),
|
||||
watershed_line)
|
||||
|
||||
output = crop(output, pad_width, copy=True)
|
||||
|
||||
return output
|
||||
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_watershed_cy.cp311-win_amd64.lib
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_watershed_cy.cp311-win_amd64.lib
vendored
Normal file
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_watershed_cy.cp311-win_amd64.pyd
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/_watershed_cy.cp311-win_amd64.pyd
vendored
Normal file
Binary file not shown.
230
.CondaPkg/env/Lib/site-packages/skimage/segmentation/active_contour_model.py
vendored
Normal file
230
.CondaPkg/env/Lib/site-packages/skimage/segmentation/active_contour_model.py
vendored
Normal file
@@ -0,0 +1,230 @@
|
||||
import numpy as np
|
||||
from scipy.interpolate import RectBivariateSpline
|
||||
|
||||
from .._shared.utils import _supported_float_type
|
||||
from ..util import img_as_float
|
||||
from ..filters import sobel
|
||||
|
||||
|
||||
def active_contour(image, snake, alpha=0.01, beta=0.1,
|
||||
w_line=0, w_edge=1, gamma=0.01,
|
||||
max_px_move=1.0,
|
||||
max_num_iter=2500, convergence=0.1,
|
||||
*,
|
||||
boundary_condition='periodic'):
|
||||
"""Active contour model.
|
||||
|
||||
Active contours by fitting snakes to features of images. Supports single
|
||||
and multichannel 2D images. Snakes can be periodic (for segmentation) or
|
||||
have fixed and/or free ends.
|
||||
The output snake has the same length as the input boundary.
|
||||
As the number of points is constant, make sure that the initial snake
|
||||
has enough points to capture the details of the final contour.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (N, M) or (N, M, 3) ndarray
|
||||
Input image.
|
||||
snake : (N, 2) ndarray
|
||||
Initial snake coordinates. For periodic boundary conditions, endpoints
|
||||
must not be duplicated.
|
||||
alpha : float, optional
|
||||
Snake length shape parameter. Higher values makes snake contract
|
||||
faster.
|
||||
beta : float, optional
|
||||
Snake smoothness shape parameter. Higher values makes snake smoother.
|
||||
w_line : float, optional
|
||||
Controls attraction to brightness. Use negative values to attract
|
||||
toward dark regions.
|
||||
w_edge : float, optional
|
||||
Controls attraction to edges. Use negative values to repel snake from
|
||||
edges.
|
||||
gamma : float, optional
|
||||
Explicit time stepping parameter.
|
||||
max_px_move : float, optional
|
||||
Maximum pixel distance to move per iteration.
|
||||
max_num_iter : int, optional
|
||||
Maximum iterations to optimize snake shape.
|
||||
convergence : float, optional
|
||||
Convergence criteria.
|
||||
boundary_condition : string, optional
|
||||
Boundary conditions for the contour. Can be one of 'periodic',
|
||||
'free', 'fixed', 'free-fixed', or 'fixed-free'. 'periodic' attaches
|
||||
the two ends of the snake, 'fixed' holds the end-points in place,
|
||||
and 'free' allows free movement of the ends. 'fixed' and 'free' can
|
||||
be combined by parsing 'fixed-free', 'free-fixed'. Parsing
|
||||
'fixed-fixed' or 'free-free' yields same behaviour as 'fixed' and
|
||||
'free', respectively.
|
||||
|
||||
Returns
|
||||
-------
|
||||
snake : (N, 2) ndarray
|
||||
Optimised snake, same shape as input parameter.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Kass, M.; Witkin, A.; Terzopoulos, D. "Snakes: Active contour
|
||||
models". International Journal of Computer Vision 1 (4): 321
|
||||
(1988). :DOI:`10.1007/BF00133570`
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.draw import circle_perimeter
|
||||
>>> from skimage.filters import gaussian
|
||||
|
||||
Create and smooth image:
|
||||
|
||||
>>> img = np.zeros((100, 100))
|
||||
>>> rr, cc = circle_perimeter(35, 45, 25)
|
||||
>>> img[rr, cc] = 1
|
||||
>>> img = gaussian(img, 2, preserve_range=False)
|
||||
|
||||
Initialize spline:
|
||||
|
||||
>>> s = np.linspace(0, 2*np.pi, 100)
|
||||
>>> init = 50 * np.array([np.sin(s), np.cos(s)]).T + 50
|
||||
|
||||
Fit spline to image:
|
||||
|
||||
>>> snake = active_contour(img, init, w_edge=0, w_line=1, coordinates='rc') # doctest: +SKIP
|
||||
>>> dist = np.sqrt((45-snake[:, 0])**2 + (35-snake[:, 1])**2) # doctest: +SKIP
|
||||
>>> int(np.mean(dist)) # doctest: +SKIP
|
||||
25
|
||||
|
||||
"""
|
||||
max_num_iter = int(max_num_iter)
|
||||
if max_num_iter <= 0:
|
||||
raise ValueError("max_num_iter should be >0.")
|
||||
convergence_order = 10
|
||||
valid_bcs = ['periodic', 'free', 'fixed', 'free-fixed',
|
||||
'fixed-free', 'fixed-fixed', 'free-free']
|
||||
if boundary_condition not in valid_bcs:
|
||||
raise ValueError("Invalid boundary condition.\n" +
|
||||
"Should be one of: "+", ".join(valid_bcs)+'.')
|
||||
|
||||
img = img_as_float(image)
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
img = img.astype(float_dtype, copy=False)
|
||||
|
||||
RGB = img.ndim == 3
|
||||
|
||||
# Find edges using sobel:
|
||||
if w_edge != 0:
|
||||
if RGB:
|
||||
edge = [sobel(img[:, :, 0]), sobel(img[:, :, 1]),
|
||||
sobel(img[:, :, 2])]
|
||||
else:
|
||||
edge = [sobel(img)]
|
||||
else:
|
||||
edge = [0]
|
||||
|
||||
# Superimpose intensity and edge images:
|
||||
if RGB:
|
||||
img = w_line*np.sum(img, axis=2) \
|
||||
+ w_edge*sum(edge)
|
||||
else:
|
||||
img = w_line*img + w_edge*edge[0]
|
||||
|
||||
# Interpolate for smoothness:
|
||||
intp = RectBivariateSpline(np.arange(img.shape[1]),
|
||||
np.arange(img.shape[0]),
|
||||
img.T, kx=2, ky=2, s=0)
|
||||
|
||||
snake_xy = snake[:, ::-1]
|
||||
x = snake_xy[:, 0].astype(float_dtype)
|
||||
y = snake_xy[:, 1].astype(float_dtype)
|
||||
n = len(x)
|
||||
xsave = np.empty((convergence_order, n), dtype=float_dtype)
|
||||
ysave = np.empty((convergence_order, n), dtype=float_dtype)
|
||||
|
||||
# Build snake shape matrix for Euler equation in double precision
|
||||
eye_n = np.eye(n, dtype=float)
|
||||
a = (np.roll(eye_n, -1, axis=0)
|
||||
+ np.roll(eye_n, -1, axis=1)
|
||||
- 2 * eye_n) # second order derivative, central difference
|
||||
b = (np.roll(eye_n, -2, axis=0)
|
||||
+ np.roll(eye_n, -2, axis=1)
|
||||
- 4 * np.roll(eye_n, -1, axis=0)
|
||||
- 4 * np.roll(eye_n, -1, axis=1)
|
||||
+ 6 * eye_n) # fourth order derivative, central difference
|
||||
A = -alpha * a + beta * b
|
||||
|
||||
# Impose boundary conditions different from periodic:
|
||||
sfixed = False
|
||||
if boundary_condition.startswith('fixed'):
|
||||
A[0, :] = 0
|
||||
A[1, :] = 0
|
||||
A[1, :3] = [1, -2, 1]
|
||||
sfixed = True
|
||||
efixed = False
|
||||
if boundary_condition.endswith('fixed'):
|
||||
A[-1, :] = 0
|
||||
A[-2, :] = 0
|
||||
A[-2, -3:] = [1, -2, 1]
|
||||
efixed = True
|
||||
sfree = False
|
||||
if boundary_condition.startswith('free'):
|
||||
A[0, :] = 0
|
||||
A[0, :3] = [1, -2, 1]
|
||||
A[1, :] = 0
|
||||
A[1, :4] = [-1, 3, -3, 1]
|
||||
sfree = True
|
||||
efree = False
|
||||
if boundary_condition.endswith('free'):
|
||||
A[-1, :] = 0
|
||||
A[-1, -3:] = [1, -2, 1]
|
||||
A[-2, :] = 0
|
||||
A[-2, -4:] = [-1, 3, -3, 1]
|
||||
efree = True
|
||||
|
||||
# Only one inversion is needed for implicit spline energy minimization:
|
||||
inv = np.linalg.inv(A + gamma * eye_n)
|
||||
# can use float_dtype once we have computed the inverse in double precision
|
||||
inv = inv.astype(float_dtype, copy=False)
|
||||
|
||||
# Explicit time stepping for image energy minimization:
|
||||
for i in range(max_num_iter):
|
||||
# RectBivariateSpline always returns float64, so call astype here
|
||||
fx = intp(x, y, dx=1, grid=False).astype(float_dtype, copy=False)
|
||||
fy = intp(x, y, dy=1, grid=False).astype(float_dtype, copy=False)
|
||||
|
||||
if sfixed:
|
||||
fx[0] = 0
|
||||
fy[0] = 0
|
||||
if efixed:
|
||||
fx[-1] = 0
|
||||
fy[-1] = 0
|
||||
if sfree:
|
||||
fx[0] *= 2
|
||||
fy[0] *= 2
|
||||
if efree:
|
||||
fx[-1] *= 2
|
||||
fy[-1] *= 2
|
||||
xn = inv @ (gamma*x + fx)
|
||||
yn = inv @ (gamma*y + fy)
|
||||
|
||||
# Movements are capped to max_px_move per iteration:
|
||||
dx = max_px_move * np.tanh(xn - x)
|
||||
dy = max_px_move * np.tanh(yn - y)
|
||||
if sfixed:
|
||||
dx[0] = 0
|
||||
dy[0] = 0
|
||||
if efixed:
|
||||
dx[-1] = 0
|
||||
dy[-1] = 0
|
||||
x += dx
|
||||
y += dy
|
||||
|
||||
# Convergence criteria needs to compare to a number of previous
|
||||
# configurations since oscillations can occur.
|
||||
j = i % (convergence_order + 1)
|
||||
if j < convergence_order:
|
||||
xsave[j, :] = x
|
||||
ysave[j, :] = y
|
||||
else:
|
||||
dist = np.min(np.max(np.abs(xsave - x[None, :])
|
||||
+ np.abs(ysave - y[None, :]), 1))
|
||||
if dist < convergence:
|
||||
break
|
||||
|
||||
return np.stack([y, x], axis=1)
|
||||
239
.CondaPkg/env/Lib/site-packages/skimage/segmentation/boundaries.py
vendored
Normal file
239
.CondaPkg/env/Lib/site-packages/skimage/segmentation/boundaries.py
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .._shared.utils import _supported_float_type
|
||||
from ..morphology import dilation, erosion, square
|
||||
from ..util import img_as_float, view_as_windows
|
||||
from ..color import gray2rgb
|
||||
|
||||
|
||||
def _find_boundaries_subpixel(label_img):
|
||||
"""See ``find_boundaries(..., mode='subpixel')``.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function puts in an empty row and column between each *actual*
|
||||
row and column of the image, for a corresponding shape of ``2s - 1``
|
||||
for every image dimension of size ``s``. These "interstitial" rows
|
||||
and columns are filled as ``True`` if they separate two labels in
|
||||
`label_img`, ``False`` otherwise.
|
||||
|
||||
I used ``view_as_windows`` to get the neighborhood of each pixel.
|
||||
Then I check whether there are two labels or more in that
|
||||
neighborhood.
|
||||
"""
|
||||
ndim = label_img.ndim
|
||||
max_label = np.iinfo(label_img.dtype).max
|
||||
|
||||
label_img_expanded = np.zeros([(2 * s - 1) for s in label_img.shape],
|
||||
label_img.dtype)
|
||||
pixels = (slice(None, None, 2), ) * ndim
|
||||
label_img_expanded[pixels] = label_img
|
||||
|
||||
edges = np.ones(label_img_expanded.shape, dtype=bool)
|
||||
edges[pixels] = False
|
||||
label_img_expanded[edges] = max_label
|
||||
windows = view_as_windows(np.pad(label_img_expanded, 1, mode='edge'),
|
||||
(3,) * ndim)
|
||||
|
||||
boundaries = np.zeros_like(edges)
|
||||
for index in np.ndindex(label_img_expanded.shape):
|
||||
if edges[index]:
|
||||
values = np.unique(windows[index].ravel())
|
||||
if len(values) > 2: # single value and max_label
|
||||
boundaries[index] = True
|
||||
return boundaries
|
||||
|
||||
|
||||
def find_boundaries(label_img, connectivity=1, mode='thick', background=0):
|
||||
"""Return bool array where boundaries between labeled regions are True.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label_img : array of int or bool
|
||||
An array in which different regions are labeled with either different
|
||||
integers or boolean values.
|
||||
connectivity : int in {1, ..., `label_img.ndim`}, optional
|
||||
A pixel is considered a boundary pixel if any of its neighbors
|
||||
has a different label. `connectivity` controls which pixels are
|
||||
considered neighbors. A connectivity of 1 (default) means
|
||||
pixels sharing an edge (in 2D) or a face (in 3D) will be
|
||||
considered neighbors. A connectivity of `label_img.ndim` means
|
||||
pixels sharing a corner will be considered neighbors.
|
||||
mode : string in {'thick', 'inner', 'outer', 'subpixel'}
|
||||
How to mark the boundaries:
|
||||
|
||||
- thick: any pixel not completely surrounded by pixels of the
|
||||
same label (defined by `connectivity`) is marked as a boundary.
|
||||
This results in boundaries that are 2 pixels thick.
|
||||
- inner: outline the pixels *just inside* of objects, leaving
|
||||
background pixels untouched.
|
||||
- outer: outline pixels in the background around object
|
||||
boundaries. When two objects touch, their boundary is also
|
||||
marked.
|
||||
- subpixel: return a doubled image, with pixels *between* the
|
||||
original pixels marked as boundary where appropriate.
|
||||
background : int, optional
|
||||
For modes 'inner' and 'outer', a definition of a background
|
||||
label is required. See `mode` for descriptions of these two.
|
||||
|
||||
Returns
|
||||
-------
|
||||
boundaries : array of bool, same shape as `label_img`
|
||||
A bool image where ``True`` represents a boundary pixel. For
|
||||
`mode` equal to 'subpixel', ``boundaries.shape[i]`` is equal
|
||||
to ``2 * label_img.shape[i] - 1`` for all ``i`` (a pixel is
|
||||
inserted in between all other pairs of pixels).
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> labels = 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, 5, 5, 5, 0, 0],
|
||||
... [0, 0, 1, 1, 1, 5, 5, 5, 0, 0],
|
||||
... [0, 0, 1, 1, 1, 5, 5, 5, 0, 0],
|
||||
... [0, 0, 1, 1, 1, 5, 5, 5, 0, 0],
|
||||
... [0, 0, 0, 0, 0, 5, 5, 5, 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)
|
||||
>>> find_boundaries(labels, mode='thick').astype(np.uint8)
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 1, 1, 0],
|
||||
[0, 1, 1, 0, 1, 1, 0, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0, 1, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
>>> find_boundaries(labels, mode='inner').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, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 0, 1, 0, 0],
|
||||
[0, 0, 1, 0, 1, 1, 0, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
>>> find_boundaries(labels, mode='outer').astype(np.uint8)
|
||||
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 1, 1, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 1, 1, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 1, 1, 0, 0, 1, 0],
|
||||
[0, 0, 1, 1, 1, 1, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
>>> labels_small = labels[::2, ::3]
|
||||
>>> labels_small
|
||||
array([[0, 0, 0, 0],
|
||||
[0, 0, 5, 0],
|
||||
[0, 1, 5, 0],
|
||||
[0, 0, 5, 0],
|
||||
[0, 0, 0, 0]], dtype=uint8)
|
||||
>>> find_boundaries(labels_small, mode='subpixel').astype(np.uint8)
|
||||
array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 1, 0, 1, 0],
|
||||
[0, 1, 1, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 1, 1, 0, 1, 0],
|
||||
[0, 0, 0, 1, 0, 1, 0],
|
||||
[0, 0, 0, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
|
||||
>>> bool_image = np.array([[False, False, False, False, False],
|
||||
... [False, False, False, False, False],
|
||||
... [False, False, True, True, True],
|
||||
... [False, False, True, True, True],
|
||||
... [False, False, True, True, True]],
|
||||
... dtype=bool)
|
||||
>>> find_boundaries(bool_image)
|
||||
array([[False, False, False, False, False],
|
||||
[False, False, True, True, True],
|
||||
[False, True, True, True, True],
|
||||
[False, True, True, False, False],
|
||||
[False, True, True, False, False]])
|
||||
"""
|
||||
if label_img.dtype == 'bool':
|
||||
label_img = label_img.astype(np.uint8)
|
||||
ndim = label_img.ndim
|
||||
footprint = ndi.generate_binary_structure(ndim, connectivity)
|
||||
if mode != 'subpixel':
|
||||
boundaries = (
|
||||
dilation(label_img, footprint) != erosion(label_img, footprint)
|
||||
)
|
||||
if mode == 'inner':
|
||||
foreground_image = (label_img != background)
|
||||
boundaries &= foreground_image
|
||||
elif mode == 'outer':
|
||||
max_label = np.iinfo(label_img.dtype).max
|
||||
background_image = (label_img == background)
|
||||
footprint = ndi.generate_binary_structure(ndim, ndim)
|
||||
inverted_background = np.array(label_img, copy=True)
|
||||
inverted_background[background_image] = max_label
|
||||
adjacent_objects = (
|
||||
(
|
||||
dilation(label_img, footprint)
|
||||
!= erosion(inverted_background, footprint)
|
||||
)
|
||||
& ~background_image
|
||||
)
|
||||
boundaries &= (background_image | adjacent_objects)
|
||||
return boundaries
|
||||
else:
|
||||
boundaries = _find_boundaries_subpixel(label_img)
|
||||
return boundaries
|
||||
|
||||
|
||||
def mark_boundaries(image, label_img, color=(1, 1, 0),
|
||||
outline_color=None, mode='outer', background_label=0):
|
||||
"""Return image with boundaries between labeled regions highlighted.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N[, 3]) array
|
||||
Grayscale or RGB image.
|
||||
label_img : (M, N) array of int
|
||||
Label array where regions are marked by different integer values.
|
||||
color : length-3 sequence, optional
|
||||
RGB color of boundaries in the output image.
|
||||
outline_color : length-3 sequence, optional
|
||||
RGB color surrounding boundaries in the output image. If None, no
|
||||
outline is drawn.
|
||||
mode : string in {'thick', 'inner', 'outer', 'subpixel'}, optional
|
||||
The mode for finding boundaries.
|
||||
background_label : int, optional
|
||||
Which label to consider background (this is only useful for
|
||||
modes ``inner`` and ``outer``).
|
||||
|
||||
Returns
|
||||
-------
|
||||
marked : (M, N, 3) array of float
|
||||
An image in which the boundaries between labels are
|
||||
superimposed on the original image.
|
||||
|
||||
See Also
|
||||
--------
|
||||
find_boundaries
|
||||
"""
|
||||
float_dtype = _supported_float_type(image.dtype)
|
||||
marked = img_as_float(image, force_copy=True)
|
||||
marked = marked.astype(float_dtype, copy=False)
|
||||
if marked.ndim == 2:
|
||||
marked = gray2rgb(marked)
|
||||
if mode == 'subpixel':
|
||||
# Here, we want to interpose an extra line of pixels between
|
||||
# each original line - except for the last axis which holds
|
||||
# the RGB information. ``ndi.zoom`` then performs the (cubic)
|
||||
# interpolation, filling in the values of the interposed pixels
|
||||
marked = ndi.zoom(marked, [2 - 1/s for s in marked.shape[:-1]] + [1],
|
||||
mode='mirror')
|
||||
boundaries = find_boundaries(label_img, mode=mode,
|
||||
background=background_label)
|
||||
if outline_color is not None:
|
||||
outlines = dilation(boundaries, square(3))
|
||||
marked[outlines] = outline_color
|
||||
marked[boundaries] = color
|
||||
return marked
|
||||
438
.CondaPkg/env/Lib/site-packages/skimage/segmentation/morphsnakes.py
vendored
Normal file
438
.CondaPkg/env/Lib/site-packages/skimage/segmentation/morphsnakes.py
vendored
Normal file
@@ -0,0 +1,438 @@
|
||||
from itertools import cycle
|
||||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .._shared.utils import check_nD
|
||||
|
||||
__all__ = ['morphological_chan_vese',
|
||||
'morphological_geodesic_active_contour',
|
||||
'inverse_gaussian_gradient',
|
||||
'disk_level_set',
|
||||
'checkerboard_level_set'
|
||||
]
|
||||
|
||||
|
||||
class _fcycle:
|
||||
|
||||
def __init__(self, iterable):
|
||||
"""Call functions from the iterable each time it is called."""
|
||||
self.funcs = cycle(iterable)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
f = next(self.funcs)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
|
||||
# SI and IS operators for 2D and 3D.
|
||||
_P2 = [np.eye(3),
|
||||
np.array([[0, 1, 0]] * 3),
|
||||
np.flipud(np.eye(3)),
|
||||
np.rot90([[0, 1, 0]] * 3)]
|
||||
_P3 = [np.zeros((3, 3, 3)) for i in range(9)]
|
||||
|
||||
_P3[0][:, :, 1] = 1
|
||||
_P3[1][:, 1, :] = 1
|
||||
_P3[2][1, :, :] = 1
|
||||
_P3[3][:, [0, 1, 2], [0, 1, 2]] = 1
|
||||
_P3[4][:, [0, 1, 2], [2, 1, 0]] = 1
|
||||
_P3[5][[0, 1, 2], :, [0, 1, 2]] = 1
|
||||
_P3[6][[0, 1, 2], :, [2, 1, 0]] = 1
|
||||
_P3[7][[0, 1, 2], [0, 1, 2], :] = 1
|
||||
_P3[8][[0, 1, 2], [2, 1, 0], :] = 1
|
||||
|
||||
|
||||
def sup_inf(u):
|
||||
"""SI operator."""
|
||||
|
||||
if np.ndim(u) == 2:
|
||||
P = _P2
|
||||
elif np.ndim(u) == 3:
|
||||
P = _P3
|
||||
else:
|
||||
raise ValueError("u has an invalid number of dimensions "
|
||||
"(should be 2 or 3)")
|
||||
|
||||
erosions = []
|
||||
for P_i in P:
|
||||
erosions.append(ndi.binary_erosion(u, P_i).astype(np.int8))
|
||||
|
||||
return np.stack(erosions, axis=0).max(0)
|
||||
|
||||
|
||||
def inf_sup(u):
|
||||
"""IS operator."""
|
||||
|
||||
if np.ndim(u) == 2:
|
||||
P = _P2
|
||||
elif np.ndim(u) == 3:
|
||||
P = _P3
|
||||
else:
|
||||
raise ValueError("u has an invalid number of dimensions "
|
||||
"(should be 2 or 3)")
|
||||
|
||||
dilations = []
|
||||
for P_i in P:
|
||||
dilations.append(ndi.binary_dilation(u, P_i).astype(np.int8))
|
||||
|
||||
return np.stack(dilations, axis=0).min(0)
|
||||
|
||||
|
||||
_curvop = _fcycle([lambda u: sup_inf(inf_sup(u)), # SIoIS
|
||||
lambda u: inf_sup(sup_inf(u))]) # ISoSI
|
||||
|
||||
|
||||
def _check_input(image, init_level_set):
|
||||
"""Check that shapes of `image` and `init_level_set` match."""
|
||||
check_nD(image, [2, 3])
|
||||
|
||||
if len(image.shape) != len(init_level_set.shape):
|
||||
raise ValueError("The dimensions of the initial level set do not "
|
||||
"match the dimensions of the image.")
|
||||
|
||||
|
||||
def _init_level_set(init_level_set, image_shape):
|
||||
"""Auxiliary function for initializing level sets with a string.
|
||||
|
||||
If `init_level_set` is not a string, it is returned as is.
|
||||
"""
|
||||
if isinstance(init_level_set, str):
|
||||
if init_level_set == 'checkerboard':
|
||||
res = checkerboard_level_set(image_shape)
|
||||
elif init_level_set == 'disk':
|
||||
res = disk_level_set(image_shape)
|
||||
else:
|
||||
raise ValueError("`init_level_set` not in "
|
||||
"['checkerboard', 'disk']")
|
||||
else:
|
||||
res = init_level_set
|
||||
return res
|
||||
|
||||
|
||||
def disk_level_set(image_shape, *, center=None, radius=None):
|
||||
"""Create a disk level set with binary values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_shape : tuple of positive integers
|
||||
Shape of the image
|
||||
center : tuple of positive integers, optional
|
||||
Coordinates of the center of the disk given in (row, column). If not
|
||||
given, it defaults to the center of the image.
|
||||
radius : float, optional
|
||||
Radius of the disk. If not given, it is set to the 75% of the
|
||||
smallest image dimension.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : array with shape `image_shape`
|
||||
Binary level set of the disk with the given `radius` and `center`.
|
||||
|
||||
See Also
|
||||
--------
|
||||
checkerboard_level_set
|
||||
"""
|
||||
|
||||
if center is None:
|
||||
center = tuple(i // 2 for i in image_shape)
|
||||
|
||||
if radius is None:
|
||||
radius = min(image_shape) * 3.0 / 8.0
|
||||
|
||||
grid = np.mgrid[[slice(i) for i in image_shape]]
|
||||
grid = (grid.T - center).T
|
||||
phi = radius - np.sqrt(np.sum((grid)**2, 0))
|
||||
res = np.int8(phi > 0)
|
||||
return res
|
||||
|
||||
|
||||
def checkerboard_level_set(image_shape, square_size=5):
|
||||
"""Create a checkerboard level set with binary values.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image_shape : tuple of positive integers
|
||||
Shape of the image.
|
||||
square_size : int, optional
|
||||
Size of the squares of the checkerboard. It defaults to 5.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : array with shape `image_shape`
|
||||
Binary level set of the checkerboard.
|
||||
|
||||
See Also
|
||||
--------
|
||||
disk_level_set
|
||||
"""
|
||||
|
||||
grid = np.mgrid[[slice(i) for i in image_shape]]
|
||||
grid = (grid // square_size)
|
||||
|
||||
# Alternate 0/1 for even/odd numbers.
|
||||
grid = grid & 1
|
||||
|
||||
checkerboard = np.bitwise_xor.reduce(grid, axis=0)
|
||||
res = np.int8(checkerboard)
|
||||
return res
|
||||
|
||||
|
||||
def inverse_gaussian_gradient(image, alpha=100.0, sigma=5.0):
|
||||
"""Inverse of gradient magnitude.
|
||||
|
||||
Compute the magnitude of the gradients in the image and then inverts the
|
||||
result in the range [0, 1]. Flat areas are assigned values close to 1,
|
||||
while areas close to borders are assigned values close to 0.
|
||||
|
||||
This function or a similar one defined by the user should be applied over
|
||||
the image as a preprocessing step before calling
|
||||
`morphological_geodesic_active_contour`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N) or (L, M, N) array
|
||||
Grayscale image or volume.
|
||||
alpha : float, optional
|
||||
Controls the steepness of the inversion. A larger value will make the
|
||||
transition between the flat areas and border areas steeper in the
|
||||
resulting array.
|
||||
sigma : float, optional
|
||||
Standard deviation of the Gaussian filter applied over the image.
|
||||
|
||||
Returns
|
||||
-------
|
||||
gimage : (M, N) or (L, M, N) array
|
||||
Preprocessed image (or volume) suitable for
|
||||
`morphological_geodesic_active_contour`.
|
||||
"""
|
||||
gradnorm = ndi.gaussian_gradient_magnitude(image, sigma, mode='nearest')
|
||||
return 1.0 / np.sqrt(1.0 + alpha * gradnorm)
|
||||
|
||||
|
||||
def morphological_chan_vese(image, num_iter, init_level_set='checkerboard',
|
||||
smoothing=1, lambda1=1, lambda2=1,
|
||||
iter_callback=lambda x: None):
|
||||
"""Morphological Active Contours without Edges (MorphACWE)
|
||||
|
||||
Active contours without edges implemented with morphological operators. It
|
||||
can be used to segment objects in images and volumes without well defined
|
||||
borders. It is required that the inside of the object looks different on
|
||||
average than the outside (i.e., the inner area of the object should be
|
||||
darker or lighter than the outer area on average).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : (M, N) or (L, M, N) array
|
||||
Grayscale image or volume to be segmented.
|
||||
num_iter : uint
|
||||
Number of num_iter to run
|
||||
init_level_set : str, (M, N) array, or (L, M, N) array
|
||||
Initial level set. If an array is given, it will be binarized and used
|
||||
as the initial level set. If a string is given, it defines the method
|
||||
to generate a reasonable initial level set with the shape of the
|
||||
`image`. Accepted values are 'checkerboard' and 'disk'. See the
|
||||
documentation of `checkerboard_level_set` and `disk_level_set`
|
||||
respectively for details about how these level sets are created.
|
||||
smoothing : uint, optional
|
||||
Number of times the smoothing operator is applied per iteration.
|
||||
Reasonable values are around 1-4. Larger values lead to smoother
|
||||
segmentations.
|
||||
lambda1 : float, optional
|
||||
Weight parameter for the outer region. If `lambda1` is larger than
|
||||
`lambda2`, the outer region will contain a larger range of values than
|
||||
the inner region.
|
||||
lambda2 : float, optional
|
||||
Weight parameter for the inner region. If `lambda2` is larger than
|
||||
`lambda1`, the inner region will contain a larger range of values than
|
||||
the outer region.
|
||||
iter_callback : function, optional
|
||||
If given, this function is called once per iteration with the current
|
||||
level set as the only argument. This is useful for debugging or for
|
||||
plotting intermediate results during the evolution.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : (M, N) or (L, M, N) array
|
||||
Final segmentation (i.e., the final level set)
|
||||
|
||||
See Also
|
||||
--------
|
||||
disk_level_set, checkerboard_level_set
|
||||
|
||||
Notes
|
||||
-----
|
||||
This is a version of the Chan-Vese algorithm that uses morphological
|
||||
operators instead of solving a partial differential equation (PDE) for the
|
||||
evolution of the contour. The set of morphological operators used in this
|
||||
algorithm are proved to be infinitesimally equivalent to the Chan-Vese PDE
|
||||
(see [1]_). However, morphological operators are do not suffer from the
|
||||
numerical stability issues typically found in PDEs (it is not necessary to
|
||||
find the right time step for the evolution), and are computationally
|
||||
faster.
|
||||
|
||||
The algorithm and its theoretical derivation are described in [1]_.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A Morphological Approach to Curvature-based Evolution of Curves and
|
||||
Surfaces, Pablo Márquez-Neila, Luis Baumela, Luis Álvarez. In IEEE
|
||||
Transactions on Pattern Analysis and Machine Intelligence (PAMI),
|
||||
2014, :DOI:`10.1109/TPAMI.2013.106`
|
||||
"""
|
||||
|
||||
init_level_set = _init_level_set(init_level_set, image.shape)
|
||||
|
||||
_check_input(image, init_level_set)
|
||||
|
||||
u = np.int8(init_level_set > 0)
|
||||
|
||||
iter_callback(u)
|
||||
|
||||
for _ in range(num_iter):
|
||||
|
||||
# inside = u > 0
|
||||
# outside = u <= 0
|
||||
c0 = (image * (1 - u)).sum() / float((1 - u).sum() + 1e-8)
|
||||
c1 = (image * u).sum() / float(u.sum() + 1e-8)
|
||||
|
||||
# Image attachment
|
||||
du = np.gradient(u)
|
||||
abs_du = np.abs(du).sum(0)
|
||||
aux = abs_du * (lambda1 * (image - c1)**2 - lambda2 * (image - c0)**2)
|
||||
|
||||
u[aux < 0] = 1
|
||||
u[aux > 0] = 0
|
||||
|
||||
# Smoothing
|
||||
for _ in range(smoothing):
|
||||
u = _curvop(u)
|
||||
|
||||
iter_callback(u)
|
||||
|
||||
return u
|
||||
|
||||
|
||||
def morphological_geodesic_active_contour(gimage, num_iter,
|
||||
init_level_set='disk', smoothing=1,
|
||||
threshold='auto', balloon=0,
|
||||
iter_callback=lambda x: None):
|
||||
"""Morphological Geodesic Active Contours (MorphGAC).
|
||||
|
||||
Geodesic active contours implemented with morphological operators. It can
|
||||
be used to segment objects with visible but noisy, cluttered, broken
|
||||
borders.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
gimage : (M, N) or (L, M, N) array
|
||||
Preprocessed image or volume to be segmented. This is very rarely the
|
||||
original image. Instead, this is usually a preprocessed version of the
|
||||
original image that enhances and highlights the borders (or other
|
||||
structures) of the object to segment.
|
||||
`morphological_geodesic_active_contour` will try to stop the contour
|
||||
evolution in areas where `gimage` is small. See
|
||||
`morphsnakes.inverse_gaussian_gradient` as an example function to
|
||||
perform this preprocessing. Note that the quality of
|
||||
`morphological_geodesic_active_contour` might greatly depend on this
|
||||
preprocessing.
|
||||
num_iter : uint
|
||||
Number of num_iter to run.
|
||||
init_level_set : str, (M, N) array, or (L, M, N) array
|
||||
Initial level set. If an array is given, it will be binarized and used
|
||||
as the initial level set. If a string is given, it defines the method
|
||||
to generate a reasonable initial level set with the shape of the
|
||||
`image`. Accepted values are 'checkerboard' and 'disk'. See the
|
||||
documentation of `checkerboard_level_set` and `disk_level_set`
|
||||
respectively for details about how these level sets are created.
|
||||
smoothing : uint, optional
|
||||
Number of times the smoothing operator is applied per iteration.
|
||||
Reasonable values are around 1-4. Larger values lead to smoother
|
||||
segmentations.
|
||||
threshold : float, optional
|
||||
Areas of the image with a value smaller than this threshold will be
|
||||
considered borders. The evolution of the contour will stop in this
|
||||
areas.
|
||||
balloon : float, optional
|
||||
Balloon force to guide the contour in non-informative areas of the
|
||||
image, i.e., areas where the gradient of the image is too small to push
|
||||
the contour towards a border. A negative value will shrink the contour,
|
||||
while a positive value will expand the contour in these areas. Setting
|
||||
this to zero will disable the balloon force.
|
||||
iter_callback : function, optional
|
||||
If given, this function is called once per iteration with the current
|
||||
level set as the only argument. This is useful for debugging or for
|
||||
plotting intermediate results during the evolution.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : (M, N) or (L, M, N) array
|
||||
Final segmentation (i.e., the final level set)
|
||||
|
||||
See Also
|
||||
--------
|
||||
inverse_gaussian_gradient, disk_level_set, checkerboard_level_set
|
||||
|
||||
Notes
|
||||
-----
|
||||
This is a version of the Geodesic Active Contours (GAC) algorithm that uses
|
||||
morphological operators instead of solving partial differential equations
|
||||
(PDEs) for the evolution of the contour. The set of morphological operators
|
||||
used in this algorithm are proved to be infinitesimally equivalent to the
|
||||
GAC PDEs (see [1]_). However, morphological operators are do not suffer
|
||||
from the numerical stability issues typically found in PDEs (e.g., it is
|
||||
not necessary to find the right time step for the evolution), and are
|
||||
computationally faster.
|
||||
|
||||
The algorithm and its theoretical derivation are described in [1]_.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] A Morphological Approach to Curvature-based Evolution of Curves and
|
||||
Surfaces, Pablo Márquez-Neila, Luis Baumela, Luis Álvarez. In IEEE
|
||||
Transactions on Pattern Analysis and Machine Intelligence (PAMI),
|
||||
2014, :DOI:`10.1109/TPAMI.2013.106`
|
||||
"""
|
||||
|
||||
image = gimage
|
||||
init_level_set = _init_level_set(init_level_set, image.shape)
|
||||
|
||||
_check_input(image, init_level_set)
|
||||
|
||||
if threshold == 'auto':
|
||||
threshold = np.percentile(image, 40)
|
||||
|
||||
structure = np.ones((3,) * len(image.shape), dtype=np.int8)
|
||||
dimage = np.gradient(image)
|
||||
# threshold_mask = image > threshold
|
||||
if balloon != 0:
|
||||
threshold_mask_balloon = image > threshold / np.abs(balloon)
|
||||
|
||||
u = np.int8(init_level_set > 0)
|
||||
|
||||
iter_callback(u)
|
||||
|
||||
for _ in range(num_iter):
|
||||
|
||||
# Balloon
|
||||
if balloon > 0:
|
||||
aux = ndi.binary_dilation(u, structure)
|
||||
elif balloon < 0:
|
||||
aux = ndi.binary_erosion(u, structure)
|
||||
if balloon != 0:
|
||||
u[threshold_mask_balloon] = aux[threshold_mask_balloon]
|
||||
|
||||
# Image attachment
|
||||
aux = np.zeros_like(image)
|
||||
du = np.gradient(u)
|
||||
for el1, el2 in zip(dimage, du):
|
||||
aux += el1 * el2
|
||||
u[aux > 0] = 1
|
||||
u[aux < 0] = 0
|
||||
|
||||
# Smoothing
|
||||
for _ in range(smoothing):
|
||||
u = _curvop(u)
|
||||
|
||||
iter_callback(u)
|
||||
|
||||
return u
|
||||
519
.CondaPkg/env/Lib/site-packages/skimage/segmentation/random_walker_segmentation.py
vendored
Normal file
519
.CondaPkg/env/Lib/site-packages/skimage/segmentation/random_walker_segmentation.py
vendored
Normal file
@@ -0,0 +1,519 @@
|
||||
"""
|
||||
Random walker segmentation algorithm
|
||||
|
||||
from *Random walks for image segmentation*, Leo Grady, IEEE Trans
|
||||
Pattern Anal Mach Intell. 2006 Nov;28(11):1768-83.
|
||||
|
||||
Installing pyamg and using the 'cg_mg' mode of random_walker improves
|
||||
significantly the performance.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from scipy import sparse, ndimage as ndi
|
||||
|
||||
from .._shared import utils
|
||||
from .._shared.utils import warn
|
||||
|
||||
# executive summary for next code block: try to import umfpack from
|
||||
# scipy, but make sure not to raise a fuss if it fails since it's only
|
||||
# needed to speed up a few cases.
|
||||
# See discussions at:
|
||||
# https://groups.google.com/d/msg/scikit-image/FrM5IGP6wh4/1hp-FtVZmfcJ
|
||||
# https://stackoverflow.com/questions/13977970/ignore-exceptions-printed-to-stderr-in-del/13977992?noredirect=1#comment28386412_13977992
|
||||
try:
|
||||
from scipy.sparse.linalg.dsolve.linsolve import umfpack
|
||||
old_del = umfpack.UmfpackContext.__del__
|
||||
|
||||
def new_del(self):
|
||||
try:
|
||||
old_del(self)
|
||||
except AttributeError:
|
||||
pass
|
||||
umfpack.UmfpackContext.__del__ = new_del
|
||||
UmfpackContext = umfpack.UmfpackContext()
|
||||
except ImportError:
|
||||
UmfpackContext = None
|
||||
|
||||
try:
|
||||
from pyamg import ruge_stuben_solver
|
||||
amg_loaded = True
|
||||
except ImportError:
|
||||
amg_loaded = False
|
||||
|
||||
from ..util import img_as_float
|
||||
|
||||
from scipy.sparse.linalg import cg, spsolve
|
||||
|
||||
|
||||
def _make_graph_edges_3d(n_x, n_y, n_z):
|
||||
"""Returns a list of edges for a 3D image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n_x : integer
|
||||
The size of the grid in the x direction.
|
||||
n_y : integer
|
||||
The size of the grid in the y direction
|
||||
n_z : integer
|
||||
The size of the grid in the z direction
|
||||
|
||||
Returns
|
||||
-------
|
||||
edges : (2, N) ndarray
|
||||
with the total number of edges::
|
||||
|
||||
N = n_x * n_y * (nz - 1) +
|
||||
n_x * (n_y - 1) * nz +
|
||||
(n_x - 1) * n_y * nz
|
||||
|
||||
Graph edges with each column describing a node-id pair.
|
||||
"""
|
||||
vertices = np.arange(n_x * n_y * n_z).reshape((n_x, n_y, n_z))
|
||||
edges_deep = np.vstack((vertices[..., :-1].ravel(),
|
||||
vertices[..., 1:].ravel()))
|
||||
edges_right = np.vstack((vertices[:, :-1].ravel(),
|
||||
vertices[:, 1:].ravel()))
|
||||
edges_down = np.vstack((vertices[:-1].ravel(), vertices[1:].ravel()))
|
||||
edges = np.hstack((edges_deep, edges_right, edges_down))
|
||||
return edges
|
||||
|
||||
|
||||
def _compute_weights_3d(data, spacing, beta, eps, multichannel):
|
||||
# Weight calculation is main difference in multispectral version
|
||||
# Original gradient**2 replaced with sum of gradients ** 2
|
||||
gradients = np.concatenate(
|
||||
[np.diff(data[..., 0], axis=ax).ravel() / spacing[ax]
|
||||
for ax in [2, 1, 0] if data.shape[ax] > 1], axis=0) ** 2
|
||||
for channel in range(1, data.shape[-1]):
|
||||
gradients += np.concatenate(
|
||||
[np.diff(data[..., channel], axis=ax).ravel() / spacing[ax]
|
||||
for ax in [2, 1, 0] if data.shape[ax] > 1], axis=0) ** 2
|
||||
|
||||
# All channels considered together in this standard deviation
|
||||
scale_factor = -beta / (10 * data.std())
|
||||
if multichannel:
|
||||
# New final term in beta to give == results in trivial case where
|
||||
# multiple identical spectra are passed.
|
||||
scale_factor /= np.sqrt(data.shape[-1])
|
||||
weights = np.exp(scale_factor * gradients)
|
||||
weights += eps
|
||||
return -weights
|
||||
|
||||
|
||||
def _build_laplacian(data, spacing, mask, beta, multichannel):
|
||||
l_x, l_y, l_z = data.shape[:3]
|
||||
edges = _make_graph_edges_3d(l_x, l_y, l_z)
|
||||
weights = _compute_weights_3d(data, spacing, beta=beta, eps=1.e-10,
|
||||
multichannel=multichannel)
|
||||
if mask is not None:
|
||||
# Remove edges of the graph connected to masked nodes, as well
|
||||
# as corresponding weights of the edges.
|
||||
mask0 = np.hstack([mask[..., :-1].ravel(), mask[:, :-1].ravel(),
|
||||
mask[:-1].ravel()])
|
||||
mask1 = np.hstack([mask[..., 1:].ravel(), mask[:, 1:].ravel(),
|
||||
mask[1:].ravel()])
|
||||
ind_mask = np.logical_and(mask0, mask1)
|
||||
edges, weights = edges[:, ind_mask], weights[ind_mask]
|
||||
|
||||
# Reassign edges labels to 0, 1, ... edges_number - 1
|
||||
_, inv_idx = np.unique(edges, return_inverse=True)
|
||||
edges = inv_idx.reshape(edges.shape)
|
||||
|
||||
# Build the sparse linear system
|
||||
pixel_nb = l_x * l_y * l_z
|
||||
i_indices = edges.ravel()
|
||||
j_indices = edges[::-1].ravel()
|
||||
data = np.hstack((weights, weights))
|
||||
lap = sparse.coo_matrix((data, (i_indices, j_indices)),
|
||||
shape=(pixel_nb, pixel_nb))
|
||||
lap.setdiag(-np.ravel(lap.sum(axis=0)))
|
||||
return lap.tocsr()
|
||||
|
||||
|
||||
def _build_linear_system(data, spacing, labels, nlabels, mask,
|
||||
beta, multichannel):
|
||||
"""
|
||||
Build the matrix A and rhs B of the linear system to solve.
|
||||
A and B are two block of the laplacian of the image graph.
|
||||
"""
|
||||
if mask is None:
|
||||
labels = labels.ravel()
|
||||
else:
|
||||
labels = labels[mask]
|
||||
|
||||
indices = np.arange(labels.size)
|
||||
seeds_mask = labels > 0
|
||||
unlabeled_indices = indices[~seeds_mask]
|
||||
seeds_indices = indices[seeds_mask]
|
||||
|
||||
lap_sparse = _build_laplacian(data, spacing, mask=mask,
|
||||
beta=beta, multichannel=multichannel)
|
||||
|
||||
rows = lap_sparse[unlabeled_indices, :]
|
||||
lap_sparse = rows[:, unlabeled_indices]
|
||||
B = -rows[:, seeds_indices]
|
||||
|
||||
seeds = labels[seeds_mask]
|
||||
seeds_mask = sparse.csc_matrix(np.hstack(
|
||||
[np.atleast_2d(seeds == lab).T for lab in range(1, nlabels + 1)]))
|
||||
rhs = B.dot(seeds_mask)
|
||||
|
||||
return lap_sparse, rhs
|
||||
|
||||
|
||||
def _solve_linear_system(lap_sparse, B, tol, mode):
|
||||
|
||||
if mode is None:
|
||||
mode = 'cg_j'
|
||||
|
||||
if mode == 'cg_mg' and not amg_loaded:
|
||||
warn('"cg_mg" not available, it requires pyamg to be installed. '
|
||||
'The "cg_j" mode will be used instead.',
|
||||
stacklevel=2)
|
||||
mode = 'cg_j'
|
||||
|
||||
if mode == 'bf':
|
||||
X = spsolve(lap_sparse, B.toarray()).T
|
||||
else:
|
||||
maxiter = None
|
||||
if mode == 'cg':
|
||||
if UmfpackContext is None:
|
||||
warn('"cg" mode may be slow because UMFPACK is not available. '
|
||||
'Consider building Scipy with UMFPACK or use a '
|
||||
'preconditioned version of CG ("cg_j" or "cg_mg" modes).',
|
||||
stacklevel=2)
|
||||
M = None
|
||||
elif mode == 'cg_j':
|
||||
M = sparse.diags(1.0 / lap_sparse.diagonal())
|
||||
else:
|
||||
# mode == 'cg_mg'
|
||||
lap_sparse = lap_sparse.tocsr()
|
||||
ml = ruge_stuben_solver(lap_sparse, coarse_solver='pinv')
|
||||
M = ml.aspreconditioner(cycle='V')
|
||||
maxiter = 30
|
||||
cg_out = [
|
||||
cg(lap_sparse, B[:, i].toarray(),
|
||||
tol=tol, atol=0, M=M, maxiter=maxiter)
|
||||
for i in range(B.shape[1])]
|
||||
if np.any([info > 0 for _, info in cg_out]):
|
||||
warn("Conjugate gradient convergence to tolerance not achieved. "
|
||||
"Consider decreasing beta to improve system conditionning.",
|
||||
stacklevel=2)
|
||||
X = np.asarray([x for x, _ in cg_out])
|
||||
|
||||
return X
|
||||
|
||||
|
||||
def _preprocess(labels):
|
||||
|
||||
label_values, inv_idx = np.unique(labels, return_inverse=True)
|
||||
if max(label_values) <= 0:
|
||||
raise ValueError('No seeds provided in label image: please ensure '
|
||||
'it contains at least one positive value')
|
||||
|
||||
if not (label_values == 0).any():
|
||||
warn('Random walker only segments unlabeled areas, where '
|
||||
'labels == 0. No zero valued areas in labels were '
|
||||
'found. Returning provided labels.',
|
||||
stacklevel=2)
|
||||
|
||||
return labels, None, None, None, None
|
||||
|
||||
# If some labeled pixels are isolated inside pruned zones, prune them
|
||||
# as well and keep the labels for the final output
|
||||
|
||||
null_mask = labels == 0
|
||||
pos_mask = labels > 0
|
||||
mask = labels >= 0
|
||||
|
||||
fill = ndi.binary_propagation(null_mask, mask=mask)
|
||||
isolated = np.logical_and(pos_mask, np.logical_not(fill))
|
||||
|
||||
pos_mask[isolated] = False
|
||||
|
||||
# If the array has pruned zones, be sure that no isolated pixels
|
||||
# exist between pruned zones (they could not be determined)
|
||||
if label_values[0] < 0 or np.any(isolated):
|
||||
isolated = np.logical_and(
|
||||
np.logical_not(ndi.binary_propagation(pos_mask, mask=mask)),
|
||||
null_mask)
|
||||
|
||||
labels[isolated] = -1
|
||||
if np.all(isolated[null_mask]):
|
||||
warn('All unlabeled pixels are isolated, they could not be '
|
||||
'determined by the random walker algorithm.',
|
||||
stacklevel=2)
|
||||
return labels, None, None, None, None
|
||||
|
||||
mask[isolated] = False
|
||||
mask = np.atleast_3d(mask)
|
||||
else:
|
||||
mask = None
|
||||
|
||||
# Reorder label values to have consecutive integers (no gaps)
|
||||
zero_idx = np.searchsorted(label_values, 0)
|
||||
labels = np.atleast_3d(inv_idx.reshape(labels.shape) - zero_idx)
|
||||
|
||||
nlabels = label_values[zero_idx + 1:].shape[0]
|
||||
|
||||
inds_isolated_seeds = np.nonzero(isolated)
|
||||
isolated_values = labels[inds_isolated_seeds]
|
||||
|
||||
return labels, nlabels, mask, inds_isolated_seeds, isolated_values
|
||||
|
||||
|
||||
@utils.channel_as_last_axis(multichannel_output=False)
|
||||
def random_walker(data, labels, beta=130, mode='cg_j', tol=1.e-3, copy=True,
|
||||
return_full_prob=False, spacing=None,
|
||||
*, prob_tol=1e-3, channel_axis=None):
|
||||
"""Random walker algorithm for segmentation from markers.
|
||||
|
||||
Random walker algorithm is implemented for gray-level or multichannel
|
||||
images.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : array_like
|
||||
Image to be segmented in phases. Gray-level `data` can be two- or
|
||||
three-dimensional; multichannel data can be three- or four-
|
||||
dimensional with `channel_axis` specifying the dimension containing
|
||||
channels. Data spacing is assumed isotropic unless the `spacing`
|
||||
keyword argument is used.
|
||||
labels : array of ints, of same shape as `data` without channels dimension
|
||||
Array of seed markers labeled with different positive integers
|
||||
for different phases. Zero-labeled pixels are unlabeled pixels.
|
||||
Negative labels correspond to inactive pixels that are not taken
|
||||
into account (they are removed from the graph). If labels are not
|
||||
consecutive integers, the labels array will be transformed so that
|
||||
labels are consecutive. In the multichannel case, `labels` should have
|
||||
the same shape as a single channel of `data`, i.e. without the final
|
||||
dimension denoting channels.
|
||||
beta : float, optional
|
||||
Penalization coefficient for the random walker motion
|
||||
(the greater `beta`, the more difficult the diffusion).
|
||||
mode : string, available options {'cg', 'cg_j', 'cg_mg', 'bf'}
|
||||
Mode for solving the linear system in the random walker algorithm.
|
||||
|
||||
- 'bf' (brute force): an LU factorization of the Laplacian is
|
||||
computed. This is fast for small images (<1024x1024), but very slow
|
||||
and memory-intensive for large images (e.g., 3-D volumes).
|
||||
- 'cg' (conjugate gradient): the linear system is solved iteratively
|
||||
using the Conjugate Gradient method from scipy.sparse.linalg. This is
|
||||
less memory-consuming than the brute force method for large images,
|
||||
but it is quite slow.
|
||||
- 'cg_j' (conjugate gradient with Jacobi preconditionner): the
|
||||
Jacobi preconditionner is applied during the Conjugate
|
||||
gradient method iterations. This may accelerate the
|
||||
convergence of the 'cg' method.
|
||||
- 'cg_mg' (conjugate gradient with multigrid preconditioner): a
|
||||
preconditioner is computed using a multigrid solver, then the
|
||||
solution is computed with the Conjugate Gradient method. This mode
|
||||
requires that the pyamg module is installed.
|
||||
tol : float, optional
|
||||
Tolerance to achieve when solving the linear system using
|
||||
the conjugate gradient based modes ('cg', 'cg_j' and 'cg_mg').
|
||||
copy : bool, optional
|
||||
If copy is False, the `labels` array will be overwritten with
|
||||
the result of the segmentation. Use copy=False if you want to
|
||||
save on memory.
|
||||
return_full_prob : bool, optional
|
||||
If True, the probability that a pixel belongs to each of the
|
||||
labels will be returned, instead of only the most likely
|
||||
label.
|
||||
spacing : iterable of floats, optional
|
||||
Spacing between voxels in each spatial dimension. If `None`, then
|
||||
the spacing between pixels/voxels in each dimension is assumed 1.
|
||||
prob_tol : float, optional
|
||||
Tolerance on the resulting probability to be in the interval [0, 1].
|
||||
If the tolerance is not satisfied, a warning is displayed.
|
||||
channel_axis : int or None, optional
|
||||
If None, the image is assumed to be a grayscale (single channel) image.
|
||||
Otherwise, this parameter indicates which axis of the array corresponds
|
||||
to channels.
|
||||
|
||||
.. versionadded:: 0.19
|
||||
``channel_axis`` was added in 0.19.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output : ndarray
|
||||
* If `return_full_prob` is False, array of ints of same shape
|
||||
and data type as `labels`, in which each pixel has been
|
||||
labeled according to the marker that reached the pixel first
|
||||
by anisotropic diffusion.
|
||||
* If `return_full_prob` is True, array of floats of shape
|
||||
`(nlabels, labels.shape)`. `output[label_nb, i, j]` is the
|
||||
probability that label `label_nb` reaches the pixel `(i, j)`
|
||||
first.
|
||||
|
||||
See Also
|
||||
--------
|
||||
skimage.morphology.watershed : watershed segmentation
|
||||
A segmentation algorithm based on mathematical morphology
|
||||
and "flooding" of regions from markers.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Multichannel inputs are scaled with all channel data combined. Ensure all
|
||||
channels are separately normalized prior to running this algorithm.
|
||||
|
||||
The `spacing` argument is specifically for anisotropic datasets, where
|
||||
data points are spaced differently in one or more spatial dimensions.
|
||||
Anisotropic data is commonly encountered in medical imaging.
|
||||
|
||||
The algorithm was first proposed in [1]_.
|
||||
|
||||
The algorithm solves the diffusion equation at infinite times for
|
||||
sources placed on markers of each phase in turn. A pixel is labeled with
|
||||
the phase that has the greatest probability to diffuse first to the pixel.
|
||||
|
||||
The diffusion equation is solved by minimizing x.T L x for each phase,
|
||||
where L is the Laplacian of the weighted graph of the image, and x is
|
||||
the probability that a marker of the given phase arrives first at a pixel
|
||||
by diffusion (x=1 on markers of the phase, x=0 on the other markers, and
|
||||
the other coefficients are looked for). Each pixel is attributed the label
|
||||
for which it has a maximal value of x. The Laplacian L of the image
|
||||
is defined as:
|
||||
|
||||
- L_ii = d_i, the number of neighbors of pixel i (the degree of i)
|
||||
- L_ij = -w_ij if i and j are adjacent pixels
|
||||
|
||||
The weight w_ij is a decreasing function of the norm of the local gradient.
|
||||
This ensures that diffusion is easier between pixels of similar values.
|
||||
|
||||
When the Laplacian is decomposed into blocks of marked and unmarked
|
||||
pixels::
|
||||
|
||||
L = M B.T
|
||||
B A
|
||||
|
||||
with first indices corresponding to marked pixels, and then to unmarked
|
||||
pixels, minimizing x.T L x for one phase amount to solving::
|
||||
|
||||
A x = - B x_m
|
||||
|
||||
where x_m = 1 on markers of the given phase, and 0 on other markers.
|
||||
This linear system is solved in the algorithm using a direct method for
|
||||
small images, and an iterative method for larger images.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Leo Grady, Random walks for image segmentation, IEEE Trans Pattern
|
||||
Anal Mach Intell. 2006 Nov;28(11):1768-83.
|
||||
:DOI:`10.1109/TPAMI.2006.233`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> a = np.zeros((10, 10)) + 0.2 * rng.random((10, 10))
|
||||
>>> a[5:8, 5:8] += 1
|
||||
>>> b = np.zeros_like(a, dtype=np.int32)
|
||||
>>> b[3, 3] = 1 # Marker for first phase
|
||||
>>> b[6, 6] = 2 # Marker for second phase
|
||||
>>> random_walker(a, b) # doctest: +SKIP
|
||||
array([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 2, 2, 2, 1, 1],
|
||||
[1, 1, 1, 1, 1, 2, 2, 2, 1, 1],
|
||||
[1, 1, 1, 1, 1, 2, 2, 2, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]], dtype=int32)
|
||||
|
||||
"""
|
||||
# Parse input data
|
||||
if mode not in ('cg_mg', 'cg', 'bf', 'cg_j', None):
|
||||
raise ValueError(
|
||||
f"{mode} is not a valid mode. Valid modes are 'cg_mg', "
|
||||
f"'cg', 'cg_j', 'bf', and None")
|
||||
|
||||
# Spacing kwarg checks
|
||||
if spacing is None:
|
||||
spacing = np.ones(3)
|
||||
elif len(spacing) == labels.ndim:
|
||||
if len(spacing) == 2:
|
||||
# Need a dummy spacing for singleton 3rd dim
|
||||
spacing = np.r_[spacing, 1.]
|
||||
spacing = np.asarray(spacing)
|
||||
else:
|
||||
raise ValueError('Input argument `spacing` incorrect, should be an '
|
||||
'iterable with one number per spatial dimension.')
|
||||
|
||||
# This algorithm expects 4-D arrays of floats, where the first three
|
||||
# dimensions are spatial and the final denotes channels. 2-D images have
|
||||
# a singleton placeholder dimension added for the third spatial dimension,
|
||||
# and single channel images likewise have a singleton added for channels.
|
||||
# The following block ensures valid input and coerces it to the correct
|
||||
# form.
|
||||
multichannel = channel_axis is not None
|
||||
if not multichannel:
|
||||
if data.ndim not in (2, 3):
|
||||
raise ValueError('For non-multichannel input, data must be of '
|
||||
'dimension 2 or 3.')
|
||||
if data.shape != labels.shape:
|
||||
raise ValueError('Incompatible data and labels shapes.')
|
||||
data = np.atleast_3d(img_as_float(data))[..., np.newaxis]
|
||||
else:
|
||||
if data.ndim not in (3, 4):
|
||||
raise ValueError('For multichannel input, data must have 3 or 4 '
|
||||
'dimensions.')
|
||||
if data.shape[:-1] != labels.shape:
|
||||
raise ValueError('Incompatible data and labels shapes.')
|
||||
data = img_as_float(data)
|
||||
if data.ndim == 3: # 2D multispectral, needs singleton in 3rd axis
|
||||
data = data[:, :, np.newaxis, :]
|
||||
|
||||
labels_shape = labels.shape
|
||||
labels_dtype = labels.dtype
|
||||
|
||||
if copy:
|
||||
labels = np.copy(labels)
|
||||
|
||||
(labels, nlabels, mask,
|
||||
inds_isolated_seeds, isolated_values) = _preprocess(labels)
|
||||
|
||||
if isolated_values is None:
|
||||
# No non isolated zero valued areas in labels were
|
||||
# found. Returning provided labels.
|
||||
if return_full_prob:
|
||||
# Return the concatenation of the masks of each unique label
|
||||
return np.concatenate([np.atleast_3d(labels == lab)
|
||||
for lab in np.unique(labels) if lab > 0],
|
||||
axis=-1)
|
||||
return labels
|
||||
|
||||
# Build the linear system (lap_sparse, B)
|
||||
lap_sparse, B = _build_linear_system(data, spacing, labels, nlabels, mask,
|
||||
beta, multichannel)
|
||||
|
||||
# Solve the linear system lap_sparse X = B
|
||||
# where X[i, j] is the probability that a marker of label i arrives
|
||||
# first at pixel j by anisotropic diffusion.
|
||||
X = _solve_linear_system(lap_sparse, B, tol, mode)
|
||||
|
||||
if X.min() < -prob_tol or X.max() > 1 + prob_tol:
|
||||
warn('The probability range is outside [0, 1] given the tolerance '
|
||||
'`prob_tol`. Consider decreasing `beta` and/or decreasing '
|
||||
'`tol`.')
|
||||
|
||||
# Build the output according to return_full_prob value
|
||||
# Put back labels of isolated seeds
|
||||
labels[inds_isolated_seeds] = isolated_values
|
||||
labels = labels.reshape(labels_shape)
|
||||
|
||||
mask = labels == 0
|
||||
mask[inds_isolated_seeds] = False
|
||||
|
||||
if return_full_prob:
|
||||
out = np.zeros((nlabels,) + labels_shape)
|
||||
for lab, (label_prob, prob) in enumerate(zip(out, X), start=1):
|
||||
label_prob[mask] = prob
|
||||
label_prob[labels == lab] = 1
|
||||
else:
|
||||
X = np.argmax(X, axis=0) + 1
|
||||
out = labels.astype(labels_dtype)
|
||||
out[mask] = X
|
||||
|
||||
return out
|
||||
409
.CondaPkg/env/Lib/site-packages/skimage/segmentation/slic_superpixels.py
vendored
Normal file
409
.CondaPkg/env/Lib/site-packages/skimage/segmentation/slic_superpixels.py
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
import math
|
||||
from collections.abc import Iterable
|
||||
from warnings import warn
|
||||
|
||||
import numpy as np
|
||||
from numpy import random
|
||||
from scipy.cluster.vq import kmeans2
|
||||
from scipy.spatial.distance import pdist, squareform
|
||||
|
||||
from .._shared import utils
|
||||
from .._shared.filters import gaussian
|
||||
from ..color import rgb2lab
|
||||
from ..util import img_as_float, regular_grid
|
||||
from ._slic import _enforce_label_connectivity_cython, _slic_cython
|
||||
|
||||
|
||||
def _get_mask_centroids(mask, n_centroids, multichannel):
|
||||
"""Find regularly spaced centroids on a mask.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mask : 3D ndarray
|
||||
The mask within which the centroids must be positioned.
|
||||
n_centroids : int
|
||||
The number of centroids to be returned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
centroids : 2D ndarray
|
||||
The coordinates of the centroids with shape (n_centroids, 3).
|
||||
steps : 1D ndarray
|
||||
The approximate distance between two seeds in all dimensions.
|
||||
|
||||
"""
|
||||
|
||||
# Get tight ROI around the mask to optimize
|
||||
coord = np.array(np.nonzero(mask), dtype=float).T
|
||||
# Fix random seed to ensure repeatability
|
||||
# Keep old-style RandomState here as expected results in tests depend on it
|
||||
rnd = random.RandomState(123)
|
||||
|
||||
# select n_centroids randomly distributed points from within the mask
|
||||
idx_full = np.arange(len(coord), dtype=int)
|
||||
idx = np.sort(rnd.choice(idx_full,
|
||||
min(n_centroids, len(coord)),
|
||||
replace=False))
|
||||
|
||||
# To save time, when n_centroids << len(coords), use only a subset of the
|
||||
# coordinates when calling k-means. Rather than the full set of coords,
|
||||
# we will use a substantially larger subset than n_centroids. Here we
|
||||
# somewhat arbitrarily choose dense_factor=10 to make the samples
|
||||
# 10 times closer together along each axis than the n_centroids samples.
|
||||
dense_factor = 10
|
||||
ndim_spatial = mask.ndim - 1 if multichannel else mask.ndim
|
||||
n_dense = int((dense_factor ** ndim_spatial) * n_centroids)
|
||||
if len(coord) > n_dense:
|
||||
# subset of points to use for the k-means calculation
|
||||
# (much denser than idx, but less than the full set)
|
||||
idx_dense = np.sort(rnd.choice(idx_full,
|
||||
n_dense,
|
||||
replace=False))
|
||||
else:
|
||||
idx_dense = Ellipsis
|
||||
centroids, _ = kmeans2(coord[idx_dense], coord[idx], iter=5)
|
||||
|
||||
# Compute the minimum distance of each centroid to the others
|
||||
dist = squareform(pdist(centroids))
|
||||
np.fill_diagonal(dist, np.inf)
|
||||
closest_pts = dist.argmin(-1)
|
||||
steps = abs(centroids - centroids[closest_pts, :]).mean(0)
|
||||
|
||||
return centroids, steps
|
||||
|
||||
|
||||
def _get_grid_centroids(image, n_centroids):
|
||||
"""Find regularly spaced centroids on the image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D, 3D or 4D ndarray
|
||||
Input image, which can be 2D or 3D, and grayscale or
|
||||
multichannel.
|
||||
n_centroids : int
|
||||
The (approximate) number of centroids to be returned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
centroids : 2D ndarray
|
||||
The coordinates of the centroids with shape (~n_centroids, 3).
|
||||
steps : 1D ndarray
|
||||
The approximate distance between two seeds in all dimensions.
|
||||
|
||||
"""
|
||||
d, h, w = image.shape[:3]
|
||||
|
||||
grid_z, grid_y, grid_x = np.mgrid[:d, :h, :w]
|
||||
slices = regular_grid(image.shape[:3], n_centroids)
|
||||
|
||||
centroids_z = grid_z[slices].ravel()[..., np.newaxis]
|
||||
centroids_y = grid_y[slices].ravel()[..., np.newaxis]
|
||||
centroids_x = grid_x[slices].ravel()[..., np.newaxis]
|
||||
|
||||
centroids = np.concatenate([centroids_z, centroids_y, centroids_x],
|
||||
axis=-1)
|
||||
|
||||
steps = np.asarray([float(s.step) if s.step is not None else 1.0
|
||||
for s in slices])
|
||||
return centroids, steps
|
||||
|
||||
|
||||
@utils.channel_as_last_axis(multichannel_output=False)
|
||||
def slic(image, n_segments=100, compactness=10., max_num_iter=10, sigma=0,
|
||||
spacing=None, convert2lab=None,
|
||||
enforce_connectivity=True, min_size_factor=0.5, max_size_factor=3,
|
||||
slic_zero=False, start_label=1, mask=None, *,
|
||||
channel_axis=-1):
|
||||
"""Segments image using k-means clustering in Color-(x,y,z) space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : 2D, 3D or 4D ndarray
|
||||
Input image, which can be 2D or 3D, and grayscale or multichannel
|
||||
(see `channel_axis` parameter).
|
||||
Input image must either be NaN-free or the NaN's must be masked out
|
||||
n_segments : int, optional
|
||||
The (approximate) number of labels in the segmented output image.
|
||||
compactness : float, optional
|
||||
Balances color proximity and space proximity. Higher values give
|
||||
more weight to space proximity, making superpixel shapes more
|
||||
square/cubic. In SLICO mode, this is the initial compactness.
|
||||
This parameter depends strongly on image contrast and on the
|
||||
shapes of objects in the image. We recommend exploring possible
|
||||
values on a log scale, e.g., 0.01, 0.1, 1, 10, 100, before
|
||||
refining around a chosen value.
|
||||
max_num_iter : int, optional
|
||||
Maximum number of iterations of k-means.
|
||||
sigma : float or array-like of floats, optional
|
||||
Width of Gaussian smoothing kernel for pre-processing for each
|
||||
dimension of the image. The same sigma is applied to each dimension in
|
||||
case of a scalar value. Zero means no smoothing.
|
||||
Note that `sigma` is automatically scaled if it is scalar and
|
||||
if a manual voxel spacing is provided (see Notes section). If
|
||||
sigma is array-like, its size must match ``image``'s number
|
||||
of spatial dimensions.
|
||||
spacing : array-like of floats, optional
|
||||
The voxel spacing along each spatial dimension. By default,
|
||||
`slic` assumes uniform spacing (same voxel resolution along
|
||||
each spatial dimension).
|
||||
This parameter controls the weights of the distances along the
|
||||
spatial dimensions during k-means clustering.
|
||||
convert2lab : bool, optional
|
||||
Whether the input should be converted to Lab colorspace prior to
|
||||
segmentation. The input image *must* be RGB. Highly recommended.
|
||||
This option defaults to ``True`` when ``channel_axis` is not None *and*
|
||||
``image.shape[-1] == 3``.
|
||||
enforce_connectivity : bool, optional
|
||||
Whether the generated segments are connected or not
|
||||
min_size_factor : float, optional
|
||||
Proportion of the minimum segment size to be removed with respect
|
||||
to the supposed segment size ```depth*width*height/n_segments```
|
||||
max_size_factor : float, optional
|
||||
Proportion of the maximum connected segment size. A value of 3 works
|
||||
in most of the cases.
|
||||
slic_zero : bool, optional
|
||||
Run SLIC-zero, the zero-parameter mode of SLIC. [2]_
|
||||
start_label : int, optional
|
||||
The labels' index start. Should be 0 or 1.
|
||||
|
||||
.. versionadded:: 0.17
|
||||
``start_label`` was introduced in 0.17
|
||||
mask : ndarray, optional
|
||||
If provided, superpixels are computed only where mask is True,
|
||||
and seed points are homogeneously distributed over the mask
|
||||
using a k-means clustering strategy. Mask number of dimensions
|
||||
must be equal to image number of spatial dimensions.
|
||||
|
||||
.. versionadded:: 0.17
|
||||
``mask`` was introduced in 0.17
|
||||
channel_axis : int or None, optional
|
||||
If None, the image is assumed to be a grayscale (single channel) image.
|
||||
Otherwise, this parameter indicates which axis of the array corresponds
|
||||
to channels.
|
||||
|
||||
.. versionadded:: 0.19
|
||||
``channel_axis`` was added in 0.19.
|
||||
|
||||
Returns
|
||||
-------
|
||||
labels : 2D or 3D array
|
||||
Integer mask indicating segment labels.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If ``convert2lab`` is set to ``True`` but the last array
|
||||
dimension is not of length 3.
|
||||
ValueError
|
||||
If ``start_label`` is not 0 or 1.
|
||||
ValueError
|
||||
If ``image`` contains unmasked NaN values.
|
||||
ValueError
|
||||
If ``image`` contains unmasked infinite values.
|
||||
ValueError
|
||||
If ``image`` is 2D but ``channel_axis`` is -1 (the default).
|
||||
|
||||
Notes
|
||||
-----
|
||||
* If `sigma > 0`, the image is smoothed using a Gaussian kernel prior to
|
||||
segmentation.
|
||||
|
||||
* If `sigma` is scalar and `spacing` is provided, the kernel width is
|
||||
divided along each dimension by the spacing. For example, if ``sigma=1``
|
||||
and ``spacing=[5, 1, 1]``, the effective `sigma` is ``[0.2, 1, 1]``. This
|
||||
ensures sensible smoothing for anisotropic images.
|
||||
|
||||
* The image is rescaled to be in [0, 1] prior to processing (masked
|
||||
values are ignored).
|
||||
|
||||
* Images of shape (M, N, 3) are interpreted as 2D RGB images by default. To
|
||||
interpret them as 3D with the last dimension having length 3, use
|
||||
`channel_axis=None`.
|
||||
|
||||
* `start_label` is introduced to handle the issue [4]_. Label indexing
|
||||
starts at 1 by default.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Radhakrishna Achanta, Appu Shaji, Kevin Smith, Aurelien Lucchi,
|
||||
Pascal Fua, and Sabine Süsstrunk, SLIC Superpixels Compared to
|
||||
State-of-the-art Superpixel Methods, TPAMI, May 2012.
|
||||
:DOI:`10.1109/TPAMI.2012.120`
|
||||
.. [2] https://www.epfl.ch/labs/ivrl/research/slic-superpixels/#SLICO
|
||||
.. [3] Irving, Benjamin. "maskSLIC: regional superpixel generation with
|
||||
application to local pathology characterisation in medical images.",
|
||||
2016, :arXiv:`1606.09518`
|
||||
.. [4] https://github.com/scikit-image/scikit-image/issues/3722
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.segmentation import slic
|
||||
>>> from skimage.data import astronaut
|
||||
>>> img = astronaut()
|
||||
>>> segments = slic(img, n_segments=100, compactness=10)
|
||||
|
||||
Increasing the compactness parameter yields more square regions:
|
||||
|
||||
>>> segments = slic(img, n_segments=100, compactness=20)
|
||||
|
||||
"""
|
||||
if image.ndim == 2 and channel_axis is not None:
|
||||
raise ValueError(
|
||||
f"channel_axis={channel_axis} indicates multichannel, which is not "
|
||||
"supported for a two-dimensional image; use channel_axis=None if "
|
||||
"the image is grayscale"
|
||||
)
|
||||
|
||||
image = img_as_float(image)
|
||||
float_dtype = utils._supported_float_type(image.dtype)
|
||||
# copy=True so subsequent in-place operations do not modify the
|
||||
# function input
|
||||
image = image.astype(float_dtype, copy=True)
|
||||
|
||||
if mask is not None:
|
||||
# Create masked_image to rescale while ignoring masked values
|
||||
mask = np.ascontiguousarray(mask, dtype=bool)
|
||||
if channel_axis is not None:
|
||||
mask_ = np.expand_dims(mask, axis=channel_axis)
|
||||
mask_ = np.broadcast_to(mask_, image.shape)
|
||||
else:
|
||||
mask_ = mask
|
||||
image_values = image[mask_]
|
||||
else:
|
||||
image_values = image
|
||||
|
||||
# Rescale image to [0, 1] to make choice of compactness insensitive to
|
||||
# input image scale.
|
||||
imin = image_values.min()
|
||||
imax = image_values.max()
|
||||
if np.isnan(imin):
|
||||
raise ValueError("unmasked NaN values in image are not supported")
|
||||
if np.isinf(imin) or np.isinf(imax):
|
||||
raise ValueError("unmasked infinite values in image are not supported")
|
||||
image -= imin
|
||||
if imax != imin:
|
||||
image /= (imax - imin)
|
||||
|
||||
use_mask = mask is not None
|
||||
dtype = image.dtype
|
||||
|
||||
is_2d = False
|
||||
|
||||
multichannel = channel_axis is not None
|
||||
if image.ndim == 2:
|
||||
# 2D grayscale image
|
||||
image = image[np.newaxis, ..., np.newaxis]
|
||||
is_2d = True
|
||||
elif image.ndim == 3 and multichannel:
|
||||
# Make 2D multichannel image 3D with depth = 1
|
||||
image = image[np.newaxis, ...]
|
||||
is_2d = True
|
||||
elif image.ndim == 3 and not multichannel:
|
||||
# Add channel as single last dimension
|
||||
image = image[..., np.newaxis]
|
||||
|
||||
if multichannel and (convert2lab or convert2lab is None):
|
||||
if image.shape[channel_axis] != 3 and convert2lab:
|
||||
raise ValueError("Lab colorspace conversion requires a RGB image.")
|
||||
elif image.shape[channel_axis] == 3:
|
||||
image = rgb2lab(image)
|
||||
|
||||
if start_label not in [0, 1]:
|
||||
raise ValueError("start_label should be 0 or 1.")
|
||||
|
||||
# initialize cluster centroids for desired number of segments
|
||||
update_centroids = False
|
||||
if use_mask:
|
||||
mask = mask.view('uint8')
|
||||
if mask.ndim == 2:
|
||||
mask = np.ascontiguousarray(mask[np.newaxis, ...])
|
||||
if mask.shape != image.shape[:3]:
|
||||
raise ValueError("image and mask should have the same shape.")
|
||||
centroids, steps = _get_mask_centroids(mask, n_segments, multichannel)
|
||||
update_centroids = True
|
||||
else:
|
||||
centroids, steps = _get_grid_centroids(image, n_segments)
|
||||
|
||||
if spacing is None:
|
||||
spacing = np.ones(3, dtype=dtype)
|
||||
elif isinstance(spacing, Iterable):
|
||||
spacing = np.asarray(spacing, dtype=dtype)
|
||||
if is_2d:
|
||||
if spacing.size != 2:
|
||||
if spacing.size == 3:
|
||||
warn("Input image is 2D: spacing number of "
|
||||
"elements must be 2. In the future, a ValueError "
|
||||
"will be raised.", FutureWarning, stacklevel=2)
|
||||
else:
|
||||
raise ValueError(f"Input image is 2D, but spacing has "
|
||||
f"{spacing.size} elements (expected 2).")
|
||||
else:
|
||||
spacing = np.insert(spacing, 0, 1)
|
||||
elif spacing.size != 3:
|
||||
raise ValueError(f"Input image is 3D, but spacing has "
|
||||
f"{spacing.size} elements (expected 3).")
|
||||
spacing = np.ascontiguousarray(spacing, dtype=dtype)
|
||||
else:
|
||||
raise TypeError("spacing must be None or iterable.")
|
||||
|
||||
if np.isscalar(sigma):
|
||||
sigma = np.array([sigma, sigma, sigma], dtype=dtype)
|
||||
sigma /= spacing
|
||||
elif isinstance(sigma, Iterable):
|
||||
sigma = np.asarray(sigma, dtype=dtype)
|
||||
if is_2d:
|
||||
if sigma.size != 2:
|
||||
if spacing.size == 3:
|
||||
warn("Input image is 2D: sigma number of "
|
||||
"elements must be 2. In the future, a ValueError "
|
||||
"will be raised.", FutureWarning, stacklevel=2)
|
||||
else:
|
||||
raise ValueError(f"Input image is 2D, but sigma has "
|
||||
f"{sigma.size} elements (expected 2).")
|
||||
else:
|
||||
sigma = np.insert(sigma, 0, 0)
|
||||
elif sigma.size != 3:
|
||||
raise ValueError(f"Input image is 3D, but sigma has "
|
||||
f"{sigma.size} elements (expected 3).")
|
||||
|
||||
if (sigma > 0).any():
|
||||
# add zero smoothing for channel dimension
|
||||
sigma = list(sigma) + [0]
|
||||
image = gaussian(image, sigma, mode='reflect')
|
||||
|
||||
n_centroids = centroids.shape[0]
|
||||
segments = np.ascontiguousarray(np.concatenate(
|
||||
[centroids, np.zeros((n_centroids, image.shape[3]))],
|
||||
axis=-1), dtype=dtype)
|
||||
|
||||
# Scaling of ratio in the same way as in the SLIC paper so the
|
||||
# values have the same meaning
|
||||
step = max(steps)
|
||||
ratio = 1.0 / compactness
|
||||
|
||||
image = np.ascontiguousarray(image * ratio, dtype=dtype)
|
||||
|
||||
if update_centroids:
|
||||
# Step 2 of the algorithm [3]_
|
||||
_slic_cython(image, mask, segments, step, max_num_iter, spacing,
|
||||
slic_zero, ignore_color=True,
|
||||
start_label=start_label)
|
||||
|
||||
labels = _slic_cython(image, mask, segments, step, max_num_iter,
|
||||
spacing, slic_zero, ignore_color=False,
|
||||
start_label=start_label)
|
||||
|
||||
if enforce_connectivity:
|
||||
if use_mask:
|
||||
segment_size = mask.sum() / n_centroids
|
||||
else:
|
||||
segment_size = math.prod(image.shape[:3]) / n_centroids
|
||||
min_size = int(min_size_factor * segment_size)
|
||||
max_size = int(max_size_factor * segment_size)
|
||||
labels = _enforce_label_connectivity_cython(
|
||||
labels, min_size, max_size, start_label=start_label)
|
||||
|
||||
if is_2d:
|
||||
labels = labels[0]
|
||||
|
||||
return labels
|
||||
0
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/__init__.py
vendored
Normal file
0
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/__init__.py
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/__pycache__/__init__.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/__pycache__/test_join.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/__pycache__/test_join.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/__pycache__/test_slic.cpython-311.pyc
vendored
Normal file
BIN
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/__pycache__/test_slic.cpython-311.pyc
vendored
Normal file
Binary file not shown.
Binary file not shown.
124
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_active_contour_model.py
vendored
Normal file
124
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_active_contour_model.py
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_equal, assert_allclose
|
||||
|
||||
from skimage import data
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.color import rgb2gray
|
||||
from skimage.filters import gaussian
|
||||
from skimage.segmentation import active_contour
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_periodic_reference(dtype):
|
||||
img = data.astronaut()
|
||||
img = rgb2gray(img)
|
||||
s = np.linspace(0, 2*np.pi, 400)
|
||||
r = 100 + 100*np.sin(s)
|
||||
c = 220 + 100*np.cos(s)
|
||||
init = np.array([r, c]).T
|
||||
img_smooth = gaussian(img, 3, preserve_range=False).astype(dtype, copy=False)
|
||||
snake = active_contour(img_smooth, init, alpha=0.015, beta=10,
|
||||
w_line=0, w_edge=1, gamma=0.001)
|
||||
assert snake.dtype == _supported_float_type(dtype)
|
||||
refr = [98, 99, 100, 101, 102, 103, 104, 105, 106, 108]
|
||||
refc = [299, 298, 298, 298, 298, 297, 297, 296, 296, 295]
|
||||
assert_equal(np.array(snake[:10, 0], dtype=np.int32), refr)
|
||||
assert_equal(np.array(snake[:10, 1], dtype=np.int32), refc)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_fixed_reference(dtype):
|
||||
img = data.text()
|
||||
r = np.linspace(136, 50, 100)
|
||||
c = np.linspace(5, 424, 100)
|
||||
init = np.array([r, c]).T
|
||||
image_smooth = gaussian(img, 1, preserve_range=False).astype(dtype, copy=False)
|
||||
snake = active_contour(image_smooth, init, boundary_condition='fixed',
|
||||
alpha=0.1, beta=1.0, w_line=-5, w_edge=0, gamma=0.1)
|
||||
assert snake.dtype == _supported_float_type(dtype)
|
||||
refr = [136, 135, 134, 133, 132, 131, 129, 128, 127, 125]
|
||||
refc = [5, 9, 13, 17, 21, 25, 30, 34, 38, 42]
|
||||
assert_equal(np.array(snake[:10, 0], dtype=np.int32), refr)
|
||||
assert_equal(np.array(snake[:10, 1], dtype=np.int32), refc)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_free_reference(dtype):
|
||||
img = data.text()
|
||||
r = np.linspace(70, 40, 100)
|
||||
c = np.linspace(5, 424, 100)
|
||||
init = np.array([r, c]).T
|
||||
img_smooth = gaussian(img, 3, preserve_range=False).astype(dtype, copy=False)
|
||||
snake = active_contour(img_smooth, init, boundary_condition='free',
|
||||
alpha=0.1, beta=1.0, w_line=-5, w_edge=0, gamma=0.1)
|
||||
assert snake.dtype == _supported_float_type(dtype)
|
||||
refr = [76, 76, 75, 74, 73, 72, 71, 70, 69, 69]
|
||||
refc = [10, 13, 16, 19, 23, 26, 29, 32, 36, 39]
|
||||
assert_equal(np.array(snake[:10, 0], dtype=np.int32), refr)
|
||||
assert_equal(np.array(snake[:10, 1], dtype=np.int32), refc)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_RGB(dtype):
|
||||
img = gaussian(data.text(), 1, preserve_range=False)
|
||||
imgR = np.zeros((img.shape[0], img.shape[1], 3), dtype=dtype)
|
||||
imgG = np.zeros((img.shape[0], img.shape[1], 3), dtype=dtype)
|
||||
imgRGB = np.zeros((img.shape[0], img.shape[1], 3), dtype=dtype)
|
||||
imgR[:, :, 0] = img
|
||||
imgG[:, :, 1] = img
|
||||
imgRGB[:, :, :] = img[:, :, None]
|
||||
r = np.linspace(136, 50, 100)
|
||||
c = np.linspace(5, 424, 100)
|
||||
init = np.array([r, c]).T
|
||||
snake = active_contour(imgR, init, boundary_condition='fixed',
|
||||
alpha=0.1, beta=1.0, w_line=-5, w_edge=0, gamma=0.1)
|
||||
float_dtype = _supported_float_type(dtype)
|
||||
assert snake.dtype == float_dtype
|
||||
refr = [136, 135, 134, 133, 132, 131, 129, 128, 127, 125]
|
||||
refc = [5, 9, 13, 17, 21, 25, 30, 34, 38, 42]
|
||||
assert_equal(np.array(snake[:10, 0], dtype=np.int32), refr)
|
||||
assert_equal(np.array(snake[:10, 1], dtype=np.int32), refc)
|
||||
snake = active_contour(imgG, init, boundary_condition='fixed',
|
||||
alpha=0.1, beta=1.0, w_line=-5, w_edge=0, gamma=0.1)
|
||||
assert snake.dtype == float_dtype
|
||||
assert_equal(np.array(snake[:10, 0], dtype=np.int32), refr)
|
||||
assert_equal(np.array(snake[:10, 1], dtype=np.int32), refc)
|
||||
snake = active_contour(imgRGB, init, boundary_condition='fixed',
|
||||
alpha=0.1, beta=1.0, w_line=-5/3., w_edge=0,
|
||||
gamma=0.1)
|
||||
assert snake.dtype == float_dtype
|
||||
assert_equal(np.array(snake[:10, 0], dtype=np.int32), refr)
|
||||
assert_equal(np.array(snake[:10, 1], dtype=np.int32), refc)
|
||||
|
||||
|
||||
def test_end_points():
|
||||
img = data.astronaut()
|
||||
img = rgb2gray(img)
|
||||
s = np.linspace(0, 2*np.pi, 400)
|
||||
r = 100 + 100*np.sin(s)
|
||||
c = 220 + 100*np.cos(s)
|
||||
init = np.array([r, c]).T
|
||||
snake = active_contour(gaussian(img, 3), init,
|
||||
boundary_condition='periodic', alpha=0.015, beta=10,
|
||||
w_line=0, w_edge=1, gamma=0.001, max_num_iter=100)
|
||||
assert np.sum(np.abs(snake[0, :]-snake[-1, :])) < 2
|
||||
snake = active_contour(gaussian(img, 3), init,
|
||||
boundary_condition='free', alpha=0.015, beta=10,
|
||||
w_line=0, w_edge=1, gamma=0.001, max_num_iter=100)
|
||||
assert np.sum(np.abs(snake[0, :]-snake[-1, :])) > 2
|
||||
snake = active_contour(gaussian(img, 3), init,
|
||||
boundary_condition='fixed', alpha=0.015, beta=10,
|
||||
w_line=0, w_edge=1, gamma=0.001, max_num_iter=100)
|
||||
assert_allclose(snake[0, :], [r[0], c[0]], atol=1e-5)
|
||||
|
||||
|
||||
def test_bad_input():
|
||||
img = np.zeros((10, 10))
|
||||
r = np.linspace(136, 50, 100)
|
||||
c = np.linspace(5, 424, 100)
|
||||
init = np.array([r, c]).T
|
||||
with pytest.raises(ValueError):
|
||||
active_contour(img, init, boundary_condition='wrong')
|
||||
with pytest.raises(ValueError):
|
||||
active_contour(img, init, max_num_iter=-15)
|
||||
137
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_boundaries.py
vendored
Normal file
137
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_boundaries.py
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_equal, assert_allclose
|
||||
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.segmentation import find_boundaries, mark_boundaries
|
||||
|
||||
|
||||
white = (1, 1, 1)
|
||||
|
||||
|
||||
def test_find_boundaries():
|
||||
image = np.zeros((10, 10), dtype=np.uint8)
|
||||
image[2:7, 2:7] = 1
|
||||
|
||||
ref = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 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, 0]])
|
||||
|
||||
result = find_boundaries(image)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
def test_find_boundaries_bool():
|
||||
image = np.zeros((5, 5), dtype=bool)
|
||||
image[2:5, 2:5] = True
|
||||
|
||||
ref = np.array([[False, False, False, False, False],
|
||||
[False, False, True, True, True],
|
||||
[False, True, True, True, True],
|
||||
[False, True, True, False, False],
|
||||
[False, True, True, False, False]], dtype=bool)
|
||||
result = find_boundaries(image)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'dtype', [np.uint8, np.float16, np.float32, np.float64]
|
||||
)
|
||||
def test_mark_boundaries(dtype):
|
||||
image = np.zeros((10, 10), dtype=dtype)
|
||||
label_image = np.zeros((10, 10), dtype=np.uint8)
|
||||
label_image[2:7, 2:7] = 1
|
||||
|
||||
ref = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 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, 0]])
|
||||
|
||||
marked = mark_boundaries(image, label_image, color=white, mode='thick')
|
||||
assert marked.dtype == _supported_float_type(dtype)
|
||||
result = np.mean(marked, axis=-1)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
ref = np.array([[0, 2, 2, 2, 2, 2, 2, 2, 0, 0],
|
||||
[2, 2, 1, 1, 1, 1, 1, 2, 2, 0],
|
||||
[2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
|
||||
[2, 1, 1, 2, 2, 2, 1, 1, 2, 0],
|
||||
[2, 1, 1, 2, 0, 2, 1, 1, 2, 0],
|
||||
[2, 1, 1, 2, 2, 2, 1, 1, 2, 0],
|
||||
[2, 1, 1, 1, 1, 1, 1, 1, 2, 0],
|
||||
[2, 2, 1, 1, 1, 1, 1, 2, 2, 0],
|
||||
[0, 2, 2, 2, 2, 2, 2, 2, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
|
||||
marked = mark_boundaries(image, label_image, color=white,
|
||||
outline_color=(2, 2, 2), mode='thick')
|
||||
result = np.mean(marked, axis=-1)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
def test_mark_boundaries_bool():
|
||||
image = np.zeros((10, 10), dtype=bool)
|
||||
label_image = np.zeros((10, 10), dtype=np.uint8)
|
||||
label_image[2:7, 2:7] = 1
|
||||
|
||||
ref = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 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, 0]])
|
||||
|
||||
marked = mark_boundaries(image, label_image, color=white, mode='thick')
|
||||
result = np.mean(marked, axis=-1)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_mark_boundaries_subpixel(dtype):
|
||||
labels = np.array([[0, 0, 0, 0],
|
||||
[0, 0, 5, 0],
|
||||
[0, 1, 5, 0],
|
||||
[0, 0, 5, 0],
|
||||
[0, 0, 0, 0]], dtype=np.uint8)
|
||||
np.random.seed(0)
|
||||
image = np.round(np.random.rand(*labels.shape), 2)
|
||||
image = image.astype(dtype, copy=False)
|
||||
marked = mark_boundaries(image, labels, color=white, mode='subpixel')
|
||||
assert marked.dtype == _supported_float_type(dtype)
|
||||
marked_proj = np.round(np.mean(marked, axis=-1), 2)
|
||||
|
||||
ref_result = np.array(
|
||||
[[ 0.55, 0.63, 0.72, 0.69, 0.6 , 0.55, 0.54],
|
||||
[ 0.45, 0.58, 0.72, 1. , 1. , 1. , 0.69],
|
||||
[ 0.42, 0.54, 0.65, 1. , 0.44, 1. , 0.89],
|
||||
[ 0.69, 1. , 1. , 1. , 0.69, 1. , 0.83],
|
||||
[ 0.96, 1. , 0.38, 1. , 0.79, 1. , 0.53],
|
||||
[ 0.89, 1. , 1. , 1. , 0.38, 1. , 0.16],
|
||||
[ 0.57, 0.78, 0.93, 1. , 0.07, 1. , 0.09],
|
||||
[ 0.2 , 0.52, 0.92, 1. , 1. , 1. , 0.54],
|
||||
[ 0.02, 0.35, 0.83, 0.9 , 0.78, 0.81, 0.87]])
|
||||
assert_allclose(marked_proj, ref_result, atol=0.01)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mode', ['thick', 'inner', 'outer', 'subpixel'])
|
||||
def test_boundaries_constant_image(mode):
|
||||
"""A constant-valued image has not boundaries."""
|
||||
ones = np.ones((8, 8), dtype=int)
|
||||
b = find_boundaries(ones, mode=mode)
|
||||
assert np.all(b == 0)
|
||||
98
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_chan_vese.py
vendored
Normal file
98
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_chan_vese.py
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
from skimage._shared.utils import _supported_float_type
|
||||
from skimage.segmentation import chan_vese
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_chan_vese_flat_level_set(dtype):
|
||||
# because the algorithm evolves the level set around the
|
||||
# zero-level, it the level-set has no zero level, the algorithm
|
||||
# will not produce results in theory. However, since a continuous
|
||||
# approximation of the delta function is used, the algorithm
|
||||
# still affects the entirety of the level-set. Therefore with
|
||||
# infinite time, the segmentation will still converge.
|
||||
img = np.zeros((10, 10), dtype=dtype)
|
||||
img[3:6, 3:6] = 1
|
||||
ls = np.full((10, 10), 1000, dtype=dtype)
|
||||
result = chan_vese(img, mu=0.0, tol=1e-3, init_level_set=ls)
|
||||
assert_array_equal(result.astype(float), np.ones((10, 10)))
|
||||
result = chan_vese(img, mu=0.0, tol=1e-3, init_level_set=-ls)
|
||||
assert_array_equal(result.astype(float), np.zeros((10, 10)))
|
||||
|
||||
|
||||
def test_chan_vese_small_disk_level_set():
|
||||
img = np.zeros((10, 10))
|
||||
img[3:6, 3:6] = 1
|
||||
result = chan_vese(img, mu=0.0, tol=1e-3, init_level_set="small disk")
|
||||
assert_array_equal(result.astype(float), img)
|
||||
|
||||
|
||||
def test_chan_vese_simple_shape():
|
||||
img = np.zeros((10, 10))
|
||||
img[3:6, 3:6] = 1
|
||||
result = chan_vese(img, mu=0.0, tol=1e-8).astype(float)
|
||||
assert_array_equal(result, img)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'dtype', [np.uint8, np.float16, np.float32, np.float64]
|
||||
)
|
||||
def test_chan_vese_extended_output(dtype):
|
||||
img = np.zeros((10, 10), dtype=dtype)
|
||||
img[3:6, 3:6] = 1
|
||||
result = chan_vese(img, mu=0.0, tol=1e-8, extended_output=True)
|
||||
float_dtype = _supported_float_type(dtype)
|
||||
assert result[1].dtype == float_dtype
|
||||
assert all(arr.dtype == float_dtype for arr in result[2])
|
||||
assert_array_equal(len(result), 3)
|
||||
|
||||
|
||||
def test_chan_vese_remove_noise():
|
||||
ref = np.zeros((10, 10))
|
||||
ref[1:6, 1:6] = np.array([[0, 1, 1, 1, 0],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 0]])
|
||||
img = ref.copy()
|
||||
img[8, 3] = 1
|
||||
result = chan_vese(img, mu=0.3, tol=1e-3, max_num_iter=100, dt=10,
|
||||
init_level_set="disk").astype(float)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
def test_chan_vese_incorrect_image_type():
|
||||
img = np.zeros((10, 10, 3))
|
||||
ls = np.zeros((10, 9))
|
||||
with pytest.raises(ValueError):
|
||||
chan_vese(img, mu=0.0, init_level_set=ls)
|
||||
|
||||
|
||||
def test_chan_vese_gap_closing():
|
||||
ref = np.zeros((20, 20))
|
||||
ref[8:15, :] = np.ones((7, 20))
|
||||
img = ref.copy()
|
||||
img[:, 6] = np.zeros(20)
|
||||
result = chan_vese(img, mu=0.7, tol=1e-3, max_num_iter=1000, dt=1000,
|
||||
init_level_set="disk").astype(float)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
def test_chan_vese_incorrect_level_set():
|
||||
img = np.zeros((10, 10))
|
||||
ls = np.zeros((10, 9))
|
||||
with pytest.raises(ValueError):
|
||||
chan_vese(img, mu=0.0, init_level_set=ls)
|
||||
with pytest.raises(ValueError):
|
||||
chan_vese(img, mu=0.0, init_level_set="a")
|
||||
|
||||
|
||||
def test_chan_vese_blank_image():
|
||||
img = np.zeros((10, 10))
|
||||
level_set = np.random.rand(10, 10)
|
||||
ref = level_set > 0
|
||||
result = chan_vese(img, mu=0.0, tol=0.0, init_level_set=level_set)
|
||||
assert_array_equal(result, ref)
|
||||
228
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_clear_border.py
vendored
Normal file
228
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_clear_border.py
vendored
Normal file
@@ -0,0 +1,228 @@
|
||||
import numpy as np
|
||||
from skimage.segmentation import clear_border
|
||||
|
||||
from skimage._shared.testing import assert_array_equal, assert_
|
||||
|
||||
|
||||
def test_clear_border():
|
||||
image = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[1, 1, 0, 0, 1, 0, 0, 1, 0],
|
||||
[1, 1, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0]])
|
||||
|
||||
# test default case
|
||||
result = clear_border(image.copy())
|
||||
ref = image.copy()
|
||||
ref[1:3, 0:2] = 0
|
||||
ref[0:2, -2] = 0
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
# test buffer
|
||||
result = clear_border(image.copy(), 1)
|
||||
assert_array_equal(result, np.zeros(result.shape))
|
||||
|
||||
# test background value
|
||||
result = clear_border(image.copy(), buffer_size=1, bgval=2)
|
||||
assert_array_equal(result, 2 * np.ones_like(image))
|
||||
|
||||
# test mask
|
||||
mask = 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]]).astype(bool)
|
||||
result = clear_border(image.copy(), mask=mask)
|
||||
ref = image.copy()
|
||||
ref[1:3, 0:2] = 0
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
def test_clear_border_3d():
|
||||
image = np.array([
|
||||
[[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[1, 0, 0, 0]],
|
||||
[[0, 0, 0, 0],
|
||||
[0, 1, 1, 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]],
|
||||
])
|
||||
# test default case
|
||||
result = clear_border(image.copy())
|
||||
ref = image.copy()
|
||||
ref[0, 3, 0] = 0
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
# test buffer
|
||||
result = clear_border(image.copy(), 1)
|
||||
assert_array_equal(result, np.zeros(result.shape))
|
||||
|
||||
# test background value
|
||||
result = clear_border(image.copy(), buffer_size=1, bgval=2)
|
||||
assert_array_equal(result, 2 * np.ones_like(image))
|
||||
|
||||
|
||||
def test_clear_border_non_binary():
|
||||
image = np.array([[1, 2, 3, 1, 2],
|
||||
[3, 3, 5, 4, 2],
|
||||
[3, 4, 5, 4, 2],
|
||||
[3, 3, 2, 1, 2]])
|
||||
|
||||
result = clear_border(image)
|
||||
expected = np.array([[0, 0, 0, 0, 0],
|
||||
[0, 0, 5, 4, 0],
|
||||
[0, 4, 5, 4, 0],
|
||||
[0, 0, 0, 0, 0]])
|
||||
|
||||
assert_array_equal(result, expected)
|
||||
assert_(not np.all(image == result))
|
||||
|
||||
|
||||
def test_clear_border_non_binary_3d():
|
||||
image3d = np.array(
|
||||
[[[1, 2, 3, 1, 2],
|
||||
[3, 3, 3, 4, 2],
|
||||
[3, 4, 3, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
[[1, 2, 3, 1, 2],
|
||||
[3, 3, 5, 4, 2],
|
||||
[3, 4, 5, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
[[1, 2, 3, 1, 2],
|
||||
[3, 3, 3, 4, 2],
|
||||
[3, 4, 3, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
])
|
||||
|
||||
result = clear_border(image3d)
|
||||
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, 0, 0, 0],
|
||||
[0, 0, 5, 0, 0],
|
||||
[0, 0, 5, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
[[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
])
|
||||
|
||||
assert_array_equal(result, expected)
|
||||
assert_(not np.all(image3d == result))
|
||||
|
||||
|
||||
def test_clear_border_non_binary_inplace():
|
||||
image = np.array([[1, 2, 3, 1, 2],
|
||||
[3, 3, 5, 4, 2],
|
||||
[3, 4, 5, 4, 2],
|
||||
[3, 3, 2, 1, 2]])
|
||||
|
||||
result = clear_border(image, out=image)
|
||||
expected = np.array([[0, 0, 0, 0, 0],
|
||||
[0, 0, 5, 4, 0],
|
||||
[0, 4, 5, 4, 0],
|
||||
[0, 0, 0, 0, 0]])
|
||||
|
||||
assert_array_equal(result, expected)
|
||||
assert_array_equal(image, result)
|
||||
|
||||
|
||||
def test_clear_border_non_binary_inplace_3d():
|
||||
image3d = np.array(
|
||||
[[[1, 2, 3, 1, 2],
|
||||
[3, 3, 3, 4, 2],
|
||||
[3, 4, 3, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
[[1, 2, 3, 1, 2],
|
||||
[3, 3, 5, 4, 2],
|
||||
[3, 4, 5, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
[[1, 2, 3, 1, 2],
|
||||
[3, 3, 3, 4, 2],
|
||||
[3, 4, 3, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
])
|
||||
|
||||
result = clear_border(image3d, out=image3d)
|
||||
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, 0, 0, 0],
|
||||
[0, 0, 5, 0, 0],
|
||||
[0, 0, 5, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
[[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
])
|
||||
|
||||
assert_array_equal(result, expected)
|
||||
assert_array_equal(image3d, result)
|
||||
|
||||
|
||||
def test_clear_border_non_binary_out():
|
||||
image = np.array([[1, 2, 3, 1, 2],
|
||||
[3, 3, 5, 4, 2],
|
||||
[3, 4, 5, 4, 2],
|
||||
[3, 3, 2, 1, 2]])
|
||||
out = np.empty_like(image)
|
||||
result = clear_border(image, out=out)
|
||||
expected = np.array([[0, 0, 0, 0, 0],
|
||||
[0, 0, 5, 4, 0],
|
||||
[0, 4, 5, 4, 0],
|
||||
[0, 0, 0, 0, 0]])
|
||||
|
||||
assert_array_equal(result, expected)
|
||||
assert_array_equal(out, result)
|
||||
|
||||
|
||||
def test_clear_border_non_binary_out_3d():
|
||||
image3d = np.array(
|
||||
[[[1, 2, 3, 1, 2],
|
||||
[3, 3, 3, 4, 2],
|
||||
[3, 4, 3, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
[[1, 2, 3, 1, 2],
|
||||
[3, 3, 5, 4, 2],
|
||||
[3, 4, 5, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
[[1, 2, 3, 1, 2],
|
||||
[3, 3, 3, 4, 2],
|
||||
[3, 4, 3, 4, 2],
|
||||
[3, 3, 2, 1, 2]],
|
||||
])
|
||||
out = np.empty_like(image3d)
|
||||
|
||||
result = clear_border(image3d, out=out)
|
||||
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, 0, 0, 0],
|
||||
[0, 0, 5, 0, 0],
|
||||
[0, 0, 5, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
[[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0]],
|
||||
])
|
||||
|
||||
assert_array_equal(result, expected)
|
||||
assert_array_equal(out, result)
|
||||
184
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_expand_labels.py
vendored
Normal file
184
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_expand_labels.py
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
from scipy import ndimage as ndi
|
||||
from skimage import data
|
||||
|
||||
import numpy as np
|
||||
|
||||
from skimage import measure
|
||||
from skimage.segmentation._expand_labels import expand_labels
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
|
||||
SAMPLE1D = np.array([0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0])
|
||||
SAMPLE1D_EXPANDED_3 = np.array([4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0])
|
||||
|
||||
# Some pixels are important edge cases with undefined behaviour:
|
||||
# these are the pixels that are at the same distance from
|
||||
# multiple labels. Ideally the label would be chosen at random
|
||||
# to avoid bias, but as we are relying on the index map returned
|
||||
# by the scipy.ndimage distance transform, what actually happens
|
||||
# is determined by the upstream implementation of the distance
|
||||
# tansform, thus we don't give any guarantees for the edge case pixels.
|
||||
#
|
||||
# Regardless, it seems prudent to have a test including an edge case
|
||||
# so we can detect whether future upstream changes in scipy.ndimage
|
||||
# modify the behaviour.
|
||||
|
||||
EDGECASE1D = np.array([0, 0, 4, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0])
|
||||
EDGECASE1D_EXPANDED_3 = np.array([4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0])
|
||||
|
||||
SAMPLE2D = 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, 1, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 2, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
|
||||
)
|
||||
|
||||
SAMPLE2D_EXPANDED_3 = np.array(
|
||||
[[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 0, 0, 2, 0],
|
||||
[1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1, 0, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 0, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 0, 0, 2, 2, 2, 2],
|
||||
[0, 0, 1, 0, 0, 0, 0, 2, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0]]
|
||||
)
|
||||
|
||||
# non-integer expansion
|
||||
SAMPLE2D_EXPANDED_1_5 = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 0, 0, 0, 2, 2, 2],
|
||||
[1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2],
|
||||
[0, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
|
||||
|
||||
|
||||
EDGECASE2D = np.array(
|
||||
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 0, 2, 2, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 0, 2, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]]
|
||||
)
|
||||
|
||||
EDGECASE2D_EXPANDED_4 = np.array(
|
||||
[[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 0],
|
||||
[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2],
|
||||
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 0],
|
||||
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 0]])
|
||||
|
||||
SAMPLE3D = np.array(
|
||||
[[[0, 0, 0, 0],
|
||||
[0, 3, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0]],
|
||||
|
||||
[[0, 0, 0, 0],
|
||||
[0, 3, 3, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0]],
|
||||
|
||||
[[0, 0, 0, 0],
|
||||
[0, 3, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 5, 0]],
|
||||
|
||||
[[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 5, 0]]])
|
||||
|
||||
SAMPLE3D_EXPANDED_2 =np.array(
|
||||
[[[3, 3, 3, 3],
|
||||
[3, 3, 3, 3],
|
||||
[3, 3, 3, 3],
|
||||
[0, 3, 5, 0]],
|
||||
|
||||
[[3, 3, 3, 3],
|
||||
[3, 3, 3, 3],
|
||||
[3, 3, 3, 3],
|
||||
[0, 5, 5, 5]],
|
||||
|
||||
[[3, 3, 3, 3],
|
||||
[3, 3, 3, 3],
|
||||
[3, 3, 5, 5],
|
||||
[5, 5, 5, 5]],
|
||||
|
||||
[[3, 3, 3, 0],
|
||||
[3, 3, 3, 0],
|
||||
[3, 3, 5, 5],
|
||||
[5, 5, 5, 5]]])
|
||||
|
||||
SAMPLE_EDGECASE_BEHAVIOUR = np.array([[0, 1, 0, 0], [2, 0, 0, 0], [0, 3, 0, 0]])
|
||||
|
||||
@testing.parametrize(
|
||||
"input_array, expected_output, expand_distance",
|
||||
[
|
||||
(SAMPLE1D, SAMPLE1D_EXPANDED_3, 3),
|
||||
(SAMPLE2D, SAMPLE2D_EXPANDED_3, 3),
|
||||
(SAMPLE2D, SAMPLE2D_EXPANDED_1_5, 1.5),
|
||||
(EDGECASE1D, EDGECASE1D_EXPANDED_3, 3),
|
||||
(EDGECASE2D, EDGECASE2D_EXPANDED_4, 4),
|
||||
(SAMPLE3D, SAMPLE3D_EXPANDED_2, 2)
|
||||
]
|
||||
)
|
||||
def test_expand_labels(input_array, expected_output, expand_distance):
|
||||
expanded = expand_labels(input_array, expand_distance)
|
||||
assert_array_equal(expanded, expected_output)
|
||||
|
||||
|
||||
@testing.parametrize('ndim', [2, 3])
|
||||
@testing.parametrize('distance', range(6))
|
||||
def test_binary_blobs(ndim, distance):
|
||||
"""Check some invariants with label expansion.
|
||||
|
||||
- New labels array should exactly contain the original labels array.
|
||||
- Distance to old labels array within new labels should never exceed input
|
||||
distance.
|
||||
- Distance beyond the expanded labels should always exceed the input
|
||||
distance.
|
||||
"""
|
||||
img = data.binary_blobs(length=64, blob_size_fraction=0.05, n_dim=ndim)
|
||||
labels = measure.label(img)
|
||||
expanded = expand_labels(labels, distance=distance)
|
||||
original_mask = labels != 0
|
||||
assert_array_equal(labels[original_mask], expanded[original_mask])
|
||||
expanded_only_mask = (expanded - labels).astype(bool)
|
||||
distance_map = ndi.distance_transform_edt(~original_mask)
|
||||
expanded_distances = distance_map[expanded_only_mask]
|
||||
if expanded_distances.size > 0:
|
||||
assert np.all(expanded_distances <= distance)
|
||||
beyond_expanded_distances = distance_map[~expanded.astype(bool)]
|
||||
if beyond_expanded_distances.size > 0:
|
||||
assert np.all(beyond_expanded_distances > distance)
|
||||
|
||||
|
||||
def test_edge_case_behaviour():
|
||||
""" Check edge case behavior to detect upstream changes
|
||||
|
||||
For edge cases where a pixel has the same distance to several regions,
|
||||
lexicographical order seems to determine which region gets to expand
|
||||
into this pixel given the current upstream behaviour in
|
||||
scipy.ndimage.distance_map_edt.
|
||||
|
||||
As a result, we expect different results when transposing the array.
|
||||
If this test fails, something has changed upstream.
|
||||
"""
|
||||
expanded = expand_labels(SAMPLE_EDGECASE_BEHAVIOUR, 1)
|
||||
expanded_transpose = expand_labels(SAMPLE_EDGECASE_BEHAVIOUR.T, 1)
|
||||
assert not np.all(expanded == expanded_transpose.T)
|
||||
85
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_felzenszwalb.py
vendored
Normal file
85
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_felzenszwalb.py
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import numpy as np
|
||||
from skimage import data
|
||||
from skimage.segmentation import felzenszwalb
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import (assert_greater, test_parallel,
|
||||
assert_equal, assert_array_equal,
|
||||
assert_warns, assert_no_warnings)
|
||||
|
||||
|
||||
@test_parallel()
|
||||
def test_grey():
|
||||
# very weak tests.
|
||||
img = np.zeros((20, 21))
|
||||
img[:10, 10:] = 0.2
|
||||
img[10:, :10] = 0.4
|
||||
img[10:, 10:] = 0.6
|
||||
seg = felzenszwalb(img, sigma=0)
|
||||
# we expect 4 segments:
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
# that mostly respect the 4 regions:
|
||||
for i in range(4):
|
||||
hist = np.histogram(img[seg == i], bins=[0, 0.1, 0.3, 0.5, 1])[0]
|
||||
assert_greater(hist[i], 40)
|
||||
|
||||
|
||||
def test_minsize():
|
||||
# single-channel:
|
||||
img = data.coins()[20:168, 0:128]
|
||||
for min_size in np.arange(10, 100, 10):
|
||||
segments = felzenszwalb(img, min_size=min_size, sigma=3)
|
||||
counts = np.bincount(segments.ravel())
|
||||
# actually want to test greater or equal.
|
||||
assert_greater(counts.min() + 1, min_size)
|
||||
# multi-channel:
|
||||
coffee = data.coffee()[::4, ::4]
|
||||
for min_size in np.arange(10, 100, 10):
|
||||
segments = felzenszwalb(coffee, min_size=min_size, sigma=3)
|
||||
counts = np.bincount(segments.ravel())
|
||||
# actually want to test greater or equal.
|
||||
assert_greater(counts.min() + 1, min_size)
|
||||
|
||||
|
||||
@testing.parametrize('channel_axis', [0, -1])
|
||||
def test_3D(channel_axis):
|
||||
grey_img = np.zeros((10, 10))
|
||||
rgb_img = np.zeros((10, 10, 3))
|
||||
three_d_img = np.zeros((10, 10, 10))
|
||||
|
||||
rgb_img = np.moveaxis(rgb_img, -1, channel_axis)
|
||||
with assert_no_warnings():
|
||||
felzenszwalb(grey_img, channel_axis=-1)
|
||||
felzenszwalb(grey_img, channel_axis=None)
|
||||
felzenszwalb(rgb_img, channel_axis=channel_axis)
|
||||
with assert_warns(RuntimeWarning):
|
||||
felzenszwalb(three_d_img, channel_axis=channel_axis)
|
||||
with testing.raises(ValueError):
|
||||
felzenszwalb(rgb_img, channel_axis=None)
|
||||
felzenszwalb(three_d_img, channel_axis=None)
|
||||
|
||||
|
||||
def test_color():
|
||||
# very weak tests.
|
||||
img = np.zeros((20, 21, 3))
|
||||
img[:10, :10, 0] = 1
|
||||
img[10:, :10, 1] = 1
|
||||
img[10:, 10:, 2] = 1
|
||||
seg = felzenszwalb(img, sigma=0)
|
||||
# we expect 4 segments:
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
assert_array_equal(seg[:10, :10], 0)
|
||||
assert_array_equal(seg[10:, :10], 2)
|
||||
assert_array_equal(seg[:10, 10:], 1)
|
||||
assert_array_equal(seg[10:, 10:], 3)
|
||||
|
||||
|
||||
def test_merging():
|
||||
# test region merging in the post-processing step
|
||||
img = np.array([[0, 0.3], [0.7, 1]])
|
||||
# With scale=0, only the post-processing is performed.
|
||||
seg = felzenszwalb(img, scale=0, sigma=0, min_size=2)
|
||||
# we expect 2 segments:
|
||||
assert_equal(len(np.unique(seg)), 2)
|
||||
assert_array_equal(seg[0, :], 0)
|
||||
assert_array_equal(seg[1, :], 1)
|
||||
211
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_join.py
vendored
Normal file
211
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_join.py
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
import numpy as np
|
||||
from skimage.segmentation import join_segmentations, relabel_sequential
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
import pytest
|
||||
|
||||
|
||||
def test_join_segmentations():
|
||||
s1 = np.array([[0, 0, 1, 1],
|
||||
[0, 2, 1, 1],
|
||||
[2, 2, 2, 1]])
|
||||
s2 = np.array([[0, 1, 1, 0],
|
||||
[0, 1, 1, 0],
|
||||
[0, 1, 1, 1]])
|
||||
|
||||
# test correct join
|
||||
# NOTE: technically, equality to j_ref is not required, only that there
|
||||
# is a one-to-one mapping between j and j_ref. I don't know of an easy way
|
||||
# to check this (i.e. not as error-prone as the function being tested)
|
||||
j = join_segmentations(s1, s2)
|
||||
j_ref = np.array([[0, 1, 3, 2],
|
||||
[0, 5, 3, 2],
|
||||
[4, 5, 5, 3]])
|
||||
assert_array_equal(j, j_ref)
|
||||
|
||||
# test correct exception when arrays are different shapes
|
||||
s3 = np.array([[0, 0, 1, 1], [0, 2, 2, 1]])
|
||||
with testing.raises(ValueError):
|
||||
join_segmentations(s1, s3)
|
||||
|
||||
|
||||
def _check_maps(ar, ar_relab, fw, inv):
|
||||
assert_array_equal(fw[ar], ar_relab)
|
||||
assert_array_equal(inv[ar_relab], ar)
|
||||
|
||||
|
||||
def test_relabel_sequential_offset1():
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42])
|
||||
ar_relab, fw, inv = relabel_sequential(ar)
|
||||
_check_maps(ar, ar_relab, fw, inv)
|
||||
ar_relab_ref = np.array([1, 1, 2, 2, 3, 5, 4])
|
||||
assert_array_equal(ar_relab, ar_relab_ref)
|
||||
fw_ref = np.zeros(100, int)
|
||||
fw_ref[1] = 1
|
||||
fw_ref[5] = 2
|
||||
fw_ref[8] = 3
|
||||
fw_ref[42] = 4
|
||||
fw_ref[99] = 5
|
||||
assert_array_equal(fw, fw_ref)
|
||||
inv_ref = np.array([0, 1, 5, 8, 42, 99])
|
||||
assert_array_equal(inv, inv_ref)
|
||||
|
||||
|
||||
def test_relabel_sequential_offset5():
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42])
|
||||
ar_relab, fw, inv = relabel_sequential(ar, offset=5)
|
||||
_check_maps(ar, ar_relab, fw, inv)
|
||||
ar_relab_ref = np.array([5, 5, 6, 6, 7, 9, 8])
|
||||
assert_array_equal(ar_relab, ar_relab_ref)
|
||||
fw_ref = np.zeros(100, int)
|
||||
fw_ref[1] = 5
|
||||
fw_ref[5] = 6
|
||||
fw_ref[8] = 7
|
||||
fw_ref[42] = 8
|
||||
fw_ref[99] = 9
|
||||
assert_array_equal(fw, fw_ref)
|
||||
inv_ref = np.array([0, 0, 0, 0, 0, 1, 5, 8, 42, 99])
|
||||
assert_array_equal(inv, inv_ref)
|
||||
|
||||
|
||||
def test_relabel_sequential_offset5_with0():
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42, 0])
|
||||
ar_relab, fw, inv = relabel_sequential(ar, offset=5)
|
||||
_check_maps(ar, ar_relab, fw, inv)
|
||||
ar_relab_ref = np.array([5, 5, 6, 6, 7, 9, 8, 0])
|
||||
assert_array_equal(ar_relab, ar_relab_ref)
|
||||
fw_ref = np.zeros(100, int)
|
||||
fw_ref[1] = 5
|
||||
fw_ref[5] = 6
|
||||
fw_ref[8] = 7
|
||||
fw_ref[42] = 8
|
||||
fw_ref[99] = 9
|
||||
assert_array_equal(fw, fw_ref)
|
||||
inv_ref = np.array([0, 0, 0, 0, 0, 1, 5, 8, 42, 99])
|
||||
assert_array_equal(inv, inv_ref)
|
||||
|
||||
|
||||
def test_relabel_sequential_dtype():
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42, 0], dtype=np.uint8)
|
||||
ar_relab, fw, inv = relabel_sequential(ar, offset=5)
|
||||
_check_maps(ar.astype(int), ar_relab, fw, inv)
|
||||
ar_relab_ref = np.array([5, 5, 6, 6, 7, 9, 8, 0])
|
||||
assert_array_equal(ar_relab, ar_relab_ref)
|
||||
fw_ref = np.zeros(100, int)
|
||||
fw_ref[1] = 5
|
||||
fw_ref[5] = 6
|
||||
fw_ref[8] = 7
|
||||
fw_ref[42] = 8
|
||||
fw_ref[99] = 9
|
||||
assert_array_equal(fw, fw_ref)
|
||||
inv_ref = np.array([0, 0, 0, 0, 0, 1, 5, 8, 42, 99])
|
||||
assert_array_equal(inv, inv_ref)
|
||||
|
||||
|
||||
def test_relabel_sequential_signed_overflow():
|
||||
imax = np.iinfo(np.int32).max
|
||||
labels = np.array([0, 1, 99, 42, 42], dtype=np.int32)
|
||||
output, fw, inv = relabel_sequential(labels, offset=imax)
|
||||
reference = np.array([0, imax, imax + 2, imax + 1, imax + 1],
|
||||
dtype=np.uint32)
|
||||
assert_array_equal(output, reference)
|
||||
assert output.dtype == reference.dtype
|
||||
|
||||
|
||||
def test_very_large_labels():
|
||||
imax = np.iinfo(np.int64).max
|
||||
labels = np.array([0, 1, imax, 42, 42], dtype=np.int64)
|
||||
output, fw, inv = relabel_sequential(labels, offset=imax)
|
||||
assert np.max(output) == imax + 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dtype', (np.byte, np.short, np.intc, int,
|
||||
np.longlong, np.ubyte, np.ushort,
|
||||
np.uintc, np.uint, np.ulonglong))
|
||||
@pytest.mark.parametrize('data_already_sequential', (False, True))
|
||||
def test_relabel_sequential_int_dtype_stability(data_already_sequential,
|
||||
dtype):
|
||||
if data_already_sequential:
|
||||
ar = np.array([1, 3, 0, 2, 5, 4], dtype=dtype)
|
||||
else:
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42, 0], dtype=dtype)
|
||||
assert all(a.dtype == dtype for a in relabel_sequential(ar))
|
||||
|
||||
|
||||
def test_relabel_sequential_int_dtype_overflow():
|
||||
ar = np.array([1, 3, 0, 2, 5, 4], dtype=np.uint8)
|
||||
offset = 254
|
||||
ar_relab, fw, inv = relabel_sequential(ar, offset=offset)
|
||||
_check_maps(ar, ar_relab, fw, inv)
|
||||
assert all(a.dtype == np.uint16 for a in (ar_relab, fw))
|
||||
assert inv.dtype == ar.dtype
|
||||
ar_relab_ref = np.where(ar > 0, ar.astype(int) + offset - 1, 0)
|
||||
assert_array_equal(ar_relab, ar_relab_ref)
|
||||
|
||||
|
||||
def test_relabel_sequential_negative_values():
|
||||
ar = np.array([1, 1, 5, -5, 8, 99, 42, 0])
|
||||
with pytest.raises(ValueError):
|
||||
relabel_sequential(ar)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('offset', (0, -3))
|
||||
@pytest.mark.parametrize('data_already_sequential', (False, True))
|
||||
def test_relabel_sequential_nonpositive_offset(data_already_sequential,
|
||||
offset):
|
||||
if data_already_sequential:
|
||||
ar = np.array([1, 3, 0, 2, 5, 4])
|
||||
else:
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42, 0])
|
||||
with pytest.raises(ValueError):
|
||||
relabel_sequential(ar, offset=offset)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('offset', (1, 5))
|
||||
@pytest.mark.parametrize('with0', (False, True))
|
||||
@pytest.mark.parametrize('input_starts_at_offset', (False, True))
|
||||
def test_relabel_sequential_already_sequential(offset, with0,
|
||||
input_starts_at_offset):
|
||||
if with0:
|
||||
ar = np.array([1, 3, 0, 2, 5, 4])
|
||||
else:
|
||||
ar = np.array([1, 3, 2, 5, 4])
|
||||
if input_starts_at_offset:
|
||||
ar[ar > 0] += offset - 1
|
||||
ar_relab, fw, inv = relabel_sequential(ar, offset=offset)
|
||||
_check_maps(ar, ar_relab, fw, inv)
|
||||
if input_starts_at_offset:
|
||||
ar_relab_ref = ar
|
||||
else:
|
||||
ar_relab_ref = np.where(ar > 0, ar + offset - 1, 0)
|
||||
assert_array_equal(ar_relab, ar_relab_ref)
|
||||
|
||||
|
||||
def test_incorrect_input_dtype():
|
||||
labels = np.array([0, 2, 2, 1, 1, 8], dtype=float)
|
||||
with testing.raises(TypeError):
|
||||
_ = relabel_sequential(labels)
|
||||
|
||||
|
||||
def test_arraymap_call():
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42, 0], dtype=np.intp)
|
||||
relabeled, fw, inv = relabel_sequential(ar)
|
||||
testing.assert_array_equal(relabeled, fw(ar))
|
||||
testing.assert_array_equal(ar, inv(relabeled))
|
||||
|
||||
|
||||
def test_arraymap_len():
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42, 0], dtype=np.intp)
|
||||
relabeled, fw, inv = relabel_sequential(ar)
|
||||
assert len(fw) == 100
|
||||
assert len(fw) == len(np.array(fw))
|
||||
assert len(inv) == 6
|
||||
assert len(inv) == len(np.array(inv))
|
||||
|
||||
|
||||
def test_arraymap_set():
|
||||
ar = np.array([1, 1, 5, 5, 8, 99, 42, 0], dtype=np.intp)
|
||||
relabeled, fw, inv = relabel_sequential(ar)
|
||||
fw[72] = 6
|
||||
assert fw[72] == 6
|
||||
141
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_morphsnakes.py
vendored
Normal file
141
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_morphsnakes.py
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_array_equal
|
||||
|
||||
from skimage.segmentation import (disk_level_set,
|
||||
inverse_gaussian_gradient,
|
||||
morphological_chan_vese,
|
||||
morphological_geodesic_active_contour)
|
||||
|
||||
|
||||
def gaussian_blob():
|
||||
coords = np.mgrid[-5:6, -5:6]
|
||||
sqrdistances = (coords ** 2).sum(0)
|
||||
return np.exp(-sqrdistances / 10)
|
||||
|
||||
|
||||
def test_morphsnakes_incorrect_image_shape():
|
||||
img = np.zeros((10, 10, 3))
|
||||
ls = np.zeros((10, 9))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
morphological_chan_vese(img, num_iter=1, init_level_set=ls)
|
||||
with pytest.raises(ValueError):
|
||||
morphological_geodesic_active_contour(img, num_iter=1,
|
||||
init_level_set=ls)
|
||||
|
||||
|
||||
def test_morphsnakes_incorrect_ndim():
|
||||
img = np.zeros((4, 4, 4, 4))
|
||||
ls = np.zeros((4, 4, 4, 4))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
morphological_chan_vese(img, num_iter=1, init_level_set=ls)
|
||||
with pytest.raises(ValueError):
|
||||
morphological_geodesic_active_contour(img, num_iter=1,
|
||||
init_level_set=ls)
|
||||
|
||||
|
||||
def test_morphsnakes_black():
|
||||
img = np.zeros((11, 11))
|
||||
ls = disk_level_set(img.shape, center=(5, 5), radius=3)
|
||||
|
||||
ref_zeros = np.zeros(img.shape, dtype=np.int8)
|
||||
ref_ones = np.ones(img.shape, dtype=np.int8)
|
||||
|
||||
acwe_ls = morphological_chan_vese(img, num_iter=6, init_level_set=ls)
|
||||
assert_array_equal(acwe_ls, ref_zeros)
|
||||
|
||||
gac_ls = morphological_geodesic_active_contour(img, num_iter=6,
|
||||
init_level_set=ls)
|
||||
assert_array_equal(gac_ls, ref_zeros)
|
||||
|
||||
gac_ls2 = morphological_geodesic_active_contour(img, num_iter=6,
|
||||
init_level_set=ls,
|
||||
balloon=1, threshold=-1,
|
||||
smoothing=0)
|
||||
assert_array_equal(gac_ls2, ref_ones)
|
||||
|
||||
assert acwe_ls.dtype == gac_ls.dtype == gac_ls2.dtype == np.int8
|
||||
|
||||
|
||||
def test_morphsnakes_simple_shape_chan_vese():
|
||||
img = gaussian_blob()
|
||||
ls1 = disk_level_set(img.shape, center=(5, 5), radius=3)
|
||||
ls2 = disk_level_set(img.shape, center=(5, 5), radius=6)
|
||||
|
||||
acwe_ls1 = morphological_chan_vese(img, num_iter=10, init_level_set=ls1)
|
||||
acwe_ls2 = morphological_chan_vese(img, num_iter=10, init_level_set=ls2)
|
||||
|
||||
assert_array_equal(acwe_ls1, acwe_ls2)
|
||||
|
||||
assert acwe_ls1.dtype == acwe_ls2.dtype == np.int8
|
||||
|
||||
|
||||
def test_morphsnakes_simple_shape_geodesic_active_contour():
|
||||
img = (disk_level_set((11, 11), center=(5, 5), radius=3.5)).astype(float)
|
||||
gimg = inverse_gaussian_gradient(img, alpha=10.0, sigma=1.0)
|
||||
ls = disk_level_set(img.shape, center=(5, 5), radius=6)
|
||||
|
||||
ref = 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, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
|
||||
dtype=np.int8)
|
||||
|
||||
gac_ls = morphological_geodesic_active_contour(gimg, num_iter=10,
|
||||
init_level_set=ls,
|
||||
balloon=-1)
|
||||
assert_array_equal(gac_ls, ref)
|
||||
assert gac_ls.dtype == np.int8
|
||||
|
||||
|
||||
def test_init_level_sets():
|
||||
image = np.zeros((6, 6))
|
||||
checkerboard_ls = morphological_chan_vese(image, 0, 'checkerboard')
|
||||
checkerboard_ref = np.array([[0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 1],
|
||||
[1, 1, 1, 1, 1, 0]], dtype=np.int8)
|
||||
|
||||
disk_ls = morphological_geodesic_active_contour(image, 0, 'disk')
|
||||
disk_ref = np.array([[0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 1, 1, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1],
|
||||
[0, 1, 1, 1, 1, 1],
|
||||
[0, 0, 1, 1, 1, 0]], dtype=np.int8)
|
||||
|
||||
assert_array_equal(checkerboard_ls, checkerboard_ref)
|
||||
assert_array_equal(disk_ls, disk_ref)
|
||||
|
||||
|
||||
def test_morphsnakes_3d():
|
||||
image = np.zeros((7, 7, 7))
|
||||
|
||||
evolution = []
|
||||
|
||||
def callback(x):
|
||||
evolution.append(x.sum())
|
||||
|
||||
ls = morphological_chan_vese(image, 5, 'disk',
|
||||
iter_callback=callback)
|
||||
|
||||
# Check that the initial disk level set is correct
|
||||
assert evolution[0] == 81
|
||||
|
||||
# Check that the final level set is correct
|
||||
assert ls.sum() == 0
|
||||
|
||||
# Check that the contour is shrinking at every iteration
|
||||
for v1, v2 in zip(evolution[:-1], evolution[1:]):
|
||||
assert v1 >= v2
|
||||
58
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_quickshift.py
vendored
Normal file
58
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_quickshift.py
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
import numpy as np
|
||||
from skimage.segmentation import quickshift
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import (assert_greater, test_parallel,
|
||||
assert_equal, assert_array_equal)
|
||||
|
||||
@test_parallel()
|
||||
@testing.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_grey(dtype):
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21))
|
||||
img[:10, 10:] = 0.2
|
||||
img[10:, :10] = 0.4
|
||||
img[10:, 10:] = 0.6
|
||||
img += 0.05 * rnd.normal(size=img.shape)
|
||||
img = img.astype(dtype, copy=False)
|
||||
seg = quickshift(img, kernel_size=2, max_dist=3, random_seed=0,
|
||||
convert2lab=False, sigma=0)
|
||||
# we expect 4 segments:
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
# that mostly respect the 4 regions:
|
||||
for i in range(4):
|
||||
hist = np.histogram(img[seg == i], bins=[0, 0.1, 0.3, 0.5, 1])[0]
|
||||
assert_greater(hist[i], 20)
|
||||
|
||||
|
||||
@testing.parametrize('dtype', [np.float32, np.float64])
|
||||
@testing.parametrize('channel_axis', [-3, -2, -1, 0, 1, 2])
|
||||
def test_color(dtype, channel_axis):
|
||||
rnd = np.random.default_rng(583428449)
|
||||
img = np.zeros((20, 21, 3))
|
||||
img[:10, :10, 0] = 1
|
||||
img[10:, :10, 1] = 1
|
||||
img[10:, 10:, 2] = 1
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
img[img > 1] = 1
|
||||
img[img < 0] = 0
|
||||
img = img.astype(dtype, copy=False)
|
||||
|
||||
img = np.moveaxis(img, source=-1, destination=channel_axis)
|
||||
seg = quickshift(img, random_seed=0, max_dist=30, kernel_size=10, sigma=0,
|
||||
channel_axis=channel_axis)
|
||||
# we expect 4 segments:
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
assert_array_equal(seg[:10, :10], 1)
|
||||
assert_array_equal(seg[10:, :10], 3)
|
||||
assert_array_equal(seg[:10, 10:], 0)
|
||||
assert_array_equal(seg[10:, 10:], 2)
|
||||
|
||||
seg2 = quickshift(img, kernel_size=1, max_dist=2, random_seed=0,
|
||||
convert2lab=False, sigma=0,
|
||||
channel_axis=channel_axis)
|
||||
# very oversegmented:
|
||||
assert len(np.unique(seg2)) > 10
|
||||
# still don't cross lines
|
||||
assert (seg2[9, :] != seg2[10, :]).all()
|
||||
assert (seg2[:, 9] != seg2[:, 10]).all()
|
||||
512
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_random_walker.py
vendored
Normal file
512
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_random_walker.py
vendored
Normal file
@@ -0,0 +1,512 @@
|
||||
import numpy as np
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage._shared.testing import xfail, arch32
|
||||
from skimage.segmentation import random_walker
|
||||
from skimage.transform import resize
|
||||
|
||||
PYAMG_MISSING_WARNING = r'pyamg|\A\Z'
|
||||
|
||||
|
||||
def make_2d_syntheticdata(lx, ly=None):
|
||||
if ly is None:
|
||||
ly = lx
|
||||
np.random.seed(1234)
|
||||
data = np.zeros((lx, ly)) + 0.1 * np.random.randn(lx, ly)
|
||||
small_l = int(lx // 5)
|
||||
data[lx // 2 - small_l:lx // 2 + small_l,
|
||||
ly // 2 - small_l:ly // 2 + small_l] = 1
|
||||
data[lx // 2 - small_l + 1:lx // 2 + small_l - 1,
|
||||
ly // 2 - small_l + 1:ly // 2 + small_l - 1] = (
|
||||
0.1 * np.random.randn(2 * small_l - 2, 2 * small_l - 2))
|
||||
data[lx // 2 - small_l, ly // 2 - small_l // 8:ly // 2 + small_l // 8] = 0
|
||||
seeds = np.zeros_like(data)
|
||||
seeds[lx // 5, ly // 5] = 1
|
||||
seeds[lx // 2 + small_l // 4, ly // 2 - small_l // 4] = 2
|
||||
return data, seeds
|
||||
|
||||
|
||||
def make_3d_syntheticdata(lx, ly=None, lz=None):
|
||||
if ly is None:
|
||||
ly = lx
|
||||
if lz is None:
|
||||
lz = lx
|
||||
np.random.seed(1234)
|
||||
data = np.zeros((lx, ly, lz)) + 0.1 * np.random.randn(lx, ly, lz)
|
||||
small_l = int(lx // 5)
|
||||
data[lx // 2 - small_l:lx // 2 + small_l,
|
||||
ly // 2 - small_l:ly // 2 + small_l,
|
||||
lz // 2 - small_l:lz // 2 + small_l] = 1
|
||||
data[lx // 2 - small_l + 1:lx // 2 + small_l - 1,
|
||||
ly // 2 - small_l + 1:ly // 2 + small_l - 1,
|
||||
lz // 2 - small_l + 1:lz // 2 + small_l - 1] = 0
|
||||
# make a hole
|
||||
hole_size = np.max([1, small_l // 8])
|
||||
data[lx // 2 - small_l,
|
||||
ly // 2 - hole_size:ly // 2 + hole_size,
|
||||
lz // 2 - hole_size:lz // 2 + hole_size] = 0
|
||||
seeds = np.zeros_like(data)
|
||||
seeds[lx // 5, ly // 5, lz // 5] = 1
|
||||
seeds[lx // 2 + small_l // 4,
|
||||
ly // 2 - small_l // 4,
|
||||
lz // 2 - small_l // 4] = 2
|
||||
return data, seeds
|
||||
|
||||
|
||||
@testing.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_2d_bf(dtype):
|
||||
lx = 70
|
||||
ly = 100
|
||||
|
||||
# have to use a smaller beta to avoid warning with lower precision input
|
||||
beta = 90 if dtype == np.float64 else 25
|
||||
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
data = data.astype(dtype, copy=False)
|
||||
labels_bf = random_walker(data, labels, beta=beta, mode='bf')
|
||||
assert (labels_bf[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
full_prob_bf = random_walker(data, labels, beta=beta, mode='bf', return_full_prob=True)
|
||||
assert (full_prob_bf[1, 25:45, 40:60] >=
|
||||
full_prob_bf[0, 25:45, 40:60]).all()
|
||||
assert data.shape == labels.shape
|
||||
# Now test with more than two labels
|
||||
labels[55, 80] = 3
|
||||
full_prob_bf = random_walker(data, labels, beta=beta, mode='bf', return_full_prob=True)
|
||||
assert (full_prob_bf[1, 25:45, 40:60] >=
|
||||
full_prob_bf[0, 25:45, 40:60]).all()
|
||||
assert len(full_prob_bf) == 3
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
@testing.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_2d_cg(dtype):
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
data = data.astype(dtype, copy=False)
|
||||
with expected_warnings(['"cg" mode']):
|
||||
labels_cg = random_walker(data, labels, beta=90, mode='cg')
|
||||
assert (labels_cg[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
with expected_warnings(['"cg" mode']):
|
||||
full_prob = random_walker(data, labels, beta=90, mode='cg',
|
||||
return_full_prob=True)
|
||||
assert (full_prob[1, 25:45, 40:60] >=
|
||||
full_prob[0, 25:45, 40:60]).all()
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
@testing.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_2d_cg_mg(dtype):
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
data = data.astype(dtype, copy=False)
|
||||
anticipated_warnings = [f'scipy.sparse.sparsetools|{PYAMG_MISSING_WARNING}']
|
||||
with expected_warnings(anticipated_warnings):
|
||||
labels_cg_mg = random_walker(data, labels, beta=90, mode='cg_mg')
|
||||
assert (labels_cg_mg[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
with expected_warnings(anticipated_warnings):
|
||||
full_prob = random_walker(data, labels, beta=90, mode='cg_mg',
|
||||
return_full_prob=True)
|
||||
assert (full_prob[1, 25:45, 40:60] >=
|
||||
full_prob[0, 25:45, 40:60]).all()
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
@testing.parametrize('dtype', [np.float16, np.float32, np.float64])
|
||||
def test_2d_cg_j(dtype):
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
data = data.astype(dtype, copy=False)
|
||||
labels_cg = random_walker(data, labels, beta=90, mode='cg_j')
|
||||
assert (labels_cg[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
full_prob = random_walker(data, labels, beta=90, mode='cg_j', return_full_prob=True)
|
||||
assert (full_prob[1, 25:45, 40:60] >= full_prob[0, 25:45, 40:60]).all()
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
def test_types():
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
data = 255 * (data - data.min()) // (data.max() - data.min())
|
||||
data = data.astype(np.uint8)
|
||||
with expected_warnings([PYAMG_MISSING_WARNING]):
|
||||
labels_cg_mg = random_walker(data, labels, beta=90, mode='cg_mg')
|
||||
assert (labels_cg_mg[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
def test_reorder_labels():
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
labels[labels == 2] = 4
|
||||
labels_bf = random_walker(data, labels, beta=90, mode='bf')
|
||||
assert (labels_bf[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
def test_2d_inactive():
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
labels[10:20, 10:20] = -1
|
||||
labels[46:50, 33:38] = -2
|
||||
labels = random_walker(data, labels, beta=90)
|
||||
assert (labels.reshape((lx, ly))[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
def test_2d_laplacian_size():
|
||||
# test case from: https://github.com/scikit-image/scikit-image/issues/5034
|
||||
# The markers here were modified from the ones in the original issue to
|
||||
# avoid a singular matrix, but still reproduce the issue.
|
||||
data = np.asarray([[12823, 12787, 12710],
|
||||
[12883, 13425, 12067],
|
||||
[11934, 11929, 12309]])
|
||||
markers = np.asarray([[0, -1, 2],
|
||||
[0, -1, 0],
|
||||
[1, 0, -1]])
|
||||
expected_labels = np.asarray([[1, -1, 2],
|
||||
[1, -1, 2],
|
||||
[1, 1, -1]])
|
||||
labels = random_walker(data, markers, beta=10)
|
||||
np.testing.assert_array_equal(labels, expected_labels)
|
||||
|
||||
|
||||
@testing.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_3d(dtype):
|
||||
n = 30
|
||||
lx, ly, lz = n, n, n
|
||||
data, labels = make_3d_syntheticdata(lx, ly, lz)
|
||||
data = data.astype(dtype, copy=False)
|
||||
with expected_warnings(['"cg" mode']):
|
||||
labels = random_walker(data, labels, mode='cg')
|
||||
assert (labels.reshape(data.shape)[13:17, 13:17, 13:17] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
def test_3d_inactive():
|
||||
n = 30
|
||||
lx, ly, lz = n, n, n
|
||||
data, labels = make_3d_syntheticdata(lx, ly, lz)
|
||||
labels[5:25, 26:29, 26:29] = -1
|
||||
with expected_warnings(['"cg" mode|CObject type']):
|
||||
labels = random_walker(data, labels, mode='cg')
|
||||
assert (labels.reshape(data.shape)[13:17, 13:17, 13:17] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
|
||||
|
||||
@testing.parametrize('channel_axis', [0, 1, -1])
|
||||
@testing.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_multispectral_2d(dtype, channel_axis):
|
||||
lx, ly = 70, 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
data = data.astype(dtype, copy=False)
|
||||
data = data[..., np.newaxis].repeat(2, axis=-1) # Expect identical output
|
||||
|
||||
data = np.moveaxis(data, -1, channel_axis)
|
||||
with expected_warnings(['"cg" mode',
|
||||
'The probability range is outside']):
|
||||
multi_labels = random_walker(data, labels, mode='cg',
|
||||
channel_axis=channel_axis)
|
||||
data = np.moveaxis(data, channel_axis, -1)
|
||||
|
||||
assert data[..., 0].shape == labels.shape
|
||||
with expected_warnings(['"cg" mode']):
|
||||
random_walker(data[..., 0], labels, mode='cg')
|
||||
assert (multi_labels.reshape(labels.shape)[25:45, 40:60] == 2).all()
|
||||
assert data[..., 0].shape == labels.shape
|
||||
|
||||
|
||||
@testing.parametrize('dtype', [np.float32, np.float64])
|
||||
def test_multispectral_3d(dtype):
|
||||
n = 30
|
||||
lx, ly, lz = n, n, n
|
||||
data, labels = make_3d_syntheticdata(lx, ly, lz)
|
||||
data = data.astype(dtype, copy=False)
|
||||
data = data[..., np.newaxis].repeat(2, axis=-1) # Expect identical output
|
||||
with expected_warnings(['"cg" mode']):
|
||||
multi_labels = random_walker(data, labels, mode='cg', channel_axis=-1)
|
||||
assert data[..., 0].shape == labels.shape
|
||||
with expected_warnings(['"cg" mode']):
|
||||
single_labels = random_walker(data[..., 0], labels, mode='cg')
|
||||
assert (multi_labels.reshape(labels.shape)[13:17, 13:17, 13:17] == 2).all()
|
||||
assert (single_labels.reshape(labels.shape)[13:17, 13:17, 13:17] == 2).all()
|
||||
assert data[..., 0].shape == labels.shape
|
||||
|
||||
|
||||
def test_spacing_0():
|
||||
n = 30
|
||||
lx, ly, lz = n, n, n
|
||||
data, _ = make_3d_syntheticdata(lx, ly, lz)
|
||||
|
||||
# Rescale `data` along Z axis
|
||||
data_aniso = np.zeros((n, n, n // 2))
|
||||
for i, yz in enumerate(data):
|
||||
data_aniso[i, :, :] = resize(yz, (n, n // 2),
|
||||
mode='constant',
|
||||
anti_aliasing=False)
|
||||
|
||||
# Generate new labels
|
||||
small_l = int(lx // 5)
|
||||
labels_aniso = np.zeros_like(data_aniso)
|
||||
labels_aniso[lx // 5, ly // 5, lz // 5] = 1
|
||||
labels_aniso[lx // 2 + small_l // 4,
|
||||
ly // 2 - small_l // 4,
|
||||
lz // 4 - small_l // 8] = 2
|
||||
|
||||
# Test with `spacing` kwarg
|
||||
with expected_warnings(['"cg" mode']):
|
||||
labels_aniso = random_walker(data_aniso, labels_aniso, mode='cg',
|
||||
spacing=(1., 1., 0.5))
|
||||
|
||||
assert (labels_aniso[13:17, 13:17, 7:9] == 2).all()
|
||||
|
||||
|
||||
@xfail(condition=arch32,
|
||||
reason=('Known test failure on 32-bit platforms. See links for '
|
||||
'details: '
|
||||
'https://github.com/scikit-image/scikit-image/issues/3091 '
|
||||
'https://github.com/scikit-image/scikit-image/issues/3092'))
|
||||
def test_spacing_1():
|
||||
n = 30
|
||||
lx, ly, lz = n, n, n
|
||||
data, _ = make_3d_syntheticdata(lx, ly, lz)
|
||||
|
||||
# Rescale `data` along Y axis
|
||||
# `resize` is not yet 3D capable, so this must be done by looping in 2D.
|
||||
data_aniso = np.zeros((n, n * 2, n))
|
||||
for i, yz in enumerate(data):
|
||||
data_aniso[i, :, :] = resize(yz, (n * 2, n),
|
||||
mode='constant',
|
||||
anti_aliasing=False)
|
||||
|
||||
# Generate new labels
|
||||
small_l = int(lx // 5)
|
||||
labels_aniso = np.zeros_like(data_aniso)
|
||||
labels_aniso[lx // 5, ly // 5, lz // 5] = 1
|
||||
labels_aniso[lx // 2 + small_l // 4,
|
||||
ly - small_l // 2,
|
||||
lz // 2 - small_l // 4] = 2
|
||||
|
||||
# Test with `spacing` kwarg
|
||||
# First, anisotropic along Y
|
||||
with expected_warnings(['"cg" mode']):
|
||||
labels_aniso = random_walker(data_aniso, labels_aniso, mode='cg',
|
||||
spacing=(1., 2., 1.))
|
||||
assert (labels_aniso[13:17, 26:34, 13:17] == 2).all()
|
||||
|
||||
# Rescale `data` along X axis
|
||||
# `resize` is not yet 3D capable, so this must be done by looping in 2D.
|
||||
data_aniso = np.zeros((n, n * 2, n))
|
||||
for i in range(data.shape[1]):
|
||||
data_aniso[i, :, :] = resize(data[:, 1, :], (n * 2, n),
|
||||
mode='constant',
|
||||
anti_aliasing=False)
|
||||
|
||||
# Generate new labels
|
||||
small_l = int(lx // 5)
|
||||
labels_aniso2 = np.zeros_like(data_aniso)
|
||||
labels_aniso2[lx // 5, ly // 5, lz // 5] = 1
|
||||
labels_aniso2[lx - small_l // 2,
|
||||
ly // 2 + small_l // 4,
|
||||
lz // 2 - small_l // 4] = 2
|
||||
|
||||
# Anisotropic along X
|
||||
with expected_warnings(['"cg" mode']):
|
||||
labels_aniso2 = random_walker(data_aniso,
|
||||
labels_aniso2,
|
||||
mode='cg', spacing=(2., 1., 1.))
|
||||
assert (labels_aniso2[26:34, 13:17, 13:17] == 2).all()
|
||||
|
||||
|
||||
def test_trivial_cases():
|
||||
# When all voxels are labeled
|
||||
img = np.ones((10, 10))
|
||||
labels = np.ones((10, 10))
|
||||
|
||||
with expected_warnings(["Returning provided labels"]):
|
||||
pass_through = random_walker(img, labels)
|
||||
np.testing.assert_array_equal(pass_through, labels)
|
||||
|
||||
# When all voxels are labeled AND return_full_prob is True
|
||||
labels[:, :5] = 3
|
||||
expected = np.concatenate(((labels == 1)[..., np.newaxis],
|
||||
(labels == 3)[..., np.newaxis]), axis=2)
|
||||
with expected_warnings(["Returning provided labels"]):
|
||||
test = random_walker(img, labels, return_full_prob=True)
|
||||
np.testing.assert_array_equal(test, expected)
|
||||
|
||||
# Unlabeled voxels not connected to seed, so nothing can be done
|
||||
img = np.full((10, 10), False)
|
||||
object_A = np.array([(6,7), (6,8), (7,7), (7,8)])
|
||||
object_B = np.array([(3,1), (4,1), (2,2), (3,2), (4,2), (2,3), (3,3)])
|
||||
for x, y in np.vstack((object_A, object_B)):
|
||||
img[y][x] = True
|
||||
|
||||
markers = np.zeros((10, 10), dtype=np.int8)
|
||||
for x, y in object_B:
|
||||
markers[y][x] = 1
|
||||
|
||||
markers[img == 0] = -1
|
||||
with expected_warnings(["All unlabeled pixels are isolated"]):
|
||||
output_labels = random_walker(img, markers)
|
||||
assert np.all(output_labels[markers == 1] == 1)
|
||||
# Here 0-labeled pixels could not be determined (no connection to seed)
|
||||
assert np.all(output_labels[markers == 0] == -1)
|
||||
with expected_warnings(["All unlabeled pixels are isolated"]):
|
||||
test = random_walker(img, markers, return_full_prob=True)
|
||||
|
||||
|
||||
def test_length2_spacing():
|
||||
# If this passes without raising an exception (warnings OK), the new
|
||||
# spacing code is working properly.
|
||||
np.random.seed(42)
|
||||
img = np.ones((10, 10)) + 0.2 * np.random.normal(size=(10, 10))
|
||||
labels = np.zeros((10, 10), dtype=np.uint8)
|
||||
labels[2, 4] = 1
|
||||
labels[6, 8] = 4
|
||||
random_walker(img, labels, spacing=(1., 2.))
|
||||
|
||||
|
||||
def test_bad_inputs():
|
||||
# Too few dimensions
|
||||
img = np.ones(10)
|
||||
labels = np.arange(10)
|
||||
with testing.raises(ValueError):
|
||||
random_walker(img, labels)
|
||||
with testing.raises(ValueError):
|
||||
random_walker(img, labels, channel_axis=-1)
|
||||
|
||||
# Too many dimensions
|
||||
np.random.seed(42)
|
||||
img = np.random.normal(size=(3, 3, 3, 3, 3))
|
||||
labels = np.arange(3 ** 5).reshape(img.shape)
|
||||
with testing.raises(ValueError):
|
||||
random_walker(img, labels)
|
||||
with testing.raises(ValueError):
|
||||
random_walker(img, labels, channel_axis=-1)
|
||||
|
||||
# Spacing incorrect length
|
||||
img = np.random.normal(size=(10, 10))
|
||||
labels = np.zeros((10, 10))
|
||||
labels[2, 4] = 2
|
||||
labels[6, 8] = 5
|
||||
with testing.raises(ValueError):
|
||||
random_walker(img, labels, spacing=(1,))
|
||||
|
||||
# Invalid mode
|
||||
img = np.random.normal(size=(10, 10))
|
||||
labels = np.zeros((10, 10))
|
||||
with testing.raises(ValueError):
|
||||
random_walker(img, labels, mode='bad')
|
||||
|
||||
|
||||
def test_isolated_seeds():
|
||||
np.random.seed(0)
|
||||
a = np.random.random((7, 7))
|
||||
mask = - np.ones(a.shape)
|
||||
# This pixel is an isolated seed
|
||||
mask[1, 1] = 1
|
||||
# Unlabeled pixels
|
||||
mask[3:, 3:] = 0
|
||||
# Seeds connected to unlabeled pixels
|
||||
mask[4, 4] = 2
|
||||
mask[6, 6] = 1
|
||||
|
||||
# Test that no error is raised, and that labels of isolated seeds are OK
|
||||
with expected_warnings(['The probability range is outside']):
|
||||
res = random_walker(a, mask)
|
||||
assert res[1, 1] == 1
|
||||
with expected_warnings(['The probability range is outside']):
|
||||
res = random_walker(a, mask, return_full_prob=True)
|
||||
assert res[0, 1, 1] == 1
|
||||
assert res[1, 1, 1] == 0
|
||||
|
||||
|
||||
def test_isolated_area():
|
||||
np.random.seed(0)
|
||||
a = np.random.random((7, 7))
|
||||
mask = - np.ones(a.shape)
|
||||
# This pixel is an isolated seed
|
||||
mask[1, 1] = 0
|
||||
# Unlabeled pixels
|
||||
mask[3:, 3:] = 0
|
||||
# Seeds connected to unlabeled pixels
|
||||
mask[4, 4] = 2
|
||||
mask[6, 6] = 1
|
||||
|
||||
# Test that no error is raised, and that labels of isolated seeds are OK
|
||||
with expected_warnings(['The probability range is outside']):
|
||||
res = random_walker(a, mask)
|
||||
assert res[1, 1] == 0
|
||||
with expected_warnings(['The probability range is outside']):
|
||||
res = random_walker(a, mask, return_full_prob=True)
|
||||
assert res[0, 1, 1] == 0
|
||||
assert res[1, 1, 1] == 0
|
||||
|
||||
|
||||
def test_prob_tol():
|
||||
np.random.seed(0)
|
||||
a = np.random.random((7, 7))
|
||||
mask = - np.ones(a.shape)
|
||||
# This pixel is an isolated seed
|
||||
mask[1, 1] = 1
|
||||
# Unlabeled pixels
|
||||
mask[3:, 3:] = 0
|
||||
# Seeds connected to unlabeled pixels
|
||||
mask[4, 4] = 2
|
||||
mask[6, 6] = 1
|
||||
|
||||
with expected_warnings(['The probability range is outside']):
|
||||
res = random_walker(a, mask, return_full_prob=True)
|
||||
|
||||
# Lower beta, no warning is expected.
|
||||
res = random_walker(a, mask, return_full_prob=True, beta=10)
|
||||
assert res[0, 1, 1] == 1
|
||||
assert res[1, 1, 1] == 0
|
||||
|
||||
# Being more prob_tol tolerant, no warning is expected.
|
||||
res = random_walker(a, mask, return_full_prob=True, prob_tol=1e-1)
|
||||
assert res[0, 1, 1] == 1
|
||||
assert res[1, 1, 1] == 0
|
||||
|
||||
# Reduced tol, no warning is expected.
|
||||
res = random_walker(a, mask, return_full_prob=True, tol=1e-9)
|
||||
assert res[0, 1, 1] == 1
|
||||
assert res[1, 1, 1] == 0
|
||||
|
||||
|
||||
def test_umfpack_import():
|
||||
from skimage.segmentation import random_walker_segmentation
|
||||
UmfpackContext = random_walker_segmentation.UmfpackContext
|
||||
try:
|
||||
# when scikit-umfpack is installed UmfpackContext should not be None
|
||||
import scikits.umfpack # noqa: F401
|
||||
assert UmfpackContext is not None
|
||||
except ImportError:
|
||||
assert UmfpackContext is None
|
||||
|
||||
|
||||
def test_empty_labels():
|
||||
image = np.random.random((5, 5))
|
||||
labels = np.zeros((5, 5), dtype=int)
|
||||
|
||||
with testing.raises(ValueError, match="No seeds provided"):
|
||||
random_walker(image, labels)
|
||||
|
||||
labels[1, 1] = -1
|
||||
with testing.raises(ValueError, match="No seeds provided"):
|
||||
random_walker(image, labels)
|
||||
|
||||
# Once seeds are provided, it should run without error
|
||||
labels[3, 3] = 1
|
||||
random_walker(image, labels)
|
||||
568
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_slic.py
vendored
Normal file
568
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_slic.py
vendored
Normal file
@@ -0,0 +1,568 @@
|
||||
from itertools import product
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from numpy.testing import assert_equal
|
||||
|
||||
from skimage import data, filters, img_as_float
|
||||
from skimage._shared.testing import test_parallel, expected_warnings
|
||||
from skimage.segmentation import slic
|
||||
|
||||
|
||||
@test_parallel()
|
||||
def test_color_2d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21, 3))
|
||||
img[:10, :10, 0] = 1
|
||||
img[10:, :10, 1] = 1
|
||||
img[10:, 10:, 2] = 1
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
img[img > 1] = 1
|
||||
img[img < 0] = 0
|
||||
seg = slic(img, n_segments=4, sigma=0, enforce_connectivity=False,
|
||||
start_label=0)
|
||||
|
||||
# we expect 4 segments
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
assert_equal(seg.shape, img.shape[:-1])
|
||||
assert_equal(seg[:10, :10], 0)
|
||||
assert_equal(seg[10:, :10], 2)
|
||||
assert_equal(seg[:10, 10:], 1)
|
||||
assert_equal(seg[10:, 10:], 3)
|
||||
|
||||
|
||||
def test_multichannel_2d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 20, 8))
|
||||
img[:10, :10, 0:2] = 1
|
||||
img[:10, 10:, 2:4] = 1
|
||||
img[10:, :10, 4:6] = 1
|
||||
img[10:, 10:, 6:8] = 1
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
img = np.clip(img, 0, 1, out=img)
|
||||
seg = slic(img, n_segments=4, enforce_connectivity=False, start_label=0)
|
||||
|
||||
# we expect 4 segments
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
assert_equal(seg.shape, img.shape[:-1])
|
||||
assert_equal(seg[:10, :10], 0)
|
||||
assert_equal(seg[10:, :10], 2)
|
||||
assert_equal(seg[:10, 10:], 1)
|
||||
assert_equal(seg[10:, 10:], 3)
|
||||
|
||||
|
||||
def test_gray_2d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21))
|
||||
img[:10, :10] = 0.33
|
||||
img[10:, :10] = 0.67
|
||||
img[10:, 10:] = 1.00
|
||||
img += 0.0033 * rnd.normal(size=img.shape)
|
||||
img[img > 1] = 1
|
||||
img[img < 0] = 0
|
||||
seg = slic(img, sigma=0, n_segments=4, compactness=1,
|
||||
channel_axis=None, convert2lab=False, start_label=0)
|
||||
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
assert_equal(seg.shape, img.shape)
|
||||
assert_equal(seg[:10, :10], 0)
|
||||
assert_equal(seg[10:, :10], 2)
|
||||
assert_equal(seg[:10, 10:], 1)
|
||||
assert_equal(seg[10:, 10:], 3)
|
||||
|
||||
|
||||
def test_gray2d_default_channel_axis():
|
||||
img = np.zeros((20, 21))
|
||||
img[:10, :10] = 0.33
|
||||
with pytest.raises(
|
||||
ValueError, match="channel_axis=-1 indicates multichannel"
|
||||
):
|
||||
slic(img)
|
||||
slic(img, channel_axis=None)
|
||||
|
||||
|
||||
def _check_segment_labels(seg1, seg2, allowed_mismatch_ratio=0.1):
|
||||
size = seg1.size
|
||||
ndiff = np.sum(seg1 != seg2)
|
||||
assert (ndiff / size) < allowed_mismatch_ratio
|
||||
|
||||
|
||||
def test_slic_consistency_across_image_magnitude():
|
||||
# verify that that images of various scales across integer and float dtypes
|
||||
# give the same segmentation result
|
||||
img_uint8 = data.cat()[:256, :128]
|
||||
img_uint16 = 256 * img_uint8.astype(np.uint16)
|
||||
img_float32 = img_as_float(img_uint8)
|
||||
img_float32_norm = img_float32 / img_float32.max()
|
||||
img_float32_offset = img_float32 + 1000
|
||||
|
||||
seg1 = slic(img_uint8)
|
||||
seg2 = slic(img_uint16)
|
||||
seg3 = slic(img_float32)
|
||||
seg4 = slic(img_float32_norm)
|
||||
seg5 = slic(img_float32_offset)
|
||||
|
||||
np.testing.assert_array_equal(seg1, seg2)
|
||||
np.testing.assert_array_equal(seg1, seg3)
|
||||
# Assert that offset has no impact on result
|
||||
np.testing.assert_array_equal(seg4, seg5)
|
||||
# Floating point cases can have mismatch due to floating point error
|
||||
# exact match was observed on x86_64, but mismatches seen no i686.
|
||||
# For now just verify that a similar number of superpixels are present in
|
||||
# each case.
|
||||
n_seg1 = seg1.max()
|
||||
n_seg4 = seg4.max()
|
||||
assert abs(n_seg1 - n_seg4) / n_seg1 < 0.5
|
||||
|
||||
|
||||
def test_color_3d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21, 22, 3))
|
||||
slices = []
|
||||
for dim_size in img.shape[:-1]:
|
||||
midpoint = dim_size // 2
|
||||
slices.append((slice(None, midpoint), slice(midpoint, None)))
|
||||
slices = list(product(*slices))
|
||||
colors = list(product(*(([0, 1],) * 3)))
|
||||
for s, c in zip(slices, colors):
|
||||
img[s] = c
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
img[img > 1] = 1
|
||||
img[img < 0] = 0
|
||||
seg = slic(img, sigma=0, n_segments=8, start_label=0)
|
||||
|
||||
assert_equal(len(np.unique(seg)), 8)
|
||||
for s, c in zip(slices, range(8)):
|
||||
assert_equal(seg[s], c)
|
||||
|
||||
|
||||
def test_gray_3d():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21, 22))
|
||||
slices = []
|
||||
for dim_size in img.shape:
|
||||
midpoint = dim_size // 2
|
||||
slices.append((slice(None, midpoint), slice(midpoint, None)))
|
||||
slices = list(product(*slices))
|
||||
shades = np.arange(0, 1.000001, 1.0 / 7)
|
||||
for s, sh in zip(slices, shades):
|
||||
img[s] = sh
|
||||
img += 0.001 * rnd.normal(size=img.shape)
|
||||
img[img > 1] = 1
|
||||
img[img < 0] = 0
|
||||
seg = slic(img, sigma=0, n_segments=8, compactness=1,
|
||||
channel_axis=None, convert2lab=False, start_label=0)
|
||||
|
||||
assert_equal(len(np.unique(seg)), 8)
|
||||
for s, c in zip(slices, range(8)):
|
||||
assert_equal(seg[s], c)
|
||||
|
||||
|
||||
def test_list_sigma():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.array([[1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1]], float)
|
||||
img += 0.1 * rnd.normal(size=img.shape)
|
||||
result_sigma = np.array([[0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 1, 1, 1]], int)
|
||||
with expected_warnings(["Input image is 2D: sigma number of "
|
||||
"elements must be 2"]):
|
||||
seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1],
|
||||
channel_axis=None, start_label=0)
|
||||
assert_equal(seg_sigma, result_sigma)
|
||||
|
||||
|
||||
def test_spacing():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.array([[1, 1, 1, 0, 0],
|
||||
[1, 1, 0, 0, 0]], float)
|
||||
result_non_spaced = np.array([[0, 0, 0, 1, 1],
|
||||
[0, 0, 1, 1, 1]], int)
|
||||
result_spaced = np.array([[0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1]], int)
|
||||
img += 0.1 * rnd.normal(size=img.shape)
|
||||
seg_non_spaced = slic(img, n_segments=2, sigma=0, channel_axis=None,
|
||||
compactness=1.0, start_label=0)
|
||||
seg_spaced = slic(img, n_segments=2, sigma=0, spacing=[500, 1],
|
||||
compactness=1.0, channel_axis=None, start_label=0)
|
||||
assert_equal(seg_non_spaced, result_non_spaced)
|
||||
assert_equal(seg_spaced, result_spaced)
|
||||
|
||||
|
||||
def test_invalid_lab_conversion():
|
||||
img = np.array([[1, 1, 1, 0, 0],
|
||||
[1, 1, 0, 0, 0]], float) + 1
|
||||
with pytest.raises(ValueError):
|
||||
slic(img, channel_axis=-1, convert2lab=True, start_label=0)
|
||||
|
||||
|
||||
def test_enforce_connectivity():
|
||||
img = np.array([[0, 0, 0, 1, 1, 1],
|
||||
[1, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 0]], float)
|
||||
|
||||
segments_connected = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=True,
|
||||
convert2lab=False, start_label=0,
|
||||
channel_axis=None)
|
||||
segments_disconnected = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=False,
|
||||
convert2lab=False, start_label=0,
|
||||
channel_axis=None)
|
||||
|
||||
# Make sure nothing fatal occurs (e.g. buffer overflow) at low values of
|
||||
# max_size_factor
|
||||
segments_connected_low_max = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=True,
|
||||
convert2lab=False,
|
||||
max_size_factor=0.8,
|
||||
start_label=0,
|
||||
channel_axis=None)
|
||||
|
||||
result_connected = np.array([[0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 1, 1, 1]], float)
|
||||
|
||||
result_disconnected = np.array([[0, 0, 0, 1, 1, 1],
|
||||
[1, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 0]], float)
|
||||
|
||||
assert_equal(segments_connected, result_connected)
|
||||
assert_equal(segments_disconnected, result_disconnected)
|
||||
assert_equal(segments_connected_low_max, result_connected)
|
||||
|
||||
|
||||
def test_slic_zero():
|
||||
# Same as test_color_2d but with slic_zero=True
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21, 3))
|
||||
img[:10, :10, 0] = 1
|
||||
img[10:, :10, 1] = 1
|
||||
img[10:, 10:, 2] = 1
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
img[img > 1] = 1
|
||||
img[img < 0] = 0
|
||||
seg = slic(img, n_segments=4, sigma=0, slic_zero=True, start_label=0)
|
||||
|
||||
# we expect 4 segments
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
assert_equal(seg.shape, img.shape[:-1])
|
||||
assert_equal(seg[:10, :10], 0)
|
||||
assert_equal(seg[10:, :10], 2)
|
||||
assert_equal(seg[:10, 10:], 1)
|
||||
assert_equal(seg[10:, 10:], 3)
|
||||
|
||||
|
||||
def test_more_segments_than_pixels():
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21))
|
||||
img[:10, :10] = 0.33
|
||||
img[10:, :10] = 0.67
|
||||
img[10:, 10:] = 1.00
|
||||
img += 0.0033 * rnd.normal(size=img.shape)
|
||||
img[img > 1] = 1
|
||||
img[img < 0] = 0
|
||||
seg = slic(img, sigma=0, n_segments=500, compactness=1,
|
||||
channel_axis=None, convert2lab=False, start_label=0)
|
||||
assert np.all(seg.ravel() == np.arange(seg.size))
|
||||
|
||||
|
||||
def test_color_2d_mask():
|
||||
rnd = np.random.default_rng(0)
|
||||
msk = np.zeros((20, 21))
|
||||
msk[2:-2, 2:-2] = 1
|
||||
img = np.zeros((20, 21, 3))
|
||||
img[:10, :10, 0] = 1
|
||||
img[10:, :10, 1] = 1
|
||||
img[10:, 10:, 2] = 1
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
np.clip(img, 0, 1, out=img)
|
||||
seg = slic(img, n_segments=4, sigma=0, enforce_connectivity=False,
|
||||
mask=msk)
|
||||
|
||||
# we expect 4 segments + masked area
|
||||
assert_equal(len(np.unique(seg)), 5)
|
||||
assert_equal(seg.shape, img.shape[:-1])
|
||||
# segments
|
||||
assert_equal(seg[2:10, 2:10], 1)
|
||||
assert_equal(seg[10:-2, 2:10], 4)
|
||||
assert_equal(seg[2:10, 10:-2], 2)
|
||||
assert_equal(seg[10:-2, 10:-2], 3)
|
||||
# non masked area
|
||||
assert_equal(seg[:2, :], 0)
|
||||
assert_equal(seg[-2:, :], 0)
|
||||
assert_equal(seg[:, :2], 0)
|
||||
assert_equal(seg[:, -2:], 0)
|
||||
|
||||
|
||||
def test_multichannel_2d_mask():
|
||||
rnd = np.random.default_rng(0)
|
||||
msk = np.zeros((20, 20))
|
||||
msk[2:-2, 2:-2] = 1
|
||||
img = np.zeros((20, 20, 8))
|
||||
img[:10, :10, 0:2] = 1
|
||||
img[:10, 10:, 2:4] = 1
|
||||
img[10:, :10, 4:6] = 1
|
||||
img[10:, 10:, 6:8] = 1
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
np.clip(img, 0, 1, out=img)
|
||||
seg = slic(img, n_segments=4, enforce_connectivity=False,
|
||||
mask=msk)
|
||||
|
||||
# we expect 4 segments + masked area
|
||||
assert_equal(len(np.unique(seg)), 5)
|
||||
assert_equal(seg.shape, img.shape[:-1])
|
||||
# segments
|
||||
assert_equal(seg[2:10, 2:10], 2)
|
||||
assert_equal(seg[2:10, 10:-2], 1)
|
||||
assert_equal(seg[10:-2, 2:10], 4)
|
||||
assert_equal(seg[10:-2, 10:-2], 3)
|
||||
# non masked area
|
||||
assert_equal(seg[:2, :], 0)
|
||||
assert_equal(seg[-2:, :], 0)
|
||||
assert_equal(seg[:, :2], 0)
|
||||
assert_equal(seg[:, -2:], 0)
|
||||
|
||||
|
||||
def test_gray_2d_mask():
|
||||
rnd = np.random.default_rng(0)
|
||||
msk = np.zeros((20, 21))
|
||||
msk[2:-2, 2:-2] = 1
|
||||
img = np.zeros((20, 21))
|
||||
img[:10, :10] = 0.33
|
||||
img[10:, :10] = 0.67
|
||||
img[10:, 10:] = 1.00
|
||||
img += 0.0033 * rnd.normal(size=img.shape)
|
||||
np.clip(img, 0, 1, out=img)
|
||||
seg = slic(img, sigma=0, n_segments=4, compactness=1,
|
||||
channel_axis=None, convert2lab=False, mask=msk)
|
||||
|
||||
assert_equal(len(np.unique(seg)), 5)
|
||||
assert_equal(seg.shape, img.shape)
|
||||
# segments
|
||||
assert_equal(seg[2:10, 2:10], 1)
|
||||
assert_equal(seg[2:10, 10:-2], 2)
|
||||
assert_equal(seg[10:-2, 2:10], 3)
|
||||
assert_equal(seg[10:-2, 10:-2], 4)
|
||||
# non masked area
|
||||
assert_equal(seg[:2, :], 0)
|
||||
assert_equal(seg[-2:, :], 0)
|
||||
assert_equal(seg[:, :2], 0)
|
||||
assert_equal(seg[:, -2:], 0)
|
||||
|
||||
|
||||
def test_list_sigma_mask():
|
||||
rnd = np.random.default_rng(0)
|
||||
msk = np.zeros((2, 6))
|
||||
msk[:, 1:-1] = 1
|
||||
img = np.array([[1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1]], float)
|
||||
img += 0.1 * rnd.normal(size=img.shape)
|
||||
result_sigma = np.array([[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0]], int)
|
||||
seg_sigma = slic(img, n_segments=2, sigma=[50, 1],
|
||||
channel_axis=None, mask=msk)
|
||||
assert_equal(seg_sigma, result_sigma)
|
||||
|
||||
|
||||
def test_spacing_mask():
|
||||
rnd = np.random.default_rng(0)
|
||||
msk = np.zeros((2, 5))
|
||||
msk[:, 1:-1] = 1
|
||||
img = np.array([[1, 1, 1, 0, 0],
|
||||
[1, 1, 0, 0, 0]], float)
|
||||
result_non_spaced = np.array([[0, 1, 1, 2, 0],
|
||||
[0, 1, 2, 2, 0]], int)
|
||||
result_spaced = np.array([[0, 1, 1, 1, 0],
|
||||
[0, 2, 2, 2, 0]], int)
|
||||
img += 0.1 * rnd.normal(size=img.shape)
|
||||
seg_non_spaced = slic(img, n_segments=2, sigma=0, channel_axis=None,
|
||||
compactness=1.0, mask=msk)
|
||||
seg_spaced = slic(img, n_segments=2, sigma=0, spacing=[50, 1],
|
||||
compactness=1.0, channel_axis=None, mask=msk)
|
||||
assert_equal(seg_non_spaced, result_non_spaced)
|
||||
assert_equal(seg_spaced, result_spaced)
|
||||
|
||||
|
||||
def test_enforce_connectivity_mask():
|
||||
msk = np.zeros((3, 6))
|
||||
msk[:, 1:-1] = 1
|
||||
img = np.array([[0, 0, 0, 1, 1, 1],
|
||||
[1, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 0]], float)
|
||||
|
||||
segments_connected = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=True,
|
||||
convert2lab=False, mask=msk, channel_axis=None)
|
||||
segments_disconnected = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=False,
|
||||
convert2lab=False, mask=msk, channel_axis=None)
|
||||
|
||||
# Make sure nothing fatal occurs (e.g. buffer overflow) at low values of
|
||||
# max_size_factor
|
||||
segments_connected_low_max = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=True,
|
||||
convert2lab=False,
|
||||
max_size_factor=0.8, mask=msk,
|
||||
channel_axis=None)
|
||||
|
||||
result_connected = np.array([[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0]], float)
|
||||
|
||||
result_disconnected = np.array([[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0]], float)
|
||||
|
||||
assert_equal(segments_connected, result_connected)
|
||||
assert_equal(segments_disconnected, result_disconnected)
|
||||
assert_equal(segments_connected_low_max, result_connected)
|
||||
|
||||
|
||||
def test_slic_zero_mask():
|
||||
|
||||
rnd = np.random.default_rng(0)
|
||||
msk = np.zeros((20, 21))
|
||||
msk[2:-2, 2:-2] = 1
|
||||
img = np.zeros((20, 21, 3))
|
||||
img[:10, :10, 0] = 1
|
||||
img[10:, :10, 1] = 1
|
||||
img[10:, 10:, 2] = 1
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
np.clip(img, 0, 1, out=img)
|
||||
seg = slic(img, n_segments=4, sigma=0, slic_zero=True,
|
||||
mask=msk)
|
||||
|
||||
# we expect 4 segments + masked area
|
||||
assert_equal(len(np.unique(seg)), 5)
|
||||
assert_equal(seg.shape, img.shape[:-1])
|
||||
# segments
|
||||
assert_equal(seg[2:10, 2:10], 1)
|
||||
assert_equal(seg[2:10, 10:-2], 2)
|
||||
assert_equal(seg[10:-2, 2:10], 3)
|
||||
assert_equal(seg[10:-2, 10:-2], 4)
|
||||
# non masked area
|
||||
assert_equal(seg[:2, :], 0)
|
||||
assert_equal(seg[-2:, :], 0)
|
||||
assert_equal(seg[:, :2], 0)
|
||||
assert_equal(seg[:, -2:], 0)
|
||||
|
||||
|
||||
def test_more_segments_than_pixels_mask():
|
||||
rnd = np.random.default_rng(0)
|
||||
msk = np.zeros((20, 21))
|
||||
msk[2:-2, 2:-2] = 1
|
||||
img = np.zeros((20, 21))
|
||||
img[:10, :10] = 0.33
|
||||
img[10:, :10] = 0.67
|
||||
img[10:, 10:] = 1.00
|
||||
img += 0.0033 * rnd.normal(size=img.shape)
|
||||
np.clip(img, 0, 1, out=img)
|
||||
seg = slic(img, sigma=0, n_segments=500, compactness=1,
|
||||
channel_axis=None, convert2lab=False, mask=msk)
|
||||
|
||||
expected = np.arange(seg[2:-2, 2:-2].size) + 1
|
||||
assert np.all(seg[2:-2, 2:-2].ravel() == expected)
|
||||
|
||||
|
||||
def test_color_3d_mask():
|
||||
|
||||
msk = np.zeros((20, 21, 22))
|
||||
msk[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21, 22, 3))
|
||||
slices = []
|
||||
for dim_size in msk.shape:
|
||||
midpoint = dim_size // 2
|
||||
slices.append((slice(None, midpoint), slice(midpoint, None)))
|
||||
slices = list(product(*slices))
|
||||
colors = list(product(*(([0, 1],) * 3)))
|
||||
for s, c in zip(slices, colors):
|
||||
img[s] = c
|
||||
img += 0.01 * rnd.normal(size=img.shape)
|
||||
np.clip(img, 0, 1, out=img)
|
||||
|
||||
seg = slic(img, sigma=0, n_segments=8, mask=msk)
|
||||
|
||||
# we expect 8 segments + masked area
|
||||
assert_equal(len(np.unique(seg)), 9)
|
||||
for s, c in zip(slices, range(1, 9)):
|
||||
assert_equal(seg[s][2:-2, 2:-2, 2:-2], c)
|
||||
|
||||
|
||||
def test_gray_3d_mask():
|
||||
|
||||
msk = np.zeros((20, 21, 22))
|
||||
msk[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
rnd = np.random.default_rng(0)
|
||||
img = np.zeros((20, 21, 22))
|
||||
slices = []
|
||||
for dim_size in img.shape:
|
||||
midpoint = dim_size // 2
|
||||
slices.append((slice(None, midpoint), slice(midpoint, None)))
|
||||
slices = list(product(*slices))
|
||||
shades = np.linspace(0, 1, 8)
|
||||
for s, sh in zip(slices, shades):
|
||||
img[s] = sh
|
||||
img += 0.001 * rnd.normal(size=img.shape)
|
||||
np.clip(img, 0, 1, out=img)
|
||||
seg = slic(img, sigma=0, n_segments=8, channel_axis=None,
|
||||
convert2lab=False, mask=msk)
|
||||
|
||||
# we expect 8 segments + masked area
|
||||
assert_equal(len(np.unique(seg)), 9)
|
||||
for s, c in zip(slices, range(1, 9)):
|
||||
assert_equal(seg[s][2:-2, 2:-2, 2:-2], c)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dtype", ['float16', 'float32', 'float64', 'uint8', 'int']
|
||||
)
|
||||
def test_dtype_support(dtype):
|
||||
img = np.random.rand(28, 28).astype(dtype)
|
||||
|
||||
# Simply run the function to assert that it runs without error
|
||||
slic(img, start_label=1, channel_axis=None)
|
||||
|
||||
|
||||
def test_start_label_fix():
|
||||
"""Tests the fix for a bug producing a label < start_label (gh-6240).
|
||||
|
||||
For the v0.19.1 release, the `img` and `slic` call as below result in two
|
||||
non-contiguous regions with value 0 despite `start_label=1`. We verify that
|
||||
the minimum label is now `start_label` as expected.
|
||||
"""
|
||||
|
||||
# generate bumpy data that gives unexpected label prior to bug fix
|
||||
rng = np.random.default_rng(9)
|
||||
img = rng.standard_normal((8, 13)) > 0
|
||||
img = filters.gaussian(img, sigma=1)
|
||||
|
||||
start_label = 1
|
||||
superp = slic(img, start_label=start_label, channel_axis=None,
|
||||
n_segments=6, compactness=0.01, enforce_connectivity=True,
|
||||
max_num_iter=10)
|
||||
assert superp.min() == start_label
|
||||
|
||||
|
||||
def test_raises_ValueError_if_input_has_NaN():
|
||||
img = np.zeros((4,5), dtype=float)
|
||||
img[2, 3] = np.NaN
|
||||
with pytest.raises(ValueError):
|
||||
slic(img, channel_axis=None)
|
||||
|
||||
mask = ~np.isnan(img)
|
||||
slic(img, mask=mask, channel_axis=None)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("inf", [-np.inf, np.inf])
|
||||
def test_raises_ValueError_if_input_has_inf(inf):
|
||||
img = np.zeros((4,5), dtype=float)
|
||||
img[2, 3] = inf
|
||||
with pytest.raises(ValueError):
|
||||
slic(img, channel_axis=None)
|
||||
|
||||
mask = np.isfinite(img)
|
||||
slic(img, mask=mask, channel_axis=None)
|
||||
498
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_watershed.py
vendored
Normal file
498
.CondaPkg/env/Lib/site-packages/skimage/segmentation/tests/test_watershed.py
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
"""test_watershed.py - tests the watershed function
|
||||
"""
|
||||
import math
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from skimage._shared.filters import gaussian
|
||||
from skimage.measure import label
|
||||
|
||||
from .._watershed import watershed
|
||||
|
||||
eps = 1e-12
|
||||
blob = np.array([[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 204, 204, 204, 204, 204, 204, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 204, 204, 183, 153, 153, 153, 153, 183, 204, 204, 255, 255, 255],
|
||||
[255, 255, 204, 183, 153, 141, 111, 103, 103, 111, 141, 153, 183, 204, 255, 255],
|
||||
[255, 255, 204, 153, 111, 94, 72, 52, 52, 72, 94, 111, 153, 204, 255, 255],
|
||||
[255, 255, 204, 153, 111, 72, 39, 1, 1, 39, 72, 111, 153, 204, 255, 255],
|
||||
[255, 255, 204, 183, 141, 111, 72, 39, 39, 72, 111, 141, 183, 204, 255, 255],
|
||||
[255, 255, 255, 204, 183, 141, 111, 72, 72, 111, 141, 183, 204, 255, 255, 255],
|
||||
[255, 255, 255, 255, 204, 183, 141, 94, 94, 141, 183, 204, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 204, 153, 103, 103, 153, 204, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 204, 183, 141, 94, 94, 141, 183, 204, 255, 255, 255, 255],
|
||||
[255, 255, 255, 204, 183, 141, 111, 72, 72, 111, 141, 183, 204, 255, 255, 255],
|
||||
[255, 255, 204, 183, 141, 111, 72, 39, 39, 72, 111, 141, 183, 204, 255, 255],
|
||||
[255, 255, 204, 153, 111, 72, 39, 1, 1, 39, 72, 111, 153, 204, 255, 255],
|
||||
[255, 255, 204, 153, 111, 94, 72, 52, 52, 72, 94, 111, 153, 204, 255, 255],
|
||||
[255, 255, 204, 183, 153, 141, 111, 103, 103, 111, 141, 153, 183, 204, 255, 255],
|
||||
[255, 255, 255, 204, 204, 183, 153, 153, 153, 153, 183, 204, 204, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 204, 204, 204, 204, 204, 204, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255],
|
||||
[255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255]])
|
||||
|
||||
|
||||
def diff(a, b):
|
||||
if not isinstance(a, np.ndarray):
|
||||
a = np.asarray(a)
|
||||
if not isinstance(b, np.ndarray):
|
||||
b = np.asarray(b)
|
||||
if (0 in a.shape) and (0 in b.shape):
|
||||
return 0.0
|
||||
b[a == 0] = 0
|
||||
if (a.dtype in [np.complex64, np.complex128] or
|
||||
b.dtype in [np.complex64, np.complex128]):
|
||||
a = np.asarray(a, np.complex128)
|
||||
b = np.asarray(b, np.complex128)
|
||||
t = ((a.real - b.real)**2).sum() + ((a.imag - b.imag)**2).sum()
|
||||
else:
|
||||
a = np.asarray(a)
|
||||
a = a.astype(np.float64)
|
||||
b = np.asarray(b)
|
||||
b = b.astype(np.float64)
|
||||
t = ((a - b)**2).sum()
|
||||
return math.sqrt(t)
|
||||
|
||||
|
||||
class TestWatershed(unittest.TestCase):
|
||||
eight = np.ones((3, 3), bool)
|
||||
|
||||
def test_watershed01(self):
|
||||
"watershed 1"
|
||||
data = 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, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = np.array([[ -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, 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]],
|
||||
np.int8)
|
||||
out = watershed(data, markers, self.eight)
|
||||
expected = np.array([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1]])
|
||||
error = diff(expected, out)
|
||||
assert error < eps
|
||||
|
||||
def test_watershed02(self):
|
||||
"watershed 2"
|
||||
data = 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, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = np.array([[-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, 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]], np.int8)
|
||||
out = watershed(data, markers)
|
||||
error = diff([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, 1, 1, 1, -1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, -1, 1, 1, 1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1]], out)
|
||||
self.assertTrue(error < eps)
|
||||
|
||||
def test_watershed03(self):
|
||||
"watershed 3"
|
||||
data = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 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],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = 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, 2, 0, 3, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 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]], np.int8)
|
||||
out = watershed(data, markers)
|
||||
error = diff([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, 0, 2, 0, 3, 0, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 0, 2, 0, 3, 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]], out)
|
||||
self.assertTrue(error < eps)
|
||||
|
||||
def test_watershed04(self):
|
||||
"watershed 4"
|
||||
data = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 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],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = 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, 2, 0, 3, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 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]], np.int8)
|
||||
out = watershed(data, markers, self.eight)
|
||||
error = diff([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, 2, 2, 0, 3, 3, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1]], out)
|
||||
self.assertTrue(error < eps)
|
||||
|
||||
def test_watershed05(self):
|
||||
"watershed 5"
|
||||
data = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 1, 1, 1, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 1, 0, 1, 0, 1, 0],
|
||||
[0, 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],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = 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, 3, 0, 2, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, -1]], np.int8)
|
||||
out = watershed(data, markers, self.eight)
|
||||
error = diff([[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, 3, 3, 0, 2, 2, -1],
|
||||
[-1, 3, 3, 0, 2, 2, -1],
|
||||
[-1, 3, 3, 0, 2, 2, -1],
|
||||
[-1, 3, 3, 0, 2, 2, -1],
|
||||
[-1, 3, 3, 0, 2, 2, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1]], out)
|
||||
self.assertTrue(error < eps)
|
||||
|
||||
def test_watershed06(self):
|
||||
"watershed 6"
|
||||
data = np.array([[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 1, 0, 0, 0, 1, 0],
|
||||
[0, 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],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0]], np.uint8)
|
||||
markers = np.array([[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0],
|
||||
[-1, 0, 0, 0, 0, 0, 0]], np.int8)
|
||||
out = watershed(data, markers, self.eight)
|
||||
error = diff([[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, 1, 1, 1, 1, 1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1],
|
||||
[-1, -1, -1, -1, -1, -1, -1]], out)
|
||||
self.assertTrue(error < eps)
|
||||
|
||||
def test_watershed07(self):
|
||||
"A regression test of a competitive case that failed"
|
||||
data = blob
|
||||
mask = (data != 255)
|
||||
markers = np.zeros(data.shape, int)
|
||||
markers[6, 7] = 1
|
||||
markers[14, 7] = 2
|
||||
out = watershed(data, markers, self.eight, mask=mask)
|
||||
#
|
||||
# The two objects should be the same size, except possibly for the
|
||||
# border region
|
||||
#
|
||||
size1 = np.sum(out == 1)
|
||||
size2 = np.sum(out == 2)
|
||||
self.assertTrue(abs(size1 - size2) <= 6)
|
||||
|
||||
def test_watershed08(self):
|
||||
"The border pixels + an edge are all the same value"
|
||||
data = blob.copy()
|
||||
data[10, 7:9] = 141
|
||||
mask = (data != 255)
|
||||
markers = np.zeros(data.shape, int)
|
||||
markers[6, 7] = 1
|
||||
markers[14, 7] = 2
|
||||
out = watershed(data, markers, self.eight, mask=mask)
|
||||
#
|
||||
# The two objects should be the same size, except possibly for the
|
||||
# border region
|
||||
#
|
||||
size1 = np.sum(out == 1)
|
||||
size2 = np.sum(out == 2)
|
||||
self.assertTrue(abs(size1 - size2) <= 6)
|
||||
|
||||
def test_watershed09(self):
|
||||
"""Test on an image of reasonable size
|
||||
|
||||
This is here both for timing (does it take forever?) and to
|
||||
ensure that the memory constraints are reasonable
|
||||
"""
|
||||
image = np.zeros((1000, 1000))
|
||||
coords = np.random.uniform(0, 1000, (100, 2)).astype(int)
|
||||
markers = np.zeros((1000, 1000), int)
|
||||
idx = 1
|
||||
for x, y in coords:
|
||||
image[x, y] = 1
|
||||
markers[x, y] = idx
|
||||
idx += 1
|
||||
|
||||
image = gaussian(image, 4, mode='reflect')
|
||||
watershed(image, markers, self.eight)
|
||||
ndi.watershed_ift(image.astype(np.uint16), markers, self.eight)
|
||||
|
||||
def test_watershed10(self):
|
||||
"watershed 10"
|
||||
data = np.array([[1, 1, 1, 1],
|
||||
[1, 1, 1, 1],
|
||||
[1, 1, 1, 1],
|
||||
[1, 1, 1, 1]], np.uint8)
|
||||
markers = np.array([[1, 0, 0, 2],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[3, 0, 0, 4]], np.int8)
|
||||
out = watershed(data, markers, self.eight)
|
||||
error = diff([[1, 1, 2, 2],
|
||||
[1, 1, 2, 2],
|
||||
[3, 3, 4, 4],
|
||||
[3, 3, 4, 4]], out)
|
||||
self.assertTrue(error < eps)
|
||||
|
||||
def test_watershed11(self):
|
||||
'''Make sure that all points on this plateau are assigned to closest seed'''
|
||||
# https://github.com/scikit-image/scikit-image/issues/803
|
||||
#
|
||||
# Make sure that no point in a level image is farther away
|
||||
# from its seed than any other
|
||||
#
|
||||
image = np.zeros((21, 21))
|
||||
markers = np.zeros((21, 21), int)
|
||||
markers[5, 5] = 1
|
||||
markers[5, 10] = 2
|
||||
markers[10, 5] = 3
|
||||
markers[10, 10] = 4
|
||||
|
||||
structure = np.array([[False, True, False],
|
||||
[True, True, True],
|
||||
[False, True, False]])
|
||||
out = watershed(image, markers, structure)
|
||||
i, j = np.mgrid[0:21, 0:21]
|
||||
d = np.dstack(
|
||||
[np.sqrt((i.astype(float)-i0)**2, (j.astype(float)-j0)**2)
|
||||
for i0, j0 in ((5, 5), (5, 10), (10, 5), (10, 10))])
|
||||
dmin = np.min(d, 2)
|
||||
self.assertTrue(np.all(d[i, j, out[i, j]-1] == dmin))
|
||||
|
||||
|
||||
def test_watershed12(self):
|
||||
"The watershed line"
|
||||
data = np.array([[203, 255, 203, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153],
|
||||
[203, 255, 203, 153, 153, 153, 102, 102, 102, 102, 102, 102, 153, 153, 153, 153],
|
||||
[203, 255, 203, 203, 153, 153, 102, 102, 77, 0, 102, 102, 153, 153, 203, 203],
|
||||
[203, 255, 255, 203, 153, 153, 153, 102, 102, 102, 102, 153, 153, 203, 203, 255],
|
||||
[203, 203, 255, 203, 203, 203, 153, 153, 153, 153, 153, 153, 203, 203, 255, 255],
|
||||
[153, 203, 255, 255, 255, 203, 203, 203, 203, 203, 203, 203, 203, 255, 255, 203],
|
||||
[153, 203, 203, 203, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 203, 203],
|
||||
[153, 153, 153, 203, 203, 203, 203, 203, 255, 203, 203, 203, 203, 203, 203, 153],
|
||||
[102, 102, 153, 153, 153, 153, 203, 203, 255, 203, 203, 255, 203, 153, 153, 153],
|
||||
[102, 102, 102, 102, 102, 153, 203, 255, 255, 203, 203, 203, 203, 153, 102, 153],
|
||||
[102, 51, 51, 102, 102, 153, 203, 255, 203, 203, 153, 153, 153, 153, 102, 153],
|
||||
[ 77, 51, 51, 102, 153, 153, 203, 255, 203, 203, 203, 153, 102, 102, 102, 153],
|
||||
[ 77, 0, 51, 102, 153, 203, 203, 255, 203, 255, 203, 153, 102, 51, 102, 153],
|
||||
[ 77, 0, 51, 102, 153, 203, 255, 255, 203, 203, 203, 153, 102, 0, 102, 153],
|
||||
[102, 0, 51, 102, 153, 203, 255, 203, 203, 153, 153, 153, 102, 102, 102, 153],
|
||||
[102, 102, 102, 102, 153, 203, 255, 203, 153, 153, 153, 153, 153, 153, 153, 153]])
|
||||
markerbin = (data==0)
|
||||
marker = label(markerbin)
|
||||
ws = watershed(data, marker, connectivity=2, watershed_line=True)
|
||||
for lab, area in zip(range(4), [34,74,74,74]):
|
||||
self.assertTrue(np.sum(ws == lab) == area)
|
||||
|
||||
|
||||
|
||||
def test_compact_watershed():
|
||||
image = np.zeros((5, 6))
|
||||
image[:, 3:] = 1
|
||||
seeds = np.zeros((5, 6), dtype=int)
|
||||
seeds[2, 0] = 1
|
||||
seeds[2, 3] = 2
|
||||
compact = watershed(image, seeds, compactness=0.01)
|
||||
expected = np.array([[1, 1, 1, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2],
|
||||
[1, 1, 1, 2, 2, 2]], dtype=int)
|
||||
np.testing.assert_equal(compact, expected)
|
||||
normal = watershed(image, seeds)
|
||||
expected = np.ones(image.shape, dtype=int)
|
||||
expected[2, 3:] = 2
|
||||
np.testing.assert_equal(normal, expected)
|
||||
|
||||
|
||||
def test_numeric_seed_watershed():
|
||||
"""Test that passing just the number of seeds to watershed works."""
|
||||
image = np.zeros((5, 6))
|
||||
image[:, 3:] = 1
|
||||
compact = watershed(image, 2, compactness=0.01)
|
||||
expected = np.array([[1, 1, 1, 1, 2, 2],
|
||||
[1, 1, 1, 1, 2, 2],
|
||||
[1, 1, 1, 1, 2, 2],
|
||||
[1, 1, 1, 1, 2, 2],
|
||||
[1, 1, 1, 1, 2, 2]], dtype=np.int32)
|
||||
np.testing.assert_equal(compact, expected)
|
||||
|
||||
|
||||
def test_incorrect_markers_shape():
|
||||
image = np.ones((5, 6))
|
||||
markers = np.ones((5, 7))
|
||||
with pytest.raises(ValueError):
|
||||
watershed(image, markers)
|
||||
|
||||
|
||||
def test_incorrect_mask_shape():
|
||||
image = np.ones((5, 6))
|
||||
mask = np.ones((5, 7))
|
||||
with pytest.raises(ValueError):
|
||||
watershed(image, markers=4, mask=mask)
|
||||
|
||||
|
||||
def test_markers_in_mask():
|
||||
data = blob
|
||||
mask = (data != 255)
|
||||
out = watershed(data, 25, connectivity=2, mask=mask)
|
||||
# There should be no markers where the mask is false
|
||||
assert np.all(out[~mask] == 0)
|
||||
|
||||
|
||||
def test_no_markers():
|
||||
data = blob
|
||||
mask = (data != 255)
|
||||
out = watershed(data, mask=mask)
|
||||
assert np.max(out) == 2
|
||||
|
||||
|
||||
def test_connectivity():
|
||||
"""
|
||||
Watershed segmentation should output different result for
|
||||
different connectivity
|
||||
when markers are calculated where None is supplied.
|
||||
Issue = 5084
|
||||
"""
|
||||
# Generate a dummy BrightnessTemperature image
|
||||
x, y = np.indices((406, 270))
|
||||
x1, y1, x2, y2, x3, y3, x4, y4 = 200, 208, 300, 120, 100, 100, 340, 208
|
||||
r1, r2, r3, r4 = 100, 50, 40, 80
|
||||
mask_circle1 = (x - x1)**2 + (y - y1)**2 < r1**2
|
||||
mask_circle2 = (x - x2)**2 + (y - y2)**2 < r2**2
|
||||
mask_circle3 = (x - x3)**2 + (y - y3)**2 < r3**2
|
||||
mask_circle4 = (x - x4)**2 + (y - y4)**2 < r4**2
|
||||
image = np.logical_or(mask_circle1, mask_circle2)
|
||||
image = np.logical_or(image, mask_circle3)
|
||||
image = np.logical_or(image, mask_circle4)
|
||||
|
||||
# calculate distance in discrete increase
|
||||
DummyBT = ndi.distance_transform_edt(image)
|
||||
DummyBT_dis = np.around(DummyBT / 12, decimals = 0)*12
|
||||
# calculate the mask
|
||||
Img_mask = np.where(DummyBT_dis == 0, 0, 1)
|
||||
|
||||
# segments for connectivity 1 and 2
|
||||
labels_c1 = watershed(200 - DummyBT_dis, mask=Img_mask, connectivity=1,
|
||||
compactness=0.01)
|
||||
labels_c2 = watershed(200 - DummyBT_dis, mask=Img_mask, connectivity=2,
|
||||
compactness=0.01)
|
||||
|
||||
# assertions
|
||||
assert np.unique(labels_c1).shape[0] == 6
|
||||
assert np.unique(labels_c2).shape[0] == 5
|
||||
|
||||
# checking via area of each individual segment.
|
||||
for lab, area in zip(range(6), [61824, 3653, 20467, 11097, 1301, 11278]):
|
||||
assert np.sum(labels_c1 == lab) == area
|
||||
|
||||
for lab, area in zip(range(5), [61824, 3653, 20466, 12386, 11291]):
|
||||
assert np.sum(labels_c2 == lab) == area
|
||||
Reference in New Issue
Block a user