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

Switch to unified view

a b/blobs_detection.py
1
from __future__ import division
2
import numpy as np
3
from scipy.ndimage import gaussian_filter, gaussian_laplace
4
import math
5
from math import sqrt, log
6
from scipy import spatial
7
from skimage.util import img_as_float
8
from skimage.feature.peak import peak_local_max
9
10
11
# code from
12
# https://github.com/emmanuelle/scikit-image/blob/0228772de6f55a053f350665dd3128b1a0193b98/skimage/feature/blob.py
13
14
# This basic blob detection algorithm is based on:
15
# http://www.cs.utah.edu/~jfishbau/advimproc/project1/ (04.04.2013)
16
# Theory behind: http://en.wikipedia.org/wiki/Blob_detection (04.04.2013)
17
18
19
def _compute_disk_overlap(d, r1, r2):
20
    """
21
    Compute surface overlap between two disks of radii ``r1`` and ``r2``,
22
    with centers separated by a distance ``d``.
23
24
    Parameters
25
    ----------
26
    d : float
27
        Distance between centers.
28
    r1 : float
29
        Radius of the first disk.
30
    r2 : float
31
        Radius of the second disk.
32
33
    Returns
34
    -------
35
    vol: float
36
        Volume of the overlap between the two disks.
37
    """
38
39
    ratio1 = (d ** 2 + r1 ** 2 - r2 ** 2) / (2 * d * r1)
40
    ratio1 = np.clip(ratio1, -1, 1)
41
    acos1 = math.acos(ratio1)
42
43
    ratio2 = (d ** 2 + r2 ** 2 - r1 ** 2) / (2 * d * r2)
44
    ratio2 = np.clip(ratio2, -1, 1)
45
    acos2 = math.acos(ratio2)
46
47
    a = -d + r2 + r1
48
    b = d - r2 + r1
49
    c = d + r2 - r1
50
    d = d + r2 + r1
51
    area = (r1 ** 2 * acos1 + r2 ** 2 * acos2 -
52
            0.5 * sqrt(abs(a * b * c * d)))
53
    return area / (math.pi * (min(r1, r2) ** 2))
54
55
56
def _compute_sphere_overlap(d, r1, r2):
57
    """
58
    Compute volume overlap between two spheres of radii ``r1`` and ``r2``,
59
    with centers separated by a distance ``d``.
60
61
    Parameters
62
    ----------
63
    d : float
64
        Distance between centers.
65
    r1 : float
66
        Radius of the first sphere.
67
    r2 : float
68
        Radius of the second sphere.
69
70
    Returns
71
    -------
72
    vol: float
73
        Volume of the overlap between the two spheres.
74
75
    Notes
76
    -----
77
    See for example http://mathworld.wolfram.com/Sphere-SphereIntersection.html
78
    for more details.
79
    """
80
    vol = (math.pi / (12 * d) * (r1 + r2 - d) ** 2 *
81
           (d ** 2 + 2 * d * (r1 + r2) - 3 * (r1 ** 2 + r2 ** 2) + 6 * r1 * r2))
82
    return vol / (4. / 3 * math.pi * min(r1, r2) ** 3)
83
84
85
def _blob_overlap(blob1, blob2):
86
    """Finds the overlapping area fraction between two blobs.
87
88
    Returns a float representing fraction of overlapped area.
89
90
    Parameters
91
    ----------
92
    blob1 : sequence of arrays
93
        A sequence of ``(row, col, sigma)`` or ``(pln, row, col, sigma)``,
94
        where ``row, col`` (or ``(pln, row, col, sigma)``) are coordinates
95
        of blob and ``sigma`` is the standard deviation of the Gaussian kernel
96
        which detected the blob.
97
    blob2 : sequence of arrays
98
        A sequence of ``(row, col, sigma)`` or ``(pln, row, col, sigma)``,
99
        where ``row, col`` (or ``(pln, row, col, sigma)``) are coordinates
100
        of blob and ``sigma`` is the standard deviation of the Gaussian kernel
101
        which detected the blob.
102
103
    Returns
104
    -------
105
    f : float
106
        Fraction of overlapped area (or volume in 3D).
107
    """
108
    n_dim = len(blob1) - 1
109
    root_ndim = sqrt(n_dim)
110
111
    # extent of the blob is given by sqrt(2)*scale
112
    r1 = blob1[-1] * root_ndim
113
    r2 = blob2[-1] * root_ndim
114
115
    d = sqrt(np.sum((blob1[:-1] - blob2[:-1]) ** 2))
116
    if d > r1 + r2:
117
        return 0
118
119
    # one blob is inside the other, the smaller blob must die
120
    if d <= abs(r1 - r2):
121
        return 1
122
123
    if n_dim == 2:
124
        return _compute_disk_overlap(d, r1, r2)
125
126
    else:  # http://mathworld.wolfram.com/Sphere-SphereIntersection.html
127
        return _compute_sphere_overlap(d, r1, r2)
128
129
130
def _prune_blobs(blobs_array, overlap):
131
    """Eliminated blobs with area overlap.
132
133
    Parameters
134
    ----------
135
    blobs_array : ndarray
136
        A 2d array with each row representing 3 (or 4) values,
137
        ``(row, col, sigma)`` or ``(pln, row, col, sigma)`` in 3D,
138
        where ``(row, col)`` (``(pln, row, col)``) are coordinates of the blob
139
        and ``sigma`` is the standard deviation of the Gaussian kernel which
140
        detected the blob.
141
    overlap : float
142
        A value between 0 and 1. If the fraction of area overlapping for 2
143
        blobs is greater than `overlap` the smaller blob is eliminated.
144
145
    Returns
146
    -------
147
    A : ndarray
148
        `array` with overlapping blobs removed.
149
    """
150
151
    # iterating again might eliminate more blobs, but one iteration suffices
152
    # for most cases
153
    if len(blobs_array) == 0:
154
        return np.array([])
155
    sigma = blobs_array[:, -1].max()
156
    distance = 2 * sigma * sqrt(blobs_array.shape[1] - 1)
157
    try:
158
        tree = spatial.cKDTree(blobs_array[:, :-1])
159
        pairs = np.array(list(tree.query_pairs(distance)))
160
    except AttributeError:  # scipy 0.9, min requirements
161
        tree = spatial.KDTree(blobs_array[:, :-1])
162
        pairs = np.array(list(tree.query_pairs(distance)))
163
    if len(pairs) == 0:
164
        return blobs_array
165
    else:
166
        for (i, j) in pairs:
167
            blob1, blob2 = blobs_array[i], blobs_array[j]
168
            if _blob_overlap(blob1, blob2) > overlap:
169
                if blob1[-1] > blob2[-1]:
170
                    blob2[-1] = 0
171
                else:
172
                    blob1[-1] = 0
173
174
    return np.array([b for b in blobs_array if b[-1] > 0])
175
176
177
def blob_dog(image, min_sigma=1, max_sigma=50, sigma_ratio=1.6, threshold=2.0,
178
             overlap=.5, ):
179
    """Finds blobs in the given grayscale image.
180
181
    Blobs are found using the Difference of Gaussian (DoG) method [1]_.
182
    For each blob found, the method returns its coordinates and the standard
183
    deviation of the Gaussian kernel that detected the blob.
184
185
    Parameters
186
    ----------
187
    image : 2D or 3D ndarray
188
        Input grayscale image, blobs are assumed to be light on dark
189
        background (white on black).
190
    min_sigma : float, optional
191
        The minimum standard deviation for Gaussian Kernel. Keep this low to
192
        detect smaller blobs.
193
    max_sigma : float, optional
194
        The maximum standard deviation for Gaussian Kernel. Keep this high to
195
        detect larger blobs.
196
    sigma_ratio : float, optional
197
        The ratio between the standard deviation of Gaussian Kernels used for
198
        computing the Difference of Gaussians
199
    threshold : float, optional.
200
        The absolute lower bound for scale space maxima. Local maxima smaller
201
        than thresh are ignored. Reduce this to detect blobs with less
202
        intensities.
203
    overlap : float, optional
204
        A value between 0 and 1. If the area of two blobs overlaps by a
205
        fraction greater than `threshold`, the smaller blob is eliminated.
206
207
    Returns
208
    -------
209
    A : (n, image.ndim + 1) ndarray
210
        A 2d array with each row representing 3 values for a 2D image,
211
        and 4 values for a 3D image: ``(r, c, sigma)`` or ``(p, r, c, sigma)``
212
        where ``(r, c)`` or ``(p, r, c)`` are coordinates of the blob and
213
        ``sigma`` is the standard deviation of the Gaussian kernel which
214
        detected the blob.
215
216
    References
217
    ----------
218
    .. [1] http://en.wikipedia.org/wiki/Blob_detection#The_difference_of_Gaussians_approach
219
220
    Examples
221
    --------
222
    >>> from skimage import data, feature
223
    >>> feature.blob_dog(data.coins(), threshold=.5, max_sigma=40)
224
    array([[  45.      ,  336.      ,   16.777216],
225
           [  52.      ,  155.      ,   16.777216],
226
           [  52.      ,  216.      ,   16.777216],
227
           [  54.      ,   42.      ,   16.777216],
228
           [  54.      ,  276.      ,   10.48576 ],
229
           [  58.      ,  100.      ,   10.48576 ],
230
           [ 120.      ,  272.      ,   16.777216],
231
           [ 124.      ,  337.      ,   10.48576 ],
232
           [ 125.      ,   45.      ,   16.777216],
233
           [ 125.      ,  208.      ,   10.48576 ],
234
           [ 127.      ,  102.      ,   10.48576 ],
235
           [ 128.      ,  154.      ,   10.48576 ],
236
           [ 185.      ,  347.      ,   16.777216],
237
           [ 193.      ,  213.      ,   16.777216],
238
           [ 194.      ,  277.      ,   16.777216],
239
           [ 195.      ,  102.      ,   16.777216],
240
           [ 196.      ,   43.      ,   10.48576 ],
241
           [ 198.      ,  155.      ,   10.48576 ],
242
           [ 260.      ,   46.      ,   16.777216],
243
           [ 261.      ,  173.      ,   16.777216],
244
           [ 263.      ,  245.      ,   16.777216],
245
           [ 263.      ,  302.      ,   16.777216],
246
           [ 267.      ,  115.      ,   10.48576 ],
247
           [ 267.      ,  359.      ,   16.777216]])
248
249
    Notes
250
    -----
251
    The radius of each blob is approximately :math:`\sqrt{2}sigma` for
252
    a 2-D image and :math:`\sqrt{3}sigma` for a 3-D image.
253
    """
254
    image = img_as_float(image)
255
256
    # k such that min_sigma*(sigma_ratio**k) > max_sigma
257
    k = int(log(float(max_sigma) / min_sigma, sigma_ratio)) + 1
258
259
    # a geometric progression of standard deviations for gaussian kernels
260
    sigma_list = np.array([min_sigma * (sigma_ratio ** i)
261
                           for i in range(k + 1)])
262
263
    gaussian_images = [gaussian_filter(image, s) for s in sigma_list]
264
265
    # computing difference between two successive Gaussian blurred images
266
    # multiplying with standard deviation provides scale invariance
267
    dog_images = [(gaussian_images[i] - gaussian_images[i + 1])
268
                  * sigma_list[i] for i in range(k)]
269
270
    # Replace by image_cube = np.stack(hessian_images, axis=-1)
271
    # When we upgrade minimal requirements to NumPy 1.10
272
    sl = (slice(None),) * image.ndim + (np.newaxis,)
273
    arrays = [np.asanyarray(arr) for arr in dog_images]
274
    extended_arrays = [arr[sl] for arr in arrays]
275
    image_cube = np.concatenate(extended_arrays, axis=-1)
276
277
    # local_maxima = get_local_maxima(image_cube, threshold)
278
    local_maxima = peak_local_max(image_cube, threshold_abs=threshold,
279
                                  footprint=np.ones((3,) * (image.ndim + 1)),
280
                                  threshold_rel=0.0,
281
                                  exclude_border=False)
282
    # Convert local_maxima to float64
283
    lm = local_maxima.astype(np.float64)
284
    # Convert the last index to its corresponding scale value
285
    lm[:, -1] = sigma_list[local_maxima[:, -1]]
286
    return _prune_blobs(lm, overlap)
287
288
289
def blob_log(image, min_sigma=1, max_sigma=50, num_sigma=10, threshold=.2,
290
             overlap=.5, log_scale=False):
291
    """Finds blobs in the given grayscale image.
292
293
    Blobs are found using the Laplacian of Gaussian (LoG) method [1]_.
294
    For each blob found, the method returns its coordinates and the standard
295
    deviation of the Gaussian kernel that detected the blob.
296
297
    Parameters
298
    ----------
299
    image : 2D or 3D ndarray
300
        Input grayscale image, blobs are assumed to be light on dark
301
        background (white on black).
302
    min_sigma : float, optional
303
        The minimum standard deviation for Gaussian Kernel. Keep this low to
304
        detect smaller blobs.
305
    max_sigma : float, optional
306
        The maximum standard deviation for Gaussian Kernel. Keep this high to
307
        detect larger blobs.
308
    num_sigma : int, optional
309
        The number of intermediate values of standard deviations to consider
310
        between `min_sigma` and `max_sigma`.
311
    threshold : float, optional.
312
        The absolute lower bound for scale space maxima. Local maxima smaller
313
        than thresh are ignored. Reduce this to detect blobs with less
314
        intensities.
315
    overlap : float, optional
316
        A value between 0 and 1. If the area of two blobs overlaps by a
317
        fraction greater than `threshold`, the smaller blob is eliminated.
318
    log_scale : bool, optional
319
        If set intermediate values of standard deviations are interpolated
320
        using a logarithmic scale to the base `10`. If not, linear
321
        interpolation is used.
322
323
    Returns
324
    -------
325
    A : (n, image.ndim + 1) ndarray
326
        A 2d array with each row representing 3 values for a 2D image,
327
        and 4 values for a 3D image: ``(r, c, sigma)`` or ``(f, r, c, sigma)``
328
        where ``(r, c)`` or ``(f, r, c)`` are coordinates of the blob and
329
        ``sigma`` is the standard deviation of the Gaussian kernel which
330
        detected the blob.
331
332
    References
333
    ----------
334
    .. [1] http://en.wikipedia.org/wiki/Blob_detection#The_Laplacian_of_Gaussian
335
336
    Examples
337
    --------
338
    >>> from skimage import data, feature, exposure
339
    >>> img = data.coins()
340
    >>> img = exposure.equalize_hist(img)  # improves detection
341
    >>> feature.blob_log(img, threshold = .3)
342
    array([[ 113.        ,  323.        ,    1.        ],
343
           [ 121.        ,  272.        ,   17.33333333],
344
           [ 124.        ,  336.        ,   11.88888889],
345
           [ 126.        ,   46.        ,   11.88888889],
346
           [ 126.        ,  208.        ,   11.88888889],
347
           [ 127.        ,  102.        ,   11.88888889],
348
           [ 128.        ,  154.        ,   11.88888889],
349
           [ 185.        ,  344.        ,   17.33333333],
350
           [ 194.        ,  213.        ,   17.33333333],
351
           [ 194.        ,  276.        ,   17.33333333],
352
           [ 197.        ,   44.        ,   11.88888889],
353
           [ 198.        ,  103.        ,   11.88888889],
354
           [ 198.        ,  155.        ,   11.88888889],
355
           [ 260.        ,  174.        ,   17.33333333],
356
           [ 263.        ,  244.        ,   17.33333333],
357
           [ 263.        ,  302.        ,   17.33333333],
358
           [ 266.        ,  115.        ,   11.88888889]])
359
360
    Notes
361
    -----
362
    The radius of each blob is approximately :math:`\sqrt{2}sigma` for
363
    a 2-D image and :math:`\sqrt{3}sigma` for a 3-D image.
364
    """
365
    image = img_as_float(image)
366
367
    if log_scale:
368
        start, stop = log(min_sigma, 10), log(max_sigma, 10)
369
        sigma_list = np.logspace(start, stop, num_sigma)
370
    else:
371
        sigma_list = np.linspace(min_sigma, max_sigma, num_sigma)
372
373
    # computing gaussian laplace
374
    # s**2 provides scale invariance
375
    gl_images = [-gaussian_laplace(image, s) * s ** 2 for s in sigma_list]
376
377
    # Replace by image_cube = np.stack(hessian_images, axis=-1)
378
    # When we upgrade minimal requirements to NumPy 1.10
379
    sl = (slice(None),) * image.ndim + (np.newaxis,)
380
    arrays = [np.asanyarray(arr) for arr in gl_images]
381
    extended_arrays = [arr[sl] for arr in arrays]
382
    image_cube = np.concatenate(extended_arrays, axis=-1)
383
384
    local_maxima = peak_local_max(image_cube, threshold_abs=threshold,
385
                                  footprint=np.ones((3,) * (image.ndim + 1)),
386
                                  threshold_rel=0.0,
387
                                  exclude_border=False)
388
389
    # Convert local_maxima to float64
390
    lm = local_maxima.astype(np.float64)
391
    # Convert the last index to its corresponding scale value
392
    lm[:, -1] = sigma_list[local_maxima[:, -1]]
393
    return _prune_blobs(lm, overlap)