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
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
|
Loading…
Add table
Add a link
Reference in a new issue