scikit-image
189 строк · 6.0 Кб
1import numpy as np
2from skimage._shared.testing import assert_almost_equal, assert_equal
3
4from skimage import data, img_as_float
5from skimage.morphology import diamond
6from skimage.feature import match_template, peak_local_max
7from skimage._shared import testing
8
9
10@testing.parametrize('dtype', [np.float32, np.float64])
11def test_template(dtype):
12size = 100
13# Float prefactors ensure that image range is between 0 and 1
14image = np.full((400, 400), 0.5, dtype=dtype)
15target = 0.1 * (np.tri(size) + np.tri(size)[::-1])
16target = target.astype(dtype, copy=False)
17target_positions = [(50, 50), (200, 200)]
18for x, y in target_positions:
19image[x : x + size, y : y + size] = target
20np.random.seed(1)
21image += 0.1 * np.random.uniform(size=(400, 400)).astype(dtype, copy=False)
22
23result = match_template(image, target)
24assert result.dtype == dtype
25delta = 5
26
27positions = peak_local_max(result, min_distance=delta)
28
29if len(positions) > 2:
30# Keep the two maximum peaks.
31intensities = result[tuple(positions.T)]
32i_maxsort = np.argsort(intensities)[::-1]
33positions = positions[i_maxsort][:2]
34
35# Sort so that order matches `target_positions`.
36positions = positions[np.argsort(positions[:, 0])]
37
38for xy_target, xy in zip(target_positions, positions):
39assert_almost_equal(xy, xy_target)
40
41
42def test_normalization():
43"""Test that `match_template` gives the correct normalization.
44
45Normalization gives 1 for a perfect match and -1 for an inverted-match.
46This test adds positive and negative squares to a zero-array and matches
47the array with a positive template.
48"""
49n = 5
50N = 20
51ipos, jpos = (2, 3)
52ineg, jneg = (12, 11)
53image = np.full((N, N), 0.5)
54image[ipos : ipos + n, jpos : jpos + n] = 1
55image[ineg : ineg + n, jneg : jneg + n] = 0
56
57# white square with a black border
58template = np.zeros((n + 2, n + 2))
59template[1 : 1 + n, 1 : 1 + n] = 1
60
61result = match_template(image, template)
62
63# get the max and min results.
64sorted_result = np.argsort(result.flat)
65iflat_min = sorted_result[0]
66iflat_max = sorted_result[-1]
67min_result = np.unravel_index(iflat_min, result.shape)
68max_result = np.unravel_index(iflat_max, result.shape)
69
70# shift result by 1 because of template border
71assert np.all((np.array(min_result) + 1) == (ineg, jneg))
72assert np.all((np.array(max_result) + 1) == (ipos, jpos))
73
74assert np.allclose(result.flat[iflat_min], -1)
75assert np.allclose(result.flat[iflat_max], 1)
76
77
78def test_no_nans():
79"""Test that `match_template` doesn't return NaN values.
80
81When image values are only slightly different, floating-point errors can
82cause a subtraction inside of a square root to go negative (without an
83explicit check that was added to `match_template`).
84"""
85np.random.seed(1)
86image = 0.5 + 1e-9 * np.random.normal(size=(20, 20))
87template = np.ones((6, 6))
88template[:3, :] = 0
89result = match_template(image, template)
90assert not np.any(np.isnan(result))
91
92
93def test_switched_arguments():
94image = np.ones((5, 5))
95template = np.ones((3, 3))
96with testing.raises(ValueError):
97match_template(template, image)
98
99
100def test_pad_input():
101"""Test `match_template` when `pad_input=True`.
102
103This test places two full templates (one with values lower than the image
104mean, the other higher) and two half templates, which are on the edges of
105the image. The two full templates should score the top (positive and
106negative) matches and the centers of the half templates should score 2nd.
107"""
108# Float prefactors ensure that image range is between 0 and 1
109template = 0.5 * diamond(2)
110image = 0.5 * np.ones((9, 19))
111mid = slice(2, 7)
112image[mid, :3] -= template[:, -3:] # half min template centered at 0
113image[mid, 4:9] += template # full max template centered at 6
114image[mid, -9:-4] -= template # full min template centered at 12
115image[mid, -3:] += template[:, :3] # half max template centered at 18
116
117result = match_template(
118image, template, pad_input=True, constant_values=image.mean()
119)
120
121# get the max and min results.
122sorted_result = np.argsort(result.flat)
123i, j = np.unravel_index(sorted_result[:2], result.shape)
124assert_equal(j, (12, 0))
125i, j = np.unravel_index(sorted_result[-2:], result.shape)
126assert_equal(j, (18, 6))
127
128
129def test_3d():
130np.random.seed(1)
131template = np.random.rand(3, 3, 3)
132image = np.zeros((12, 12, 12))
133
134image[3:6, 5:8, 4:7] = template
135
136result = match_template(image, template)
137
138assert_equal(result.shape, (10, 10, 10))
139assert_equal(np.unravel_index(result.argmax(), result.shape), (3, 5, 4))
140
141
142def test_3d_pad_input():
143np.random.seed(1)
144template = np.random.rand(3, 3, 3)
145image = np.zeros((12, 12, 12))
146
147image[3:6, 5:8, 4:7] = template
148
149result = match_template(image, template, pad_input=True)
150
151assert_equal(result.shape, (12, 12, 12))
152assert_equal(np.unravel_index(result.argmax(), result.shape), (4, 6, 5))
153
154
155def test_padding_reflect():
156template = diamond(2)
157image = np.zeros((10, 10))
158image[2:7, :3] = template[:, -3:]
159
160result = match_template(image, template, pad_input=True, mode='reflect')
161
162assert_equal(np.unravel_index(result.argmax(), result.shape), (4, 0))
163
164
165def test_wrong_input():
166image = np.ones((5, 5, 1))
167template = np.ones((3, 3))
168with testing.raises(ValueError):
169match_template(template, image)
170
171image = np.ones((5, 5))
172template = np.ones((3, 3, 2))
173with testing.raises(ValueError):
174match_template(template, image)
175
176image = np.ones((5, 5, 3, 3))
177template = np.ones((3, 3, 2))
178with testing.raises(ValueError):
179match_template(template, image)
180
181
182def test_bounding_values():
183image = img_as_float(data.page())
184template = np.zeros((3, 3))
185template[1, 1] = 1
186result = match_template(image, template)
187print(result.max())
188assert result.max() < 1 + 1e-7
189assert result.min() > -1 - 1e-7
190