Diff of /blobs_detection.py [000000] .. [70b6b3]

Switch to side-by-side view

--- a
+++ b/blobs_detection.py
@@ -0,0 +1,393 @@
+from __future__ import division
+import numpy as np
+from scipy.ndimage import gaussian_filter, gaussian_laplace
+import math
+from math import sqrt, log
+from scipy import spatial
+from skimage.util import img_as_float
+from skimage.feature.peak import peak_local_max
+
+
+# code from
+# https://github.com/emmanuelle/scikit-image/blob/0228772de6f55a053f350665dd3128b1a0193b98/skimage/feature/blob.py
+
+# This basic blob detection algorithm is based on:
+# http://www.cs.utah.edu/~jfishbau/advimproc/project1/ (04.04.2013)
+# Theory behind: http://en.wikipedia.org/wiki/Blob_detection (04.04.2013)
+
+
+def _compute_disk_overlap(d, r1, r2):
+    """
+    Compute surface overlap between two disks of radii ``r1`` and ``r2``,
+    with centers separated by a distance ``d``.
+
+    Parameters
+    ----------
+    d : float
+        Distance between centers.
+    r1 : float
+        Radius of the first disk.
+    r2 : float
+        Radius of the second disk.
+
+    Returns
+    -------
+    vol: float
+        Volume of the overlap between the two disks.
+    """
+
+    ratio1 = (d ** 2 + r1 ** 2 - r2 ** 2) / (2 * d * r1)
+    ratio1 = np.clip(ratio1, -1, 1)
+    acos1 = math.acos(ratio1)
+
+    ratio2 = (d ** 2 + r2 ** 2 - r1 ** 2) / (2 * d * r2)
+    ratio2 = np.clip(ratio2, -1, 1)
+    acos2 = math.acos(ratio2)
+
+    a = -d + r2 + r1
+    b = d - r2 + r1
+    c = d + r2 - r1
+    d = d + r2 + r1
+    area = (r1 ** 2 * acos1 + r2 ** 2 * acos2 -
+            0.5 * sqrt(abs(a * b * c * d)))
+    return area / (math.pi * (min(r1, r2) ** 2))
+
+
+def _compute_sphere_overlap(d, r1, r2):
+    """
+    Compute volume overlap between two spheres of radii ``r1`` and ``r2``,
+    with centers separated by a distance ``d``.
+
+    Parameters
+    ----------
+    d : float
+        Distance between centers.
+    r1 : float
+        Radius of the first sphere.
+    r2 : float
+        Radius of the second sphere.
+
+    Returns
+    -------
+    vol: float
+        Volume of the overlap between the two spheres.
+
+    Notes
+    -----
+    See for example http://mathworld.wolfram.com/Sphere-SphereIntersection.html
+    for more details.
+    """
+    vol = (math.pi / (12 * d) * (r1 + r2 - d) ** 2 *
+           (d ** 2 + 2 * d * (r1 + r2) - 3 * (r1 ** 2 + r2 ** 2) + 6 * r1 * r2))
+    return vol / (4. / 3 * math.pi * min(r1, r2) ** 3)
+
+
+def _blob_overlap(blob1, blob2):
+    """Finds the overlapping area fraction between two blobs.
+
+    Returns a float representing fraction of overlapped area.
+
+    Parameters
+    ----------
+    blob1 : sequence of arrays
+        A sequence of ``(row, col, sigma)`` or ``(pln, row, col, sigma)``,
+        where ``row, col`` (or ``(pln, row, col, sigma)``) are coordinates
+        of blob and ``sigma`` is the standard deviation of the Gaussian kernel
+        which detected the blob.
+    blob2 : sequence of arrays
+        A sequence of ``(row, col, sigma)`` or ``(pln, row, col, sigma)``,
+        where ``row, col`` (or ``(pln, row, col, sigma)``) are coordinates
+        of blob and ``sigma`` is the standard deviation of the Gaussian kernel
+        which detected the blob.
+
+    Returns
+    -------
+    f : float
+        Fraction of overlapped area (or volume in 3D).
+    """
+    n_dim = len(blob1) - 1
+    root_ndim = sqrt(n_dim)
+
+    # extent of the blob is given by sqrt(2)*scale
+    r1 = blob1[-1] * root_ndim
+    r2 = blob2[-1] * root_ndim
+
+    d = sqrt(np.sum((blob1[:-1] - blob2[:-1]) ** 2))
+    if d > r1 + r2:
+        return 0
+
+    # one blob is inside the other, the smaller blob must die
+    if d <= abs(r1 - r2):
+        return 1
+
+    if n_dim == 2:
+        return _compute_disk_overlap(d, r1, r2)
+
+    else:  # http://mathworld.wolfram.com/Sphere-SphereIntersection.html
+        return _compute_sphere_overlap(d, r1, r2)
+
+
+def _prune_blobs(blobs_array, overlap):
+    """Eliminated blobs with area overlap.
+
+    Parameters
+    ----------
+    blobs_array : ndarray
+        A 2d array with each row representing 3 (or 4) values,
+        ``(row, col, sigma)`` or ``(pln, row, col, sigma)`` in 3D,
+        where ``(row, col)`` (``(pln, row, col)``) are coordinates of the blob
+        and ``sigma`` is the standard deviation of the Gaussian kernel which
+        detected the blob.
+    overlap : float
+        A value between 0 and 1. If the fraction of area overlapping for 2
+        blobs is greater than `overlap` the smaller blob is eliminated.
+
+    Returns
+    -------
+    A : ndarray
+        `array` with overlapping blobs removed.
+    """
+
+    # iterating again might eliminate more blobs, but one iteration suffices
+    # for most cases
+    if len(blobs_array) == 0:
+        return np.array([])
+    sigma = blobs_array[:, -1].max()
+    distance = 2 * sigma * sqrt(blobs_array.shape[1] - 1)
+    try:
+        tree = spatial.cKDTree(blobs_array[:, :-1])
+        pairs = np.array(list(tree.query_pairs(distance)))
+    except AttributeError:  # scipy 0.9, min requirements
+        tree = spatial.KDTree(blobs_array[:, :-1])
+        pairs = np.array(list(tree.query_pairs(distance)))
+    if len(pairs) == 0:
+        return blobs_array
+    else:
+        for (i, j) in pairs:
+            blob1, blob2 = blobs_array[i], blobs_array[j]
+            if _blob_overlap(blob1, blob2) > overlap:
+                if blob1[-1] > blob2[-1]:
+                    blob2[-1] = 0
+                else:
+                    blob1[-1] = 0
+
+    return np.array([b for b in blobs_array if b[-1] > 0])
+
+
+def blob_dog(image, min_sigma=1, max_sigma=50, sigma_ratio=1.6, threshold=2.0,
+             overlap=.5, ):
+    """Finds blobs in the given grayscale image.
+
+    Blobs are found using the Difference of Gaussian (DoG) method [1]_.
+    For each blob found, the method returns its coordinates and the standard
+    deviation of the Gaussian kernel that detected the blob.
+
+    Parameters
+    ----------
+    image : 2D or 3D ndarray
+        Input grayscale image, blobs are assumed to be light on dark
+        background (white on black).
+    min_sigma : float, optional
+        The minimum standard deviation for Gaussian Kernel. Keep this low to
+        detect smaller blobs.
+    max_sigma : float, optional
+        The maximum standard deviation for Gaussian Kernel. Keep this high to
+        detect larger blobs.
+    sigma_ratio : float, optional
+        The ratio between the standard deviation of Gaussian Kernels used for
+        computing the Difference of Gaussians
+    threshold : float, optional.
+        The absolute lower bound for scale space maxima. Local maxima smaller
+        than thresh are ignored. Reduce this to detect blobs with less
+        intensities.
+    overlap : float, optional
+        A value between 0 and 1. If the area of two blobs overlaps by a
+        fraction greater than `threshold`, the smaller blob is eliminated.
+
+    Returns
+    -------
+    A : (n, image.ndim + 1) ndarray
+        A 2d array with each row representing 3 values for a 2D image,
+        and 4 values for a 3D image: ``(r, c, sigma)`` or ``(p, r, c, sigma)``
+        where ``(r, c)`` or ``(p, r, c)`` are coordinates of the blob and
+        ``sigma`` is the standard deviation of the Gaussian kernel which
+        detected the blob.
+
+    References
+    ----------
+    .. [1] http://en.wikipedia.org/wiki/Blob_detection#The_difference_of_Gaussians_approach
+
+    Examples
+    --------
+    >>> from skimage import data, feature
+    >>> feature.blob_dog(data.coins(), threshold=.5, max_sigma=40)
+    array([[  45.      ,  336.      ,   16.777216],
+           [  52.      ,  155.      ,   16.777216],
+           [  52.      ,  216.      ,   16.777216],
+           [  54.      ,   42.      ,   16.777216],
+           [  54.      ,  276.      ,   10.48576 ],
+           [  58.      ,  100.      ,   10.48576 ],
+           [ 120.      ,  272.      ,   16.777216],
+           [ 124.      ,  337.      ,   10.48576 ],
+           [ 125.      ,   45.      ,   16.777216],
+           [ 125.      ,  208.      ,   10.48576 ],
+           [ 127.      ,  102.      ,   10.48576 ],
+           [ 128.      ,  154.      ,   10.48576 ],
+           [ 185.      ,  347.      ,   16.777216],
+           [ 193.      ,  213.      ,   16.777216],
+           [ 194.      ,  277.      ,   16.777216],
+           [ 195.      ,  102.      ,   16.777216],
+           [ 196.      ,   43.      ,   10.48576 ],
+           [ 198.      ,  155.      ,   10.48576 ],
+           [ 260.      ,   46.      ,   16.777216],
+           [ 261.      ,  173.      ,   16.777216],
+           [ 263.      ,  245.      ,   16.777216],
+           [ 263.      ,  302.      ,   16.777216],
+           [ 267.      ,  115.      ,   10.48576 ],
+           [ 267.      ,  359.      ,   16.777216]])
+
+    Notes
+    -----
+    The radius of each blob is approximately :math:`\sqrt{2}sigma` for
+    a 2-D image and :math:`\sqrt{3}sigma` for a 3-D image.
+    """
+    image = img_as_float(image)
+
+    # k such that min_sigma*(sigma_ratio**k) > max_sigma
+    k = int(log(float(max_sigma) / min_sigma, sigma_ratio)) + 1
+
+    # a geometric progression of standard deviations for gaussian kernels
+    sigma_list = np.array([min_sigma * (sigma_ratio ** i)
+                           for i in range(k + 1)])
+
+    gaussian_images = [gaussian_filter(image, s) for s in sigma_list]
+
+    # computing difference between two successive Gaussian blurred images
+    # multiplying with standard deviation provides scale invariance
+    dog_images = [(gaussian_images[i] - gaussian_images[i + 1])
+                  * sigma_list[i] for i in range(k)]
+
+    # Replace by image_cube = np.stack(hessian_images, axis=-1)
+    # When we upgrade minimal requirements to NumPy 1.10
+    sl = (slice(None),) * image.ndim + (np.newaxis,)
+    arrays = [np.asanyarray(arr) for arr in dog_images]
+    extended_arrays = [arr[sl] for arr in arrays]
+    image_cube = np.concatenate(extended_arrays, axis=-1)
+
+    # local_maxima = get_local_maxima(image_cube, threshold)
+    local_maxima = peak_local_max(image_cube, threshold_abs=threshold,
+                                  footprint=np.ones((3,) * (image.ndim + 1)),
+                                  threshold_rel=0.0,
+                                  exclude_border=False)
+    # Convert local_maxima to float64
+    lm = local_maxima.astype(np.float64)
+    # Convert the last index to its corresponding scale value
+    lm[:, -1] = sigma_list[local_maxima[:, -1]]
+    return _prune_blobs(lm, overlap)
+
+
+def blob_log(image, min_sigma=1, max_sigma=50, num_sigma=10, threshold=.2,
+             overlap=.5, log_scale=False):
+    """Finds blobs in the given grayscale image.
+
+    Blobs are found using the Laplacian of Gaussian (LoG) method [1]_.
+    For each blob found, the method returns its coordinates and the standard
+    deviation of the Gaussian kernel that detected the blob.
+
+    Parameters
+    ----------
+    image : 2D or 3D ndarray
+        Input grayscale image, blobs are assumed to be light on dark
+        background (white on black).
+    min_sigma : float, optional
+        The minimum standard deviation for Gaussian Kernel. Keep this low to
+        detect smaller blobs.
+    max_sigma : float, optional
+        The maximum standard deviation for Gaussian Kernel. Keep this high to
+        detect larger blobs.
+    num_sigma : int, optional
+        The number of intermediate values of standard deviations to consider
+        between `min_sigma` and `max_sigma`.
+    threshold : float, optional.
+        The absolute lower bound for scale space maxima. Local maxima smaller
+        than thresh are ignored. Reduce this to detect blobs with less
+        intensities.
+    overlap : float, optional
+        A value between 0 and 1. If the area of two blobs overlaps by a
+        fraction greater than `threshold`, the smaller blob is eliminated.
+    log_scale : bool, optional
+        If set intermediate values of standard deviations are interpolated
+        using a logarithmic scale to the base `10`. If not, linear
+        interpolation is used.
+
+    Returns
+    -------
+    A : (n, image.ndim + 1) ndarray
+        A 2d array with each row representing 3 values for a 2D image,
+        and 4 values for a 3D image: ``(r, c, sigma)`` or ``(f, r, c, sigma)``
+        where ``(r, c)`` or ``(f, r, c)`` are coordinates of the blob and
+        ``sigma`` is the standard deviation of the Gaussian kernel which
+        detected the blob.
+
+    References
+    ----------
+    .. [1] http://en.wikipedia.org/wiki/Blob_detection#The_Laplacian_of_Gaussian
+
+    Examples
+    --------
+    >>> from skimage import data, feature, exposure
+    >>> img = data.coins()
+    >>> img = exposure.equalize_hist(img)  # improves detection
+    >>> feature.blob_log(img, threshold = .3)
+    array([[ 113.        ,  323.        ,    1.        ],
+           [ 121.        ,  272.        ,   17.33333333],
+           [ 124.        ,  336.        ,   11.88888889],
+           [ 126.        ,   46.        ,   11.88888889],
+           [ 126.        ,  208.        ,   11.88888889],
+           [ 127.        ,  102.        ,   11.88888889],
+           [ 128.        ,  154.        ,   11.88888889],
+           [ 185.        ,  344.        ,   17.33333333],
+           [ 194.        ,  213.        ,   17.33333333],
+           [ 194.        ,  276.        ,   17.33333333],
+           [ 197.        ,   44.        ,   11.88888889],
+           [ 198.        ,  103.        ,   11.88888889],
+           [ 198.        ,  155.        ,   11.88888889],
+           [ 260.        ,  174.        ,   17.33333333],
+           [ 263.        ,  244.        ,   17.33333333],
+           [ 263.        ,  302.        ,   17.33333333],
+           [ 266.        ,  115.        ,   11.88888889]])
+
+    Notes
+    -----
+    The radius of each blob is approximately :math:`\sqrt{2}sigma` for
+    a 2-D image and :math:`\sqrt{3}sigma` for a 3-D image.
+    """
+    image = img_as_float(image)
+
+    if log_scale:
+        start, stop = log(min_sigma, 10), log(max_sigma, 10)
+        sigma_list = np.logspace(start, stop, num_sigma)
+    else:
+        sigma_list = np.linspace(min_sigma, max_sigma, num_sigma)
+
+    # computing gaussian laplace
+    # s**2 provides scale invariance
+    gl_images = [-gaussian_laplace(image, s) * s ** 2 for s in sigma_list]
+
+    # Replace by image_cube = np.stack(hessian_images, axis=-1)
+    # When we upgrade minimal requirements to NumPy 1.10
+    sl = (slice(None),) * image.ndim + (np.newaxis,)
+    arrays = [np.asanyarray(arr) for arr in gl_images]
+    extended_arrays = [arr[sl] for arr in arrays]
+    image_cube = np.concatenate(extended_arrays, axis=-1)
+
+    local_maxima = peak_local_max(image_cube, threshold_abs=threshold,
+                                  footprint=np.ones((3,) * (image.ndim + 1)),
+                                  threshold_rel=0.0,
+                                  exclude_border=False)
+
+    # Convert local_maxima to float64
+    lm = local_maxima.astype(np.float64)
+    # Convert the last index to its corresponding scale value
+    lm[:, -1] = sigma_list[local_maxima[:, -1]]
+    return _prune_blobs(lm, overlap)