tobac 1.6.2__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.
- tobac/__init__.py +112 -0
- tobac/analysis/__init__.py +31 -0
- tobac/analysis/cell_analysis.py +628 -0
- tobac/analysis/feature_analysis.py +212 -0
- tobac/analysis/spatial.py +619 -0
- tobac/centerofgravity.py +226 -0
- tobac/feature_detection.py +1758 -0
- tobac/merge_split.py +324 -0
- tobac/plotting.py +2321 -0
- tobac/segmentation/__init__.py +10 -0
- tobac/segmentation/watershed_segmentation.py +1316 -0
- tobac/testing.py +1179 -0
- tobac/tests/segmentation_tests/test_iris_xarray_segmentation.py +0 -0
- tobac/tests/segmentation_tests/test_segmentation.py +1183 -0
- tobac/tests/segmentation_tests/test_segmentation_time_pad.py +104 -0
- tobac/tests/test_analysis_spatial.py +1109 -0
- tobac/tests/test_convert.py +265 -0
- tobac/tests/test_datetime.py +216 -0
- tobac/tests/test_decorators.py +148 -0
- tobac/tests/test_feature_detection.py +1321 -0
- tobac/tests/test_generators.py +273 -0
- tobac/tests/test_import.py +24 -0
- tobac/tests/test_iris_xarray_match_utils.py +244 -0
- tobac/tests/test_merge_split.py +351 -0
- tobac/tests/test_pbc_utils.py +497 -0
- tobac/tests/test_sample_data.py +197 -0
- tobac/tests/test_testing.py +747 -0
- tobac/tests/test_tracking.py +714 -0
- tobac/tests/test_utils.py +650 -0
- tobac/tests/test_utils_bulk_statistics.py +789 -0
- tobac/tests/test_utils_coordinates.py +328 -0
- tobac/tests/test_utils_internal.py +97 -0
- tobac/tests/test_xarray_utils.py +232 -0
- tobac/tracking.py +613 -0
- tobac/utils/__init__.py +27 -0
- tobac/utils/bulk_statistics.py +360 -0
- tobac/utils/datetime.py +184 -0
- tobac/utils/decorators.py +540 -0
- tobac/utils/general.py +753 -0
- tobac/utils/generators.py +87 -0
- tobac/utils/internal/__init__.py +2 -0
- tobac/utils/internal/coordinates.py +430 -0
- tobac/utils/internal/iris_utils.py +462 -0
- tobac/utils/internal/label_props.py +82 -0
- tobac/utils/internal/xarray_utils.py +439 -0
- tobac/utils/mask.py +364 -0
- tobac/utils/periodic_boundaries.py +419 -0
- tobac/wrapper.py +244 -0
- tobac-1.6.2.dist-info/METADATA +154 -0
- tobac-1.6.2.dist-info/RECORD +53 -0
- tobac-1.6.2.dist-info/WHEEL +5 -0
- tobac-1.6.2.dist-info/licenses/LICENSE +29 -0
- tobac-1.6.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import numpy as np
|
|
3
|
+
import tobac.utils.periodic_boundaries as pbc_utils
|
|
4
|
+
import tobac.testing as tb_test
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_calc_distance_coords_pbc_2D():
|
|
8
|
+
"""Tests ```tobac.utils.calc_distance_coords_pbc```
|
|
9
|
+
Currently tests:
|
|
10
|
+
two points in normal space
|
|
11
|
+
Periodicity along hdim_1, hdim_2, and corners
|
|
12
|
+
"""
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
# Test first two points in normal space with varying PBC conditions
|
|
16
|
+
for max_dims in [
|
|
17
|
+
np.array([0, 0]),
|
|
18
|
+
np.array([10, 0]),
|
|
19
|
+
np.array([0, 10]),
|
|
20
|
+
np.array([10, 10]),
|
|
21
|
+
]:
|
|
22
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
23
|
+
np.array((0, 0)), np.array((0, 0)), max_dims
|
|
24
|
+
) == pytest.approx(0)
|
|
25
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
26
|
+
np.array((0, 0)), np.array((0, 1)), max_dims
|
|
27
|
+
) == pytest.approx(1)
|
|
28
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
29
|
+
np.array((0, 0)), np.array((3, 1)), max_dims
|
|
30
|
+
) == pytest.approx(10**0.5, rel=1e-3)
|
|
31
|
+
|
|
32
|
+
# Now test two points that will be closer along the hdim_1 boundary for cases without PBCs
|
|
33
|
+
for max_dims in [np.array([10, 0]), np.array([10, 10])]:
|
|
34
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
35
|
+
np.array((0, 0)), np.array((9, 0)), max_dims
|
|
36
|
+
) == pytest.approx(1)
|
|
37
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
38
|
+
np.array((9, 0)), np.array((0, 0)), max_dims
|
|
39
|
+
) == pytest.approx(1)
|
|
40
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
41
|
+
np.array((0, 4)), np.array((7, 3)), max_dims
|
|
42
|
+
) == pytest.approx(10**0.5)
|
|
43
|
+
|
|
44
|
+
# Test the same points, except without PBCs
|
|
45
|
+
for max_dims in [np.array([0, 0]), np.array([0, 10])]:
|
|
46
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
47
|
+
np.array((0, 0)), np.array((9, 0)), max_dims
|
|
48
|
+
) == pytest.approx(9)
|
|
49
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
50
|
+
np.array((9, 0)), np.array((0, 0)), max_dims
|
|
51
|
+
) == pytest.approx(9)
|
|
52
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
53
|
+
np.array((0, 4)), np.array((7, 3)), max_dims
|
|
54
|
+
) == pytest.approx(50**0.5)
|
|
55
|
+
|
|
56
|
+
# Now test two points that will be closer along the hdim_2 boundary for cases without PBCs
|
|
57
|
+
for max_dims in [np.array([0, 10]), np.array([10, 10])]:
|
|
58
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
59
|
+
np.array((0, 0)), np.array((0, 9)), max_dims
|
|
60
|
+
) == pytest.approx(1)
|
|
61
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
62
|
+
np.array((0, 9)), np.array((0, 0)), max_dims
|
|
63
|
+
) == pytest.approx(1)
|
|
64
|
+
|
|
65
|
+
# Test the same points, except without PBCs
|
|
66
|
+
for max_dims in [np.array([0, 0]), np.array([10, 0])]:
|
|
67
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
68
|
+
np.array((0, 0)), np.array((0, 9)), max_dims
|
|
69
|
+
) == pytest.approx(9)
|
|
70
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
71
|
+
np.array((0, 9)), np.array((0, 0)), max_dims
|
|
72
|
+
) == pytest.approx(9)
|
|
73
|
+
|
|
74
|
+
# Test points that will be closer for the both
|
|
75
|
+
max_dims = np.array([10, 10])
|
|
76
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
77
|
+
np.array((9, 9)), np.array((0, 0)), max_dims
|
|
78
|
+
) == pytest.approx(1.4142135, rel=1e-3)
|
|
79
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
80
|
+
np.array((0, 9)), np.array((9, 0)), max_dims
|
|
81
|
+
) == pytest.approx(1.4142135, rel=1e-3)
|
|
82
|
+
|
|
83
|
+
# Test the corner points for no PBCs
|
|
84
|
+
max_dims = np.array([0, 0])
|
|
85
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
86
|
+
np.array((9, 9)), np.array((0, 0)), max_dims
|
|
87
|
+
) == pytest.approx(12.727922, rel=1e-3)
|
|
88
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
89
|
+
np.array((0, 9)), np.array((9, 0)), max_dims
|
|
90
|
+
) == pytest.approx(12.727922, rel=1e-3)
|
|
91
|
+
|
|
92
|
+
# Test the corner points for hdim_1 and hdim_2
|
|
93
|
+
for max_dims in [np.array([10, 0]), np.array([0, 10])]:
|
|
94
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
95
|
+
np.array((9, 9)), np.array((0, 0)), max_dims
|
|
96
|
+
) == pytest.approx(9.055385)
|
|
97
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
98
|
+
np.array((0, 9)), np.array((9, 0)), max_dims
|
|
99
|
+
) == pytest.approx(9.055385)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_calc_distance_coords_pbc_3D():
|
|
103
|
+
"""Tests ```tobac.utils.calc_distance_coords_pbc```
|
|
104
|
+
Currently tests:
|
|
105
|
+
two points in normal space
|
|
106
|
+
Periodicity along hdim_1, hdim_2, and corners
|
|
107
|
+
"""
|
|
108
|
+
import numpy as np
|
|
109
|
+
|
|
110
|
+
# Test first two points in normal space with varying PBC conditions
|
|
111
|
+
for max_dims in [
|
|
112
|
+
np.array([0, 0, 0]),
|
|
113
|
+
np.array([0, 10, 0]),
|
|
114
|
+
np.array([0, 0, 10]),
|
|
115
|
+
np.array([0, 10, 10]),
|
|
116
|
+
]:
|
|
117
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
118
|
+
np.array((0, 0, 0)), np.array((0, 0, 0)), max_dims
|
|
119
|
+
) == pytest.approx(0)
|
|
120
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
121
|
+
np.array((0, 0, 0)), np.array((0, 0, 1)), max_dims
|
|
122
|
+
) == pytest.approx(1)
|
|
123
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
124
|
+
np.array((0, 0, 0)), np.array((3, 3, 1)), max_dims
|
|
125
|
+
) == pytest.approx(4.3588989, rel=1e-3)
|
|
126
|
+
|
|
127
|
+
# Now test two points that will be closer along the hdim_1 boundary for cases without PBCs
|
|
128
|
+
for max_dims in [np.array([0, 10, 0]), np.array([0, 10, 10])]:
|
|
129
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
130
|
+
np.array((0, 0, 0)), np.array((0, 9, 0)), max_dims
|
|
131
|
+
) == pytest.approx(1)
|
|
132
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
133
|
+
np.array((0, 9, 0)), np.array((0, 0, 0)), max_dims
|
|
134
|
+
) == pytest.approx(1)
|
|
135
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
136
|
+
np.array((4, 0, 4)), np.array((3, 7, 3)), max_dims
|
|
137
|
+
) == pytest.approx(3.3166247)
|
|
138
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
139
|
+
np.array((4, 0, 4)), np.array((3, 7, 3)), max_dims
|
|
140
|
+
) == pytest.approx(3.3166247)
|
|
141
|
+
|
|
142
|
+
# Test the same points, except without PBCs
|
|
143
|
+
for max_dims in [np.array([0, 0, 0]), np.array([0, 0, 10])]:
|
|
144
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
145
|
+
np.array((0, 0, 0)), np.array((0, 9, 0)), max_dims
|
|
146
|
+
) == pytest.approx(9)
|
|
147
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
148
|
+
np.array((0, 9, 0)), np.array((0, 0, 0)), max_dims
|
|
149
|
+
) == pytest.approx(9)
|
|
150
|
+
|
|
151
|
+
# Now test two points that will be closer along the hdim_2 boundary for cases without PBCs
|
|
152
|
+
for max_dims in [np.array([0, 0, 10]), np.array([0, 10, 10])]:
|
|
153
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
154
|
+
np.array((0, 0, 0)), np.array((0, 0, 9)), max_dims
|
|
155
|
+
) == pytest.approx(1)
|
|
156
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
157
|
+
np.array((0, 0, 9)), np.array((0, 0, 0)), max_dims
|
|
158
|
+
) == pytest.approx(1)
|
|
159
|
+
|
|
160
|
+
# Test the same points, except without PBCs
|
|
161
|
+
for max_dims in [np.array([0, 0, 0]), np.array([0, 10, 0])]:
|
|
162
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
163
|
+
np.array((0, 0, 0)), np.array((0, 0, 9)), max_dims
|
|
164
|
+
) == pytest.approx(9)
|
|
165
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
166
|
+
np.array((0, 0, 9)), np.array((0, 0, 0)), max_dims
|
|
167
|
+
) == pytest.approx(9)
|
|
168
|
+
|
|
169
|
+
# Test points that will be closer for the both
|
|
170
|
+
max_dims = np.array([0, 10, 10])
|
|
171
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
172
|
+
np.array((0, 9, 9)), np.array((0, 0, 0)), max_dims
|
|
173
|
+
) == pytest.approx(1.4142135, rel=1e-3)
|
|
174
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
175
|
+
np.array((0, 0, 9)), np.array((0, 9, 0)), max_dims
|
|
176
|
+
) == pytest.approx(1.4142135, rel=1e-3)
|
|
177
|
+
|
|
178
|
+
# Test the corner points for no PBCs
|
|
179
|
+
max_dims = np.array([0, 0, 0])
|
|
180
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
181
|
+
np.array((0, 9, 9)), np.array((0, 0, 0)), max_dims
|
|
182
|
+
) == pytest.approx(12.727922, rel=1e-3)
|
|
183
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
184
|
+
np.array((0, 0, 9)), np.array((0, 9, 0)), max_dims
|
|
185
|
+
) == pytest.approx(12.727922, rel=1e-3)
|
|
186
|
+
|
|
187
|
+
# Test the corner points for hdim_1 and hdim_2
|
|
188
|
+
for max_dims in [np.array([0, 10, 0]), np.array([0, 0, 10])]:
|
|
189
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
190
|
+
np.array((0, 9, 9)), np.array((0, 0, 0)), max_dims
|
|
191
|
+
) == pytest.approx(9.055385)
|
|
192
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
193
|
+
np.array((0, 0, 9)), np.array((0, 9, 0)), max_dims
|
|
194
|
+
) == pytest.approx(9.055385)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def test_invalid_limit_names():
|
|
198
|
+
"""
|
|
199
|
+
Test that invalid_limit_names will correctly return the names of keywords that equal None
|
|
200
|
+
"""
|
|
201
|
+
assert pbc_utils.invalid_limit_names() == []
|
|
202
|
+
assert pbc_utils.invalid_limit_names(a=0, b=0) == []
|
|
203
|
+
assert pbc_utils.invalid_limit_names(a=None, b=0) == ["a"]
|
|
204
|
+
assert pbc_utils.invalid_limit_names(a=0, b=None) == ["b"]
|
|
205
|
+
assert pbc_utils.invalid_limit_names(a=None, b=None) == ["a", "b"]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_validate_pbc_dims():
|
|
209
|
+
"""
|
|
210
|
+
Test validate_pbc_dims works correctly and raise exceptions appropriately
|
|
211
|
+
"""
|
|
212
|
+
# Assert that size is only returned if PBCs are required in that dimension
|
|
213
|
+
assert pbc_utils.validate_pbc_dims(0, 10, 0, 15, "none") == (0, 0)
|
|
214
|
+
assert pbc_utils.validate_pbc_dims(0, 10, 0, 15, "hdim_1") == (10, 0)
|
|
215
|
+
assert pbc_utils.validate_pbc_dims(0, 10, 0, 15, "hdim_2") == (0, 15)
|
|
216
|
+
assert pbc_utils.validate_pbc_dims(0, 10, 0, 15, "both") == (10, 15)
|
|
217
|
+
|
|
218
|
+
# Assert that giving None for limits of dimensions that are not PBCs is handled correctly
|
|
219
|
+
assert pbc_utils.validate_pbc_dims(None, None, None, None, "none") == (0, 0)
|
|
220
|
+
assert pbc_utils.validate_pbc_dims(0, 10, None, None, "hdim_1") == (10, 0)
|
|
221
|
+
assert pbc_utils.validate_pbc_dims(None, None, 0, 15, "hdim_2") == (0, 15)
|
|
222
|
+
|
|
223
|
+
# Test that an invalid option for PBC_flag raises the correct error
|
|
224
|
+
with pytest.raises(pbc_utils.PBCflagError):
|
|
225
|
+
_ = pbc_utils.validate_pbc_dims(0, 10, 0, 15, "invalid_pbc_option")
|
|
226
|
+
|
|
227
|
+
# Test that providing None for the limits of a PBC dimension raises the correct error
|
|
228
|
+
with pytest.raises(pbc_utils.PBCLimitError):
|
|
229
|
+
_ = pbc_utils.validate_pbc_dims(None, None, 0, 15, "hdim_1")
|
|
230
|
+
with pytest.raises(pbc_utils.PBCLimitError):
|
|
231
|
+
_ = pbc_utils.validate_pbc_dims(None, None, None, 15, "hdim_2")
|
|
232
|
+
with pytest.raises(pbc_utils.PBCLimitError):
|
|
233
|
+
_ = pbc_utils.validate_pbc_dims(0, None, 0, None, "both")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@pytest.mark.parametrize(
|
|
237
|
+
"loc_1, loc_2, bounds, PBC_flag, expected_dist",
|
|
238
|
+
[
|
|
239
|
+
((0, 0, 0), (0, 0, 9), (0, 10, 0, 10), "both", 1),
|
|
240
|
+
],
|
|
241
|
+
)
|
|
242
|
+
def test_calc_distance_coords_pbc_param(loc_1, loc_2, bounds, PBC_flag, expected_dist):
|
|
243
|
+
"""Tests ```tobac.utils.calc_distance_coords_pbc``` in a parameterized way
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
loc_1: tuple
|
|
248
|
+
First point location, either in 2D or 3D space (assumed z, h1, h2)
|
|
249
|
+
loc_2: tuple
|
|
250
|
+
Second point location, either in 2D or 3D space (assumed z, h1, h2)
|
|
251
|
+
bounds: tuple
|
|
252
|
+
hdim_1/hdim_2 bounds as (h1_min, h1_max, h2_min, h2_max)
|
|
253
|
+
PBC_flag : {'none', 'hdim_1', 'hdim_2', 'both'}
|
|
254
|
+
Sets whether to use periodic boundaries, and if so in which directions.
|
|
255
|
+
'none' means that we do not have periodic boundaries
|
|
256
|
+
'hdim_1' means that we are periodic along hdim1
|
|
257
|
+
'hdim_2' means that we are periodic along hdim2
|
|
258
|
+
'both' means that we are periodic along both horizontal dimensions
|
|
259
|
+
expected_dist: float
|
|
260
|
+
Expected distance between the two points
|
|
261
|
+
"""
|
|
262
|
+
h1_size, h2_size = pbc_utils.validate_pbc_dims(
|
|
263
|
+
bounds[0],
|
|
264
|
+
bounds[1],
|
|
265
|
+
bounds[2],
|
|
266
|
+
bounds[3],
|
|
267
|
+
PBC_flag,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if len(loc_1) == 3:
|
|
271
|
+
max_dims = np.array([0, h1_size, h2_size])
|
|
272
|
+
else:
|
|
273
|
+
max_dims = np.array([h1_size, h2_size])
|
|
274
|
+
|
|
275
|
+
assert pbc_utils.calc_distance_coords_pbc(
|
|
276
|
+
np.array(loc_1),
|
|
277
|
+
np.array(loc_2),
|
|
278
|
+
max_dims,
|
|
279
|
+
) == pytest.approx(expected_dist)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_get_pbc_coordinates():
|
|
283
|
+
"""Tests tobac.util.get_pbc_coordinates.
|
|
284
|
+
Currently runs the following tests:
|
|
285
|
+
For an invalid PBC_flag, we raise an error
|
|
286
|
+
For PBC_flag of 'none', we truncate the box and give a valid box.
|
|
287
|
+
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
with pytest.raises(ValueError):
|
|
291
|
+
pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, 1, 4, "c")
|
|
292
|
+
|
|
293
|
+
# Test PBC_flag of none
|
|
294
|
+
|
|
295
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, 1, 4, "none") == [
|
|
296
|
+
(1, 4, 1, 4),
|
|
297
|
+
]
|
|
298
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -1, 4, 1, 4, "none") == [
|
|
299
|
+
(0, 4, 1, 4),
|
|
300
|
+
]
|
|
301
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 12, 1, 4, "none") == [
|
|
302
|
+
(1, 10, 1, 4),
|
|
303
|
+
]
|
|
304
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 12, -1, 4, "none") == [
|
|
305
|
+
(1, 10, 0, 4),
|
|
306
|
+
]
|
|
307
|
+
|
|
308
|
+
# Test PBC_flag with hdim_1
|
|
309
|
+
# Simple case, no PBC overlapping
|
|
310
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, 1, 4, "hdim_1") == [
|
|
311
|
+
(1, 4, 1, 4),
|
|
312
|
+
]
|
|
313
|
+
# PBC going on the min side
|
|
314
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -1, 4, 1, 4, "hdim_1") == [
|
|
315
|
+
(0, 4, 1, 4),
|
|
316
|
+
(9, 10, 1, 4),
|
|
317
|
+
]
|
|
318
|
+
# PBC going on the min side; should be truncated in hdim_2.
|
|
319
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -1, 4, -1, 4, "hdim_1") == [
|
|
320
|
+
(0, 4, 0, 4),
|
|
321
|
+
(9, 10, 0, 4),
|
|
322
|
+
]
|
|
323
|
+
# PBC going on the max side only
|
|
324
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 4, 12, 1, 4, "hdim_1") == [
|
|
325
|
+
(4, 10, 1, 4),
|
|
326
|
+
(0, 2, 1, 4),
|
|
327
|
+
]
|
|
328
|
+
# PBC overlapping
|
|
329
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -4, 12, 1, 4, "hdim_1") == [
|
|
330
|
+
(0, 10, 1, 4),
|
|
331
|
+
]
|
|
332
|
+
|
|
333
|
+
# Test PBC_flag with hdim_2
|
|
334
|
+
# Simple case, no PBC overlapping
|
|
335
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, 1, 4, "hdim_2") == [
|
|
336
|
+
(1, 4, 1, 4),
|
|
337
|
+
]
|
|
338
|
+
# PBC going on the min side
|
|
339
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, -1, 4, "hdim_2") == [
|
|
340
|
+
(1, 4, 0, 4),
|
|
341
|
+
(1, 4, 9, 10),
|
|
342
|
+
]
|
|
343
|
+
# PBC going on the min side with truncation in hdim_1
|
|
344
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -4, 4, -1, 4, "hdim_2") == [
|
|
345
|
+
(0, 4, 0, 4),
|
|
346
|
+
(0, 4, 9, 10),
|
|
347
|
+
]
|
|
348
|
+
# PBC going on the max side
|
|
349
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, 4, 12, "hdim_2") == [
|
|
350
|
+
(1, 4, 4, 10),
|
|
351
|
+
(1, 4, 0, 2),
|
|
352
|
+
]
|
|
353
|
+
# PBC overlapping
|
|
354
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, -4, 12, "hdim_2") == [
|
|
355
|
+
(1, 4, 0, 10),
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
# Test PBC_flag with both
|
|
359
|
+
# Simple case, no PBC overlapping
|
|
360
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, 1, 4, "both") == [
|
|
361
|
+
(1, 4, 1, 4),
|
|
362
|
+
]
|
|
363
|
+
# hdim_1 only testing
|
|
364
|
+
# PBC on the min side of hdim_1 only
|
|
365
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -1, 4, 1, 4, "both") == [
|
|
366
|
+
(0, 4, 1, 4),
|
|
367
|
+
(9, 10, 1, 4),
|
|
368
|
+
]
|
|
369
|
+
# PBC on the max side of hdim_1 only
|
|
370
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 4, 12, 1, 4, "both") == [
|
|
371
|
+
(4, 10, 1, 4),
|
|
372
|
+
(0, 2, 1, 4),
|
|
373
|
+
]
|
|
374
|
+
# PBC overlapping on max side of hdim_1 only
|
|
375
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -4, 12, 1, 4, "both") == [
|
|
376
|
+
(0, 10, 1, 4),
|
|
377
|
+
]
|
|
378
|
+
# hdim_2 only testing
|
|
379
|
+
# PBC on the min side of hdim_2 only
|
|
380
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, -1, 4, "both") == [
|
|
381
|
+
(1, 4, 0, 4),
|
|
382
|
+
(1, 4, 9, 10),
|
|
383
|
+
]
|
|
384
|
+
# PBC on the max side of hdim_2 only
|
|
385
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, 4, 12, "both") == [
|
|
386
|
+
(1, 4, 4, 10),
|
|
387
|
+
(1, 4, 0, 2),
|
|
388
|
+
]
|
|
389
|
+
# PBC overlapping on max side of hdim_2 only
|
|
390
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 1, 4, -4, 12, "both") == [
|
|
391
|
+
(1, 4, 0, 10),
|
|
392
|
+
]
|
|
393
|
+
# hdim_1 and hdim_2 testing simultaneous
|
|
394
|
+
# both larger than the actual domain
|
|
395
|
+
assert pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -1, 12, -4, 14, "both") == [
|
|
396
|
+
(0, 10, 0, 10),
|
|
397
|
+
]
|
|
398
|
+
# min in hdim_1 and hdim_2
|
|
399
|
+
assert tb_test.lists_equal_without_order(
|
|
400
|
+
pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -3, 3, -4, 2, "both"),
|
|
401
|
+
[(0, 3, 0, 2), (0, 3, 6, 10), (7, 10, 6, 10), (7, 10, 0, 2)],
|
|
402
|
+
)
|
|
403
|
+
# max in hdim_1, min in hdim_2
|
|
404
|
+
assert tb_test.lists_equal_without_order(
|
|
405
|
+
pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 5, 12, -4, 2, "both"),
|
|
406
|
+
[(5, 10, 0, 2), (5, 10, 6, 10), (0, 2, 6, 10), (0, 2, 0, 2)],
|
|
407
|
+
)
|
|
408
|
+
# max in hdim_1 and hdim_2
|
|
409
|
+
assert tb_test.lists_equal_without_order(
|
|
410
|
+
pbc_utils.get_pbc_coordinates(0, 10, 0, 10, 5, 12, 7, 15, "both"),
|
|
411
|
+
[(5, 10, 7, 10), (5, 10, 0, 5), (0, 2, 0, 5), (0, 2, 7, 10)],
|
|
412
|
+
)
|
|
413
|
+
# min in hdim_1, max in hdim_2
|
|
414
|
+
assert tb_test.lists_equal_without_order(
|
|
415
|
+
pbc_utils.get_pbc_coordinates(0, 10, 0, 10, -3, 3, 7, 15, "both"),
|
|
416
|
+
[(0, 3, 7, 10), (0, 3, 0, 5), (7, 10, 0, 5), (7, 10, 7, 10)],
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def test_build_distance_function_3D():
|
|
421
|
+
"""Tests ```pbc_utils.build_distance_function```
|
|
422
|
+
Currently tests:
|
|
423
|
+
that this produces an object that is suitable to call from trackpy
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
test_func = pbc_utils.build_distance_function(0, 10, 0, 10, "both", True)
|
|
427
|
+
assert test_func(np.array((0, 9, 9)), np.array((0, 0, 0))) == pytest.approx(
|
|
428
|
+
1.4142135
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
test_func = pbc_utils.build_distance_function(0, 10, None, None, "hdim_1", True)
|
|
432
|
+
assert test_func(np.array((0, 9, 9)), np.array((0, 0, 9))) == pytest.approx(1)
|
|
433
|
+
|
|
434
|
+
test_func = pbc_utils.build_distance_function(None, None, 0, 10, "hdim_2", True)
|
|
435
|
+
assert test_func(np.array((0, 9, 9)), np.array((0, 9, 0))) == pytest.approx(1)
|
|
436
|
+
|
|
437
|
+
test_func = pbc_utils.build_distance_function(None, None, None, None, "none", True)
|
|
438
|
+
assert test_func(np.array((0, 9, 9)), np.array((0, 0, 0))) == pytest.approx(
|
|
439
|
+
(2 * 81) ** 0.5
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def test_build_distance_function_2D():
|
|
444
|
+
"""Tests ```pbc_utils.build_distance_function```
|
|
445
|
+
Currently tests:
|
|
446
|
+
that this produces an object that is suitable to call from trackpy
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
test_func = pbc_utils.build_distance_function(0, 10, 0, 10, "both", False)
|
|
450
|
+
assert test_func(np.array((9, 9)), np.array((0, 0))) == pytest.approx(1.4142135)
|
|
451
|
+
|
|
452
|
+
test_func = pbc_utils.build_distance_function(0, 10, None, None, "hdim_1", False)
|
|
453
|
+
assert test_func(np.array((9, 9)), np.array((0, 9))) == pytest.approx(1)
|
|
454
|
+
|
|
455
|
+
test_func = pbc_utils.build_distance_function(None, None, 0, 10, "hdim_2", False)
|
|
456
|
+
assert test_func(np.array((9, 9)), np.array((9, 0))) == pytest.approx(1)
|
|
457
|
+
|
|
458
|
+
test_func = pbc_utils.build_distance_function(None, None, None, None, "none", False)
|
|
459
|
+
assert test_func(np.array((9, 9)), np.array((0, 0))) == pytest.approx(
|
|
460
|
+
(2 * 81) ** 0.5
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def test_weighted_circmean() -> None:
|
|
465
|
+
"""
|
|
466
|
+
Test that weighted_circmean gives the expected results compared to scipy.stats.circmean
|
|
467
|
+
"""
|
|
468
|
+
from scipy.stats import circmean
|
|
469
|
+
|
|
470
|
+
values = np.arange(0, 12)
|
|
471
|
+
weights = np.ones([12])
|
|
472
|
+
high, low = 5, 0
|
|
473
|
+
assert pbc_utils.weighted_circmean(
|
|
474
|
+
values, weights, high=high, low=low
|
|
475
|
+
) == pytest.approx(circmean(values, high=high, low=low))
|
|
476
|
+
|
|
477
|
+
# Test if weights are set to values other than 1
|
|
478
|
+
assert pbc_utils.weighted_circmean(
|
|
479
|
+
values, weights / 5, high=high, low=low
|
|
480
|
+
) == pytest.approx(circmean(values, high=high, low=low))
|
|
481
|
+
assert pbc_utils.weighted_circmean(
|
|
482
|
+
values, weights * 7, high=high, low=low
|
|
483
|
+
) == pytest.approx(circmean(values, high=high, low=low))
|
|
484
|
+
|
|
485
|
+
# set some weights to zero
|
|
486
|
+
weights[[4, 7, 9]] = 0
|
|
487
|
+
assert pbc_utils.weighted_circmean(
|
|
488
|
+
values, weights, high=high, low=low
|
|
489
|
+
) == pytest.approx(circmean(values[weights.astype(bool)], high=high, low=low))
|
|
490
|
+
|
|
491
|
+
# Set some non-zero weights
|
|
492
|
+
weights[[4, 7]] = 2
|
|
493
|
+
weights[9] = 3
|
|
494
|
+
duplicated_values = np.concatenate([values, [4, 7, 9, 9]])
|
|
495
|
+
assert pbc_utils.weighted_circmean(
|
|
496
|
+
values, weights, high=high, low=low
|
|
497
|
+
) == pytest.approx(circmean(duplicated_values, high=high, low=low))
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for tobac based on simple sample datasets with moving blobs. These tests should be adapted to be more modular in the future.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from tobac.testing import (
|
|
6
|
+
make_sample_data_2D_3blobs,
|
|
7
|
+
make_sample_data_2D_3blobs_inv,
|
|
8
|
+
make_sample_data_3D_3blobs,
|
|
9
|
+
)
|
|
10
|
+
from tobac import (
|
|
11
|
+
feature_detection_multithreshold,
|
|
12
|
+
linking_trackpy,
|
|
13
|
+
get_spacings,
|
|
14
|
+
segmentation_2D,
|
|
15
|
+
segmentation_3D,
|
|
16
|
+
)
|
|
17
|
+
from iris.analysis import MEAN, MAX, MIN
|
|
18
|
+
from pandas.testing import assert_frame_equal
|
|
19
|
+
from numpy.testing import assert_allclose
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_sample_data():
|
|
24
|
+
"""
|
|
25
|
+
Test to make sure that sample datasets in the following tests are set up the right way
|
|
26
|
+
"""
|
|
27
|
+
sample_data = make_sample_data_2D_3blobs()
|
|
28
|
+
sample_data_inv = make_sample_data_2D_3blobs_inv()
|
|
29
|
+
|
|
30
|
+
assert sample_data.coord("projection_x_coordinate") == sample_data_inv.coord(
|
|
31
|
+
"projection_x_coordinate"
|
|
32
|
+
)
|
|
33
|
+
assert sample_data.coord("projection_y_coordinate") == sample_data_inv.coord(
|
|
34
|
+
"projection_y_coordinate"
|
|
35
|
+
)
|
|
36
|
+
assert sample_data.coord("time") == sample_data_inv.coord("time")
|
|
37
|
+
minimum = sample_data.collapsed(
|
|
38
|
+
("time", "projection_x_coordinate", "projection_y_coordinate"), MIN
|
|
39
|
+
).data
|
|
40
|
+
minimum_inv = sample_data_inv.collapsed(
|
|
41
|
+
("time", "projection_x_coordinate", "projection_y_coordinate"), MIN
|
|
42
|
+
).data
|
|
43
|
+
assert_allclose(minimum, minimum_inv)
|
|
44
|
+
mean = sample_data.collapsed(
|
|
45
|
+
("time", "projection_x_coordinate", "projection_y_coordinate"), MEAN
|
|
46
|
+
).data
|
|
47
|
+
mean_inv = sample_data_inv.collapsed(
|
|
48
|
+
("time", "projection_x_coordinate", "projection_y_coordinate"), MEAN
|
|
49
|
+
).data
|
|
50
|
+
assert_allclose(mean, mean_inv)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_tracking_coord_order():
|
|
54
|
+
"""
|
|
55
|
+
Test a tracking applications to make sure that coordinate order does not lead to different results
|
|
56
|
+
"""
|
|
57
|
+
sample_data = make_sample_data_2D_3blobs()
|
|
58
|
+
sample_data_inv = make_sample_data_2D_3blobs_inv()
|
|
59
|
+
# Keyword arguments for feature detection step:
|
|
60
|
+
parameters_features = {}
|
|
61
|
+
parameters_features["position_threshold"] = "weighted_diff"
|
|
62
|
+
parameters_features["sigma_threshold"] = 0.5
|
|
63
|
+
parameters_features["min_distance"] = 0
|
|
64
|
+
parameters_features["sigma_threshold"] = 1
|
|
65
|
+
parameters_features["threshold"] = [3, 5, 10] # m/s
|
|
66
|
+
parameters_features["n_erosion_threshold"] = 0
|
|
67
|
+
parameters_features["n_min_threshold"] = 3
|
|
68
|
+
|
|
69
|
+
# calculate dxy,dt
|
|
70
|
+
dxy, dt = get_spacings(sample_data)
|
|
71
|
+
dxy_inv, dt_inv = get_spacings(sample_data_inv)
|
|
72
|
+
|
|
73
|
+
# Test that dt and dxy are the same for different order of coordinates
|
|
74
|
+
assert_allclose(dxy, dxy_inv)
|
|
75
|
+
assert_allclose(dt, dt_inv)
|
|
76
|
+
|
|
77
|
+
# Test that dt and dxy are as expected
|
|
78
|
+
assert_allclose(dt, 60)
|
|
79
|
+
assert_allclose(dxy, 1000)
|
|
80
|
+
|
|
81
|
+
# Find features
|
|
82
|
+
Features = feature_detection_multithreshold(sample_data, dxy, **parameters_features)
|
|
83
|
+
Features_inv = feature_detection_multithreshold(
|
|
84
|
+
sample_data_inv, dxy_inv, **parameters_features
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Assert that output of feature detection not empty:
|
|
88
|
+
assert type(Features) == pd.core.frame.DataFrame
|
|
89
|
+
assert type(Features_inv) == pd.core.frame.DataFrame
|
|
90
|
+
assert not Features.empty
|
|
91
|
+
assert not Features_inv.empty
|
|
92
|
+
|
|
93
|
+
# perform watershedding segmentation
|
|
94
|
+
parameters_segmentation = {}
|
|
95
|
+
parameters_segmentation["target"] = "maximum"
|
|
96
|
+
parameters_segmentation["method"] = "watershed"
|
|
97
|
+
|
|
98
|
+
segmentation_mask, features_segmentation = segmentation_2D(
|
|
99
|
+
Features, sample_data, dxy=dxy, **parameters_segmentation
|
|
100
|
+
)
|
|
101
|
+
segmentation_mask_inv, features_segmentation = segmentation_2D(
|
|
102
|
+
Features_inv, sample_data_inv, dxy=dxy_inv, **parameters_segmentation
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# perform trajectory linking
|
|
106
|
+
|
|
107
|
+
parameters_linking = {}
|
|
108
|
+
parameters_linking["method_linking"] = "predict"
|
|
109
|
+
parameters_linking["adaptive_stop"] = 0.2
|
|
110
|
+
parameters_linking["adaptive_step"] = 0.95
|
|
111
|
+
parameters_linking["extrapolate"] = 0
|
|
112
|
+
parameters_linking["order"] = 1
|
|
113
|
+
parameters_linking["subnetwork_size"] = 100
|
|
114
|
+
parameters_linking["memory"] = 0
|
|
115
|
+
parameters_linking["time_cell_min"] = 5 * 60
|
|
116
|
+
parameters_linking["method_linking"] = "predict"
|
|
117
|
+
parameters_linking["v_max"] = 100
|
|
118
|
+
|
|
119
|
+
Track = linking_trackpy(Features, sample_data, dt=dt, dxy=dxy, **parameters_linking)
|
|
120
|
+
Track_inv = linking_trackpy(
|
|
121
|
+
Features_inv, sample_data_inv, dt=dt_inv, dxy=dxy_inv, **parameters_linking
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_tracking_3D():
|
|
126
|
+
"""
|
|
127
|
+
Test a tracking applications to make sure that coordinate order does not lead to different results
|
|
128
|
+
"""
|
|
129
|
+
sample_data = make_sample_data_3D_3blobs()
|
|
130
|
+
sample_data_inv = make_sample_data_3D_3blobs(invert_xy=True)
|
|
131
|
+
# Keyword arguments for feature detection step:
|
|
132
|
+
parameters_features = {}
|
|
133
|
+
parameters_features["position_threshold"] = "weighted_diff"
|
|
134
|
+
parameters_features["sigma_threshold"] = 0.5
|
|
135
|
+
parameters_features["min_distance"] = 0
|
|
136
|
+
parameters_features["sigma_threshold"] = 1
|
|
137
|
+
parameters_features["threshold"] = [3, 5, 10] # m/s
|
|
138
|
+
parameters_features["n_erosion_threshold"] = 0
|
|
139
|
+
parameters_features["n_min_threshold"] = 3
|
|
140
|
+
|
|
141
|
+
sample_data_max = sample_data.collapsed("geopotential_height", MAX)
|
|
142
|
+
sample_data_max_inv = sample_data.collapsed("geopotential_height", MAX)
|
|
143
|
+
|
|
144
|
+
# calculate dxy,dt
|
|
145
|
+
dxy, dt = get_spacings(sample_data_max)
|
|
146
|
+
dxy_inv, dt_inv = get_spacings(sample_data_max_inv)
|
|
147
|
+
|
|
148
|
+
# Test that dt and dxy are the same for different order of coordinates
|
|
149
|
+
assert_allclose(dxy, dxy_inv)
|
|
150
|
+
assert_allclose(dt, dt_inv)
|
|
151
|
+
|
|
152
|
+
# Test that dt and dxy are as expected
|
|
153
|
+
assert_allclose(dt, 120)
|
|
154
|
+
assert_allclose(dxy, 1000)
|
|
155
|
+
|
|
156
|
+
# Find features
|
|
157
|
+
Features = feature_detection_multithreshold(
|
|
158
|
+
sample_data_max, dxy, **parameters_features
|
|
159
|
+
)
|
|
160
|
+
Features_inv = feature_detection_multithreshold(
|
|
161
|
+
sample_data_max_inv, dxy_inv, **parameters_features
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# perform watershedding segmentation
|
|
165
|
+
parameters_segmentation = {}
|
|
166
|
+
parameters_segmentation["target"] = "maximum"
|
|
167
|
+
parameters_segmentation["method"] = "watershed"
|
|
168
|
+
|
|
169
|
+
segmentation_mask, features_segmentation = segmentation_3D(
|
|
170
|
+
Features, sample_data_max, dxy=dxy, **parameters_segmentation
|
|
171
|
+
)
|
|
172
|
+
segmentation_mask_inv, features_segmentation = segmentation_3D(
|
|
173
|
+
Features_inv, sample_data_max_inv, dxy=dxy_inv, **parameters_segmentation
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# perform trajectory linking
|
|
177
|
+
|
|
178
|
+
parameters_linking = {}
|
|
179
|
+
parameters_linking["method_linking"] = "predict"
|
|
180
|
+
parameters_linking["adaptive_stop"] = 0.2
|
|
181
|
+
parameters_linking["adaptive_step"] = 0.95
|
|
182
|
+
parameters_linking["extrapolate"] = 0
|
|
183
|
+
parameters_linking["order"] = 1
|
|
184
|
+
parameters_linking["subnetwork_size"] = 100
|
|
185
|
+
parameters_linking["memory"] = 0
|
|
186
|
+
parameters_linking["time_cell_min"] = 5 * 60
|
|
187
|
+
parameters_linking["method_linking"] = "predict"
|
|
188
|
+
parameters_linking["v_max"] = 100
|
|
189
|
+
|
|
190
|
+
Track = linking_trackpy(Features, sample_data, dt=dt, dxy=dxy, **parameters_linking)
|
|
191
|
+
Track_inv = linking_trackpy(
|
|
192
|
+
Features_inv, sample_data_inv, dt=dt_inv, dxy=dxy_inv, **parameters_linking
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Assert that output of feature detection not empty:
|
|
196
|
+
assert not Track.empty
|
|
197
|
+
assert not Track_inv.empty
|