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.
Files changed (53) hide show
  1. tobac/__init__.py +112 -0
  2. tobac/analysis/__init__.py +31 -0
  3. tobac/analysis/cell_analysis.py +628 -0
  4. tobac/analysis/feature_analysis.py +212 -0
  5. tobac/analysis/spatial.py +619 -0
  6. tobac/centerofgravity.py +226 -0
  7. tobac/feature_detection.py +1758 -0
  8. tobac/merge_split.py +324 -0
  9. tobac/plotting.py +2321 -0
  10. tobac/segmentation/__init__.py +10 -0
  11. tobac/segmentation/watershed_segmentation.py +1316 -0
  12. tobac/testing.py +1179 -0
  13. tobac/tests/segmentation_tests/test_iris_xarray_segmentation.py +0 -0
  14. tobac/tests/segmentation_tests/test_segmentation.py +1183 -0
  15. tobac/tests/segmentation_tests/test_segmentation_time_pad.py +104 -0
  16. tobac/tests/test_analysis_spatial.py +1109 -0
  17. tobac/tests/test_convert.py +265 -0
  18. tobac/tests/test_datetime.py +216 -0
  19. tobac/tests/test_decorators.py +148 -0
  20. tobac/tests/test_feature_detection.py +1321 -0
  21. tobac/tests/test_generators.py +273 -0
  22. tobac/tests/test_import.py +24 -0
  23. tobac/tests/test_iris_xarray_match_utils.py +244 -0
  24. tobac/tests/test_merge_split.py +351 -0
  25. tobac/tests/test_pbc_utils.py +497 -0
  26. tobac/tests/test_sample_data.py +197 -0
  27. tobac/tests/test_testing.py +747 -0
  28. tobac/tests/test_tracking.py +714 -0
  29. tobac/tests/test_utils.py +650 -0
  30. tobac/tests/test_utils_bulk_statistics.py +789 -0
  31. tobac/tests/test_utils_coordinates.py +328 -0
  32. tobac/tests/test_utils_internal.py +97 -0
  33. tobac/tests/test_xarray_utils.py +232 -0
  34. tobac/tracking.py +613 -0
  35. tobac/utils/__init__.py +27 -0
  36. tobac/utils/bulk_statistics.py +360 -0
  37. tobac/utils/datetime.py +184 -0
  38. tobac/utils/decorators.py +540 -0
  39. tobac/utils/general.py +753 -0
  40. tobac/utils/generators.py +87 -0
  41. tobac/utils/internal/__init__.py +2 -0
  42. tobac/utils/internal/coordinates.py +430 -0
  43. tobac/utils/internal/iris_utils.py +462 -0
  44. tobac/utils/internal/label_props.py +82 -0
  45. tobac/utils/internal/xarray_utils.py +439 -0
  46. tobac/utils/mask.py +364 -0
  47. tobac/utils/periodic_boundaries.py +419 -0
  48. tobac/wrapper.py +244 -0
  49. tobac-1.6.2.dist-info/METADATA +154 -0
  50. tobac-1.6.2.dist-info/RECORD +53 -0
  51. tobac-1.6.2.dist-info/WHEEL +5 -0
  52. tobac-1.6.2.dist-info/licenses/LICENSE +29 -0
  53. 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