scikit-image
646 строк · 21.5 Кб
1import itertools
2
3import numpy as np
4import pytest
5from numpy.testing import assert_array_almost_equal, assert_array_equal, assert_equal
6from scipy import ndimage as ndi
7
8from skimage._shared._warnings import expected_warnings
9from skimage.feature import peak
10
11
12np.random.seed(21)
13
14
15class TestPeakLocalMax:
16def test_trivial_case(self):
17trivial = np.zeros((25, 25))
18peak_indices = peak.peak_local_max(trivial, min_distance=1)
19assert type(peak_indices) is np.ndarray
20assert peak_indices.size == 0
21
22def test_noisy_peaks(self):
23peak_locations = [(7, 7), (7, 13), (13, 7), (13, 13)]
24
25# image with noise of amplitude 0.8 and peaks of amplitude 1
26image = 0.8 * np.random.rand(20, 20)
27for r, c in peak_locations:
28image[r, c] = 1
29
30peaks_detected = peak.peak_local_max(image, min_distance=5)
31
32assert len(peaks_detected) == len(peak_locations)
33for loc in peaks_detected:
34assert tuple(loc) in peak_locations
35
36def test_relative_threshold(self):
37image = np.zeros((5, 5), dtype=np.uint8)
38image[1, 1] = 10
39image[3, 3] = 20
40peaks = peak.peak_local_max(image, min_distance=1, threshold_rel=0.5)
41assert len(peaks) == 1
42assert_array_almost_equal(peaks, [(3, 3)])
43
44def test_absolute_threshold(self):
45image = np.zeros((5, 5), dtype=np.uint8)
46image[1, 1] = 10
47image[3, 3] = 20
48peaks = peak.peak_local_max(image, min_distance=1, threshold_abs=10)
49assert len(peaks) == 1
50assert_array_almost_equal(peaks, [(3, 3)])
51
52def test_constant_image(self):
53image = np.full((20, 20), 128, dtype=np.uint8)
54peaks = peak.peak_local_max(image, min_distance=1)
55assert len(peaks) == 0
56
57def test_flat_peak(self):
58image = np.zeros((5, 5), dtype=np.uint8)
59image[1:3, 1:3] = 10
60peaks = peak.peak_local_max(image, min_distance=1)
61assert len(peaks) == 4
62
63def test_sorted_peaks(self):
64image = np.zeros((5, 5), dtype=np.uint8)
65image[1, 1] = 20
66image[3, 3] = 10
67peaks = peak.peak_local_max(image, min_distance=1)
68assert peaks.tolist() == [[1, 1], [3, 3]]
69
70image = np.zeros((3, 10))
71image[1, (1, 3, 5, 7)] = (1, 2, 3, 4)
72peaks = peak.peak_local_max(image, min_distance=1)
73assert peaks.tolist() == [[1, 7], [1, 5], [1, 3], [1, 1]]
74
75def test_num_peaks(self):
76image = np.zeros((7, 7), dtype=np.uint8)
77image[1, 1] = 10
78image[1, 3] = 11
79image[1, 5] = 12
80image[3, 5] = 8
81image[5, 3] = 7
82assert len(peak.peak_local_max(image, min_distance=1, threshold_abs=0)) == 5
83peaks_limited = peak.peak_local_max(
84image, min_distance=1, threshold_abs=0, num_peaks=2
85)
86assert len(peaks_limited) == 2
87assert (1, 3) in peaks_limited
88assert (1, 5) in peaks_limited
89peaks_limited = peak.peak_local_max(
90image, min_distance=1, threshold_abs=0, num_peaks=4
91)
92assert len(peaks_limited) == 4
93assert (1, 3) in peaks_limited
94assert (1, 5) in peaks_limited
95assert (1, 1) in peaks_limited
96assert (3, 5) in peaks_limited
97
98def test_num_peaks_and_labels(self):
99image = np.zeros((7, 7), dtype=np.uint8)
100labels = np.zeros((7, 7), dtype=np.uint8) + 20
101image[1, 1] = 10
102image[1, 3] = 11
103image[1, 5] = 12
104image[3, 5] = 8
105image[5, 3] = 7
106peaks_limited = peak.peak_local_max(
107image, min_distance=1, threshold_abs=0, labels=labels
108)
109assert len(peaks_limited) == 5
110peaks_limited = peak.peak_local_max(
111image, min_distance=1, threshold_abs=0, labels=labels, num_peaks=2
112)
113assert len(peaks_limited) == 2
114
115def test_num_peaks_tot_vs_labels_4quadrants(self):
116np.random.seed(21)
117image = np.random.uniform(size=(20, 30))
118i, j = np.mgrid[0:20, 0:30]
119labels = 1 + (i >= 10) + (j >= 15) * 2
120result = peak.peak_local_max(
121image,
122labels=labels,
123min_distance=1,
124threshold_rel=0,
125num_peaks=np.inf,
126num_peaks_per_label=2,
127)
128assert len(result) == 8
129result = peak.peak_local_max(
130image,
131labels=labels,
132min_distance=1,
133threshold_rel=0,
134num_peaks=np.inf,
135num_peaks_per_label=1,
136)
137assert len(result) == 4
138result = peak.peak_local_max(
139image,
140labels=labels,
141min_distance=1,
142threshold_rel=0,
143num_peaks=2,
144num_peaks_per_label=2,
145)
146assert len(result) == 2
147
148def test_num_peaks3D(self):
149# Issue 1354: the old code only hold for 2D arrays
150# and this code would die with IndexError
151image = np.zeros((10, 10, 100))
152image[5, 5, ::5] = np.arange(20)
153peaks_limited = peak.peak_local_max(image, min_distance=1, num_peaks=2)
154assert len(peaks_limited) == 2
155
156def test_reorder_labels(self):
157image = np.random.uniform(size=(40, 60))
158i, j = np.mgrid[0:40, 0:60]
159labels = 1 + (i >= 20) + (j >= 30) * 2
160labels[labels == 4] = 5
161i, j = np.mgrid[-3:4, -3:4]
162footprint = i * i + j * j <= 9
163expected = np.zeros(image.shape, float)
164for imin, imax in ((0, 20), (20, 40)):
165for jmin, jmax in ((0, 30), (30, 60)):
166expected[imin:imax, jmin:jmax] = ndi.maximum_filter(
167image[imin:imax, jmin:jmax], footprint=footprint
168)
169expected = expected == image
170peak_idx = peak.peak_local_max(
171image,
172labels=labels,
173min_distance=1,
174threshold_rel=0,
175footprint=footprint,
176exclude_border=False,
177)
178result = np.zeros_like(expected, dtype=bool)
179result[tuple(peak_idx.T)] = True
180assert (result == expected).all()
181
182def test_indices_with_labels(self):
183image = np.random.uniform(size=(40, 60))
184i, j = np.mgrid[0:40, 0:60]
185labels = 1 + (i >= 20) + (j >= 30) * 2
186i, j = np.mgrid[-3:4, -3:4]
187footprint = i * i + j * j <= 9
188expected = np.zeros(image.shape, float)
189for imin, imax in ((0, 20), (20, 40)):
190for jmin, jmax in ((0, 30), (30, 60)):
191expected[imin:imax, jmin:jmax] = ndi.maximum_filter(
192image[imin:imax, jmin:jmax], footprint=footprint
193)
194expected = np.stack(np.nonzero(expected == image), axis=-1)
195expected = expected[np.argsort(image[tuple(expected.T)])[::-1]]
196result = peak.peak_local_max(
197image,
198labels=labels,
199min_distance=1,
200threshold_rel=0,
201footprint=footprint,
202exclude_border=False,
203)
204result = result[np.argsort(image[tuple(result.T)])[::-1]]
205assert (result == expected).all()
206
207def test_ndarray_exclude_border(self):
208nd_image = np.zeros((5, 5, 5))
209nd_image[[1, 0, 0], [0, 1, 0], [0, 0, 1]] = 1
210nd_image[3, 0, 0] = 1
211nd_image[2, 2, 2] = 1
212expected = np.array([[2, 2, 2]], dtype=int)
213expectedNoBorder = np.array([[0, 0, 1], [2, 2, 2], [3, 0, 0]], dtype=int)
214result = peak.peak_local_max(nd_image, min_distance=2, exclude_border=2)
215assert_array_equal(result, expected)
216# Check that bools work as expected
217assert_array_equal(
218peak.peak_local_max(nd_image, min_distance=2, exclude_border=2),
219peak.peak_local_max(nd_image, min_distance=2, exclude_border=True),
220)
221assert_array_equal(
222peak.peak_local_max(nd_image, min_distance=2, exclude_border=0),
223peak.peak_local_max(nd_image, min_distance=2, exclude_border=False),
224)
225
226# Check both versions with no border
227result = peak.peak_local_max(nd_image, min_distance=2, exclude_border=0)
228assert_array_equal(result, expectedNoBorder)
229peak_idx = peak.peak_local_max(nd_image, exclude_border=False)
230result = np.zeros_like(nd_image, dtype=bool)
231result[tuple(peak_idx.T)] = True
232assert_array_equal(result, nd_image.astype(bool))
233
234def test_empty(self):
235image = np.zeros((10, 20))
236labels = np.zeros((10, 20), int)
237result = peak.peak_local_max(
238image,
239labels=labels,
240footprint=np.ones((3, 3), bool),
241min_distance=1,
242threshold_rel=0,
243exclude_border=False,
244)
245assert result.shape == (0, image.ndim)
246
247def test_empty_non2d_indices(self):
248image = np.zeros((10, 10, 10))
249result = peak.peak_local_max(
250image,
251footprint=np.ones((3, 3, 3), bool),
252min_distance=1,
253threshold_rel=0,
254exclude_border=False,
255)
256assert result.shape == (0, image.ndim)
257
258def test_one_point(self):
259image = np.zeros((10, 20))
260labels = np.zeros((10, 20), int)
261image[5, 5] = 1
262labels[5, 5] = 1
263peak_idx = peak.peak_local_max(
264image,
265labels=labels,
266footprint=np.ones((3, 3), bool),
267min_distance=1,
268threshold_rel=0,
269exclude_border=False,
270)
271result = np.zeros_like(image, dtype=bool)
272result[tuple(peak_idx.T)] = True
273assert np.all(result == (labels == 1))
274
275def test_adjacent_and_same(self):
276image = np.zeros((10, 20))
277labels = np.zeros((10, 20), int)
278image[5, 5:6] = 1
279labels[5, 5:6] = 1
280expected = np.stack(np.where(labels == 1), axis=-1)
281result = peak.peak_local_max(
282image,
283labels=labels,
284footprint=np.ones((3, 3), bool),
285min_distance=1,
286threshold_rel=0,
287exclude_border=False,
288)
289assert_array_equal(result, expected)
290
291def test_adjacent_and_different(self):
292image = np.zeros((10, 20))
293labels = np.zeros((10, 20), int)
294image[5, 5] = 1
295image[5, 6] = 0.5
296labels[5, 5:6] = 1
297expected = np.stack(np.where(image == 1), axis=-1)
298result = peak.peak_local_max(
299image,
300labels=labels,
301footprint=np.ones((3, 3), bool),
302min_distance=1,
303threshold_rel=0,
304exclude_border=False,
305)
306assert_array_equal(result, expected)
307result = peak.peak_local_max(
308image, labels=labels, min_distance=1, threshold_rel=0, exclude_border=False
309)
310assert_array_equal(result, expected)
311
312def test_not_adjacent_and_different(self):
313image = np.zeros((10, 20))
314labels = np.zeros((10, 20), int)
315image[5, 5] = 1
316image[5, 8] = 0.5
317labels[image > 0] = 1
318expected = np.stack(np.where(labels == 1), axis=-1)
319result = peak.peak_local_max(
320image,
321labels=labels,
322footprint=np.ones((3, 3), bool),
323min_distance=1,
324threshold_rel=0,
325exclude_border=False,
326)
327assert_array_equal(result, expected)
328
329def test_two_objects(self):
330image = np.zeros((10, 20))
331labels = np.zeros((10, 20), int)
332image[5, 5] = 1
333image[5, 15] = 0.5
334labels[5, 5] = 1
335labels[5, 15] = 2
336expected = np.stack(np.where(labels > 0), axis=-1)
337result = peak.peak_local_max(
338image,
339labels=labels,
340footprint=np.ones((3, 3), bool),
341min_distance=1,
342threshold_rel=0,
343exclude_border=False,
344)
345assert_array_equal(result, expected)
346
347def test_adjacent_different_objects(self):
348image = np.zeros((10, 20))
349labels = np.zeros((10, 20), int)
350image[5, 5] = 1
351image[5, 6] = 0.5
352labels[5, 5] = 1
353labels[5, 6] = 2
354expected = np.stack(np.where(labels > 0), axis=-1)
355result = peak.peak_local_max(
356image,
357labels=labels,
358footprint=np.ones((3, 3), bool),
359min_distance=1,
360threshold_rel=0,
361exclude_border=False,
362)
363assert_array_equal(result, expected)
364
365def test_four_quadrants(self):
366image = np.random.uniform(size=(20, 30))
367i, j = np.mgrid[0:20, 0:30]
368labels = 1 + (i >= 10) + (j >= 15) * 2
369i, j = np.mgrid[-3:4, -3:4]
370footprint = i * i + j * j <= 9
371expected = np.zeros(image.shape, float)
372for imin, imax in ((0, 10), (10, 20)):
373for jmin, jmax in ((0, 15), (15, 30)):
374expected[imin:imax, jmin:jmax] = ndi.maximum_filter(
375image[imin:imax, jmin:jmax], footprint=footprint
376)
377expected = expected == image
378peak_idx = peak.peak_local_max(
379image,
380labels=labels,
381footprint=footprint,
382min_distance=1,
383threshold_rel=0,
384exclude_border=False,
385)
386result = np.zeros_like(image, dtype=bool)
387result[tuple(peak_idx.T)] = True
388assert np.all(result == expected)
389
390def test_disk(self):
391'''regression test of img-1194, footprint = [1]
392Test peak.peak_local_max when every point is a local maximum
393'''
394image = np.random.uniform(size=(10, 20))
395footprint = np.array([[1]])
396peak_idx = peak.peak_local_max(
397image,
398labels=np.ones((10, 20), int),
399footprint=footprint,
400min_distance=1,
401threshold_rel=0,
402threshold_abs=-1,
403exclude_border=False,
404)
405result = np.zeros_like(image, dtype=bool)
406result[tuple(peak_idx.T)] = True
407assert np.all(result)
408peak_idx = peak.peak_local_max(
409image, footprint=footprint, threshold_abs=-1, exclude_border=False
410)
411result = np.zeros_like(image, dtype=bool)
412result[tuple(peak_idx.T)] = True
413assert np.all(result)
414
415def test_3D(self):
416image = np.zeros((30, 30, 30))
417image[15, 15, 15] = 1
418image[5, 5, 5] = 1
419assert_array_equal(
420peak.peak_local_max(image, min_distance=10, threshold_rel=0), [[15, 15, 15]]
421)
422assert_array_equal(
423peak.peak_local_max(image, min_distance=6, threshold_rel=0), [[15, 15, 15]]
424)
425assert sorted(
426peak.peak_local_max(
427image, min_distance=10, threshold_rel=0, exclude_border=False
428).tolist()
429) == [[5, 5, 5], [15, 15, 15]]
430assert sorted(
431peak.peak_local_max(image, min_distance=5, threshold_rel=0).tolist()
432) == [[5, 5, 5], [15, 15, 15]]
433
434def test_4D(self):
435image = np.zeros((30, 30, 30, 30))
436image[15, 15, 15, 15] = 1
437image[5, 5, 5, 5] = 1
438assert_array_equal(
439peak.peak_local_max(image, min_distance=10, threshold_rel=0),
440[[15, 15, 15, 15]],
441)
442assert_array_equal(
443peak.peak_local_max(image, min_distance=6, threshold_rel=0),
444[[15, 15, 15, 15]],
445)
446assert sorted(
447peak.peak_local_max(
448image, min_distance=10, threshold_rel=0, exclude_border=False
449).tolist()
450) == [[5, 5, 5, 5], [15, 15, 15, 15]]
451assert sorted(
452peak.peak_local_max(image, min_distance=5, threshold_rel=0).tolist()
453) == [[5, 5, 5, 5], [15, 15, 15, 15]]
454
455def test_threshold_rel_default(self):
456image = np.ones((5, 5))
457
458image[2, 2] = 1
459assert len(peak.peak_local_max(image)) == 0
460
461image[2, 2] = 2
462assert_array_equal(peak.peak_local_max(image), [[2, 2]])
463
464image[2, 2] = 0
465with expected_warnings(["When min_distance < 1"]):
466assert len(peak.peak_local_max(image, min_distance=0)) == image.size - 1
467
468def test_peak_at_border(self):
469image = np.full((10, 10), -2)
470image[2, 4] = -1
471image[3, 0] = -1
472
473peaks = peak.peak_local_max(image, min_distance=3)
474assert peaks.size == 0
475
476peaks = peak.peak_local_max(image, min_distance=3, exclude_border=0)
477assert len(peaks) == 2
478assert [2, 4] in peaks
479assert [3, 0] in peaks
480
481
482@pytest.mark.parametrize(
483["indices"],
484[[indices] for indices in itertools.product(range(5), range(5))],
485)
486def test_exclude_border(indices):
487image = np.zeros((5, 5))
488image[indices] = 1
489
490# exclude_border = False, means it will always be found.
491assert len(peak.peak_local_max(image, exclude_border=False)) == 1
492
493# exclude_border = 0, means it will always be found.
494assert len(peak.peak_local_max(image, exclude_border=0)) == 1
495
496# exclude_border = True, min_distance=1 means it will be found unless it's
497# on the edge.
498if indices[0] in (0, 4) or indices[1] in (0, 4):
499expected_peaks = 0
500else:
501expected_peaks = 1
502assert (
503len(peak.peak_local_max(image, min_distance=1, exclude_border=True))
504== expected_peaks
505)
506
507# exclude_border = (1, 0) means it will be found unless it's on the edge of
508# the first dimension.
509if indices[0] in (0, 4):
510expected_peaks = 0
511else:
512expected_peaks = 1
513assert len(peak.peak_local_max(image, exclude_border=(1, 0))) == expected_peaks
514
515# exclude_border = (0, 1) means it will be found unless it's on the edge of
516# the second dimension.
517if indices[1] in (0, 4):
518expected_peaks = 0
519else:
520expected_peaks = 1
521assert len(peak.peak_local_max(image, exclude_border=(0, 1))) == expected_peaks
522
523
524def test_exclude_border_errors():
525image = np.zeros((5, 5))
526
527# exclude_border doesn't have the right cardinality.
528with pytest.raises(ValueError):
529assert peak.peak_local_max(image, exclude_border=(1,))
530
531# exclude_border doesn't have the right type
532with pytest.raises(TypeError):
533assert peak.peak_local_max(image, exclude_border=1.0)
534
535# exclude_border is a tuple of the right cardinality but contains
536# non-integer values.
537with pytest.raises(ValueError):
538assert peak.peak_local_max(image, exclude_border=(1, 'a'))
539
540# exclude_border is a tuple of the right cardinality but contains a
541# negative value.
542with pytest.raises(ValueError):
543assert peak.peak_local_max(image, exclude_border=(1, -1))
544
545# exclude_border is a negative value.
546with pytest.raises(ValueError):
547assert peak.peak_local_max(image, exclude_border=-1)
548
549
550def test_input_values_with_labels():
551# Issue #5235: input values may be modified when labels are used
552
553img = np.random.rand(128, 128)
554labels = np.zeros((128, 128), int)
555
556labels[10:20, 10:20] = 1
557labels[12:16, 12:16] = 0
558
559img_before = img.copy()
560
561_ = peak.peak_local_max(img, labels=labels)
562
563assert_array_equal(img, img_before)
564
565
566class TestProminentPeaks:
567def test_isolated_peaks(self):
568image = np.zeros((15, 15))
569x0, y0, i0 = (12, 8, 1)
570x1, y1, i1 = (2, 2, 1)
571x2, y2, i2 = (5, 13, 1)
572image[y0, x0] = i0
573image[y1, x1] = i1
574image[y2, x2] = i2
575out = peak._prominent_peaks(image)
576assert len(out[0]) == 3
577for i, x, y in zip(out[0], out[1], out[2]):
578assert i in (i0, i1, i2)
579assert x in (x0, x1, x2)
580assert y in (y0, y1, y2)
581
582def test_threshold(self):
583image = np.zeros((15, 15))
584x0, y0, i0 = (12, 8, 10)
585x1, y1, i1 = (2, 2, 8)
586x2, y2, i2 = (5, 13, 10)
587image[y0, x0] = i0
588image[y1, x1] = i1
589image[y2, x2] = i2
590out = peak._prominent_peaks(image, threshold=None)
591assert len(out[0]) == 3
592for i, x, y in zip(out[0], out[1], out[2]):
593assert i in (i0, i1, i2)
594assert x in (x0, x1, x2)
595out = peak._prominent_peaks(image, threshold=9)
596assert len(out[0]) == 2
597for i, x, y in zip(out[0], out[1], out[2]):
598assert i in (i0, i2)
599assert x in (x0, x2)
600assert y in (y0, y2)
601
602def test_peaks_in_contact(self):
603image = np.zeros((15, 15))
604x0, y0, i0 = (8, 8, 1)
605x1, y1, i1 = (7, 7, 1) # prominent peak
606x2, y2, i2 = (6, 6, 1)
607image[y0, x0] = i0
608image[y1, x1] = i1
609image[y2, x2] = i2
610out = peak._prominent_peaks(
611image,
612min_xdistance=3,
613min_ydistance=3,
614)
615assert_equal(out[0], np.array((i1,)))
616assert_equal(out[1], np.array((x1,)))
617assert_equal(out[2], np.array((y1,)))
618
619def test_input_labels_unmodified(self):
620image = np.zeros((10, 20))
621labels = np.zeros((10, 20), int)
622image[5, 5] = 1
623labels[5, 5] = 3
624labelsin = labels.copy()
625peak.peak_local_max(
626image,
627labels=labels,
628footprint=np.ones((3, 3), bool),
629min_distance=1,
630threshold_rel=0,
631exclude_border=False,
632)
633assert np.all(labels == labelsin)
634
635def test_many_objects(self):
636mask = np.zeros([500, 500], dtype=bool)
637x, y = np.indices((500, 500))
638x_c = x // 20 * 20 + 10
639y_c = y // 20 * 20 + 10
640mask[(x - x_c) ** 2 + (y - y_c) ** 2 < 8**2] = True
641labels, num_objs = ndi.label(mask)
642dist = ndi.distance_transform_edt(mask)
643local_max = peak.peak_local_max(
644dist, min_distance=20, exclude_border=False, labels=labels
645)
646assert len(local_max) == 625
647