dkist-processing-math 2.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- changelog/.gitempty +0 -0
- dkist_processing_math/__init__.py +9 -0
- dkist_processing_math/arithmetic.py +125 -0
- dkist_processing_math/feature.py +62 -0
- dkist_processing_math/linear_algebra.py +109 -0
- dkist_processing_math/statistics.py +40 -0
- dkist_processing_math/tests/__init__.py +0 -0
- dkist_processing_math/tests/conftest.py +68 -0
- dkist_processing_math/tests/test_arithmetic.py +128 -0
- dkist_processing_math/tests/test_feature.py +76 -0
- dkist_processing_math/tests/test_linear_algebra.py +190 -0
- dkist_processing_math/tests/test_statistics.py +62 -0
- dkist_processing_math/tests/test_transform.py +548 -0
- dkist_processing_math/transform/__init__.py +24 -0
- dkist_processing_math/transform/affine.py +260 -0
- dkist_processing_math/transform/binary.py +37 -0
- dkist_processing_math/transform/binning.py +192 -0
- dkist_processing_math/transform/hough.py +44 -0
- dkist_processing_math-2.1.1.dist-info/METADATA +107 -0
- dkist_processing_math-2.1.1.dist-info/RECORD +29 -0
- dkist_processing_math-2.1.1.dist-info/WHEEL +5 -0
- dkist_processing_math-2.1.1.dist-info/top_level.txt +4 -0
- docs/Makefile +134 -0
- docs/changelog.rst +7 -0
- docs/conf.py +46 -0
- docs/index.rst +9 -0
- docs/make.bat +170 -0
- docs/requirements.txt +1 -0
- licenses/LICENSE.rst +11 -0
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
from scipy import ndimage as spnd
|
|
4
|
+
from skimage import transform as transform
|
|
5
|
+
|
|
6
|
+
from dkist_processing_math.transform import affine_transform_arrays
|
|
7
|
+
from dkist_processing_math.transform import bin_arrays
|
|
8
|
+
from dkist_processing_math.transform import do_hough
|
|
9
|
+
from dkist_processing_math.transform import make_binary
|
|
10
|
+
from dkist_processing_math.transform import rotate_arrays_about_point
|
|
11
|
+
from dkist_processing_math.transform import rotation_matrix
|
|
12
|
+
from dkist_processing_math.transform import scale_matrix
|
|
13
|
+
from dkist_processing_math.transform import shear_matrix
|
|
14
|
+
from dkist_processing_math.transform import translate_arrays
|
|
15
|
+
from dkist_processing_math.transform import translation_matrix
|
|
16
|
+
from dkist_processing_math.transform.binning import resize_arrays
|
|
17
|
+
from dkist_processing_math.transform.binning import resize_arrays_local_mean
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture()
|
|
21
|
+
def binned_arrays(numpy_arrays):
|
|
22
|
+
"""
|
|
23
|
+
Create a list of binned arrays from numpy_arrays
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
numpy_arrays: List[np.ndarray]
|
|
28
|
+
A list of arrays to be binned
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
List[np.ndarray]
|
|
33
|
+
A list of 2x2 binned versions of the input arrays
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
temp_arrays = np.zeros((2, 2, 2), dtype=numpy_arrays[0].dtype)
|
|
38
|
+
for array, temp_array in zip(numpy_arrays, temp_arrays):
|
|
39
|
+
for row in range(temp_array.shape[1]):
|
|
40
|
+
for column in range(temp_array.shape[0]):
|
|
41
|
+
temp_array[column, row] = array[
|
|
42
|
+
column * 5 : (column + 1) * 5, row * 5 : (row + 1) * 5
|
|
43
|
+
].mean()
|
|
44
|
+
return temp_arrays
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest.fixture()
|
|
48
|
+
def binned_array(numpy_array, binned_arrays):
|
|
49
|
+
"""
|
|
50
|
+
Create a binned array from numpy_array
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
numpy_array: np.ndarray
|
|
55
|
+
The array to be binned
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
np.ndarray
|
|
60
|
+
A 2x2 binned version of the input array
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
return binned_arrays[0]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.fixture()
|
|
67
|
+
def resized_arrays(numpy_arrays):
|
|
68
|
+
temp_arrays = np.zeros((2, 20, 100), dtype=numpy_arrays[0].dtype)
|
|
69
|
+
for array, temp_array in zip(numpy_arrays, temp_arrays):
|
|
70
|
+
temp_array[:, :] = transform.resize_local_mean(array, (20, 100), preserve_range=True)
|
|
71
|
+
return temp_arrays
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.fixture()
|
|
75
|
+
def resized_array(resized_arrays):
|
|
76
|
+
return resized_arrays[0]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_bin_arrays(numpy_arrays, binned_arrays):
|
|
80
|
+
"""
|
|
81
|
+
Given: an iterable of numpy_arrays
|
|
82
|
+
When: binning an iterable of numpy_arrays
|
|
83
|
+
Then: an Iterator of binned versions of the input arrays is returned
|
|
84
|
+
"""
|
|
85
|
+
results = bin_arrays(numpy_arrays, (5, 5))
|
|
86
|
+
for result, binned_array in zip(results, binned_arrays):
|
|
87
|
+
np.testing.assert_allclose(result, binned_array)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_bin_arrays_single_array(numpy_array, binned_array):
|
|
91
|
+
"""
|
|
92
|
+
Given: an single numpy_array
|
|
93
|
+
When: binning an iterable of numpy_arrays
|
|
94
|
+
Then: an Iterator containing the binned version of the input arrays is returned
|
|
95
|
+
"""
|
|
96
|
+
result = next(bin_arrays(numpy_array, (5, 5)))
|
|
97
|
+
np.testing.assert_allclose(result, binned_array)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_bin_arrays_bad_bin_factor(numpy_array):
|
|
101
|
+
"""
|
|
102
|
+
Given: a single numpy array
|
|
103
|
+
When: binning an array using a bad binning factor
|
|
104
|
+
Then: an error is raised
|
|
105
|
+
"""
|
|
106
|
+
with pytest.raises(ValueError):
|
|
107
|
+
next(bin_arrays(numpy_array, (3, 3)))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_resize_arrays(numpy_arrays, resized_arrays):
|
|
111
|
+
"""
|
|
112
|
+
Given: an iterable of numpy_arrays
|
|
113
|
+
When: resizing an iterable of numpy_arrays
|
|
114
|
+
Then: an Iterator of resized versions of the input arrays is returned
|
|
115
|
+
"""
|
|
116
|
+
results = resize_arrays(numpy_arrays, (20, 100))
|
|
117
|
+
for result, resized_array in zip(results, resized_arrays):
|
|
118
|
+
np.testing.assert_allclose(result, resized_array)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_resize_arrays_single_array(numpy_array, resized_array):
|
|
122
|
+
"""
|
|
123
|
+
Given: a single numpy array
|
|
124
|
+
When: resizing that array
|
|
125
|
+
Then: an Iterator of resized versions of the input arrays is returned
|
|
126
|
+
"""
|
|
127
|
+
result = next(resize_arrays(numpy_array, (20, 100)))
|
|
128
|
+
np.testing.assert_allclose(result, resized_array)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_resize_arrays_local_mean(numpy_arrays, resized_arrays):
|
|
132
|
+
"""
|
|
133
|
+
Given: an iterable of numpy_arrays
|
|
134
|
+
When: resizing an iterable of numpy_arrays
|
|
135
|
+
Then: an Iterator of resized versions of the input arrays is returned
|
|
136
|
+
"""
|
|
137
|
+
results = resize_arrays_local_mean(numpy_arrays, (20, 100))
|
|
138
|
+
for result, resized_array in zip(results, resized_arrays):
|
|
139
|
+
np.testing.assert_allclose(result, resized_array)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_resize_arrays_local_mean_single_array(numpy_array, resized_array):
|
|
143
|
+
"""
|
|
144
|
+
Given: a single numpy array
|
|
145
|
+
When: resizing that array
|
|
146
|
+
Then: an Iterator of resized versions of the input arrays is returned
|
|
147
|
+
"""
|
|
148
|
+
result = next(resize_arrays_local_mean(numpy_array, (20, 100)))
|
|
149
|
+
np.testing.assert_allclose(result, resized_array)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_affine_transform_specify_matrix(numpy_array):
|
|
153
|
+
"""
|
|
154
|
+
Given: a numpy_array
|
|
155
|
+
When: applying the affine transform and specifying the full transformation matrix
|
|
156
|
+
Then: an Iterator containing the transformed array is returned
|
|
157
|
+
"""
|
|
158
|
+
angle = np.radians(90)
|
|
159
|
+
# Create the Affine transform for the rotation
|
|
160
|
+
rotation = transform.AffineTransform(rotation=angle)
|
|
161
|
+
# Offset to the center of the array for the rotation
|
|
162
|
+
p = np.array([4.5, 4.5])
|
|
163
|
+
# Create the Affine transform for the translation
|
|
164
|
+
trans = transform.AffineTransform(translation=p)
|
|
165
|
+
# Create the composite transform matrix for the rotation about the center
|
|
166
|
+
tform = trans.params @ rotation.params @ np.linalg.inv(trans.params)
|
|
167
|
+
# Perform the transformation
|
|
168
|
+
result = affine_transform_arrays(numpy_array, matrix=tform)
|
|
169
|
+
# Compare against the same rotation computed with a different method.
|
|
170
|
+
np.testing.assert_allclose(next(result), transform.rotate(numpy_array, -90, center=(4.5, 4.5)))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_affine_transform_specify_rotate(numpy_array):
|
|
174
|
+
"""
|
|
175
|
+
Given: a numpy_array
|
|
176
|
+
When: applying the affine transform and specifying a rotation angle and rotation point
|
|
177
|
+
Then: an Iterator containing the rotated array is returned
|
|
178
|
+
"""
|
|
179
|
+
angle = np.radians(90)
|
|
180
|
+
rotation = transform.AffineTransform(rotation=angle)
|
|
181
|
+
# Offset to the center of the array for the rotation
|
|
182
|
+
p = np.array([4.5, 4.5, 0])
|
|
183
|
+
# Transform the offset to rotated space
|
|
184
|
+
o = p - np.dot(rotation.params, p)
|
|
185
|
+
result = affine_transform_arrays(numpy_array, rotation=np.radians(90), translation=o[0:2][::-1])
|
|
186
|
+
np.testing.assert_allclose(next(result), spnd.rotate(numpy_array, -90))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_affine_transform_specify_scale_x(numpy_array):
|
|
190
|
+
"""
|
|
191
|
+
Given: a numpy_array
|
|
192
|
+
When: applying the affine transform and specifying a scale factor for the X axis only
|
|
193
|
+
Then: an Iterator containing the array scaled along the X axis is returned
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
num_rows = numpy_array.shape[0]
|
|
197
|
+
num_cols = numpy_array.shape[1]
|
|
198
|
+
desired_output = np.zeros_like(numpy_array)
|
|
199
|
+
desired_output[0:num_rows, 0 : num_cols // 2] = numpy_array[0:num_rows, 0:num_cols:2]
|
|
200
|
+
result = affine_transform_arrays(numpy_array, scale=(0.5, 1.0))
|
|
201
|
+
np.testing.assert_allclose(next(result), desired_output)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_affine_transform_specify_scale_y(numpy_array):
|
|
205
|
+
"""
|
|
206
|
+
Given: a numpy_array
|
|
207
|
+
When: applying the affine transform and specifying a scale factor for the Y axis only
|
|
208
|
+
Then: an Iterator containing the array scaled along the Y axis is returned
|
|
209
|
+
"""
|
|
210
|
+
num_rows = numpy_array.shape[0]
|
|
211
|
+
num_cols = numpy_array.shape[1]
|
|
212
|
+
desired_output = np.zeros_like(numpy_array)
|
|
213
|
+
desired_output[0 : num_rows // 2, 0:num_cols] = numpy_array[0:num_rows:2, 0:num_cols]
|
|
214
|
+
result = affine_transform_arrays(numpy_array, scale=(1.0, 0.5))
|
|
215
|
+
np.testing.assert_allclose(next(result), desired_output)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_affine_transform_specify_scale_both(numpy_array):
|
|
219
|
+
"""
|
|
220
|
+
Given: a numpy_array
|
|
221
|
+
When: applying the affine transform and specifying a scale factor for each axis
|
|
222
|
+
Then: an Iterator containing the array scaled separately along each axis is returned
|
|
223
|
+
"""
|
|
224
|
+
tform = transform.SimilarityTransform(scale=0.5)
|
|
225
|
+
desired_output = transform.warp(numpy_array, tform.inverse)
|
|
226
|
+
result = affine_transform_arrays(numpy_array, scale=(0.5, 0.5))
|
|
227
|
+
np.testing.assert_allclose(next(result), desired_output)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_affine_transform_specify_scale_scalar(numpy_array):
|
|
231
|
+
"""
|
|
232
|
+
Given: a numpy_array
|
|
233
|
+
When: applying the affine transform and specifying a scalar scale factor to be applied to both axes
|
|
234
|
+
Then: an Iterator containing the array scaled identically on each axis is returned
|
|
235
|
+
"""
|
|
236
|
+
tform = transform.SimilarityTransform(scale=0.5)
|
|
237
|
+
desired_output = transform.warp(numpy_array, tform.inverse)
|
|
238
|
+
result = affine_transform_arrays(numpy_array, scale=0.5)
|
|
239
|
+
np.testing.assert_allclose(next(result), desired_output)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_affine_transform_specify_shear(numpy_array):
|
|
243
|
+
"""
|
|
244
|
+
Given: a numpy_array
|
|
245
|
+
When: applying the affine transform and specifying a shear angle
|
|
246
|
+
Then: an Iterator containing the sheared array is returned
|
|
247
|
+
"""
|
|
248
|
+
shear_mat = np.array([[1, 0.1, 0], [0.2, 1, 0], [0, 0, 1]])
|
|
249
|
+
shear_mat_inv = np.linalg.inv(shear_mat)
|
|
250
|
+
desired_output = transform.warp(numpy_array, shear_mat_inv)
|
|
251
|
+
result = affine_transform_arrays(numpy_array, shear=(0.1, 0.2))
|
|
252
|
+
np.testing.assert_allclose(next(result), desired_output)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def test_affine_transform_specify_translate(numpy_array):
|
|
256
|
+
"""
|
|
257
|
+
Given: a numpy_array
|
|
258
|
+
When: applying the affine transform and specifying a translation vector
|
|
259
|
+
Then: an Iterator containing the translated array is returned
|
|
260
|
+
"""
|
|
261
|
+
result = affine_transform_arrays(numpy_array, translation=(2, 5))
|
|
262
|
+
desired_output = spnd.shift(numpy_array, (2, 5))
|
|
263
|
+
np.testing.assert_allclose(next(result), desired_output)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_affine_transform_all_params_none(numpy_array):
|
|
267
|
+
"""
|
|
268
|
+
Given: a numpy_array
|
|
269
|
+
When: applying the affine transform and specifying no parameters
|
|
270
|
+
Then: an error is raised
|
|
271
|
+
"""
|
|
272
|
+
with pytest.raises(ValueError):
|
|
273
|
+
affine_transform_arrays(numpy_array)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def test_affine_transform_matrix_and_one_param(numpy_array):
|
|
277
|
+
"""
|
|
278
|
+
Given: a numpy_array
|
|
279
|
+
When: applying the affine transform and specifying both a full matrix and one or more transform parameters
|
|
280
|
+
Then: an error is raised
|
|
281
|
+
"""
|
|
282
|
+
with pytest.raises(ValueError):
|
|
283
|
+
affine_transform_arrays(numpy_array, matrix=np.ones((2, 2)), shear=0.1)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def test_affine_transform_matrix_wrong_shape(numpy_array):
|
|
287
|
+
"""
|
|
288
|
+
Given: a numpy_array
|
|
289
|
+
When: applying the affine transform and specifying both a full matrix and one or more transform parameters
|
|
290
|
+
Then: an error is raised
|
|
291
|
+
"""
|
|
292
|
+
with pytest.raises(ValueError):
|
|
293
|
+
affine_transform_arrays(numpy_array, matrix=np.ones((2, 2)))
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_affine_transform_arrays(numpy_arrays):
|
|
297
|
+
"""
|
|
298
|
+
Given: an iterable of numpy_arrays
|
|
299
|
+
When: applying the affine transform
|
|
300
|
+
Then: a Iterator containing the transformed arrays is returned
|
|
301
|
+
"""
|
|
302
|
+
results = affine_transform_arrays(numpy_arrays, translation=(7, 3))
|
|
303
|
+
desired_outputs = [spnd.shift(array, (7, 3)) for array in numpy_arrays]
|
|
304
|
+
for result, desired_output in zip(results, desired_outputs):
|
|
305
|
+
np.testing.assert_allclose(result, desired_output)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def test_affine_transform_arrays_single_array(numpy_array):
|
|
309
|
+
"""
|
|
310
|
+
Given: a single numpy array
|
|
311
|
+
When: applying the affine transform to an iterable of arrays
|
|
312
|
+
Then: an Iterator containing the transformed array is returned
|
|
313
|
+
"""
|
|
314
|
+
results = affine_transform_arrays(numpy_array, translation=(2, 3))
|
|
315
|
+
result = next(results)
|
|
316
|
+
desired_output = spnd.shift(numpy_array, (2, 3))
|
|
317
|
+
np.testing.assert_allclose(result, desired_output)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_rotate_arrays_about_point(numpy_arrays):
|
|
321
|
+
"""
|
|
322
|
+
Given: an iterable of numpy arrays
|
|
323
|
+
When: rotating an iterable of arrays about a point
|
|
324
|
+
Then: an Iterator containing the rotated arrays is returned
|
|
325
|
+
"""
|
|
326
|
+
angle = -45
|
|
327
|
+
p = np.array([5, 2])
|
|
328
|
+
results = rotate_arrays_about_point(numpy_arrays, angle=np.radians(angle), point=p)
|
|
329
|
+
desired_outputs = [transform.rotate(array, angle, center=p) for array in numpy_arrays]
|
|
330
|
+
for result, desired_output in zip(results, desired_outputs):
|
|
331
|
+
np.testing.assert_allclose(result, desired_output)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def test_rotate_arrays_about_point_single_array(numpy_array):
|
|
335
|
+
"""
|
|
336
|
+
Given: a single numpy array
|
|
337
|
+
When: rotating an iterable of arrays about a point
|
|
338
|
+
Then: an Iterator containing the rotated array is returned
|
|
339
|
+
"""
|
|
340
|
+
angle = -45
|
|
341
|
+
p = np.array([5, 2])
|
|
342
|
+
results = rotate_arrays_about_point(numpy_array, angle=np.radians(angle), point=p)
|
|
343
|
+
result = next(results)
|
|
344
|
+
desired_output = transform.rotate(numpy_array, angle, center=p)
|
|
345
|
+
np.testing.assert_allclose(result, desired_output)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def test_translate_arrays(numpy_arrays):
|
|
349
|
+
"""
|
|
350
|
+
Given: an iterable of numpy arrays
|
|
351
|
+
When: translating an iterable of arrays by a vector
|
|
352
|
+
Then: an Iterator containing the translated arrays is returned
|
|
353
|
+
"""
|
|
354
|
+
results = translate_arrays(numpy_arrays, translation=(5, 2))
|
|
355
|
+
desired_outputs = [spnd.shift(array, (5, 2)) for array in numpy_arrays]
|
|
356
|
+
for result, desired_output in zip(results, desired_outputs):
|
|
357
|
+
np.testing.assert_allclose(result, desired_output)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def test_translate_arrays_single_array(numpy_array):
|
|
361
|
+
"""
|
|
362
|
+
Given: a single numpy array
|
|
363
|
+
When: translating an iterable of arrays by a vector
|
|
364
|
+
Then: an Iterator containing the translated array is returned
|
|
365
|
+
"""
|
|
366
|
+
results = translate_arrays(numpy_array, translation=(5, 2))
|
|
367
|
+
result = next(results)
|
|
368
|
+
desired_output = spnd.shift(numpy_array, (5, 2))
|
|
369
|
+
np.testing.assert_allclose(result, desired_output)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def test_scale_matrix(numpy_array):
|
|
373
|
+
"""
|
|
374
|
+
Given: a single numpy array
|
|
375
|
+
When: creating a scale matrix operator
|
|
376
|
+
Then: a correct scale matrix is produced
|
|
377
|
+
"""
|
|
378
|
+
scale_mat = scale_matrix((0.2, 0.4))
|
|
379
|
+
desired_output = affine_transform_arrays(numpy_array, scale=(0.2, 0.4))
|
|
380
|
+
result = affine_transform_arrays(numpy_array, matrix=scale_mat)
|
|
381
|
+
np.testing.assert_allclose(next(result), next(desired_output))
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def test_rotation_matrix(numpy_array):
|
|
385
|
+
"""
|
|
386
|
+
Given: a single numpy array
|
|
387
|
+
When: creating a rotation matrix operator
|
|
388
|
+
Then: a correct rotation matrix is produced
|
|
389
|
+
"""
|
|
390
|
+
angle = np.radians(30)
|
|
391
|
+
rotation_mat = rotation_matrix(angle)
|
|
392
|
+
desired_output = affine_transform_arrays(numpy_array, rotation=angle)
|
|
393
|
+
result = affine_transform_arrays(numpy_array, matrix=rotation_mat)
|
|
394
|
+
np.testing.assert_allclose(next(result), next(desired_output))
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def test_shear_matrix(numpy_array):
|
|
398
|
+
"""
|
|
399
|
+
Given: a single numpy array
|
|
400
|
+
When: creating a shear matrix operator
|
|
401
|
+
Then: a correct shear matrix is produced
|
|
402
|
+
"""
|
|
403
|
+
shear = (0.1, 0.2)
|
|
404
|
+
shear_mat = shear_matrix(shear)
|
|
405
|
+
desired_shear_mat = np.array([[1, 0.1, 0], [0.2, 1, 0], [0, 0, 1]])
|
|
406
|
+
np.testing.assert_allclose(shear_mat, desired_shear_mat)
|
|
407
|
+
desired_output = affine_transform_arrays(numpy_array, shear=shear)
|
|
408
|
+
result = affine_transform_arrays(numpy_array, matrix=shear_mat)
|
|
409
|
+
np.testing.assert_allclose(next(result), next(desired_output))
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def test_shear_matrix_scalar(numpy_array):
|
|
413
|
+
"""
|
|
414
|
+
Given: a single numpy array
|
|
415
|
+
When: creating a shear matrix operator
|
|
416
|
+
Then: a correct shear matrix is produced
|
|
417
|
+
"""
|
|
418
|
+
shear = 0.1
|
|
419
|
+
shear_mat = shear_matrix(shear)
|
|
420
|
+
desired_shear_mat = np.array([[1, 0.1, 0], [0.1, 1, 0], [0, 0, 1]])
|
|
421
|
+
np.testing.assert_allclose(shear_mat, desired_shear_mat)
|
|
422
|
+
desired_output = affine_transform_arrays(numpy_array, shear=shear)
|
|
423
|
+
result = affine_transform_arrays(numpy_array, matrix=shear_mat)
|
|
424
|
+
np.testing.assert_allclose(next(result), next(desired_output))
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def test_translation_matrix(numpy_array):
|
|
428
|
+
"""
|
|
429
|
+
Given: a single numpy array
|
|
430
|
+
When: creating a translation matrix operator
|
|
431
|
+
Then: a correct translation matrix is produced
|
|
432
|
+
"""
|
|
433
|
+
trans = (2.3, 5.7)
|
|
434
|
+
trans_mat = translation_matrix(trans)
|
|
435
|
+
desired_output = affine_transform_arrays(numpy_array, translation=trans)
|
|
436
|
+
result = affine_transform_arrays(numpy_array, matrix=trans_mat)
|
|
437
|
+
np.testing.assert_allclose(next(result), next(desired_output))
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def test_translation_matrix_scalar(numpy_array):
|
|
441
|
+
"""
|
|
442
|
+
Given: a single numpy array
|
|
443
|
+
When: creating a translation matrix operator
|
|
444
|
+
Then: a correct translation matrix is produced
|
|
445
|
+
"""
|
|
446
|
+
trans = 4.2
|
|
447
|
+
trans_mat = translation_matrix(trans)
|
|
448
|
+
desired_output = affine_transform_arrays(numpy_array, translation=trans)
|
|
449
|
+
result = affine_transform_arrays(numpy_array, matrix=trans_mat)
|
|
450
|
+
np.testing.assert_allclose(next(result), next(desired_output))
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def test_matrix_compositions(numpy_array):
|
|
454
|
+
"""
|
|
455
|
+
Given: a single numpy array
|
|
456
|
+
When: creating a composition of transform matrices
|
|
457
|
+
Then: a correct transform results from the composition
|
|
458
|
+
"""
|
|
459
|
+
trans = (2.3, 5.7)
|
|
460
|
+
trans_mat = translation_matrix(trans)
|
|
461
|
+
shear = (0.1, 0.2)
|
|
462
|
+
shear_mat = shear_matrix(shear)
|
|
463
|
+
angle = np.radians(30)
|
|
464
|
+
rotation_mat = rotation_matrix(angle)
|
|
465
|
+
scale = (0.2, 0.4)
|
|
466
|
+
scale_mat = scale_matrix(scale)
|
|
467
|
+
# The order in which the transforms are applied reads right to left:
|
|
468
|
+
composition = trans_mat @ rotation_mat @ scale_mat @ shear_mat
|
|
469
|
+
result = affine_transform_arrays(numpy_array, matrix=composition)
|
|
470
|
+
desired_output = affine_transform_arrays(
|
|
471
|
+
numpy_array, scale=scale, shear=shear, rotation=angle, translation=trans
|
|
472
|
+
)
|
|
473
|
+
np.testing.assert_allclose(next(result), next(desired_output))
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def test_make_binary_image():
|
|
477
|
+
"""
|
|
478
|
+
Given: a single numpy array
|
|
479
|
+
When: generating a binary image for values in the array below a threshold
|
|
480
|
+
Then: a correct binary array results from the generation
|
|
481
|
+
"""
|
|
482
|
+
input_array = np.ones((10, 10), dtype=float)
|
|
483
|
+
input_array[:5, :] += 1.0
|
|
484
|
+
result = make_binary(input_array) # Something ridiculous
|
|
485
|
+
assert isinstance(result, np.ndarray)
|
|
486
|
+
|
|
487
|
+
expected = np.ones((10, 10), dtype=int)
|
|
488
|
+
expected[:5, :] = 0
|
|
489
|
+
np.testing.assert_equal(result, expected)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def test_make_binary_trigger_cutoff():
|
|
493
|
+
"""
|
|
494
|
+
Given: a single numpy array
|
|
495
|
+
When: calling make_binary in such a way that the result will converge to all 0s or all 1s
|
|
496
|
+
Then: make_binary returns before a single value array is created
|
|
497
|
+
"""
|
|
498
|
+
rng = np.random.default_rng()
|
|
499
|
+
input_array = rng.standard_normal((100, 100))
|
|
500
|
+
result = make_binary(input_array, numotsu=100)
|
|
501
|
+
frac = np.sum(result) / result.size
|
|
502
|
+
assert frac >= 1e-3
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def test_do_hough(numpy_array):
|
|
506
|
+
"""
|
|
507
|
+
Given: a single numpy array
|
|
508
|
+
When: performing a hough line transformation
|
|
509
|
+
Then: return the correct hough space, array of angle values, and array of distance values
|
|
510
|
+
"""
|
|
511
|
+
binary = make_binary(numpy_array)
|
|
512
|
+
|
|
513
|
+
# Rho and Theta ranges
|
|
514
|
+
thetas = np.linspace(-np.pi / 4, np.pi / 4, 1500)
|
|
515
|
+
width, height = binary.shape
|
|
516
|
+
diag_len = np.ceil(np.sqrt(width * width + height * height)) # max_dist
|
|
517
|
+
diag_len = int(diag_len)
|
|
518
|
+
num_rhos = 2 * diag_len + 1
|
|
519
|
+
rhos = np.linspace(-diag_len, diag_len, num_rhos)
|
|
520
|
+
# Cache some reusable values
|
|
521
|
+
cos_t = np.cos(thetas)
|
|
522
|
+
sin_t = np.sin(thetas)
|
|
523
|
+
num_thetas = len(thetas)
|
|
524
|
+
# Hough accumulator array of theta vs rho
|
|
525
|
+
accumulator = np.zeros((num_rhos, num_thetas), dtype=np.uint64)
|
|
526
|
+
y_idxs, x_idxs = np.nonzero(binary) # (row, col) indexes to edges
|
|
527
|
+
for i in range(len(x_idxs)):
|
|
528
|
+
x = x_idxs[i]
|
|
529
|
+
y = y_idxs[i]
|
|
530
|
+
for t_idx in range(num_thetas):
|
|
531
|
+
# Calculate rho. diag_len is added for a positive index
|
|
532
|
+
rho = round(x * cos_t[t_idx] + y * sin_t[t_idx]) + diag_len
|
|
533
|
+
accumulator[rho, t_idx] += 1
|
|
534
|
+
desired_H = accumulator
|
|
535
|
+
desired_thetas = thetas
|
|
536
|
+
desired_rhos = rhos
|
|
537
|
+
assert isinstance(desired_H, np.ndarray)
|
|
538
|
+
assert isinstance(desired_thetas, np.ndarray)
|
|
539
|
+
assert isinstance(desired_rhos, np.ndarray)
|
|
540
|
+
|
|
541
|
+
result_H, result_theta, result_rho = do_hough(binary, theta_min=-np.pi / 4, theta_max=np.pi / 4)
|
|
542
|
+
assert isinstance(result_H, np.ndarray)
|
|
543
|
+
assert isinstance(result_theta, np.ndarray)
|
|
544
|
+
assert isinstance(result_rho, np.ndarray)
|
|
545
|
+
|
|
546
|
+
np.testing.assert_allclose(result_H, desired_H)
|
|
547
|
+
np.testing.assert_allclose(result_theta, desired_thetas)
|
|
548
|
+
np.testing.assert_allclose(result_rho, desired_rhos)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""init."""
|
|
2
|
+
from .affine import affine_transform_arrays
|
|
3
|
+
from .affine import rotate_arrays_about_point
|
|
4
|
+
from .affine import rotation_matrix
|
|
5
|
+
from .affine import scale_matrix
|
|
6
|
+
from .affine import shear_matrix
|
|
7
|
+
from .affine import translate_arrays
|
|
8
|
+
from .affine import translation_matrix
|
|
9
|
+
from .binary import make_binary
|
|
10
|
+
from .binning import bin_arrays
|
|
11
|
+
from .hough import do_hough
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"affine_transform_arrays",
|
|
15
|
+
"translate_arrays",
|
|
16
|
+
"rotate_arrays_about_point",
|
|
17
|
+
"rotation_matrix",
|
|
18
|
+
"translation_matrix",
|
|
19
|
+
"scale_matrix",
|
|
20
|
+
"shear_matrix",
|
|
21
|
+
"make_binary",
|
|
22
|
+
"bin_arrays",
|
|
23
|
+
"do_hough",
|
|
24
|
+
]
|