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,260 @@
|
|
|
1
|
+
"""Affine transforms."""
|
|
2
|
+
from functools import partial
|
|
3
|
+
from typing import Iterable
|
|
4
|
+
from typing import Iterator
|
|
5
|
+
from typing import Tuple
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from skimage import transform as skit
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def affine_transform_arrays(
|
|
13
|
+
arrays: Union[Iterable[np.ndarray], np.ndarray],
|
|
14
|
+
matrix: np.ndarray = None,
|
|
15
|
+
scale: Union[Tuple[float, float], np.ndarray, float] = None,
|
|
16
|
+
translation: Union[Tuple[float, float], np.ndarray, float] = None,
|
|
17
|
+
rotation: float = None,
|
|
18
|
+
shear: Union[Tuple[float, float], np.ndarray, float] = None,
|
|
19
|
+
**kwargs,
|
|
20
|
+
) -> Iterator[np.ndarray]:
|
|
21
|
+
"""
|
|
22
|
+
Transform an iterable of input arrays using a generalized affine transform operator.
|
|
23
|
+
|
|
24
|
+
A transform matrix may be specified or separate parameters for scale, translation,
|
|
25
|
+
rotation and shear may be specified instead. This method abstracts the use of the
|
|
26
|
+
scikit-image affine transform, which may easily be replaced with one from scipy or
|
|
27
|
+
another origin. If transform parameters are specified, they are applied in this order:
|
|
28
|
+
shear, scale, rotation, translation
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
arrays
|
|
33
|
+
The array(s) to be transformed
|
|
34
|
+
matrix
|
|
35
|
+
The transformation matrix to be used for the transform. If specified, none of
|
|
36
|
+
[translation, rotation, or shear] may be used. Optional. If matrix is not specified,
|
|
37
|
+
at least one of the following must be used: scale, translation, rotation, shear
|
|
38
|
+
scale
|
|
39
|
+
The scale factor to be applied in the transform: (Sx, Sy). If a scalar is used, the same
|
|
40
|
+
value is applied to each axis.
|
|
41
|
+
translation
|
|
42
|
+
The translation to be applied in the transform: (Tx, Ty) in pixel units.
|
|
43
|
+
rotation
|
|
44
|
+
The rotation angle, in radians, to be applied to the transformation.
|
|
45
|
+
A positive angle is counter-clockwise in a right handed coordinate system
|
|
46
|
+
shear
|
|
47
|
+
The shear factor to be applied in the transform: (Shx, Shy). If a scalar is used, the same
|
|
48
|
+
value is applied to each axis.
|
|
49
|
+
**kwargs
|
|
50
|
+
Optional arguments to be passed on to skimage.transform.warp() if desired. See
|
|
51
|
+
https://scikit-image.org/docs/stable/api/skimage.transform.html#skimage.transform.warp
|
|
52
|
+
for more details.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
The transformed array(s)
|
|
57
|
+
|
|
58
|
+
"""
|
|
59
|
+
tform = None
|
|
60
|
+
any_param_set = any(param is not None for param in [scale, translation, rotation, shear])
|
|
61
|
+
|
|
62
|
+
# We do our own pre-checks here so that we're not totally tied to use scikit-image in the future
|
|
63
|
+
#
|
|
64
|
+
if not any_param_set and matrix is None:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
"You must specify matrix or at least one of scale, rotate, shear, or shift."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
elif any_param_set and matrix is not None:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"You cannot specify both matrix and any of scale, rotate, shear, or shift."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
elif matrix is not None:
|
|
75
|
+
if matrix.shape != (3, 3):
|
|
76
|
+
raise ValueError(f"Invalid shape of transformation matrix: {matrix.shape}")
|
|
77
|
+
tform = skit.AffineTransform(matrix=matrix)
|
|
78
|
+
|
|
79
|
+
elif any_param_set:
|
|
80
|
+
# Construct the matrix from parameters
|
|
81
|
+
if scale is None:
|
|
82
|
+
scale = (1.0, 1.0)
|
|
83
|
+
elif np.isscalar(scale):
|
|
84
|
+
scale = (scale, scale)
|
|
85
|
+
|
|
86
|
+
if translation is None:
|
|
87
|
+
translation = (0.0, 0.0)
|
|
88
|
+
elif np.isscalar(translation):
|
|
89
|
+
translation = (translation, translation)
|
|
90
|
+
|
|
91
|
+
if rotation is None:
|
|
92
|
+
rotation = 0.0
|
|
93
|
+
|
|
94
|
+
if shear is None:
|
|
95
|
+
shear = (0.0, 0.0)
|
|
96
|
+
|
|
97
|
+
translation = translation[::-1] # Because skimage and numpy have different axes ordering
|
|
98
|
+
shear_mat = shear_matrix(shear)
|
|
99
|
+
temp_tform = skit.AffineTransform(scale=scale, translation=translation, rotation=rotation)
|
|
100
|
+
tform_mat = temp_tform.params @ shear_mat
|
|
101
|
+
tform = skit.AffineTransform(matrix=tform_mat)
|
|
102
|
+
|
|
103
|
+
arrays = [arrays] if isinstance(arrays, np.ndarray) else arrays
|
|
104
|
+
partial_warp = partial(skit.warp, inverse_map=tform.inverse, **kwargs)
|
|
105
|
+
return map(partial_warp, arrays)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def rotate_arrays_about_point(
|
|
109
|
+
arrays: Union[Iterable[np.ndarray], np.ndarray],
|
|
110
|
+
angle: float = 0.0,
|
|
111
|
+
point: Union[Tuple[float, float], np.ndarray] = None,
|
|
112
|
+
**kwargs,
|
|
113
|
+
) -> Iterator[np.ndarray]:
|
|
114
|
+
"""
|
|
115
|
+
Rotate an iterable of arrays about a specified point.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
arrays
|
|
120
|
+
The array(s) to be rotated
|
|
121
|
+
|
|
122
|
+
angle
|
|
123
|
+
The angle, in radians, for the rotation to be applied, optional. Default is zero
|
|
124
|
+
A positive angle is counter-clockwise in a right handed coordinate system and
|
|
125
|
+
clockwise in a left-handed coordinate system
|
|
126
|
+
|
|
127
|
+
point
|
|
128
|
+
The point, in pixel coordinates (x, y) about which to rotate. Optional. Default is None,
|
|
129
|
+
meaning the center of the array will be used. Note that the origin pixel is centered at
|
|
130
|
+
(0, 0) and has extent: [-0.5: 0.5, -0.5: 0.5]
|
|
131
|
+
|
|
132
|
+
kwargs
|
|
133
|
+
Any arguments to be passed to skimage.transform.rotate. See
|
|
134
|
+
https://scikit-image.org/docs/stable/api/skimage.transform.html#skimage.transform.rotate
|
|
135
|
+
for more details.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
An Iterator yielding the rotated arrays.
|
|
140
|
+
|
|
141
|
+
"""
|
|
142
|
+
arrays = [arrays] if isinstance(arrays, np.ndarray) else arrays
|
|
143
|
+
partial_rotate = partial(skit.rotate, angle=np.degrees(angle), center=point, **kwargs)
|
|
144
|
+
return map(partial_rotate, arrays)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def translate_arrays(
|
|
148
|
+
arrays: Union[Iterable[np.ndarray], np.ndarray],
|
|
149
|
+
translation: Union[Tuple[float, float], np.ndarray] = (0.0, 0.0),
|
|
150
|
+
**kwargs,
|
|
151
|
+
) -> Iterator[np.ndarray]:
|
|
152
|
+
"""
|
|
153
|
+
Translate an iterable of arrays by a specified vector.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
arrays
|
|
158
|
+
The array(s) to be translated
|
|
159
|
+
|
|
160
|
+
translation
|
|
161
|
+
The translation to be applied in the transform: (Tx, Ty) in pixel units. Optional.
|
|
162
|
+
|
|
163
|
+
**kwargs
|
|
164
|
+
Optional arguments to be passed on to skimage.transform.warp() if desired. See
|
|
165
|
+
https://scikit-image.org/docs/stable/api/skimage.transform.html#skimage.transform.warp
|
|
166
|
+
for more details.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
np.ndarray
|
|
171
|
+
The translated array
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
arrays = [arrays] if isinstance(arrays, np.ndarray) else arrays
|
|
175
|
+
return affine_transform_arrays(arrays, translation=translation, **kwargs)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def scale_matrix(scale_factors: Union[Tuple[float, float], float] = (1.0, 1.0)) -> np.ndarray:
|
|
179
|
+
"""
|
|
180
|
+
Create an Affine Transform matrix for a scale operation.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
scale_factors
|
|
185
|
+
The scale factors to use in scaling the array. If a scalar is passed, it
|
|
186
|
+
is applied to both axes.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
A 3x3 homogeneous transform matrix implementing the scaling
|
|
191
|
+
|
|
192
|
+
"""
|
|
193
|
+
return skit.AffineTransform(scale=scale_factors).params
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def rotation_matrix(angle: float = 0.0) -> np.ndarray:
|
|
197
|
+
"""
|
|
198
|
+
Create an Affine Transform matrix for a rotation operation.
|
|
199
|
+
|
|
200
|
+
This assumes the rotation is about (0, 0) and it is up to the user
|
|
201
|
+
to calculate the appropriate translation offset to be applied in the
|
|
202
|
+
skimage.warp() method to achieve rotation about an arbitrary point
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
angle
|
|
207
|
+
The angle of rotation in radians. This represents counter-clockwise rotation
|
|
208
|
+
in a right-handed coordinate system and clockwise rotation in a left-handed
|
|
209
|
+
system
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
A 3x3 homogeneous transform matrix implementing the rotation
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
return skit.AffineTransform(rotation=angle).params
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def shear_matrix(shear_factors: Union[Tuple[float, float], float] = (1.0, 1.0)) -> np.ndarray:
|
|
220
|
+
"""
|
|
221
|
+
Create an Affine Transform matrix for a shear operation.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
shear_factors
|
|
226
|
+
The shear scaling factors.
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
A 3x3 homogeneous transform matrix implementing the shear
|
|
231
|
+
|
|
232
|
+
"""
|
|
233
|
+
if isinstance(shear_factors, float):
|
|
234
|
+
shear_factors = shear_factors, shear_factors
|
|
235
|
+
shear_mat = np.eye(3, 3)
|
|
236
|
+
shear_mat[0, 1] = shear_factors[0]
|
|
237
|
+
shear_mat[1, 0] = shear_factors[1]
|
|
238
|
+
return shear_mat
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def translation_matrix(offsets: Union[Tuple[float, float], float] = (1.0, 1.0)) -> np.ndarray:
|
|
242
|
+
"""
|
|
243
|
+
Create an Affine Transform matrix for a translation operation.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
offsets
|
|
248
|
+
The translation ofsets.
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
A 3x3 homogeneous transform matrix implementing the translation
|
|
253
|
+
|
|
254
|
+
"""
|
|
255
|
+
if isinstance(offsets, float):
|
|
256
|
+
offsets = offsets, offsets
|
|
257
|
+
trans_mat = np.eye(3, 3)
|
|
258
|
+
trans_mat[0, 2] = offsets[1]
|
|
259
|
+
trans_mat[1, 2] = offsets[0]
|
|
260
|
+
return trans_mat
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Functionality for binary images."""
|
|
2
|
+
import numpy
|
|
3
|
+
import numpy as np
|
|
4
|
+
from skimage import filters as skif
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def make_binary(data: numpy.ndarray, numotsu: int = 1) -> np.ndarray:
|
|
8
|
+
"""Generate a binary image from an input float or int array.
|
|
9
|
+
|
|
10
|
+
A threshold value is chosen via Otsu's method and all values below the threshold are set to 1 while everything
|
|
11
|
+
else is set to 0. Thus the result is technically an _inverse_ binary image, which is useful for isolating the
|
|
12
|
+
target grid as a feature of interest.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
data
|
|
17
|
+
Data to convert to binary
|
|
18
|
+
|
|
19
|
+
numotsu
|
|
20
|
+
The number of times to perform thresholding using Otsu's method. Numbers larger than 1 are useful if the
|
|
21
|
+
pixel distribution of the data has > 2 modes.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
An array of 1's and 0's corresponding to the thresholded input data.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
threshold = np.inf
|
|
29
|
+
for i in range(numotsu):
|
|
30
|
+
tmp = skif.threshold_otsu(data[data < threshold])
|
|
31
|
+
frac = np.sum(data < tmp) / data.size
|
|
32
|
+
# TODO: make this a parameter
|
|
33
|
+
if frac < 1e-3:
|
|
34
|
+
break
|
|
35
|
+
threshold = tmp
|
|
36
|
+
binary = np.array(data < threshold, dtype=int)
|
|
37
|
+
return binary
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Binning transforms."""
|
|
2
|
+
from functools import partial
|
|
3
|
+
from typing import Iterable
|
|
4
|
+
from typing import Iterator
|
|
5
|
+
from typing import Literal
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from skimage import transform as skit
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def bin_arrays(
|
|
14
|
+
arrays: Union[Iterable[np.ndarray], np.ndarray], bin_factors: (int, int)
|
|
15
|
+
) -> Iterator[np.ndarray]:
|
|
16
|
+
"""
|
|
17
|
+
Bin an iterable of arrays by summation over an integral number of pixels in each dimension.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
arrays
|
|
22
|
+
The arrays to be binned
|
|
23
|
+
|
|
24
|
+
bin_factors
|
|
25
|
+
The bin factors for each axis, which must be integral divisors of each array dimension
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
An Iterator yielding the binned arrays
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
arrays = [arrays] if isinstance(arrays, np.ndarray) else arrays
|
|
33
|
+
partial_bin = partial(_bin_array, bin_factors=bin_factors)
|
|
34
|
+
return map(partial_bin, arrays)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _bin_array(array: np.ndarray, bin_factors: (int, int)) -> np.ndarray:
|
|
38
|
+
"""
|
|
39
|
+
Bin an array by summation over an integral number of pixels in each dimension.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
array
|
|
44
|
+
The array to be binned
|
|
45
|
+
|
|
46
|
+
bin_factors
|
|
47
|
+
The bin factors for each axis, which must be integral divisors of each array dimension
|
|
48
|
+
|
|
49
|
+
Returns
|
|
50
|
+
-------
|
|
51
|
+
The binned array
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
input_shape = array.shape
|
|
55
|
+
if input_shape[0] % bin_factors[0] or input_shape[1] % bin_factors[1]:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
"Array dimensions must be an integral multiple of the binning factor\n"
|
|
58
|
+
f"Shape of array = {input_shape}, binning factors = {bin_factors}"
|
|
59
|
+
)
|
|
60
|
+
return skit.downscale_local_mean(array, bin_factors)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def resize_arrays(
|
|
64
|
+
arrays: Union[Iterable[np.ndarray], np.ndarray],
|
|
65
|
+
output_shape: Tuple[int, ...],
|
|
66
|
+
order: Literal[0, 1, 2, 3, 4, 5] = 0,
|
|
67
|
+
mode: Literal["constant", "edge", "symmetric", "reflect", "wrap"] = "edge",
|
|
68
|
+
) -> Iterator[np.ndarray]:
|
|
69
|
+
"""
|
|
70
|
+
Resize an iterable of arrays using specified interpolation order.
|
|
71
|
+
|
|
72
|
+
This function is more geared towards up-sampling arrays smoothly. For more accurate downsampling see
|
|
73
|
+
`resize_arrays_local_mean`
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
arrays
|
|
78
|
+
The arrays to be binned
|
|
79
|
+
|
|
80
|
+
output_shape
|
|
81
|
+
Shape to bin the input arrays to
|
|
82
|
+
|
|
83
|
+
order
|
|
84
|
+
The order of interpolation:
|
|
85
|
+
|
|
86
|
+
0: Nearest-neighbor
|
|
87
|
+
1: Bi-linear (default)
|
|
88
|
+
2: Bi-quadratic
|
|
89
|
+
3: Bi-cubic
|
|
90
|
+
4: Bi-quartic
|
|
91
|
+
5: Bi-quintic
|
|
92
|
+
|
|
93
|
+
mode
|
|
94
|
+
Points outside the boundaries of the input are filled according to the given mode. See `numpy.pad` for explanations.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
An Iterator yielding the binned arrays
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
arrays = [arrays] if isinstance(arrays, np.ndarray) else arrays
|
|
102
|
+
partial_bin = partial(_resize_array, output_shape=output_shape, order=order, mode=mode)
|
|
103
|
+
return map(partial_bin, arrays)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _resize_array(
|
|
107
|
+
array: np.ndarray,
|
|
108
|
+
output_shape: Tuple[int, ...],
|
|
109
|
+
order: Literal[0, 1, 2, 3, 4, 5] = 0,
|
|
110
|
+
mode: Literal["constant", "edge", "symmetric", "reflect", "wrap"] = "edge",
|
|
111
|
+
) -> np.ndarray:
|
|
112
|
+
"""
|
|
113
|
+
Resize an array using the given interpolation order.
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
array
|
|
118
|
+
The array to be binned
|
|
119
|
+
|
|
120
|
+
output_shape
|
|
121
|
+
Shape to bin the input arrays to
|
|
122
|
+
|
|
123
|
+
order
|
|
124
|
+
The order of interpolation:
|
|
125
|
+
|
|
126
|
+
0: Nearest-neighbor
|
|
127
|
+
1: Bi-linear (default)
|
|
128
|
+
2: Bi-quadratic
|
|
129
|
+
3: Bi-cubic
|
|
130
|
+
4: Bi-quartic
|
|
131
|
+
5: Bi-quintic
|
|
132
|
+
|
|
133
|
+
mode
|
|
134
|
+
Points outside the boundaries of the input are filled according to the given mode. See `numpy.pad` for explanations.
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
The binned array
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
return skit.resize(
|
|
142
|
+
array, output_shape, preserve_range=True, anti_aliasing=False, order=order, mode=mode
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def resize_arrays_local_mean(
|
|
147
|
+
arrays: Union[Iterable[np.ndarray], np.ndarray], output_shape: Tuple[int, ...]
|
|
148
|
+
) -> Iterator[np.ndarray]:
|
|
149
|
+
"""
|
|
150
|
+
Bin an iterable of arrays by summation over an integral number of pixels in each dimension.
|
|
151
|
+
|
|
152
|
+
This function is good for downsampling arrays, but acts like nearest-neighbor when upsampling.
|
|
153
|
+
See `resize` for more flexible upsampling options.
|
|
154
|
+
|
|
155
|
+
Note that when downsampling this method is sensitive to extreme outliers.
|
|
156
|
+
|
|
157
|
+
Parameters
|
|
158
|
+
----------
|
|
159
|
+
arrays
|
|
160
|
+
The arrays to be binned
|
|
161
|
+
|
|
162
|
+
output_shape
|
|
163
|
+
Shape to bin the input arrays to
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
An Iterator yielding the binned arrays
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
arrays = [arrays] if isinstance(arrays, np.ndarray) else arrays
|
|
171
|
+
partial_bin = partial(_resize_array_local_mean, output_shape=output_shape)
|
|
172
|
+
return map(partial_bin, arrays)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _resize_array_local_mean(array: np.ndarray, output_shape: Tuple[int, ...]) -> np.ndarray:
|
|
176
|
+
"""
|
|
177
|
+
Bin an array by summation over an integral number of pixels in each dimension.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
array
|
|
182
|
+
The array to be binned
|
|
183
|
+
|
|
184
|
+
output_shape
|
|
185
|
+
Shape to bin the input arrays to
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
The binned array
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
return skit.resize_local_mean(array, output_shape, preserve_range=True)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Hough transforms."""
|
|
2
|
+
from typing import Tuple
|
|
3
|
+
|
|
4
|
+
import numpy
|
|
5
|
+
import numpy as np
|
|
6
|
+
from skimage import transform as skit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def do_hough(
|
|
10
|
+
binary: numpy.ndarray, theta_min: float, theta_max: float, numtheta: int = 1500
|
|
11
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
12
|
+
"""
|
|
13
|
+
Perform a Hough line transform on input data.
|
|
14
|
+
|
|
15
|
+
Resolution in the angle dimension can be increased via numtheta.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
binary
|
|
20
|
+
An integer array of 1's and 0's
|
|
21
|
+
|
|
22
|
+
theta_min
|
|
23
|
+
The minimum angle to use for the hough transform fitting
|
|
24
|
+
|
|
25
|
+
theta_max
|
|
26
|
+
The maximum angle to use for the hough transform fitting
|
|
27
|
+
|
|
28
|
+
numtheta
|
|
29
|
+
The number of samples in the theta dimension of the Hough transform.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
A tuple containing:
|
|
34
|
+
- The 2D Hough space (i.e., accumulator) of the input image.
|
|
35
|
+
- A 1D array of the angle values corresponding to the Hough accumulator. Angles will range from -pi/4 to 3/4 pi and the length will be equal to numtheta.
|
|
36
|
+
- A 1D array of the distance values corresponding to the Hough accumulator. This array is entirely determined by skimage.transform.hough_line
|
|
37
|
+
|
|
38
|
+
"""
|
|
39
|
+
hough = skit.hough_line(binary, theta=np.linspace(theta_min, theta_max, numtheta))
|
|
40
|
+
hough_accumulator = hough[0]
|
|
41
|
+
theta = hough[1]
|
|
42
|
+
rho = hough[2]
|
|
43
|
+
|
|
44
|
+
return hough_accumulator, theta, rho
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: dkist-processing-math
|
|
3
|
+
Version: 2.1.1
|
|
4
|
+
Summary: Common math functionality used in the DKIST calibrations suite
|
|
5
|
+
Author-email: NSO / AURA <dkistdc@nso.edu>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://nso.edu/dkist/data-center/
|
|
8
|
+
Project-URL: Repository, https://bitbucket.org/dkistdc/dkist-processing-math/
|
|
9
|
+
Project-URL: Documentation, https://docs.dkist.nso.edu/projects/math
|
|
10
|
+
Project-URL: Help, https://nso.atlassian.net/servicedesk/customer/portal/5
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Description-Content-Type: text/x-rst
|
|
16
|
+
Requires-Dist: dkist-processing-common >=4.0.0
|
|
17
|
+
Requires-Dist: numpy >=1.24.0
|
|
18
|
+
Requires-Dist: scikit-image >=0.21.0
|
|
19
|
+
Requires-Dist: scipy >=1.11.0
|
|
20
|
+
Requires-Dist: peakutils >=1.3.3
|
|
21
|
+
Provides-Extra: docs
|
|
22
|
+
Requires-Dist: sphinx ; extra == 'docs'
|
|
23
|
+
Requires-Dist: sphinx-astropy ; extra == 'docs'
|
|
24
|
+
Requires-Dist: sphinx-changelog ; extra == 'docs'
|
|
25
|
+
Requires-Dist: sphinx-autoapi ; extra == 'docs'
|
|
26
|
+
Requires-Dist: pytest ; extra == 'docs'
|
|
27
|
+
Requires-Dist: towncrier ; extra == 'docs'
|
|
28
|
+
Requires-Dist: dkist-sphinx-theme ; extra == 'docs'
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest ; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest-cov ; extra == 'test'
|
|
32
|
+
Requires-Dist: pytest-mock ; extra == 'test'
|
|
33
|
+
Requires-Dist: hypothesis ; extra == 'test'
|
|
34
|
+
Requires-Dist: towncrier ; extra == 'test'
|
|
35
|
+
|
|
36
|
+
dkist-processing-math
|
|
37
|
+
=====================
|
|
38
|
+
|
|
39
|
+
dkist-processing-math contains the math functions used throughout the DKIST data processing software stack.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Development
|
|
43
|
+
-----------
|
|
44
|
+
.. code-block:: bash
|
|
45
|
+
|
|
46
|
+
git clone git@bitbucket.org:dkistdc/dkist-processing-visp.git
|
|
47
|
+
cd dkist-processing-visp
|
|
48
|
+
pre-commit install
|
|
49
|
+
pip install -e .[test]
|
|
50
|
+
pytest -v --cov dkist_processing_visp
|
|
51
|
+
|
|
52
|
+
Changelog
|
|
53
|
+
#########
|
|
54
|
+
|
|
55
|
+
When you make **any** change to this repository it **MUST** be accompanied by a changelog file.
|
|
56
|
+
The changelog for this repository uses the `towncrier <https://github.com/twisted/towncrier>`__ package.
|
|
57
|
+
Entries in the changelog for the next release are added as individual files (one per change) to the ``changelog/`` directory.
|
|
58
|
+
|
|
59
|
+
Writing a Changelog Entry
|
|
60
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
61
|
+
|
|
62
|
+
A changelog entry accompanying a change should be added to the ``changelog/`` directory.
|
|
63
|
+
The name of a file in this directory follows a specific template::
|
|
64
|
+
|
|
65
|
+
<PULL REQUEST NUMBER>.<TYPE>[.<COUNTER>].rst
|
|
66
|
+
|
|
67
|
+
The fields have the following meanings:
|
|
68
|
+
|
|
69
|
+
* ``<PULL REQUEST NUMBER>``: This is the number of the pull request, so people can jump from the changelog entry to the diff on BitBucket.
|
|
70
|
+
* ``<TYPE>``: This is the type of the change and must be one of the values described below.
|
|
71
|
+
* ``<COUNTER>``: This is an optional field, if you make more than one change of the same type you can append a counter to the subsequent changes, i.e. ``100.bugfix.rst`` and ``100.bugfix.1.rst`` for two bugfix changes in the same PR.
|
|
72
|
+
|
|
73
|
+
The list of possible types is defined the the towncrier section of ``pyproject.toml``, the types are:
|
|
74
|
+
|
|
75
|
+
* ``feature``: This change is a new code feature.
|
|
76
|
+
* ``bugfix``: This is a change which fixes a bug.
|
|
77
|
+
* ``doc``: A documentation change.
|
|
78
|
+
* ``removal``: A deprecation or removal of public API.
|
|
79
|
+
* ``misc``: Any small change which doesn't fit anywhere else, such as a change to the package infrastructure.
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
Rendering the Changelog at Release Time
|
|
83
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
84
|
+
|
|
85
|
+
When you are about to tag a release first you must run ``towncrier`` to render the changelog.
|
|
86
|
+
The steps for this are as follows:
|
|
87
|
+
|
|
88
|
+
* Run `towncrier build --version vx.y.z` using the version number you want to tag.
|
|
89
|
+
* Agree to have towncrier remove the fragments.
|
|
90
|
+
* Add and commit your changes.
|
|
91
|
+
* Tag the release.
|
|
92
|
+
|
|
93
|
+
**NOTE:** If you forget to add a Changelog entry to a tagged release (either manually or automatically with ``towncrier``)
|
|
94
|
+
then the Bitbucket pipeline will fail. To be able to use the same tag you must delete it locally and on the remote branch:
|
|
95
|
+
|
|
96
|
+
.. code-block:: bash
|
|
97
|
+
|
|
98
|
+
# First, actually update the CHANGELOG and commit the update
|
|
99
|
+
git commit
|
|
100
|
+
|
|
101
|
+
# Delete tags
|
|
102
|
+
git tag -d vWHATEVER.THE.VERSION
|
|
103
|
+
git push --delete origin vWHATEVER.THE.VERSION
|
|
104
|
+
|
|
105
|
+
# Re-tag with the same version
|
|
106
|
+
git tag vWHATEVER.THE.VERSION
|
|
107
|
+
git push --tags origin main
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
changelog/.gitempty,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
dkist_processing_math/__init__.py,sha256=Mf7liCQC9M5YgFrz2NZ0dZ5smvcuw47ww6MC9mijAHw,249
|
|
3
|
+
dkist_processing_math/arithmetic.py,sha256=h8E4DUBGQLDUiNWmZb2kZRrLMmjEpdl1S_mIi3ntZ6w,3694
|
|
4
|
+
dkist_processing_math/feature.py,sha256=vwrkXBlK7wM6-KFaWxhBsMpHiHbpsWOiuGOhf3PRLSY,2003
|
|
5
|
+
dkist_processing_math/linear_algebra.py,sha256=3ycmWklgLI89NjQgwgY81Zv1m_L4jJXHnt70TzQDVWQ,4179
|
|
6
|
+
dkist_processing_math/statistics.py,sha256=Dgt1gSDQDN2XGV38xSQ3ZUmyIeGMZRoBl2vGHcADAYs,1233
|
|
7
|
+
dkist_processing_math/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
dkist_processing_math/tests/conftest.py,sha256=Pg7lDltF_N7SVIjAi0rRADXBftsSzw0o94-SfKW1Zko,1333
|
|
9
|
+
dkist_processing_math/tests/test_arithmetic.py,sha256=18YI4Fr_m0KU0dYR8_ef7CMmnOlb-Zc4I8ERgoYdxdA,5658
|
|
10
|
+
dkist_processing_math/tests/test_feature.py,sha256=ZB8r_IUrAyTAlKbxEXwr8Q2zS7KPmlJhaU1l0guK0Xc,2311
|
|
11
|
+
dkist_processing_math/tests/test_linear_algebra.py,sha256=k-XqIIBPsenNMKqo2nQnDkkN-6T1nvVT8BrJnupAIFU,6495
|
|
12
|
+
dkist_processing_math/tests/test_statistics.py,sha256=0_bFuKxcu6hfimScB5du38qzABq2syX7SQrA9ZjwbuA,2173
|
|
13
|
+
dkist_processing_math/tests/test_transform.py,sha256=M761Yceh3FKV00udvb6_AeKuyBpQrUAG-mLfO-PbHmc,20253
|
|
14
|
+
dkist_processing_math/transform/__init__.py,sha256=gZGyavJgtuquv8vc_VkXraJjz_8MkDTk9UwGQhthHwM,617
|
|
15
|
+
dkist_processing_math/transform/affine.py,sha256=wJvL-R3DMpOmEnCKL8YI8eRUE7enH2Vg-mSLLpfCEoE,8595
|
|
16
|
+
dkist_processing_math/transform/binary.py,sha256=64FhSBScuVCykHYA8Oh99wQodqVUiPdbtpFGIMl5BX8,1206
|
|
17
|
+
dkist_processing_math/transform/binning.py,sha256=1zSX4SIJnc_LiQY-oOCeZhy-zGAp5ALJFXhJ7Pz6uDI,5172
|
|
18
|
+
dkist_processing_math/transform/hough.py,sha256=o0l9YaYgMBqeKACHT8lZ4PbMZVJwjD-cE1kKrFg8mog,1370
|
|
19
|
+
docs/Makefile,sha256=qnlVz6PuBqE39NfHWuUnHhNEA-EFgT2-WJNNNy9ttfk,4598
|
|
20
|
+
docs/changelog.rst,sha256=S2jPASsWlQxSlAPqdvNrYvhk9k3FcFWNXFNDYXBSjl4,120
|
|
21
|
+
docs/conf.py,sha256=wo1DmmqFac7fMpPLXZrY69vCXb3cji4Y7eYWOphUu_w,1835
|
|
22
|
+
docs/index.rst,sha256=qNJUPrbv1dEdn_QLn5pZOhMTNikWVNAeSQnV76hFnis,103
|
|
23
|
+
docs/make.bat,sha256=mBAhtURwhQ7yc95pqwJzlhqBSvRknr1aqZ5s8NKvdKs,4513
|
|
24
|
+
docs/requirements.txt,sha256=Kbl_X4c7RQZw035YTeNB63We6I7pvXFU4T0Uflp2yDY,29
|
|
25
|
+
licenses/LICENSE.rst,sha256=piZaQplkzOMmH1NXg6QIdo9wwo9pPCoHkvm2-DmH76E,1462
|
|
26
|
+
dkist_processing_math-2.1.1.dist-info/METADATA,sha256=GPD3CVAME94Z-utZGhZTZm-jbGtE97P8RlqU5PwSk8Q,4301
|
|
27
|
+
dkist_processing_math-2.1.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
|
28
|
+
dkist_processing_math-2.1.1.dist-info/top_level.txt,sha256=7NeFrOlR4xhZ3LMT4jN99LQugukjr6l9u3w_s6b-uO4,46
|
|
29
|
+
dkist_processing_math-2.1.1.dist-info/RECORD,,
|