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,351 @@
|
|
|
1
|
+
"""Module to test tobac.merge_split"""
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
import datetime
|
|
8
|
+
|
|
9
|
+
import tobac.testing as tbtest
|
|
10
|
+
import tobac.tracking as tbtrack
|
|
11
|
+
import tobac.merge_split as mergesplit
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_merge_split_MEST():
|
|
15
|
+
"""Tests tobac.merge_split.merge_split_cells by
|
|
16
|
+
generating two cells, colliding them into one another,
|
|
17
|
+
and merging them.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
cell_1 = tbtest.generate_single_feature(
|
|
21
|
+
1,
|
|
22
|
+
1,
|
|
23
|
+
min_h1=0,
|
|
24
|
+
max_h1=100,
|
|
25
|
+
min_h2=0,
|
|
26
|
+
max_h2=100,
|
|
27
|
+
frame_start=0,
|
|
28
|
+
num_frames=2,
|
|
29
|
+
spd_h1=20,
|
|
30
|
+
spd_h2=20,
|
|
31
|
+
start_date=datetime.datetime(2022, 1, 1, 0),
|
|
32
|
+
)
|
|
33
|
+
cell_1["feature"] = [1, 3]
|
|
34
|
+
|
|
35
|
+
cell_2 = tbtest.generate_single_feature(
|
|
36
|
+
1,
|
|
37
|
+
100,
|
|
38
|
+
min_h1=0,
|
|
39
|
+
max_h1=101,
|
|
40
|
+
min_h2=0,
|
|
41
|
+
max_h2=101,
|
|
42
|
+
frame_start=0,
|
|
43
|
+
num_frames=2,
|
|
44
|
+
spd_h1=20,
|
|
45
|
+
spd_h2=-20,
|
|
46
|
+
start_date=datetime.datetime(2022, 1, 1, 0),
|
|
47
|
+
)
|
|
48
|
+
cell_2["feature"] = [2, 4]
|
|
49
|
+
cell_3 = tbtest.generate_single_feature(
|
|
50
|
+
30,
|
|
51
|
+
50,
|
|
52
|
+
min_h1=0,
|
|
53
|
+
max_h1=100,
|
|
54
|
+
min_h2=0,
|
|
55
|
+
max_h2=100,
|
|
56
|
+
frame_start=2,
|
|
57
|
+
num_frames=2,
|
|
58
|
+
spd_h1=20,
|
|
59
|
+
spd_h2=0,
|
|
60
|
+
start_date=datetime.datetime(2022, 1, 1, 0, 10),
|
|
61
|
+
)
|
|
62
|
+
cell_3["feature"] = [5, 6]
|
|
63
|
+
features = pd.concat([cell_1, cell_2, cell_3])
|
|
64
|
+
output = tbtrack.linking_trackpy(features, None, 1, 1, v_max=40)
|
|
65
|
+
|
|
66
|
+
dist_between = np.sqrt(
|
|
67
|
+
np.power(
|
|
68
|
+
output[output["frame"] == 1].iloc[0]["hdim_1"]
|
|
69
|
+
- output[output["frame"] == 1].iloc[1]["hdim_1"],
|
|
70
|
+
2,
|
|
71
|
+
)
|
|
72
|
+
+ np.power(
|
|
73
|
+
output[output["frame"] == 1].iloc[0]["hdim_2"]
|
|
74
|
+
- output[output["frame"] == 1].iloc[1]["hdim_2"],
|
|
75
|
+
2,
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Test a successful merger
|
|
80
|
+
mergesplit_output_merged = mergesplit.merge_split_MEST(
|
|
81
|
+
output, dxy=10, distance=(dist_between + 50) * 10
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# These cells should have merged together.
|
|
85
|
+
assert len(mergesplit_output_merged["track"]) == 1
|
|
86
|
+
|
|
87
|
+
# Test an unsuccessful merger.
|
|
88
|
+
mergesplit_output_unmerged = mergesplit.merge_split_MEST(
|
|
89
|
+
output, dxy=10, distance=(dist_between - 50) * 10
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# These cells should NOT have merged together.
|
|
93
|
+
print(mergesplit_output_unmerged["track"])
|
|
94
|
+
assert len(mergesplit_output_unmerged["track"]) == 2
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_merge_split_MEST_PBC():
|
|
98
|
+
"""
|
|
99
|
+
Test PBC handling for merge_split_MEST
|
|
100
|
+
"""
|
|
101
|
+
test_features = pd.DataFrame(
|
|
102
|
+
{
|
|
103
|
+
"feature": [1, 2, 3, 4],
|
|
104
|
+
"hdim_1": [1, 89, 1, 99],
|
|
105
|
+
"hdim_2": [50, 50, 50, 50],
|
|
106
|
+
"cell": [1, 2, 1, 2],
|
|
107
|
+
"frame": [0, 0, 1, 1],
|
|
108
|
+
"time": [
|
|
109
|
+
datetime.datetime(2000, 1, 1),
|
|
110
|
+
datetime.datetime(2000, 1, 1),
|
|
111
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
112
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
113
|
+
],
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
# Test without PBCs
|
|
117
|
+
mergesplit_output_no_pbc = mergesplit.merge_split_MEST(
|
|
118
|
+
test_features,
|
|
119
|
+
dxy=1,
|
|
120
|
+
distance=25,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
assert len(mergesplit_output_no_pbc["track"]) == 2
|
|
124
|
+
|
|
125
|
+
# Test with PBC in hdim_1, cells should merge
|
|
126
|
+
mergesplit_output_hdim_1_pbc = mergesplit.merge_split_MEST(
|
|
127
|
+
test_features,
|
|
128
|
+
dxy=1,
|
|
129
|
+
distance=25,
|
|
130
|
+
PBC_flag="hdim_1",
|
|
131
|
+
min_h1=0,
|
|
132
|
+
max_h1=100,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
assert len(mergesplit_output_hdim_1_pbc["track"]) == 1
|
|
136
|
+
|
|
137
|
+
# Test with PBC in hdim_2, cells should not merge
|
|
138
|
+
mergesplit_output_hdim_2_pbc = mergesplit.merge_split_MEST(
|
|
139
|
+
test_features,
|
|
140
|
+
dxy=1,
|
|
141
|
+
distance=25,
|
|
142
|
+
PBC_flag="hdim_2",
|
|
143
|
+
min_h2=0,
|
|
144
|
+
max_h2=100,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
assert len(mergesplit_output_hdim_2_pbc["track"]) == 2
|
|
148
|
+
|
|
149
|
+
# Test with PBC in both dimensions, cells should merge
|
|
150
|
+
mergesplit_output_both_pbc = mergesplit.merge_split_MEST(
|
|
151
|
+
test_features,
|
|
152
|
+
dxy=1,
|
|
153
|
+
distance=25,
|
|
154
|
+
PBC_flag="both",
|
|
155
|
+
min_h1=0,
|
|
156
|
+
max_h1=100,
|
|
157
|
+
min_h2=0,
|
|
158
|
+
max_h2=100,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
assert len(mergesplit_output_both_pbc["track"]) == 1
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_merge_split_MEST_frame_len():
|
|
165
|
+
"""
|
|
166
|
+
Test the frame_len parameter of merge_split_MEST
|
|
167
|
+
"""
|
|
168
|
+
test_features = pd.DataFrame(
|
|
169
|
+
{
|
|
170
|
+
"feature": [1, 2, 3, 4],
|
|
171
|
+
"hdim_1": [50, 50, 50, 50],
|
|
172
|
+
"hdim_2": [50, 50, 50, 50],
|
|
173
|
+
"cell": [1, 1, 2, 2],
|
|
174
|
+
"frame": [0, 1, 3, 4],
|
|
175
|
+
"time": [
|
|
176
|
+
datetime.datetime(2000, 1, 1),
|
|
177
|
+
datetime.datetime(2000, 1, 1),
|
|
178
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
179
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
180
|
+
],
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Test with short frame_len, expect no link
|
|
185
|
+
mergesplit_output = mergesplit.merge_split_MEST(
|
|
186
|
+
test_features,
|
|
187
|
+
dxy=1,
|
|
188
|
+
distance=25,
|
|
189
|
+
frame_len=1,
|
|
190
|
+
)
|
|
191
|
+
assert len(mergesplit_output["track"]) == 2
|
|
192
|
+
|
|
193
|
+
# Test with longer frame_len, expect link
|
|
194
|
+
mergesplit_output = mergesplit.merge_split_MEST(
|
|
195
|
+
test_features,
|
|
196
|
+
dxy=1,
|
|
197
|
+
distance=25,
|
|
198
|
+
frame_len=2,
|
|
199
|
+
)
|
|
200
|
+
assert len(mergesplit_output["track"]) == 1
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_merge_split_MEST_no_cell():
|
|
204
|
+
"""
|
|
205
|
+
Test merge/split in cases with features with no cell
|
|
206
|
+
"""
|
|
207
|
+
test_features = pd.DataFrame(
|
|
208
|
+
{
|
|
209
|
+
"feature": [1, 2, 3],
|
|
210
|
+
"hdim_1": [25, 30, 50],
|
|
211
|
+
"hdim_2": [25, 30, 50],
|
|
212
|
+
"cell": [1, -1, 1],
|
|
213
|
+
"frame": [0, 0, 1],
|
|
214
|
+
"time": [
|
|
215
|
+
datetime.datetime(2000, 1, 1),
|
|
216
|
+
datetime.datetime(2000, 1, 1),
|
|
217
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
218
|
+
],
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
mergesplit_output = mergesplit.merge_split_MEST(
|
|
223
|
+
test_features,
|
|
224
|
+
dxy=1,
|
|
225
|
+
distance=25,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
assert len(mergesplit_output["track"]) == 1
|
|
229
|
+
|
|
230
|
+
assert mergesplit_output["feature_parent_cell_id"].values[1] == -1
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def test_merge_split_MEST_3D():
|
|
234
|
+
"""
|
|
235
|
+
Test merge/split support for 3D tracks and dz input
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
test_features = pd.DataFrame(
|
|
239
|
+
{
|
|
240
|
+
"feature": [1, 2, 3, 4],
|
|
241
|
+
"vdim": [1, 2, 1, 2],
|
|
242
|
+
"hdim_1": [50, 40, 50, 40],
|
|
243
|
+
"hdim_2": [50, 50, 50, 50],
|
|
244
|
+
"cell": [1, 2, 1, 2],
|
|
245
|
+
"frame": [0, 0, 1, 1],
|
|
246
|
+
"altitude": [500, 750, 1500, 2000],
|
|
247
|
+
"time": [
|
|
248
|
+
datetime.datetime(2000, 1, 1),
|
|
249
|
+
datetime.datetime(2000, 1, 1),
|
|
250
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
251
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
252
|
+
],
|
|
253
|
+
}
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Test with dz=10, expect merge
|
|
257
|
+
mergesplit_output_3d_merge = mergesplit.merge_split_MEST(
|
|
258
|
+
test_features,
|
|
259
|
+
dxy=1,
|
|
260
|
+
dz=10,
|
|
261
|
+
distance=20,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
assert len(mergesplit_output_3d_merge["track"]) == 1
|
|
265
|
+
|
|
266
|
+
# Test with dz=25, expect no merge
|
|
267
|
+
mergesplit_output_3d_nomerge = mergesplit.merge_split_MEST(
|
|
268
|
+
test_features,
|
|
269
|
+
dxy=1,
|
|
270
|
+
dz=25,
|
|
271
|
+
distance=20,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
assert len(mergesplit_output_3d_nomerge["track"]) == 2
|
|
275
|
+
|
|
276
|
+
# Test providing vertical_coord
|
|
277
|
+
mergesplit_output_3d_coord_merge = mergesplit.merge_split_MEST(
|
|
278
|
+
test_features,
|
|
279
|
+
dxy=1,
|
|
280
|
+
vertical_coord="altitude",
|
|
281
|
+
distance=1100,
|
|
282
|
+
)
|
|
283
|
+
mergesplit_output_3d_coord_nomerge = mergesplit.merge_split_MEST(
|
|
284
|
+
test_features,
|
|
285
|
+
dxy=1,
|
|
286
|
+
vertical_coord="altitude",
|
|
287
|
+
distance=20,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
assert len(mergesplit_output_3d_coord_merge["track"]) == 1
|
|
291
|
+
assert len(mergesplit_output_3d_coord_nomerge["track"]) == 2
|
|
292
|
+
|
|
293
|
+
# Test auto find vertical_coord
|
|
294
|
+
mergesplit_output_3d_coord_merge = mergesplit.merge_split_MEST(
|
|
295
|
+
test_features,
|
|
296
|
+
dxy=1,
|
|
297
|
+
distance=1100,
|
|
298
|
+
)
|
|
299
|
+
mergesplit_output_3d_coord_nomerge = mergesplit.merge_split_MEST(
|
|
300
|
+
test_features,
|
|
301
|
+
dxy=1,
|
|
302
|
+
distance=20,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
assert len(mergesplit_output_3d_coord_merge["track"]) == 1
|
|
306
|
+
assert len(mergesplit_output_3d_coord_nomerge["track"]) == 2
|
|
307
|
+
|
|
308
|
+
# Test error if both dz and coord are provided
|
|
309
|
+
with pytest.raises(ValueError):
|
|
310
|
+
mergesplit.merge_split_MEST(
|
|
311
|
+
test_features,
|
|
312
|
+
dxy=1,
|
|
313
|
+
dz=1,
|
|
314
|
+
vertical_coord="auto",
|
|
315
|
+
distance=20,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
# Test that wrong vertical coord name causes an error
|
|
319
|
+
with pytest.raises(ValueError):
|
|
320
|
+
mergesplit.merge_split_MEST(
|
|
321
|
+
test_features,
|
|
322
|
+
dxy=1,
|
|
323
|
+
vertical_coord="invalid_coord_name",
|
|
324
|
+
distance=20,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
# Test that auto search fails if coordinate name is not in default list
|
|
328
|
+
test_features = pd.DataFrame(
|
|
329
|
+
{
|
|
330
|
+
"feature": [1, 2, 3, 4],
|
|
331
|
+
"vdim": [1, 2, 1, 2],
|
|
332
|
+
"hdim_1": [50, 40, 50, 40],
|
|
333
|
+
"hdim_2": [50, 50, 50, 50],
|
|
334
|
+
"cell": [1, 2, 1, 2],
|
|
335
|
+
"frame": [0, 0, 1, 1],
|
|
336
|
+
"invalid_coord_name": [500, 1500, 1000, 2000],
|
|
337
|
+
"time": [
|
|
338
|
+
datetime.datetime(2000, 1, 1),
|
|
339
|
+
datetime.datetime(2000, 1, 1),
|
|
340
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
341
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
342
|
+
],
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
with pytest.raises(ValueError):
|
|
347
|
+
mergesplit.merge_split_MEST(
|
|
348
|
+
test_features,
|
|
349
|
+
dxy=1,
|
|
350
|
+
distance=20,
|
|
351
|
+
)
|