scikit-image
588 строк · 15.8 Кб
1import math
2
3import numpy as np
4import pytest
5from numpy.testing import assert_almost_equal
6
7from skimage import feature
8from skimage.draw import disk
9from skimage.draw.draw3d import ellipsoid
10from skimage.feature import blob_dog, blob_doh, blob_log
11from skimage.feature.blob import _blob_overlap
12
13
14@pytest.mark.parametrize('dtype', [np.uint8, np.float16, np.float32, np.float64])
15@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
16def test_blob_dog(dtype, threshold_type):
17r2 = math.sqrt(2)
18img = np.ones((512, 512), dtype=dtype)
19
20xs, ys = disk((400, 130), 5)
21img[xs, ys] = 255
22
23xs, ys = disk((100, 300), 25)
24img[xs, ys] = 255
25
26xs, ys = disk((200, 350), 45)
27img[xs, ys] = 255
28
29if threshold_type == 'absolute':
30threshold = 2.0
31if img.dtype.kind != 'f':
32# account for internal scaling to [0, 1] by img_as_float
33threshold /= np.ptp(img)
34threshold_rel = None
35elif threshold_type == 'relative':
36threshold = None
37threshold_rel = 0.5
38
39blobs = blob_dog(
40img,
41min_sigma=4,
42max_sigma=50,
43threshold=threshold,
44threshold_rel=threshold_rel,
45)
46
47def radius(x):
48return r2 * x[2]
49
50s = sorted(blobs, key=radius)
51thresh = 5
52ratio_thresh = 0.25
53
54b = s[0]
55assert abs(b[0] - 400) <= thresh
56assert abs(b[1] - 130) <= thresh
57assert abs(radius(b) - 5) <= ratio_thresh * 5
58
59b = s[1]
60assert abs(b[0] - 100) <= thresh
61assert abs(b[1] - 300) <= thresh
62assert abs(radius(b) - 25) <= ratio_thresh * 25
63
64b = s[2]
65assert abs(b[0] - 200) <= thresh
66assert abs(b[1] - 350) <= thresh
67assert abs(radius(b) - 45) <= ratio_thresh * 45
68
69# Testing no peaks
70img_empty = np.zeros((100, 100), dtype=dtype)
71assert blob_dog(img_empty).size == 0
72
73
74@pytest.mark.parametrize('dtype', [np.uint8, np.float16, np.float32, np.float64])
75@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
76def test_blob_dog_3d(dtype, threshold_type):
77# Testing 3D
78r = 10
79pad = 10
80im3 = ellipsoid(r, r, r)
81im3 = np.pad(im3, pad, mode='constant')
82
83if threshold_type == 'absolute':
84threshold = 0.001
85threshold_rel = 0
86elif threshold_type == 'relative':
87threshold = 0
88threshold_rel = 0.5
89
90blobs = blob_dog(
91im3,
92min_sigma=3,
93max_sigma=10,
94sigma_ratio=1.2,
95threshold=threshold,
96threshold_rel=threshold_rel,
97)
98b = blobs[0]
99
100assert b.shape == (4,)
101assert b[0] == r + pad + 1
102assert b[1] == r + pad + 1
103assert b[2] == r + pad + 1
104assert abs(math.sqrt(3) * b[3] - r) < 1.1
105
106
107@pytest.mark.parametrize('dtype', [np.uint8, np.float16, np.float32, np.float64])
108@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
109def test_blob_dog_3d_anisotropic(dtype, threshold_type):
110# Testing 3D anisotropic
111r = 10
112pad = 10
113im3 = ellipsoid(r / 2, r, r)
114im3 = np.pad(im3, pad, mode='constant')
115
116if threshold_type == 'absolute':
117threshold = 0.001
118threshold_rel = None
119elif threshold_type == 'relative':
120threshold = None
121threshold_rel = 0.5
122
123blobs = blob_dog(
124im3.astype(dtype, copy=False),
125min_sigma=[1.5, 3, 3],
126max_sigma=[5, 10, 10],
127sigma_ratio=1.2,
128threshold=threshold,
129threshold_rel=threshold_rel,
130)
131b = blobs[0]
132
133assert b.shape == (6,)
134assert b[0] == r / 2 + pad + 1
135assert b[1] == r + pad + 1
136assert b[2] == r + pad + 1
137assert abs(math.sqrt(3) * b[3] - r / 2) < 1.1
138assert abs(math.sqrt(3) * b[4] - r) < 1.1
139assert abs(math.sqrt(3) * b[5] - r) < 1.1
140
141
142@pytest.mark.parametrize("disc_center", [(5, 5), (5, 20)])
143@pytest.mark.parametrize("exclude_border", [6, (6, 6), (4, 15)])
144def test_blob_dog_exclude_border(disc_center, exclude_border):
145# Testing exclude border
146
147# image where blob is disc_center px from borders, radius 5
148img = np.ones((512, 512))
149xs, ys = disk(disc_center, 5)
150img[xs, ys] = 255
151blobs = blob_dog(
152img,
153min_sigma=1.5,
154max_sigma=5,
155sigma_ratio=1.2,
156)
157assert blobs.shape[0] == 1, "one blob should have been detected"
158b = blobs[0]
159assert b[0] == disc_center[0], f"blob should be {disc_center[0]} px from x border"
160assert b[1] == disc_center[1], f"blob should be {disc_center[1]} px from y border"
161
162blobs = blob_dog(
163img,
164min_sigma=1.5,
165max_sigma=5,
166sigma_ratio=1.2,
167exclude_border=exclude_border,
168)
169
170if disc_center == (5, 20) and exclude_border == (4, 15):
171assert blobs.shape[0] == 1, "one blob should have been detected"
172b = blobs[0]
173assert (
174b[0] == disc_center[0]
175), f"blob should be {disc_center[0]} px from x border"
176assert (
177b[1] == disc_center[1]
178), f"blob should be {disc_center[1]} px from y border"
179else:
180msg = "zero blobs should be detected, as only blob is 5 px from border"
181assert blobs.shape[0] == 0, msg
182
183
184@pytest.mark.parametrize('anisotropic', [False, True])
185@pytest.mark.parametrize('ndim', [1, 2, 3, 4])
186@pytest.mark.parametrize('function_name', ['blob_dog', 'blob_log'])
187def test_nd_blob_no_peaks_shape(function_name, ndim, anisotropic):
188# uniform image so no blobs will be found
189z = np.zeros((16,) * ndim, dtype=np.float32)
190if anisotropic:
191max_sigma = 8 + np.arange(ndim)
192else:
193max_sigma = 8
194blob_func = getattr(feature, function_name)
195blobs = blob_func(z, max_sigma=max_sigma)
196# z.ndim + (z.ndim sigmas if anisotropic, only one sigma otherwise)
197expected_shape = 2 * z.ndim if anisotropic else z.ndim + 1
198assert blobs.shape == (0, expected_shape)
199
200
201@pytest.mark.parametrize('dtype', [np.uint8, np.float16, np.float32, np.float64])
202@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
203def test_blob_log(dtype, threshold_type):
204r2 = math.sqrt(2)
205img = np.ones((256, 256), dtype=dtype)
206
207xs, ys = disk((200, 65), 5)
208img[xs, ys] = 255
209
210xs, ys = disk((80, 25), 15)
211img[xs, ys] = 255
212
213xs, ys = disk((50, 150), 25)
214img[xs, ys] = 255
215
216xs, ys = disk((100, 175), 30)
217img[xs, ys] = 255
218
219if threshold_type == 'absolute':
220threshold = 1
221if img.dtype.kind != 'f':
222# account for internal scaling to [0, 1] by img_as_float
223threshold /= np.ptp(img)
224threshold_rel = None
225elif threshold_type == 'relative':
226threshold = None
227threshold_rel = 0.5
228
229blobs = blob_log(
230img, min_sigma=5, max_sigma=20, threshold=threshold, threshold_rel=threshold_rel
231)
232
233def radius(x):
234return r2 * x[2]
235
236s = sorted(blobs, key=radius)
237thresh = 3
238
239b = s[0]
240assert abs(b[0] - 200) <= thresh
241assert abs(b[1] - 65) <= thresh
242assert abs(radius(b) - 5) <= thresh
243
244b = s[1]
245assert abs(b[0] - 80) <= thresh
246assert abs(b[1] - 25) <= thresh
247assert abs(radius(b) - 15) <= thresh
248
249b = s[2]
250assert abs(b[0] - 50) <= thresh
251assert abs(b[1] - 150) <= thresh
252assert abs(radius(b) - 25) <= thresh
253
254b = s[3]
255assert abs(b[0] - 100) <= thresh
256assert abs(b[1] - 175) <= thresh
257assert abs(radius(b) - 30) <= thresh
258
259# Testing log scale
260blobs = blob_log(
261img,
262min_sigma=5,
263max_sigma=20,
264threshold=threshold,
265threshold_rel=threshold_rel,
266log_scale=True,
267)
268
269b = s[0]
270assert abs(b[0] - 200) <= thresh
271assert abs(b[1] - 65) <= thresh
272assert abs(radius(b) - 5) <= thresh
273
274b = s[1]
275assert abs(b[0] - 80) <= thresh
276assert abs(b[1] - 25) <= thresh
277assert abs(radius(b) - 15) <= thresh
278
279b = s[2]
280assert abs(b[0] - 50) <= thresh
281assert abs(b[1] - 150) <= thresh
282assert abs(radius(b) - 25) <= thresh
283
284b = s[3]
285assert abs(b[0] - 100) <= thresh
286assert abs(b[1] - 175) <= thresh
287assert abs(radius(b) - 30) <= thresh
288
289# Testing no peaks
290img_empty = np.zeros((100, 100))
291assert blob_log(img_empty).size == 0
292
293
294def test_blob_log_no_warnings():
295img = np.ones((11, 11))
296
297xs, ys = disk((5, 5), 2)
298img[xs, ys] = 255
299
300xs, ys = disk((7, 6), 2)
301img[xs, ys] = 255
302
303blob_log(img, max_sigma=20, num_sigma=10, threshold=0.1)
304
305
306def test_blob_log_3d():
307# Testing 3D
308r = 6
309pad = 10
310im3 = ellipsoid(r, r, r)
311im3 = np.pad(im3, pad, mode='constant')
312
313blobs = blob_log(im3, min_sigma=3, max_sigma=10)
314b = blobs[0]
315
316assert b.shape == (4,)
317assert b[0] == r + pad + 1
318assert b[1] == r + pad + 1
319assert b[2] == r + pad + 1
320assert abs(math.sqrt(3) * b[3] - r) < 1
321
322
323def test_blob_log_3d_anisotropic():
324# Testing 3D anisotropic
325r = 6
326pad = 10
327im3 = ellipsoid(r / 2, r, r)
328im3 = np.pad(im3, pad, mode='constant')
329
330blobs = blob_log(
331im3,
332min_sigma=[1, 2, 2],
333max_sigma=[5, 10, 10],
334)
335
336b = blobs[0]
337assert b.shape == (6,)
338assert b[0] == r / 2 + pad + 1
339assert b[1] == r + pad + 1
340assert b[2] == r + pad + 1
341assert abs(math.sqrt(3) * b[3] - r / 2) < 1
342assert abs(math.sqrt(3) * b[4] - r) < 1
343assert abs(math.sqrt(3) * b[5] - r) < 1
344
345
346@pytest.mark.parametrize("disc_center", [(5, 5), (5, 20)])
347@pytest.mark.parametrize("exclude_border", [6, (6, 6), (4, 15)])
348def test_blob_log_exclude_border(disc_center, exclude_border):
349# image where blob is disc_center px from borders, radius 5
350img = np.ones((512, 512))
351xs, ys = disk(disc_center, 5)
352img[xs, ys] = 255
353
354blobs = blob_log(
355img,
356min_sigma=1.5,
357max_sigma=5,
358)
359assert blobs.shape[0] == 1
360b = blobs[0]
361assert b[0] == disc_center[0], f"blob should be {disc_center[0]} px from x border"
362assert b[1] == disc_center[1], f"blob should be {disc_center[1]} px from y border"
363
364blobs = blob_log(
365img,
366min_sigma=1.5,
367max_sigma=5,
368exclude_border=exclude_border,
369)
370
371if disc_center == (5, 20) and exclude_border == (4, 15):
372assert blobs.shape[0] == 1, "one blob should have been detected"
373b = blobs[0]
374assert (
375b[0] == disc_center[0]
376), f"blob should be {disc_center[0]} px from x border"
377assert (
378b[1] == disc_center[1]
379), f"blob should be {disc_center[1]} px from y border"
380else:
381msg = "zero blobs should be detected, as only blob is 5 px from border"
382assert blobs.shape[0] == 0, msg
383
384
385@pytest.mark.parametrize("dtype", [np.uint8, np.float16, np.float32])
386@pytest.mark.parametrize('threshold_type', ['absolute', 'relative'])
387def test_blob_doh(dtype, threshold_type):
388img = np.ones((512, 512), dtype=dtype)
389
390xs, ys = disk((400, 130), 20)
391img[xs, ys] = 255
392
393xs, ys = disk((460, 50), 30)
394img[xs, ys] = 255
395
396xs, ys = disk((100, 300), 40)
397img[xs, ys] = 255
398
399xs, ys = disk((200, 350), 50)
400img[xs, ys] = 255
401
402if threshold_type == 'absolute':
403# Note: have to either scale up threshold or rescale the image to the
404# range [0, 1] internally.
405threshold = 0.05
406if img.dtype.kind == 'f':
407# account for lack of internal scaling to [0, 1] by img_as_float
408ptp = np.ptp(img)
409threshold *= ptp**2
410threshold_rel = None
411elif threshold_type == 'relative':
412threshold = None
413threshold_rel = 0.5
414
415blobs = blob_doh(
416img,
417min_sigma=1,
418max_sigma=60,
419num_sigma=10,
420threshold=threshold,
421threshold_rel=threshold_rel,
422)
423
424def radius(x):
425return x[2]
426
427s = sorted(blobs, key=radius)
428thresh = 4
429
430b = s[0]
431assert abs(b[0] - 400) <= thresh
432assert abs(b[1] - 130) <= thresh
433assert abs(radius(b) - 20) <= thresh
434
435b = s[1]
436assert abs(b[0] - 460) <= thresh
437assert abs(b[1] - 50) <= thresh
438assert abs(radius(b) - 30) <= thresh
439
440b = s[2]
441assert abs(b[0] - 100) <= thresh
442assert abs(b[1] - 300) <= thresh
443assert abs(radius(b) - 40) <= thresh
444
445b = s[3]
446assert abs(b[0] - 200) <= thresh
447assert abs(b[1] - 350) <= thresh
448assert abs(radius(b) - 50) <= thresh
449
450
451def test_blob_doh_log_scale():
452img = np.ones((512, 512), dtype=np.uint8)
453
454xs, ys = disk((400, 130), 20)
455img[xs, ys] = 255
456
457xs, ys = disk((460, 50), 30)
458img[xs, ys] = 255
459
460xs, ys = disk((100, 300), 40)
461img[xs, ys] = 255
462
463xs, ys = disk((200, 350), 50)
464img[xs, ys] = 255
465
466blobs = blob_doh(
467img, min_sigma=1, max_sigma=60, num_sigma=10, log_scale=True, threshold=0.05
468)
469
470def radius(x):
471return x[2]
472
473s = sorted(blobs, key=radius)
474thresh = 10
475
476b = s[0]
477assert abs(b[0] - 400) <= thresh
478assert abs(b[1] - 130) <= thresh
479assert abs(radius(b) - 20) <= thresh
480
481b = s[2]
482assert abs(b[0] - 460) <= thresh
483assert abs(b[1] - 50) <= thresh
484assert abs(radius(b) - 30) <= thresh
485
486b = s[1]
487assert abs(b[0] - 100) <= thresh
488assert abs(b[1] - 300) <= thresh
489assert abs(radius(b) - 40) <= thresh
490
491b = s[3]
492assert abs(b[0] - 200) <= thresh
493assert abs(b[1] - 350) <= thresh
494assert abs(radius(b) - 50) <= thresh
495
496
497def test_blob_doh_no_peaks():
498# Testing no peaks
499img_empty = np.zeros((100, 100))
500assert blob_doh(img_empty).size == 0
501
502
503def test_blob_doh_overlap():
504img = np.ones((256, 256), dtype=np.uint8)
505
506xs, ys = disk((100, 100), 20)
507img[xs, ys] = 255
508
509xs, ys = disk((120, 100), 30)
510img[xs, ys] = 255
511
512blobs = blob_doh(img, min_sigma=1, max_sigma=60, num_sigma=10, threshold=0.05)
513
514assert len(blobs) == 1
515
516
517def test_blob_log_overlap_3d():
518r1, r2 = 7, 6
519pad1, pad2 = 11, 12
520blob1 = ellipsoid(r1, r1, r1)
521blob1 = np.pad(blob1, pad1, mode='constant')
522blob2 = ellipsoid(r2, r2, r2)
523blob2 = np.pad(
524blob2, [(pad2, pad2), (pad2 - 9, pad2 + 9), (pad2, pad2)], mode='constant'
525)
526im3 = np.logical_or(blob1, blob2)
527
528blobs = blob_log(im3, min_sigma=2, max_sigma=10, overlap=0.1)
529assert len(blobs) == 1
530
531
532def test_blob_overlap_3d_anisotropic():
533# Two spheres with distance between centers equal to radius
534# One sphere is much smaller than the other so about half of it is within
535# the bigger sphere.
536s3 = math.sqrt(3)
537overlap = _blob_overlap(
538np.array([0, 0, 0, 2 / s3, 10 / s3, 10 / s3]),
539np.array([0, 0, 10, 0.2 / s3, 1 / s3, 1 / s3]),
540sigma_dim=3,
541)
542assert_almost_equal(overlap, 0.48125)
543overlap = _blob_overlap(
544np.array([0, 0, 0, 2 / s3, 10 / s3, 10 / s3]),
545np.array([2, 0, 0, 0.2 / s3, 1 / s3, 1 / s3]),
546sigma_dim=3,
547)
548assert_almost_equal(overlap, 0.48125)
549
550
551def test_blob_log_anisotropic():
552image = np.zeros((50, 50))
553image[20, 10:20] = 1
554isotropic_blobs = blob_log(image, min_sigma=0.5, max_sigma=2, num_sigma=3)
555assert len(isotropic_blobs) > 1 # many small blobs found in line
556ani_blobs = blob_log(
557image, min_sigma=[0.5, 5], max_sigma=[2, 20], num_sigma=3
558) # 10x anisotropy, line is 1x10
559assert len(ani_blobs) == 1 # single anisotropic blob found
560
561
562def test_blob_log_overlap_3d_anisotropic():
563r1, r2 = 7, 6
564pad1, pad2 = 11, 12
565blob1 = ellipsoid(r1, r1, r1)
566blob1 = np.pad(blob1, pad1, mode='constant')
567blob2 = ellipsoid(r2, r2, r2)
568blob2 = np.pad(
569blob2, [(pad2, pad2), (pad2 - 9, pad2 + 9), (pad2, pad2)], mode='constant'
570)
571im3 = np.logical_or(blob1, blob2)
572
573blobs = blob_log(im3, min_sigma=[2, 2.01, 2.005], max_sigma=10, overlap=0.1)
574assert len(blobs) == 1
575
576# Two circles with distance between centers equal to radius
577overlap = _blob_overlap(
578np.array([0, 0, 10 / math.sqrt(2)]), np.array([0, 10, 10 / math.sqrt(2)])
579)
580assert_almost_equal(
581overlap, 1.0 / math.pi * (2 * math.acos(1.0 / 2) - math.sqrt(3) / 2.0)
582)
583
584
585def test_no_blob():
586im = np.zeros((10, 10))
587blobs = blob_log(im, min_sigma=2, max_sigma=5, num_sigma=4)
588assert len(blobs) == 0
589