Fixed database typo and removed unnecessary class identifier.
This commit is contained in:
parent
00ad49a143
commit
45fb349a7d
5098 changed files with 952558 additions and 85 deletions
38
venv/Lib/site-packages/skimage/segmentation/__init__.py
Normal file
38
venv/Lib/site-packages/skimage/segmentation/__init__.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
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,
|
||||
circle_level_set,
|
||||
disk_level_set, checkerboard_level_set)
|
||||
from ..morphology import flood, flood_fill
|
||||
|
||||
|
||||
__all__ = ['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',
|
||||
'circle_level_set',
|
||||
'disk_level_set',
|
||||
'checkerboard_level_set',
|
||||
'flood',
|
||||
'flood_fill',
|
||||
]
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
338
venv/Lib/site-packages/skimage/segmentation/_chan_vese.py
Normal file
338
venv/Lib/site-packages/skimage/segmentation/_chan_vese.py
Normal file
|
@ -0,0 +1,338 @@
|
|||
import numpy as np
|
||||
from scipy.ndimage import distance_transform_edt as distance
|
||||
|
||||
|
||||
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):
|
||||
"""Generates a checkerboard level set function.
|
||||
|
||||
According to Pascal Getreuer, such a level set function has fast convergence.
|
||||
"""
|
||||
yv = np.arange(image_size[0]).reshape(image_size[0], 1)
|
||||
xv = np.arange(image_size[1])
|
||||
return (np.sin(np.pi/square_size*yv) *
|
||||
np.sin(np.pi/square_size*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):
|
||||
"""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)
|
||||
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
|
||||
|
||||
|
||||
def chan_vese(image, mu=0.25, lambda1=1.0, lambda2=1.0, tol=1e-3, max_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_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.")
|
||||
|
||||
phi = _cv_init_level_set(init_level_set, image.shape)
|
||||
|
||||
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 - 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_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
|
106
venv/Lib/site-packages/skimage/segmentation/_clear_border.py
Normal file
106
venv/Lib/site-packages/skimage/segmentation/_clear_border.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
import numpy as np
|
||||
from ..measure import label
|
||||
|
||||
|
||||
def clear_border(labels, buffer_size=0, bgval=0, in_place=False, mask=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.
|
||||
in_place : bool, optional
|
||||
Whether or not to manipulate the labels array in-place.
|
||||
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.
|
||||
|
||||
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(np.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]])
|
||||
|
||||
"""
|
||||
image = labels
|
||||
|
||||
if any((buffer_size >= s for s in image.shape)) and mask is None:
|
||||
# ignore buffer_size if mask
|
||||
raise ValueError("buffer size may not be greater than image size")
|
||||
|
||||
if mask is not None:
|
||||
err_msg = "image and mask should have the same shape but are {} and {}"
|
||||
assert image.shape == mask.shape, \
|
||||
err_msg.format(image.shape, mask.shape)
|
||||
if mask.dtype != np.bool_:
|
||||
raise TypeError("mask should be of type bool.")
|
||||
borders = ~mask
|
||||
else:
|
||||
# create borders with buffer_size
|
||||
borders = np.zeros_like(image, dtype=np.bool_)
|
||||
ext = buffer_size + 1
|
||||
slstart = slice(ext)
|
||||
slend = slice(-ext, None)
|
||||
slices = [slice(s) for s in image.shape]
|
||||
for d in range(image.ndim):
|
||||
slicedim = list(slices)
|
||||
slicedim[d] = slstart
|
||||
borders[tuple(slicedim)] = True
|
||||
slicedim[d] = slend
|
||||
borders[tuple(slicedim)] = True
|
||||
# Re-label, in case we are dealing with a binary image
|
||||
# and to get consistent labeling
|
||||
labels = label(image, background=0)
|
||||
number = np.max(labels) + 1
|
||||
|
||||
# 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.ravel()].reshape(labels.shape)
|
||||
|
||||
if not in_place:
|
||||
image = image.copy()
|
||||
|
||||
# clear border pixels
|
||||
image[mask] = bgval
|
||||
|
||||
return image
|
64
venv/Lib/site-packages/skimage/segmentation/_felzenszwalb.py
Normal file
64
venv/Lib/site-packages/skimage/segmentation/_felzenszwalb.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import numpy as np
|
||||
|
||||
from ._felzenszwalb_cy import _felzenszwalb_cython
|
||||
|
||||
|
||||
def felzenszwalb(image, scale=1, sigma=0.8, min_size=20, multichannel=True):
|
||||
"""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.
|
||||
multichannel : bool, optional (default: True)
|
||||
Whether the last axis of the image is to be interpreted as multiple
|
||||
channels. A value of False, for a 3D image, is not currently supported.
|
||||
|
||||
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 not multichannel 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)
|
Binary file not shown.
155
venv/Lib/site-packages/skimage/segmentation/_join.py
Normal file
155
venv/Lib/site-packages/skimage/segmentation/_join.py
Normal file
|
@ -0,0 +1,155 @@
|
|||
import numpy as np
|
||||
from .._shared.utils import deprecated
|
||||
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. " +
|
||||
"s1.shape: %s, s2.shape: %s" % (s1.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
|
||||
|
||||
# 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 input_type.type(out_vals[-1]) == out_vals[-1]:
|
||||
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
|
74
venv/Lib/site-packages/skimage/segmentation/_quickshift.py
Normal file
74
venv/Lib/site-packages/skimage/segmentation/_quickshift.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
import numpy as np
|
||||
|
||||
import scipy.ndimage as ndi
|
||||
|
||||
from ..util import img_as_float
|
||||
from ..color import rgb2lab
|
||||
|
||||
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):
|
||||
"""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.
|
||||
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.
|
||||
|
||||
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))
|
||||
if convert2lab:
|
||||
if image.shape[2] != 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 = ndi.gaussian_filter(image, [sigma, sigma, 0])
|
||||
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
|
Binary file not shown.
BIN
venv/Lib/site-packages/skimage/segmentation/_slic.cp36-win32.pyd
Normal file
BIN
venv/Lib/site-packages/skimage/segmentation/_slic.cp36-win32.pyd
Normal file
Binary file not shown.
227
venv/Lib/site-packages/skimage/segmentation/_watershed.py
Normal file
227
venv/Lib/site-packages/skimage/segmentation/_watershed.py
Normal file
|
@ -0,0 +1,227 @@
|
|||
"""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.
|
||||
|
||||
Originally part of CellProfiler, code licensed under both GPL and BSD licenses.
|
||||
Website: http://www.cellprofiler.org
|
||||
|
||||
Copyright (c) 2003-2009 Massachusetts Institute of Technology
|
||||
Copyright (c) 2009-2011 Broad Institute
|
||||
All rights reserved.
|
||||
|
||||
Original author: Lee Kamentsky
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from 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 = ("`mask` (shape {}) must have same shape as "
|
||||
"`image` (shape {})".format(mask.shape, image.shape))
|
||||
raise ValueError(message)
|
||||
if markers is None:
|
||||
markers_bool = local_minima(image, connectivity=connectivity) * mask
|
||||
markers = ndi.label(markers_bool)[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 = ("`markers` (shape {}) must have same shape as "
|
||||
"`image` (shape {})".format(markers.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, ...) of integers
|
||||
Data array where the lowest value points are labeled first.
|
||||
markers : int, or ndarray of int, same shape as `image`, optional
|
||||
The desired number of markers, or an array marking the basins with the
|
||||
values to be assigned in the label matrix. Zero means not a marker. If
|
||||
``None`` (no markers given), the local minima of the image are used as
|
||||
markers.
|
||||
connectivity : ndarray, optional
|
||||
An array with the same number of dimensions as `image` whose
|
||||
non-zero elements indicate neighbors for connection.
|
||||
Following the scipy convention, default is a one-connected array of
|
||||
the dimension of the image.
|
||||
offset : array_like of shape image.ndim, optional
|
||||
offset of the connectivity (one offset per dimension)
|
||||
mask : ndarray of bools or 0s and 1s, optional
|
||||
Array of same shape as `image`. Only points at which mask == True
|
||||
will be labeled.
|
||||
compactness : float, optional
|
||||
Use compact watershed [3]_ with given compactness parameter.
|
||||
Higher values result in more regularly-shaped watershed basins.
|
||||
watershed_line : bool, optional
|
||||
If watershed_line is True, a one-pixel wide line separates the regions
|
||||
obtained by the watershed algorithm. The line has the label 0.
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : ndarray
|
||||
A labeled matrix of the same type and shape as markers
|
||||
|
||||
See also
|
||||
--------
|
||||
skimage.segmentation.random_walker: random walker segmentation
|
||||
A segmentation algorithm based on anisotropic diffusion, usually
|
||||
slower than the watershed but with good results on noisy data and
|
||||
boundaries with holes.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function implements a watershed algorithm [1]_ [2]_ that apportions
|
||||
pixels into marked basins. The algorithm uses a priority queue to hold
|
||||
the pixels with the metric for the priority queue being pixel value, then
|
||||
the time of entry into the queue - this settles ties in favor of the
|
||||
closest marker.
|
||||
|
||||
Some ideas taken from
|
||||
Soille, "Automated Basin Delineation from Digital Elevation Models Using
|
||||
Mathematical Morphology", Signal Processing 20 (1990) 171-182
|
||||
|
||||
The most important insight in the paper is that entry time onto the queue
|
||||
solves two problems: a pixel should be assigned to the neighbor with the
|
||||
largest gradient or, if there is no gradient, pixels on a plateau should
|
||||
be split between markers on opposite sides.
|
||||
|
||||
This implementation converts all arguments to specific, lowest common
|
||||
denominator types, then passes these to a C algorithm.
|
||||
|
||||
Markers can be determined manually, or automatically using for example
|
||||
the local minima of the gradient of the image, or the local maxima of the
|
||||
distance function to the background for separating overlapping objects
|
||||
(see example).
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] https://en.wikipedia.org/wiki/Watershed_%28image_processing%29
|
||||
|
||||
.. [2] http://cmm.ensmp.fr/~beucher/wtshed.html
|
||||
|
||||
.. [3] Peer Neubert & Peter Protzel (2014). Compact Watershed and
|
||||
Preemptive SLIC: On Improving Trade-offs of Superpixel Segmentation
|
||||
Algorithms. ICPR 2014, pp 996-1001. :DOI:`10.1109/ICPR.2014.181`
|
||||
https://www.tu-chemnitz.de/etit/proaut/publications/cws_pSLIC_ICPR.pdf
|
||||
|
||||
Examples
|
||||
--------
|
||||
The watershed algorithm is useful to separate overlapping objects.
|
||||
|
||||
We first generate an initial image with two overlapping circles:
|
||||
|
||||
>>> x, y = np.indices((80, 80))
|
||||
>>> x1, y1, x2, y2 = 28, 28, 44, 52
|
||||
>>> r1, r2 = 16, 20
|
||||
>>> mask_circle1 = (x - x1)**2 + (y - y1)**2 < r1**2
|
||||
>>> mask_circle2 = (x - x2)**2 + (y - y2)**2 < r2**2
|
||||
>>> image = np.logical_or(mask_circle1, mask_circle2)
|
||||
|
||||
Next, we want to separate the two circles. We generate markers at the
|
||||
maxima of the distance to the background:
|
||||
|
||||
>>> from scipy import ndimage as ndi
|
||||
>>> distance = ndi.distance_transform_edt(image)
|
||||
>>> from skimage.feature import peak_local_max
|
||||
>>> local_maxi = peak_local_max(distance, labels=image,
|
||||
... footprint=np.ones((3, 3)),
|
||||
... indices=False)
|
||||
>>> markers = ndi.label(local_maxi)[0]
|
||||
|
||||
Finally, we run the watershed on the image and markers:
|
||||
|
||||
>>> labels = watershed(-distance, markers, mask=image)
|
||||
|
||||
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
|
Binary file not shown.
|
@ -0,0 +1,245 @@
|
|||
from warnings import warn
|
||||
import numpy as np
|
||||
from scipy.interpolate import RectBivariateSpline
|
||||
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,
|
||||
bc=None, max_px_move=1.0,
|
||||
max_iterations=2500, convergence=0.1,
|
||||
*,
|
||||
boundary_condition='periodic',
|
||||
coordinates=None):
|
||||
"""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.
|
||||
bc : deprecated; use ``boundary_condition``
|
||||
DEPRECATED. See ``boundary_condition`` below.
|
||||
max_px_move : float, optional
|
||||
Maximum pixel distance to move per iteration.
|
||||
max_iterations : 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.
|
||||
coordinates : {'rc' or 'xy'}, optional
|
||||
Whether to use rc or xy coordinates. The 'xy' option (current default)
|
||||
will be removed in version 0.18.
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
if bc is not None:
|
||||
message = ('The keyword argument `bc` to `active_contour` has been '
|
||||
'renamed. Use `boundary_condition=` instead. `bc` will be '
|
||||
'removed in scikit-image v0.18.')
|
||||
warn(message, stacklevel=2)
|
||||
boundary_condition = bc
|
||||
if coordinates is None:
|
||||
message = ('The coordinates used by `active_contour` will change '
|
||||
'from xy coordinates (transposed from image dimensions) to '
|
||||
'rc coordinates in scikit-image 0.18. Set '
|
||||
"`coordinates='rc'` to silence this warning. "
|
||||
"`coordinates='xy'` will restore the old behavior until "
|
||||
'0.18, but will stop working thereafter.')
|
||||
warn(message, category=FutureWarning, stacklevel=2)
|
||||
coordinates = 'xy'
|
||||
snake_xy = snake
|
||||
if coordinates == 'rc':
|
||||
snake_xy = snake[:, ::-1]
|
||||
max_iterations = int(max_iterations)
|
||||
if max_iterations <= 0:
|
||||
raise ValueError("max_iterations 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)
|
||||
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)
|
||||
|
||||
x, y = snake_xy[:, 0].astype(np.float), snake_xy[:, 1].astype(np.float)
|
||||
n = len(x)
|
||||
xsave = np.empty((convergence_order, n))
|
||||
ysave = np.empty((convergence_order, n))
|
||||
|
||||
# Build snake shape matrix for Euler equation
|
||||
a = np.roll(np.eye(n), -1, axis=0) + \
|
||||
np.roll(np.eye(n), -1, axis=1) - \
|
||||
2*np.eye(n) # second order derivative, central difference
|
||||
b = np.roll(np.eye(n), -2, axis=0) + \
|
||||
np.roll(np.eye(n), -2, axis=1) - \
|
||||
4*np.roll(np.eye(n), -1, axis=0) - \
|
||||
4*np.roll(np.eye(n), -1, axis=1) + \
|
||||
6*np.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*np.eye(n))
|
||||
|
||||
# Explicit time stepping for image energy minimization:
|
||||
for i in range(max_iterations):
|
||||
fx = intp(x, y, dx=1, grid=False)
|
||||
fy = intp(x, y, dy=1, grid=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
|
||||
|
||||
if coordinates == 'xy':
|
||||
return np.stack([x, y], axis=1)
|
||||
else:
|
||||
return np.stack([y, x], axis=1)
|
230
venv/Lib/site-packages/skimage/segmentation/boundaries.py
Normal file
230
venv/Lib/site-packages/skimage/segmentation/boundaries.py
Normal file
|
@ -0,0 +1,230 @@
|
|||
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
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='constant', constant_values=0),
|
||||
(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=np.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
|
||||
selem = ndi.generate_binary_structure(ndim, connectivity)
|
||||
if mode != 'subpixel':
|
||||
boundaries = dilation(label_img, selem) != erosion(label_img, selem)
|
||||
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)
|
||||
selem = 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, selem) !=
|
||||
erosion(inverted_background, selem)) &
|
||||
~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
|
||||
"""
|
||||
marked = img_as_float(image, force_copy=True)
|
||||
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='reflect')
|
||||
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
|
484
venv/Lib/site-packages/skimage/segmentation/morphsnakes.py
Normal file
484
venv/Lib/site-packages/skimage/segmentation/morphsnakes.py
Normal file
|
@ -0,0 +1,484 @@
|
|||
import warnings
|
||||
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',
|
||||
'circle_level_set',
|
||||
'disk_level_set',
|
||||
'checkerboard_level_set'
|
||||
]
|
||||
|
||||
|
||||
class _fcycle(object):
|
||||
|
||||
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))
|
||||
|
||||
return np.array(erosions, dtype=np.int8).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))
|
||||
|
||||
return np.array(dilations, dtype=np.int8).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)
|
||||
# TODO: remove me in 0.19.0
|
||||
elif init_level_set == 'circle':
|
||||
res = circle_level_set(image_shape)
|
||||
elif init_level_set == 'disk':
|
||||
res = disk_level_set(image_shape)
|
||||
else:
|
||||
raise ValueError("`init_level_set` not in "
|
||||
"['checkerboard', 'circle', 'disk']")
|
||||
else:
|
||||
res = init_level_set
|
||||
return res
|
||||
|
||||
|
||||
def circle_level_set(image_shape, center=None, radius=None):
|
||||
"""Create a circle 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 circle given in (row, column). If not
|
||||
given, it defaults to the center of the image.
|
||||
radius : float, optional
|
||||
Radius of the circle. 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 circle with the given `radius` and `center`.
|
||||
|
||||
Warns
|
||||
-----
|
||||
Deprecated:
|
||||
.. versionadded:: 0.17
|
||||
|
||||
This function is deprecated and will be removed in scikit-image 0.19.
|
||||
Please use the function named ``disk_level_set`` instead.
|
||||
|
||||
See also
|
||||
--------
|
||||
checkerboard_level_set
|
||||
"""
|
||||
warnings.warn("circle_level_set is deprecated in favor of "
|
||||
"disk_level_set."
|
||||
"circle_level_set will be removed in version 0.19",
|
||||
FutureWarning, stacklevel=2)
|
||||
|
||||
return disk_level_set(image_shape, center=center, radius=radius)
|
||||
|
||||
|
||||
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
|
||||
--------
|
||||
circle_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, iterations, 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.
|
||||
iterations : uint
|
||||
Number of iterations 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 'circle'. See the
|
||||
documentation of `checkerboard_level_set` and `circle_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
|
||||
--------
|
||||
circle_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(iterations):
|
||||
|
||||
# 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, iterations,
|
||||
init_level_set='circle', 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.
|
||||
iterations : uint
|
||||
Number of iterations 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 'circle'. See the
|
||||
documentation of `checkerboard_level_set` and `circle_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, circle_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(iterations):
|
||||
|
||||
# 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
|
|
@ -0,0 +1,505 @@
|
|||
"""
|
||||
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.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 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
|
||||
import scipy
|
||||
from distutils.version import LooseVersion as Version
|
||||
import functools
|
||||
|
||||
if Version(scipy.__version__) >= Version('1.1'):
|
||||
cg = functools.partial(cg, atol=0)
|
||||
|
||||
|
||||
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 = edges.shape[1]
|
||||
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)
|
||||
M = ml.aspreconditioner(cycle='V')
|
||||
maxiter = 30
|
||||
cg_out = [
|
||||
cg(lap_sparse, B[:, i].toarray(), tol=tol, 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 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
|
||||
|
||||
|
||||
def random_walker(data, labels, beta=130, mode='cg_j', tol=1.e-3, copy=True,
|
||||
multichannel=False, return_full_prob=False, spacing=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 (multichannel=True) with the highest dimension denoting
|
||||
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 applyed 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.
|
||||
multichannel : bool, optional
|
||||
If True, input data is parsed as multichannel data (see 'data' above
|
||||
for proper input format in this case).
|
||||
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.
|
||||
|
||||
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
|
||||
--------
|
||||
>>> np.random.seed(0)
|
||||
>>> a = np.zeros((10, 10)) + 0.2 * np.random.rand(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)
|
||||
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(
|
||||
"{mode} is not a valid mode. Valid modes are 'cg_mg',"
|
||||
" 'cg', 'cg_j', 'bf' and None".format(mode=mode))
|
||||
|
||||
# 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.
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
if return_full_prob:
|
||||
mask = labels == 0
|
||||
|
||||
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[labels == 0] = X
|
||||
|
||||
return out
|
38
venv/Lib/site-packages/skimage/segmentation/setup.py
Normal file
38
venv/Lib/site-packages/skimage/segmentation/setup.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
from skimage._build import cython
|
||||
|
||||
base_path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def configuration(parent_package='', top_path=None):
|
||||
from numpy.distutils.misc_util import Configuration, get_numpy_include_dirs
|
||||
|
||||
config = Configuration('segmentation', parent_package, top_path)
|
||||
|
||||
cython(['_watershed_cy.pyx',
|
||||
'_felzenszwalb_cy.pyx',
|
||||
'_quickshift_cy.pyx',
|
||||
'_slic.pyx',
|
||||
], working_path=base_path)
|
||||
config.add_extension('_watershed_cy', sources=['_watershed_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_felzenszwalb_cy', sources=['_felzenszwalb_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_quickshift_cy', sources=['_quickshift_cy.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
config.add_extension('_slic', sources=['_slic.c'],
|
||||
include_dirs=[get_numpy_include_dirs()])
|
||||
|
||||
return config
|
||||
|
||||
if __name__ == '__main__':
|
||||
from numpy.distutils.core import setup
|
||||
setup(maintainer='scikit-image Developers',
|
||||
maintainer_email='scikit-image@python.org',
|
||||
description='Segmentation Algorithms',
|
||||
url='https://github.com/scikit-image/scikit-image',
|
||||
license='SciPy License (BSD Style)',
|
||||
**(configuration(top_path='').todict())
|
||||
)
|
307
venv/Lib/site-packages/skimage/segmentation/slic_superpixels.py
Normal file
307
venv/Lib/site-packages/skimage/segmentation/slic_superpixels.py
Normal file
|
@ -0,0 +1,307 @@
|
|||
import warnings
|
||||
import functools
|
||||
import collections as coll
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
from scipy.spatial.distance import pdist, squareform
|
||||
from scipy.cluster.vq import kmeans2
|
||||
from numpy import random
|
||||
|
||||
from ._slic import (_slic_cython, _enforce_label_connectivity_cython)
|
||||
from ..util import img_as_float, regular_grid
|
||||
from ..color import rgb2lab
|
||||
|
||||
|
||||
def _get_mask_centroids(mask, n_centroids):
|
||||
"""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
|
||||
rnd = random.RandomState(123)
|
||||
idx = np.sort(rnd.choice(np.arange(len(coord), dtype=int),
|
||||
min(n_centroids, len(coord)),
|
||||
replace=False))
|
||||
centroids, _ = kmeans2(coord, coord[idx])
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
def slic(image, n_segments=100, compactness=10., max_iter=10, sigma=0,
|
||||
spacing=None, multichannel=True, convert2lab=None,
|
||||
enforce_connectivity=True, min_size_factor=0.5, max_size_factor=3,
|
||||
slic_zero=False, start_label=None, mask=None):
|
||||
"""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 `multichannel` parameter).
|
||||
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_iter : int, optional
|
||||
Maximum number of iterations of k-means.
|
||||
sigma : float or (3,) 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 a
|
||||
manual voxel spacing is provided (see Notes section).
|
||||
spacing : (3,) array-like of floats, optional
|
||||
The voxel spacing along each image dimension. By default, `slic`
|
||||
assumes uniform spacing (same voxel resolution along z, y and x).
|
||||
This parameter controls the weights of the distances along z, y,
|
||||
and x during k-means clustering.
|
||||
multichannel : bool, optional
|
||||
Whether the last axis of the image is to be interpreted as multiple
|
||||
channels or another spatial dimension.
|
||||
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 ``multichannel=True`` *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.
|
||||
mask : 2D 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
* 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
|
||||
`multichannel=False`.
|
||||
|
||||
* `start_label` is introduced to handle the issue [4]_. The labels
|
||||
indexing starting at 0 will be deprecated in future versions. If
|
||||
`mask` is not `None` labels indexing starts at 1 and masked area
|
||||
is set to 0.
|
||||
|
||||
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)
|
||||
|
||||
"""
|
||||
|
||||
image = img_as_float(image)
|
||||
use_mask = mask is not None
|
||||
dtype = image.dtype
|
||||
|
||||
is_2d = False
|
||||
|
||||
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[-1] != 3 and convert2lab:
|
||||
raise ValueError("Lab colorspace conversion requires a RGB image.")
|
||||
elif image.shape[-1] == 3:
|
||||
image = rgb2lab(image)
|
||||
|
||||
if start_label is None:
|
||||
if use_mask:
|
||||
start_label = 1
|
||||
else:
|
||||
warnings.warn("skimage.measure.label's indexing starts from 0. " +
|
||||
"In future version it will start from 1. " +
|
||||
"To disable this warning, explicitely " +
|
||||
"set the `start_label` parameter to 1.",
|
||||
FutureWarning, stacklevel=2)
|
||||
start_label = 0
|
||||
|
||||
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 = np.ascontiguousarray(mask, dtype=np.bool).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)
|
||||
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, (list, tuple)):
|
||||
spacing = np.ascontiguousarray(spacing, dtype=dtype)
|
||||
|
||||
if not isinstance(sigma, coll.Iterable):
|
||||
sigma = np.array([sigma, sigma, sigma], dtype=dtype)
|
||||
sigma /= spacing.astype(dtype)
|
||||
elif isinstance(sigma, (list, tuple)):
|
||||
sigma = np.array(sigma, dtype=dtype)
|
||||
if (sigma > 0).any():
|
||||
# add zero smoothing for multichannel dimension
|
||||
sigma = list(sigma) + [0]
|
||||
image = ndi.gaussian_filter(image, sigma)
|
||||
|
||||
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_iter, spacing,
|
||||
slic_zero, ignore_color=True,
|
||||
start_label=start_label)
|
||||
|
||||
labels = _slic_cython(image, mask, segments, step, max_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 = np.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,0 +1,9 @@
|
|||
from ..._shared.testing import setup_test, teardown_test
|
||||
|
||||
|
||||
def setup():
|
||||
setup_test()
|
||||
|
||||
|
||||
def teardown():
|
||||
teardown_test()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,146 @@
|
|||
import numpy as np
|
||||
from skimage import data
|
||||
from skimage.color import rgb2gray
|
||||
from skimage.filters import gaussian
|
||||
from skimage.segmentation import active_contour
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_equal, assert_allclose
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
def test_periodic_reference():
|
||||
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, alpha=0.015, beta=10,
|
||||
w_line=0, w_edge=1, gamma=0.001, coordinates='rc')
|
||||
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)
|
||||
|
||||
|
||||
def test_fixed_reference():
|
||||
img = data.text()
|
||||
r = np.linspace(136, 50, 100)
|
||||
c = np.linspace(5, 424, 100)
|
||||
init = np.array([r, c]).T
|
||||
snake = active_contour(gaussian(img, 1), init, boundary_condition='fixed',
|
||||
alpha=0.1, beta=1.0, w_line=-5, w_edge=0, gamma=0.1,
|
||||
coordinates='rc')
|
||||
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)
|
||||
|
||||
|
||||
def test_free_reference():
|
||||
img = data.text()
|
||||
r = np.linspace(70, 40, 100)
|
||||
c = np.linspace(5, 424, 100)
|
||||
init = np.array([r, c]).T
|
||||
snake = active_contour(gaussian(img, 3), init, boundary_condition='free',
|
||||
alpha=0.1, beta=1.0, w_line=-5, w_edge=0, gamma=0.1,
|
||||
coordinates='rc')
|
||||
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)
|
||||
|
||||
|
||||
def test_RGB():
|
||||
img = gaussian(data.text(), 1)
|
||||
imgR = np.zeros((img.shape[0], img.shape[1], 3))
|
||||
imgG = np.zeros((img.shape[0], img.shape[1], 3))
|
||||
imgRGB = np.zeros((img.shape[0], img.shape[1], 3))
|
||||
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,
|
||||
coordinates='rc')
|
||||
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,
|
||||
coordinates='rc')
|
||||
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, coordinates='rc')
|
||||
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_iterations=100,
|
||||
coordinates='rc')
|
||||
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_iterations=100,
|
||||
coordinates='rc')
|
||||
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_iterations=100,
|
||||
coordinates='rc')
|
||||
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 testing.raises(ValueError):
|
||||
active_contour(img, init, boundary_condition='wrong',
|
||||
coordinates='rc')
|
||||
with testing.raises(ValueError):
|
||||
active_contour(img, init, max_iterations=-15,
|
||||
coordinates='rc')
|
||||
|
||||
|
||||
def test_bc_deprecation():
|
||||
with expected_warnings(['boundary_condition']):
|
||||
img = rgb2gray(data.astronaut())
|
||||
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,
|
||||
bc='periodic', alpha=0.015, beta=10,
|
||||
w_line=0, w_edge=1, gamma=0.001,
|
||||
max_iterations=100, coordinates='rc')
|
||||
|
||||
|
||||
def test_xy_coord_warning():
|
||||
# this should raise ValueError after 0.18.
|
||||
with expected_warnings(['xy coordinates']):
|
||||
img = rgb2gray(data.astronaut())
|
||||
s = np.linspace(0, 2*np.pi, 400)
|
||||
x = 100 + 100*np.sin(s)
|
||||
y = 220 + 100*np.cos(s)
|
||||
init = np.array([x, y]).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_iterations=100)
|
|
@ -0,0 +1,120 @@
|
|||
import numpy as np
|
||||
from skimage.segmentation import find_boundaries, mark_boundaries
|
||||
|
||||
from skimage._shared.testing import assert_array_equal, assert_allclose
|
||||
|
||||
|
||||
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=np.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=np.bool)
|
||||
result = find_boundaries(image)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
def test_mark_boundaries():
|
||||
image = np.zeros((10, 10))
|
||||
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)
|
||||
|
||||
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=np.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)
|
||||
|
||||
|
||||
def test_mark_boundaries_subpixel():
|
||||
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)
|
||||
marked = mark_boundaries(image, labels, color=white, mode='subpixel')
|
||||
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)
|
|
@ -0,0 +1,90 @@
|
|||
import numpy as np
|
||||
from skimage.segmentation import chan_vese
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
|
||||
|
||||
def test_chan_vese_flat_level_set():
|
||||
# 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))
|
||||
img[3:6, 3:6] = np.ones((3, 3))
|
||||
ls = np.full((10, 10), 1000)
|
||||
result = chan_vese(img, mu=0.0, tol=1e-3, init_level_set=ls)
|
||||
assert_array_equal(result.astype(np.float), np.ones((10, 10)))
|
||||
result = chan_vese(img, mu=0.0, tol=1e-3, init_level_set=-ls)
|
||||
assert_array_equal(result.astype(np.float), np.zeros((10, 10)))
|
||||
|
||||
|
||||
def test_chan_vese_small_disk_level_set():
|
||||
img = np.zeros((10, 10))
|
||||
img[3:6, 3:6] = np.ones((3, 3))
|
||||
result = chan_vese(img, mu=0.0, tol=1e-3, init_level_set="small disk")
|
||||
assert_array_equal(result.astype(np.float), img)
|
||||
|
||||
|
||||
def test_chan_vese_simple_shape():
|
||||
img = np.zeros((10, 10))
|
||||
img[3:6, 3:6] = np.ones((3, 3))
|
||||
result = chan_vese(img, mu=0.0, tol=1e-8).astype(np.float)
|
||||
assert_array_equal(result, img)
|
||||
|
||||
|
||||
def test_chan_vese_extended_output():
|
||||
img = np.zeros((10, 10))
|
||||
img[3:6, 3:6] = np.ones((3, 3))
|
||||
result = chan_vese(img, mu=0.0, tol=1e-8, extended_output=True)
|
||||
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_iter=100, dt=10,
|
||||
init_level_set="disk").astype(np.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 testing.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_iter=1000, dt=1000,
|
||||
init_level_set="disk").astype(np.float)
|
||||
assert_array_equal(result, ref)
|
||||
|
||||
|
||||
def test_chan_vese_incorrect_level_set():
|
||||
img = np.zeros((10, 10))
|
||||
ls = np.zeros((10, 9))
|
||||
with testing.raises(ValueError):
|
||||
chan_vese(img, mu=0.0, init_level_set=ls)
|
||||
with testing.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)
|
|
@ -0,0 +1,175 @@
|
|||
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(np.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, in_place=True)
|
||||
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, in_place=True)
|
||||
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)
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
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)
|
||||
|
||||
|
||||
def test_3D():
|
||||
grey_img = np.zeros((10, 10))
|
||||
rgb_img = np.zeros((10, 10, 3))
|
||||
three_d_img = np.zeros((10, 10, 10))
|
||||
with assert_no_warnings():
|
||||
felzenszwalb(grey_img, multichannel=True)
|
||||
felzenszwalb(grey_img, multichannel=False)
|
||||
felzenszwalb(rgb_img, multichannel=True)
|
||||
with assert_warns(RuntimeWarning):
|
||||
felzenszwalb(three_d_img, multichannel=True)
|
||||
with testing.raises(ValueError):
|
||||
felzenszwalb(rgb_img, multichannel=False)
|
||||
felzenszwalb(three_d_img, multichannel=False)
|
||||
|
||||
|
||||
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
venv/Lib/site-packages/skimage/segmentation/tests/test_join.py
Normal file
211
venv/Lib/site-packages/skimage/segmentation/tests/test_join.py
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, np.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(np.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
|
|
@ -0,0 +1,149 @@
|
|||
import numpy as np
|
||||
from skimage.segmentation import (morphological_chan_vese,
|
||||
morphological_geodesic_active_contour,
|
||||
inverse_gaussian_gradient,
|
||||
circle_level_set,
|
||||
disk_level_set)
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import assert_array_equal
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
|
||||
|
||||
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 testing.raises(ValueError):
|
||||
morphological_chan_vese(img, iterations=1, init_level_set=ls)
|
||||
with testing.raises(ValueError):
|
||||
morphological_geodesic_active_contour(img, iterations=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 testing.raises(ValueError):
|
||||
morphological_chan_vese(img, iterations=1, init_level_set=ls)
|
||||
with testing.raises(ValueError):
|
||||
morphological_geodesic_active_contour(img, iterations=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, iterations=6, init_level_set=ls)
|
||||
assert_array_equal(acwe_ls, ref_zeros)
|
||||
|
||||
gac_ls = morphological_geodesic_active_contour(img, iterations=6,
|
||||
init_level_set=ls)
|
||||
assert_array_equal(gac_ls, ref_zeros)
|
||||
|
||||
gac_ls2 = morphological_geodesic_active_contour(img, iterations=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, iterations=10, init_level_set=ls1)
|
||||
acwe_ls2 = morphological_chan_vese(img, iterations=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 = np.float_(disk_level_set((11, 11), center=(5, 5), radius=3.5))
|
||||
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, iterations=10,
|
||||
init_level_set=ls,
|
||||
balloon=-1)
|
||||
assert_array_equal(gac_ls, ref)
|
||||
assert gac_ls.dtype == np.int8
|
||||
|
||||
|
||||
def test_deprecated_circle_level_set():
|
||||
img = gaussian_blob()
|
||||
with expected_warnings(['circle_level_set is deprecated']):
|
||||
ls1 = circle_level_set(img.shape, (5, 5), 3)
|
||||
|
||||
|
||||
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
|
|
@ -0,0 +1,49 @@
|
|||
import numpy as np
|
||||
from skimage.segmentation import quickshift
|
||||
|
||||
from skimage._shared.testing import (assert_greater, test_parallel,
|
||||
assert_equal, assert_array_equal)
|
||||
|
||||
|
||||
@test_parallel()
|
||||
def test_grey():
|
||||
rnd = np.random.RandomState(0)
|
||||
img = np.zeros((20, 21))
|
||||
img[:10, 10:] = 0.2
|
||||
img[10:, :10] = 0.4
|
||||
img[10:, 10:] = 0.6
|
||||
img += 0.1 * rnd.normal(size=img.shape)
|
||||
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)
|
||||
|
||||
|
||||
def test_color():
|
||||
rnd = np.random.RandomState(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 = quickshift(img, random_seed=0, max_dist=30, kernel_size=10, sigma=0)
|
||||
# we expect 4 segments:
|
||||
assert_equal(len(np.unique(seg)), 4)
|
||||
assert_array_equal(seg[:10, :10], 1)
|
||||
assert_array_equal(seg[10:, :10], 2)
|
||||
assert_array_equal(seg[:10, 10:], 0)
|
||||
assert_array_equal(seg[10:, 10:], 3)
|
||||
|
||||
seg2 = quickshift(img, kernel_size=1, max_dist=2, random_seed=0,
|
||||
convert2lab=False, sigma=0)
|
||||
# very oversegmented:
|
||||
assert_equal(len(np.unique(seg2)), 7)
|
||||
# still don't cross lines
|
||||
assert (seg2[9, :] != seg2[10, :]).all()
|
||||
assert (seg2[:, 9] != seg2[:, 10]).all()
|
|
@ -0,0 +1,439 @@
|
|||
import numpy as np
|
||||
from skimage.segmentation import random_walker
|
||||
from skimage.transform import resize
|
||||
from skimage._shared._warnings import expected_warnings
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import xfail, arch32
|
||||
import scipy
|
||||
from distutils.version import LooseVersion as Version
|
||||
|
||||
|
||||
# older versions of scipy raise a warning with new NumPy because they use
|
||||
# numpy.rank() instead of arr.ndim or numpy.linalg.matrix_rank.
|
||||
SCIPY_RANK_WARNING = r'numpy.linalg.matrix_rank|\A\Z'
|
||||
PYAMG_MISSING_WARNING = r'pyamg|\A\Z'
|
||||
PYAMG_OR_SCIPY_WARNING = SCIPY_RANK_WARNING + '|' + PYAMG_MISSING_WARNING
|
||||
|
||||
if Version(scipy.__version__) < '1.3':
|
||||
NUMPY_MATRIX_WARNING = 'matrix subclass'
|
||||
else:
|
||||
NUMPY_MATRIX_WARNING = None
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def test_2d_bf():
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
labels_bf = random_walker(data, labels, beta=90, mode='bf')
|
||||
assert (labels_bf[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
full_prob_bf = random_walker(data, labels, beta=90, 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
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
full_prob_bf = random_walker(data, labels, beta=90, 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
|
||||
|
||||
|
||||
def test_2d_cg():
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
with expected_warnings(['"cg" mode' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
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' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
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
|
||||
return data, labels_cg
|
||||
|
||||
|
||||
def test_2d_cg_mg():
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
anticipated_warnings = [
|
||||
'scipy.sparse.sparsetools|%s' % PYAMG_OR_SCIPY_WARNING,
|
||||
NUMPY_MATRIX_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
|
||||
return data, labels_cg_mg
|
||||
|
||||
|
||||
def test_2d_cg_j():
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
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
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
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_OR_SCIPY_WARNING, NUMPY_MATRIX_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
|
||||
return data, labels_cg_mg
|
||||
|
||||
|
||||
def test_reorder_labels():
|
||||
lx = 70
|
||||
ly = 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
labels[labels == 2] = 4
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
labels_bf = random_walker(data, labels, beta=90, mode='bf')
|
||||
assert (labels_bf[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
return data, labels_bf
|
||||
|
||||
|
||||
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
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
labels = random_walker(data, labels, beta=90)
|
||||
assert (labels.reshape((lx, ly))[25:45, 40:60] == 2).all()
|
||||
assert data.shape == labels.shape
|
||||
return data, labels
|
||||
|
||||
|
||||
def test_3d():
|
||||
n = 30
|
||||
lx, ly, lz = n, n, n
|
||||
data, labels = make_3d_syntheticdata(lx, ly, lz)
|
||||
with expected_warnings(['"cg" mode' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
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
|
||||
return data, labels
|
||||
|
||||
|
||||
def test_3d_inactive():
|
||||
n = 30
|
||||
lx, ly, lz = n, n, n
|
||||
data, labels = make_3d_syntheticdata(lx, ly, lz)
|
||||
old_labels = np.copy(labels)
|
||||
labels[5:25, 26:29, 26:29] = -1
|
||||
after_labels = np.copy(labels)
|
||||
with expected_warnings(['"cg" mode|CObject type' + '|'
|
||||
+ SCIPY_RANK_WARNING, NUMPY_MATRIX_WARNING]):
|
||||
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
|
||||
return data, labels, old_labels, after_labels
|
||||
|
||||
|
||||
def test_multispectral_2d():
|
||||
lx, ly = 70, 100
|
||||
data, labels = make_2d_syntheticdata(lx, ly)
|
||||
data = data[..., np.newaxis].repeat(2, axis=-1) # Expect identical output
|
||||
with expected_warnings(['"cg" mode' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
multi_labels = random_walker(data, labels, mode='cg',
|
||||
multichannel=True)
|
||||
assert data[..., 0].shape == labels.shape
|
||||
with expected_warnings(['"cg" mode' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
single_labels = 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
|
||||
return data, multi_labels, single_labels, labels
|
||||
|
||||
|
||||
def test_multispectral_3d():
|
||||
n = 30
|
||||
lx, ly, lz = n, n, n
|
||||
data, labels = make_3d_syntheticdata(lx, ly, lz)
|
||||
data = data[..., np.newaxis].repeat(2, axis=-1) # Expect identical output
|
||||
with expected_warnings(['"cg" mode' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
multi_labels = random_walker(data, labels, mode='cg',
|
||||
multichannel=True)
|
||||
assert data[..., 0].shape == labels.shape
|
||||
with expected_warnings(['"cg" mode' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
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
|
||||
return data, multi_labels, single_labels, labels
|
||||
|
||||
|
||||
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' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
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' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
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' + '|' + SCIPY_RANK_WARNING,
|
||||
NUMPY_MATRIX_WARNING]):
|
||||
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 connexion 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
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
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, multichannel=True)
|
||||
|
||||
# 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, multichannel=True)
|
||||
|
||||
# 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([NUMPY_MATRIX_WARNING]):
|
||||
res = random_walker(a, mask)
|
||||
assert res[1, 1] == 1
|
||||
with expected_warnings([NUMPY_MATRIX_WARNING]):
|
||||
res = random_walker(a, mask, return_full_prob=True)
|
||||
assert res[0, 1, 1] == 1
|
||||
assert res[1, 1, 1] == 0
|
474
venv/Lib/site-packages/skimage/segmentation/tests/test_slic.py
Normal file
474
venv/Lib/site-packages/skimage/segmentation/tests/test_slic.py
Normal file
|
@ -0,0 +1,474 @@
|
|||
from itertools import product
|
||||
|
||||
import pytest
|
||||
import numpy as np
|
||||
from skimage.segmentation import slic
|
||||
|
||||
from skimage._shared import testing
|
||||
from skimage._shared.testing import test_parallel, assert_equal
|
||||
|
||||
|
||||
@test_parallel()
|
||||
def test_color_2d():
|
||||
rnd = np.random.RandomState(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.RandomState(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.RandomState(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,
|
||||
multichannel=False, 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_color_3d():
|
||||
rnd = np.random.RandomState(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.RandomState(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,
|
||||
multichannel=False, 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.RandomState(0)
|
||||
img = np.array([[1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 1, 1, 1]], np.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]], np.int)
|
||||
seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1],
|
||||
multichannel=False, start_label=0)
|
||||
assert_equal(seg_sigma, result_sigma)
|
||||
|
||||
|
||||
def test_spacing():
|
||||
rnd = np.random.RandomState(0)
|
||||
img = np.array([[1, 1, 1, 0, 0],
|
||||
[1, 1, 0, 0, 0]], np.float)
|
||||
result_non_spaced = np.array([[0, 0, 0, 1, 1],
|
||||
[0, 0, 1, 1, 1]], np.int)
|
||||
result_spaced = np.array([[0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1]], np.int)
|
||||
img += 0.1 * rnd.normal(size=img.shape)
|
||||
seg_non_spaced = slic(img, n_segments=2, sigma=0, multichannel=False,
|
||||
compactness=1.0, start_label=0)
|
||||
seg_spaced = slic(img, n_segments=2, sigma=0, spacing=[1, 500, 1],
|
||||
compactness=1.0, multichannel=False, 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]], np.float) + 1
|
||||
with testing.raises(ValueError):
|
||||
slic(img, multichannel=True, 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]], np.float)
|
||||
|
||||
segments_connected = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=True,
|
||||
convert2lab=False, start_label=0)
|
||||
segments_disconnected = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=False,
|
||||
convert2lab=False, start_label=0)
|
||||
|
||||
# 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)
|
||||
|
||||
result_connected = np.array([[0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 1, 1, 1],
|
||||
[0, 0, 0, 1, 1, 1]], np.float)
|
||||
|
||||
result_disconnected = np.array([[0, 0, 0, 1, 1, 1],
|
||||
[1, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 1, 1, 0]], np.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.RandomState(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.RandomState(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,
|
||||
multichannel=False, convert2lab=False, start_label=0)
|
||||
assert np.all(seg.ravel() == np.arange(seg.size))
|
||||
|
||||
|
||||
def test_color_2d_mask():
|
||||
rnd = np.random.RandomState(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.RandomState(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.RandomState(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,
|
||||
multichannel=False, 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.RandomState(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]], np.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]], np.int)
|
||||
seg_sigma = slic(img, n_segments=2, sigma=[1, 50, 1],
|
||||
multichannel=False, mask=msk)
|
||||
assert_equal(seg_sigma, result_sigma)
|
||||
|
||||
|
||||
def test_spacing_mask():
|
||||
rnd = np.random.RandomState(0)
|
||||
msk = np.zeros((2, 5))
|
||||
msk[:, 1:-1] = 1
|
||||
img = np.array([[1, 1, 1, 0, 0],
|
||||
[1, 1, 0, 0, 0]], np.float)
|
||||
result_non_spaced = np.array([[0, 1, 1, 2, 0],
|
||||
[0, 1, 2, 2, 0]], np.int)
|
||||
result_spaced = np.array([[0, 1, 1, 1, 0],
|
||||
[0, 2, 2, 2, 0]], np.int)
|
||||
img += 0.1 * rnd.normal(size=img.shape)
|
||||
seg_non_spaced = slic(img, n_segments=2, sigma=0, multichannel=False,
|
||||
compactness=1.0, mask=msk)
|
||||
seg_spaced = slic(img, n_segments=2, sigma=0, spacing=[1, 50, 1],
|
||||
compactness=1.0, multichannel=False, 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]], np.float)
|
||||
|
||||
segments_connected = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=True,
|
||||
convert2lab=False, mask=msk)
|
||||
segments_disconnected = slic(img, 2, compactness=0.0001,
|
||||
enforce_connectivity=False,
|
||||
convert2lab=False, mask=msk)
|
||||
|
||||
# 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)
|
||||
|
||||
result_connected = np.array([[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0]], np.float)
|
||||
|
||||
result_disconnected = np.array([[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0],
|
||||
[0, 1, 1, 2, 2, 0]], np.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.RandomState(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.RandomState(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,
|
||||
multichannel=False, 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.RandomState(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.RandomState(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, multichannel=False,
|
||||
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", ['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)
|
|
@ -0,0 +1,498 @@
|
|||
"""test_watershed.py - tests the watershed function
|
||||
|
||||
Originally part of CellProfiler, code licensed under both GPL and BSD licenses.
|
||||
Website: http://www.cellprofiler.org
|
||||
|
||||
Copyright (c) 2003-2009 Massachusetts Institute of Technology
|
||||
Copyright (c) 2009-2011 Broad Institute
|
||||
All rights reserved.
|
||||
|
||||
Original author: Lee Kamentsky
|
||||
"""
|
||||
#Portions of this test were taken from scipy's watershed test in test_ndimage.py
|
||||
#
|
||||
# Copyright (C) 2003-2005 Peter J. Verveer
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided
|
||||
# with the distribution.
|
||||
#
|
||||
# 3. The name of the author may not be used to endorse or promote
|
||||
# products derived from this software without specific prior
|
||||
# written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
|
||||
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
import math
|
||||
import unittest
|
||||
import pytest
|
||||
import numpy as np
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
from .._watershed import watershed
|
||||
from skimage.measure import label
|
||||
|
||||
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 = ndi.gaussian_filter(image, 4)
|
||||
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():
|
||||
with pytest.raises(ValueError):
|
||||
image = np.ones((5, 6))
|
||||
markers = np.ones((5, 7))
|
||||
output = watershed(image, markers)
|
||||
|
||||
|
||||
def test_incorrect_mask_shape():
|
||||
with pytest.raises(ValueError):
|
||||
image = np.ones((5, 6))
|
||||
mask = np.ones((5, 7))
|
||||
output = 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
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
np.testing.run_module_suite()
|
Loading…
Add table
Add a link
Reference in a new issue