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.
@@ -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
+ ]