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,714 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test for the trackpy tracking functions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import datetime
|
|
7
|
+
import copy
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
from pandas.testing import assert_frame_equal
|
|
13
|
+
import trackpy as tp
|
|
14
|
+
|
|
15
|
+
import tobac.testing
|
|
16
|
+
import tobac.tracking
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def convert_cell_dtype_if_appropriate(output, expected_output):
|
|
20
|
+
"""Helper function to convert datatype of output if
|
|
21
|
+
necessary. Fixes a bug in testing on some OS/Python versions that cause
|
|
22
|
+
default int types to be different
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
output: pd.DataFrame
|
|
27
|
+
the pandas dataframe to base cell datatype off of
|
|
28
|
+
expected_output: pd.DataFrame
|
|
29
|
+
the pandas dataframe to change the cell datatype
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
expected_output: pd.DataFrame
|
|
34
|
+
an adjusted dataframe with a matching int dtype
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# if they are already the same datatype, can return.
|
|
38
|
+
if output["cell"].dtype == expected_output["cell"].dtype:
|
|
39
|
+
return expected_output
|
|
40
|
+
|
|
41
|
+
if output["cell"].dtype == np.int32:
|
|
42
|
+
expected_output["cell"] = expected_output["cell"].astype(np.int32)
|
|
43
|
+
|
|
44
|
+
if output["cell"].dtype == np.int64:
|
|
45
|
+
expected_output["cell"] = expected_output["cell"].astype(np.int64)
|
|
46
|
+
|
|
47
|
+
return expected_output
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_linking_trackpy():
|
|
51
|
+
"""Function to test ```tobac.tracking.linking_trackpy```
|
|
52
|
+
Currently tests:
|
|
53
|
+
2D tracking
|
|
54
|
+
3D tracking
|
|
55
|
+
3D tracking with PBCs
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# Test 2D tracking of a simple moving feature
|
|
59
|
+
test_feature = tobac.testing.generate_single_feature(
|
|
60
|
+
1,
|
|
61
|
+
1,
|
|
62
|
+
min_h1=0,
|
|
63
|
+
max_h1=100,
|
|
64
|
+
min_h2=0,
|
|
65
|
+
max_h2=100,
|
|
66
|
+
frame_start=0,
|
|
67
|
+
num_frames=5,
|
|
68
|
+
spd_h1=1,
|
|
69
|
+
spd_h2=1,
|
|
70
|
+
PBC_flag="none",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
expected_out_feature = copy.deepcopy(test_feature)
|
|
74
|
+
expected_out_feature["cell"] = 1
|
|
75
|
+
|
|
76
|
+
actual_out_feature = tobac.tracking.linking_trackpy(
|
|
77
|
+
test_feature,
|
|
78
|
+
None,
|
|
79
|
+
5,
|
|
80
|
+
1000,
|
|
81
|
+
v_max=10000,
|
|
82
|
+
method_linking="predict",
|
|
83
|
+
PBC_flag="none",
|
|
84
|
+
)
|
|
85
|
+
# Just want to remove the time_cell column here.
|
|
86
|
+
actual_out_feature = actual_out_feature[
|
|
87
|
+
["hdim_1", "hdim_2", "frame", "feature", "time", "cell", "idx"]
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
expected_out_feature = convert_cell_dtype_if_appropriate(
|
|
91
|
+
actual_out_feature, expected_out_feature
|
|
92
|
+
)
|
|
93
|
+
assert_frame_equal(
|
|
94
|
+
expected_out_feature.sort_index(axis=1), actual_out_feature.sort_index(axis=1)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Test 3D tracking of a simple moving feature
|
|
98
|
+
test_feature = tobac.testing.generate_single_feature(
|
|
99
|
+
1,
|
|
100
|
+
1,
|
|
101
|
+
start_v=1,
|
|
102
|
+
min_h1=0,
|
|
103
|
+
max_h1=100,
|
|
104
|
+
min_h2=0,
|
|
105
|
+
max_h2=100,
|
|
106
|
+
frame_start=0,
|
|
107
|
+
num_frames=5,
|
|
108
|
+
spd_h1=1,
|
|
109
|
+
spd_h2=1,
|
|
110
|
+
spd_v=1,
|
|
111
|
+
PBC_flag="none",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
expected_out_feature = copy.deepcopy(test_feature)
|
|
115
|
+
expected_out_feature["cell"] = 1
|
|
116
|
+
|
|
117
|
+
actual_out_feature = tobac.tracking.linking_trackpy(
|
|
118
|
+
test_feature,
|
|
119
|
+
None,
|
|
120
|
+
5,
|
|
121
|
+
1000,
|
|
122
|
+
dz=1000,
|
|
123
|
+
v_max=10000,
|
|
124
|
+
method_linking="random",
|
|
125
|
+
PBC_flag="none",
|
|
126
|
+
vertical_coord=None,
|
|
127
|
+
)
|
|
128
|
+
# Just want to remove the time_cell column here.
|
|
129
|
+
actual_out_feature = actual_out_feature[
|
|
130
|
+
["hdim_1", "hdim_2", "vdim", "frame", "feature", "time", "cell", "idx"]
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
expected_out_feature = convert_cell_dtype_if_appropriate(
|
|
134
|
+
actual_out_feature, expected_out_feature
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
assert_frame_equal(
|
|
138
|
+
expected_out_feature.sort_index(axis=1), actual_out_feature.sort_index(axis=1)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Test 3D tracking of a simple moving feature with periodic boundaries on hdim_1
|
|
142
|
+
test_feature = tobac.testing.generate_single_feature(
|
|
143
|
+
1,
|
|
144
|
+
1,
|
|
145
|
+
start_v=1,
|
|
146
|
+
min_h1=0,
|
|
147
|
+
max_h1=10,
|
|
148
|
+
min_h2=0,
|
|
149
|
+
max_h2=10,
|
|
150
|
+
frame_start=0,
|
|
151
|
+
num_frames=8,
|
|
152
|
+
spd_h1=3,
|
|
153
|
+
spd_h2=1,
|
|
154
|
+
spd_v=1,
|
|
155
|
+
PBC_flag="hdim_1",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
expected_out_feature = copy.deepcopy(test_feature)
|
|
159
|
+
expected_out_feature["cell"] = 1
|
|
160
|
+
|
|
161
|
+
actual_out_feature = tobac.tracking.linking_trackpy(
|
|
162
|
+
test_feature,
|
|
163
|
+
None,
|
|
164
|
+
1,
|
|
165
|
+
1,
|
|
166
|
+
dz=1,
|
|
167
|
+
min_h1=0,
|
|
168
|
+
max_h1=10,
|
|
169
|
+
min_h2=0,
|
|
170
|
+
max_h2=10,
|
|
171
|
+
v_max=4,
|
|
172
|
+
method_linking="random",
|
|
173
|
+
vertical_coord=None,
|
|
174
|
+
PBC_flag="hdim_1",
|
|
175
|
+
)
|
|
176
|
+
# Just want to remove the time_cell column here.
|
|
177
|
+
actual_out_feature = actual_out_feature[
|
|
178
|
+
["hdim_1", "hdim_2", "vdim", "frame", "feature", "time", "cell", "idx"]
|
|
179
|
+
]
|
|
180
|
+
expected_out_feature = convert_cell_dtype_if_appropriate(
|
|
181
|
+
actual_out_feature, expected_out_feature
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
assert_frame_equal(
|
|
185
|
+
expected_out_feature.sort_index(axis=1), actual_out_feature.sort_index(axis=1)
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Test 3D tracking of a simple moving feature with periodic boundaries on hdim_2
|
|
189
|
+
test_feature = tobac.testing.generate_single_feature(
|
|
190
|
+
1,
|
|
191
|
+
1,
|
|
192
|
+
start_v=1,
|
|
193
|
+
min_h1=0,
|
|
194
|
+
max_h1=10,
|
|
195
|
+
min_h2=0,
|
|
196
|
+
max_h2=10,
|
|
197
|
+
frame_start=0,
|
|
198
|
+
num_frames=8,
|
|
199
|
+
spd_h1=1,
|
|
200
|
+
spd_h2=3,
|
|
201
|
+
spd_v=1,
|
|
202
|
+
PBC_flag="hdim_2",
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
expected_out_feature = copy.deepcopy(test_feature)
|
|
206
|
+
expected_out_feature["cell"] = 1
|
|
207
|
+
|
|
208
|
+
actual_out_feature = tobac.tracking.linking_trackpy(
|
|
209
|
+
test_feature,
|
|
210
|
+
None,
|
|
211
|
+
1,
|
|
212
|
+
1,
|
|
213
|
+
dz=1,
|
|
214
|
+
min_h1=0,
|
|
215
|
+
max_h1=10,
|
|
216
|
+
min_h2=0,
|
|
217
|
+
max_h2=10,
|
|
218
|
+
v_max=4,
|
|
219
|
+
method_linking="random",
|
|
220
|
+
vertical_coord=None,
|
|
221
|
+
PBC_flag="hdim_2",
|
|
222
|
+
)
|
|
223
|
+
# Just want to remove the time_cell column here.
|
|
224
|
+
actual_out_feature = actual_out_feature.drop("time_cell", axis=1)
|
|
225
|
+
expected_out_feature = convert_cell_dtype_if_appropriate(
|
|
226
|
+
actual_out_feature, expected_out_feature
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
assert_frame_equal(
|
|
230
|
+
expected_out_feature.sort_index(axis=1), actual_out_feature.sort_index(axis=1)
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Test 3D tracking of a simple moving feature with periodic boundaries on both hdim_1 and hdim_2
|
|
234
|
+
test_feature = tobac.testing.generate_single_feature(
|
|
235
|
+
1,
|
|
236
|
+
1,
|
|
237
|
+
start_v=1,
|
|
238
|
+
min_h1=0,
|
|
239
|
+
max_h1=10,
|
|
240
|
+
min_h2=0,
|
|
241
|
+
max_h2=10,
|
|
242
|
+
frame_start=0,
|
|
243
|
+
num_frames=8,
|
|
244
|
+
spd_h1=3,
|
|
245
|
+
spd_h2=3,
|
|
246
|
+
spd_v=0,
|
|
247
|
+
PBC_flag="both",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
expected_out_feature = copy.deepcopy(test_feature)
|
|
251
|
+
expected_out_feature["cell"] = 1
|
|
252
|
+
|
|
253
|
+
actual_out_feature = tobac.tracking.linking_trackpy(
|
|
254
|
+
test_feature,
|
|
255
|
+
None,
|
|
256
|
+
1,
|
|
257
|
+
1,
|
|
258
|
+
dz=1,
|
|
259
|
+
min_h1=0,
|
|
260
|
+
max_h1=10,
|
|
261
|
+
min_h2=0,
|
|
262
|
+
max_h2=10,
|
|
263
|
+
v_max=5,
|
|
264
|
+
method_linking="random",
|
|
265
|
+
vertical_coord=None,
|
|
266
|
+
PBC_flag="both",
|
|
267
|
+
)
|
|
268
|
+
# Just want to remove the time_cell column here.
|
|
269
|
+
actual_out_feature = actual_out_feature[
|
|
270
|
+
["hdim_1", "hdim_2", "vdim", "frame", "feature", "time", "cell", "idx"]
|
|
271
|
+
]
|
|
272
|
+
expected_out_feature = convert_cell_dtype_if_appropriate(
|
|
273
|
+
actual_out_feature, expected_out_feature
|
|
274
|
+
)
|
|
275
|
+
assert_frame_equal(
|
|
276
|
+
expected_out_feature.sort_index(axis=1), actual_out_feature.sort_index(axis=1)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@pytest.mark.parametrize(
|
|
281
|
+
"point_init, speed, dxy, actual_dz, v_max, use_dz, features_connected",
|
|
282
|
+
[
|
|
283
|
+
((0, 0, 0), (1, 0, 0), 1000, 100, 200, True, True),
|
|
284
|
+
((0, 0, 0), (1, 0, 0), 1000, 100, 200, False, True),
|
|
285
|
+
((0, 0, 0), (5, 0, 0), 1000, 100, 200, True, False),
|
|
286
|
+
((0, 0, 0), (5, 0, 0), 1000, 100, 200, False, False),
|
|
287
|
+
],
|
|
288
|
+
)
|
|
289
|
+
def test_3D_tracking_min_dist_z(
|
|
290
|
+
point_init, speed, dxy, actual_dz, v_max, use_dz, features_connected
|
|
291
|
+
):
|
|
292
|
+
"""Tests ```tobac.tracking.linking_trackpy``` with
|
|
293
|
+
points in z with varying distances between them.
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
point_init: 3D array-like
|
|
298
|
+
Initial point (z, y, x)
|
|
299
|
+
speed: 3D array-like
|
|
300
|
+
Speed of the feature (z, y, x)
|
|
301
|
+
dxy: float
|
|
302
|
+
grid spacing for dx and dy
|
|
303
|
+
actual_dz: float
|
|
304
|
+
grid spacing for Z
|
|
305
|
+
use_dz: bool
|
|
306
|
+
True to use the passed in constant dz, False
|
|
307
|
+
to use the calculated vertical coordinates
|
|
308
|
+
features_connected: bool
|
|
309
|
+
Do we expect the features to be connected?
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
test_feature = tobac.testing.generate_single_feature(
|
|
313
|
+
start_h1=point_init[1],
|
|
314
|
+
start_h2=point_init[2],
|
|
315
|
+
start_v=point_init[0],
|
|
316
|
+
min_h1=0,
|
|
317
|
+
max_h1=100,
|
|
318
|
+
min_h2=0,
|
|
319
|
+
max_h2=100,
|
|
320
|
+
frame_start=0,
|
|
321
|
+
num_frames=2,
|
|
322
|
+
spd_h1=speed[1],
|
|
323
|
+
spd_h2=speed[2],
|
|
324
|
+
spd_v=speed[0],
|
|
325
|
+
PBC_flag="none",
|
|
326
|
+
)
|
|
327
|
+
if not use_dz:
|
|
328
|
+
test_feature["z"] = test_feature["vdim"] * actual_dz
|
|
329
|
+
|
|
330
|
+
expected_out_feature = copy.deepcopy(test_feature)
|
|
331
|
+
|
|
332
|
+
if features_connected:
|
|
333
|
+
expected_out_feature["cell"] = 1
|
|
334
|
+
else:
|
|
335
|
+
expected_out_feature["cell"] = -1
|
|
336
|
+
|
|
337
|
+
common_params = {
|
|
338
|
+
"features": test_feature,
|
|
339
|
+
"field_in": None,
|
|
340
|
+
"dt": 1,
|
|
341
|
+
"time_cell_min": 1,
|
|
342
|
+
"dxy": dxy,
|
|
343
|
+
"v_max": v_max,
|
|
344
|
+
"method_linking": "random",
|
|
345
|
+
"cell_number_unassigned": -1,
|
|
346
|
+
}
|
|
347
|
+
if use_dz:
|
|
348
|
+
common_params["dz"] = actual_dz
|
|
349
|
+
common_params["vertical_coord"] = None
|
|
350
|
+
else:
|
|
351
|
+
common_params["vertical_coord"] = "z"
|
|
352
|
+
|
|
353
|
+
actual_out_feature = tobac.tracking.linking_trackpy(**common_params)
|
|
354
|
+
# Just want to remove the time_cell column here.
|
|
355
|
+
actual_out_feature = actual_out_feature.drop("time_cell", axis=1)
|
|
356
|
+
expected_out_feature = convert_cell_dtype_if_appropriate(
|
|
357
|
+
actual_out_feature, expected_out_feature
|
|
358
|
+
)
|
|
359
|
+
assert_frame_equal(
|
|
360
|
+
expected_out_feature.sort_index(axis=1), actual_out_feature.sort_index(axis=1)
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Check that we only add two columns, and all the other columns are the same as the input features
|
|
364
|
+
assert len(actual_out_feature.columns.tolist()) == len(
|
|
365
|
+
set(actual_out_feature.columns.tolist())
|
|
366
|
+
)
|
|
367
|
+
assert set(actual_out_feature.columns.tolist()) - set(
|
|
368
|
+
test_feature.columns.tolist()
|
|
369
|
+
) == {"cell"}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@pytest.mark.parametrize(
|
|
373
|
+
"max_trackpy, max_tobac, adaptive_step, adaptive_stop",
|
|
374
|
+
[(5, 10, None, None), (5, 10, 0.9, 0.1)],
|
|
375
|
+
)
|
|
376
|
+
def test_keep_trackpy_parameters(max_trackpy, max_tobac, adaptive_step, adaptive_stop):
|
|
377
|
+
"""
|
|
378
|
+
Tests that tobac does not change the parameters of trackpy
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
tp.linking.Linker.MAX_SUB_NET_SIZE = max_trackpy
|
|
382
|
+
tp.linking.Linker.MAX_SUB_NET_SIZE_ADAPTIVE = max_trackpy
|
|
383
|
+
|
|
384
|
+
expected_value = tp.linking.Linker.MAX_SUB_NET_SIZE
|
|
385
|
+
expected_value_adaptive = tp.linking.Linker.MAX_SUB_NET_SIZE_ADAPTIVE
|
|
386
|
+
|
|
387
|
+
data = tobac.testing.make_simple_sample_data_2D()
|
|
388
|
+
dxy, dt = tobac.utils.get_spacings(data)
|
|
389
|
+
features = tobac.feature_detection.feature_detection_multithreshold(
|
|
390
|
+
data, dxy, threshold=0.1
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
track = tobac.linking_trackpy(
|
|
394
|
+
features,
|
|
395
|
+
data,
|
|
396
|
+
dt=dt,
|
|
397
|
+
dxy=dxy,
|
|
398
|
+
v_max=100,
|
|
399
|
+
adaptive_step=adaptive_step,
|
|
400
|
+
adaptive_stop=adaptive_stop,
|
|
401
|
+
subnetwork_size=max_tobac,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
assert expected_value == tp.linking.Linker.MAX_SUB_NET_SIZE
|
|
405
|
+
assert expected_value_adaptive == tp.linking.Linker.MAX_SUB_NET_SIZE_ADAPTIVE
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def test_trackpy_predict():
|
|
409
|
+
"""Function to test if linking_trackpy() with method='predict' correctly links two
|
|
410
|
+
features at constant speeds crossing each other.
|
|
411
|
+
"""
|
|
412
|
+
|
|
413
|
+
cell_1 = tobac.testing.generate_single_feature(
|
|
414
|
+
1,
|
|
415
|
+
1,
|
|
416
|
+
min_h1=0,
|
|
417
|
+
max_h1=101,
|
|
418
|
+
min_h2=0,
|
|
419
|
+
max_h2=101,
|
|
420
|
+
frame_start=0,
|
|
421
|
+
num_frames=5,
|
|
422
|
+
spd_h1=20,
|
|
423
|
+
spd_h2=20,
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
cell_1_expected = copy.deepcopy(cell_1)
|
|
427
|
+
cell_1_expected["cell"] = 1
|
|
428
|
+
|
|
429
|
+
cell_2 = tobac.testing.generate_single_feature(
|
|
430
|
+
1,
|
|
431
|
+
100,
|
|
432
|
+
min_h1=0,
|
|
433
|
+
max_h1=101,
|
|
434
|
+
min_h2=0,
|
|
435
|
+
max_h2=101,
|
|
436
|
+
frame_start=0,
|
|
437
|
+
num_frames=5,
|
|
438
|
+
spd_h1=20,
|
|
439
|
+
spd_h2=-20,
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
cell_2_expected = copy.deepcopy(cell_2)
|
|
443
|
+
cell_2_expected["cell"] = np.int32(2)
|
|
444
|
+
|
|
445
|
+
features = pd.concat([cell_1, cell_2], ignore_index=True, verify_integrity=True)
|
|
446
|
+
expected_output = pd.concat(
|
|
447
|
+
[cell_1_expected, cell_2_expected], ignore_index=True, verify_integrity=True
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
output = tobac.linking_trackpy(
|
|
451
|
+
features, None, 1, 1, d_max=100, method_linking="predict"
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
output_random = tobac.linking_trackpy(
|
|
455
|
+
features, None, 1, 1, d_max=100, method_linking="random"
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
# check that the two methods of linking produce different results for this case
|
|
459
|
+
assert not output_random.equals(output)
|
|
460
|
+
|
|
461
|
+
# sorting and dropping indices for comparison with the expected output
|
|
462
|
+
output = output[["hdim_1", "hdim_2", "frame", "idx", "time", "feature", "cell"]]
|
|
463
|
+
expected_output = convert_cell_dtype_if_appropriate(output, expected_output)
|
|
464
|
+
|
|
465
|
+
assert_frame_equal(expected_output.sort_index(), output.sort_index())
|
|
466
|
+
|
|
467
|
+
# Check that we only add two columns, and all the other columns are the same as the input features
|
|
468
|
+
assert len(output.columns.tolist()) == len(set(output.columns.tolist()))
|
|
469
|
+
assert set(output.columns.tolist()) - set(features.columns.tolist()) == {"cell"}
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def test_tracking_extrapolation():
|
|
473
|
+
"""Tests the extrapolation capabilities of tracking.
|
|
474
|
+
Right now, this is not implemented, so it will raise an error.
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
cell_1 = tobac.testing.generate_single_feature(
|
|
478
|
+
1,
|
|
479
|
+
1,
|
|
480
|
+
min_h1=0,
|
|
481
|
+
max_h1=100,
|
|
482
|
+
min_h2=0,
|
|
483
|
+
max_h2=100,
|
|
484
|
+
frame_start=0,
|
|
485
|
+
num_frames=5,
|
|
486
|
+
spd_h1=20,
|
|
487
|
+
spd_h2=20,
|
|
488
|
+
)
|
|
489
|
+
with pytest.raises(NotImplementedError):
|
|
490
|
+
output = tobac.linking_trackpy(
|
|
491
|
+
cell_1, None, 1, 1, d_max=100, method_linking="predict", extrapolate=1
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def test_argument_logic():
|
|
496
|
+
"""Tests whether missing arguments are handled correctly,
|
|
497
|
+
i.e. whether a ValueError is raised if neither d_min, d_max nor v_max have
|
|
498
|
+
been provided to tobac.linking_trackpy.
|
|
499
|
+
"""
|
|
500
|
+
cell_1 = tobac.testing.generate_single_feature(
|
|
501
|
+
1,
|
|
502
|
+
1,
|
|
503
|
+
min_h1=0,
|
|
504
|
+
max_h1=100,
|
|
505
|
+
min_h2=0,
|
|
506
|
+
max_h2=100,
|
|
507
|
+
frame_start=0,
|
|
508
|
+
num_frames=5,
|
|
509
|
+
spd_h1=20,
|
|
510
|
+
spd_h2=20,
|
|
511
|
+
)
|
|
512
|
+
with pytest.raises(ValueError):
|
|
513
|
+
output = tobac.linking_trackpy(
|
|
514
|
+
cell_1, None, 1, 1, d_min=None, d_max=None, v_max=None
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def test_untracked_nat():
|
|
519
|
+
"""
|
|
520
|
+
Tests to make sure that the untracked cells don't have timedelta assigned.
|
|
521
|
+
"""
|
|
522
|
+
features = tobac.testing.generate_single_feature(
|
|
523
|
+
1,
|
|
524
|
+
1,
|
|
525
|
+
min_h1=0,
|
|
526
|
+
max_h1=101,
|
|
527
|
+
min_h2=0,
|
|
528
|
+
max_h2=101,
|
|
529
|
+
frame_start=0,
|
|
530
|
+
num_frames=2,
|
|
531
|
+
spd_h1=50,
|
|
532
|
+
spd_h2=50,
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
output = tobac.linking_trackpy(
|
|
536
|
+
features,
|
|
537
|
+
None,
|
|
538
|
+
1,
|
|
539
|
+
1,
|
|
540
|
+
d_max=40,
|
|
541
|
+
method_linking="random",
|
|
542
|
+
cell_number_unassigned=-1,
|
|
543
|
+
time_cell_min=2,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
assert np.all(output["cell"].values == np.array([-1, -1]))
|
|
547
|
+
# NaT values cannot be compared, so instead we check for null values
|
|
548
|
+
# and check for the data type
|
|
549
|
+
assert np.all(pd.isnull(output["time_cell"]))
|
|
550
|
+
# the exact data type depends on architecture, so
|
|
551
|
+
# instead just check by name
|
|
552
|
+
assert output["time_cell"].dtype.name == "timedelta64[ns]"
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@pytest.mark.parametrize(
|
|
556
|
+
"cell_time_lengths, min_time_length, expected_there",
|
|
557
|
+
[
|
|
558
|
+
(
|
|
559
|
+
[0, 5, 10, 120, 300],
|
|
560
|
+
0,
|
|
561
|
+
[True, True, True, True, True],
|
|
562
|
+
),
|
|
563
|
+
(
|
|
564
|
+
[0, 5, 10, 120, 300],
|
|
565
|
+
5,
|
|
566
|
+
[False, False, True, True, True],
|
|
567
|
+
),
|
|
568
|
+
(
|
|
569
|
+
[0, 5, 10, 120, 300],
|
|
570
|
+
6,
|
|
571
|
+
[False, False, True, True, True],
|
|
572
|
+
),
|
|
573
|
+
(
|
|
574
|
+
[0, 5, 10, 120, 300],
|
|
575
|
+
120,
|
|
576
|
+
[False, False, False, False, True],
|
|
577
|
+
),
|
|
578
|
+
(
|
|
579
|
+
[0, 5, 10, 120, 300],
|
|
580
|
+
900,
|
|
581
|
+
[False, False, False, False, False],
|
|
582
|
+
),
|
|
583
|
+
],
|
|
584
|
+
)
|
|
585
|
+
def test_time_cell_min(
|
|
586
|
+
cell_time_lengths: list[int],
|
|
587
|
+
min_time_length: int,
|
|
588
|
+
expected_there: list[bool],
|
|
589
|
+
):
|
|
590
|
+
"""
|
|
591
|
+
Tests time_cell_min in particle-based tracking
|
|
592
|
+
Parameters
|
|
593
|
+
----------
|
|
594
|
+
cell_time_lengths: list[int]
|
|
595
|
+
Length that each cell appears for
|
|
596
|
+
min_time_length: int
|
|
597
|
+
time_cell_min value
|
|
598
|
+
expected_there: list[bool]
|
|
599
|
+
whether to expect the cell there.
|
|
600
|
+
|
|
601
|
+
"""
|
|
602
|
+
# delta time automatically set by smallest difference between cell lengths
|
|
603
|
+
delta_time = 1
|
|
604
|
+
# how far horizontally to separate test features
|
|
605
|
+
sep_factor = 2
|
|
606
|
+
all_feats = list()
|
|
607
|
+
# generate dataframes
|
|
608
|
+
for i, cell_time in enumerate(cell_time_lengths):
|
|
609
|
+
curr_feat = tobac.testing.generate_single_feature(
|
|
610
|
+
start_h1=i * sep_factor,
|
|
611
|
+
start_h2=i * sep_factor,
|
|
612
|
+
spd_h1=1,
|
|
613
|
+
spd_h2=1,
|
|
614
|
+
max_h1=1000,
|
|
615
|
+
max_h2=1000,
|
|
616
|
+
num_frames=cell_time // delta_time,
|
|
617
|
+
dt=datetime.timedelta(seconds=delta_time),
|
|
618
|
+
)
|
|
619
|
+
curr_feat["orig_cell_num"] = i
|
|
620
|
+
|
|
621
|
+
all_feats.append(curr_feat)
|
|
622
|
+
all_feats_df = tobac.utils.combine_feature_dataframes(all_feats)
|
|
623
|
+
|
|
624
|
+
all_feats_tracked = tobac.tracking.linking_trackpy(
|
|
625
|
+
all_feats_df,
|
|
626
|
+
field_in=None,
|
|
627
|
+
dt=delta_time,
|
|
628
|
+
dxy=1000,
|
|
629
|
+
v_max=(1000 * 2) / delta_time,
|
|
630
|
+
cell_number_unassigned=-1,
|
|
631
|
+
time_cell_min=min_time_length,
|
|
632
|
+
)
|
|
633
|
+
all_feats_tracked_drop_no_cells = all_feats_tracked[all_feats_tracked["cell"] != -1]
|
|
634
|
+
|
|
635
|
+
for i, cell_expected in enumerate(expected_there):
|
|
636
|
+
if cell_expected:
|
|
637
|
+
expected_val = cell_time_lengths[i] // delta_time
|
|
638
|
+
else:
|
|
639
|
+
expected_val = 0
|
|
640
|
+
assert (
|
|
641
|
+
np.sum(all_feats_tracked_drop_no_cells["orig_cell_num"] == i)
|
|
642
|
+
== expected_val
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
def test_trackpy_predict_PBC():
|
|
647
|
+
"""Test if predictive tracking with PBCs works correctly"""
|
|
648
|
+
|
|
649
|
+
test_features = pd.DataFrame(
|
|
650
|
+
{
|
|
651
|
+
"feature": [1, 2, 3, 4, 5, 6, 7, 8],
|
|
652
|
+
"hdim_1": [85, 15, 95, 5, 5, 95, 15, 85],
|
|
653
|
+
"hdim_2": [50, 45, 50, 45, 50, 45, 50, 45],
|
|
654
|
+
"frame": [0, 0, 1, 1, 2, 2, 3, 3],
|
|
655
|
+
"time": [
|
|
656
|
+
datetime.datetime(2000, 1, 1),
|
|
657
|
+
datetime.datetime(2000, 1, 1),
|
|
658
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
659
|
+
datetime.datetime(2000, 1, 1, 0, 5),
|
|
660
|
+
datetime.datetime(2000, 1, 1, 0, 10),
|
|
661
|
+
datetime.datetime(2000, 1, 1, 0, 10),
|
|
662
|
+
datetime.datetime(2000, 1, 1, 0, 15),
|
|
663
|
+
datetime.datetime(2000, 1, 1, 0, 15),
|
|
664
|
+
],
|
|
665
|
+
}
|
|
666
|
+
)
|
|
667
|
+
|
|
668
|
+
output_random_no_pbc = tobac.linking_trackpy(
|
|
669
|
+
test_features, None, 1, 1, d_max=10, method_linking="random"
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
# Assert cell does not cross border
|
|
673
|
+
assert output_random_no_pbc["cell"].tolist() == [1, 2, 1, 2, 2, 1, 2, 1]
|
|
674
|
+
|
|
675
|
+
output_random_pbc = tobac.linking_trackpy(
|
|
676
|
+
test_features,
|
|
677
|
+
None,
|
|
678
|
+
1,
|
|
679
|
+
1,
|
|
680
|
+
d_max=10,
|
|
681
|
+
method_linking="random",
|
|
682
|
+
PBC_flag="hdim_1",
|
|
683
|
+
min_h1=0,
|
|
684
|
+
max_h1=100,
|
|
685
|
+
min_h2=0,
|
|
686
|
+
max_h2=100,
|
|
687
|
+
)
|
|
688
|
+
|
|
689
|
+
# Assert cell does not cross border even with PBC because of random tracking
|
|
690
|
+
assert output_random_pbc["cell"].tolist() == [1, 2, 1, 2, 2, 1, 2, 1]
|
|
691
|
+
|
|
692
|
+
output_predict_no_pbc = tobac.linking_trackpy(
|
|
693
|
+
test_features, None, 1, 1, d_max=10, method_linking="predict"
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
# Assert that without PBCs predictive tracking creates 4 cells because the d_max criteria is too small
|
|
697
|
+
assert output_predict_no_pbc["cell"].tolist() == [1, 2, 1, 2, 3, 4, 3, 4]
|
|
698
|
+
|
|
699
|
+
output_predict_pbc = tobac.linking_trackpy(
|
|
700
|
+
test_features,
|
|
701
|
+
None,
|
|
702
|
+
1,
|
|
703
|
+
1,
|
|
704
|
+
d_max=10,
|
|
705
|
+
method_linking="predict",
|
|
706
|
+
PBC_flag="hdim_1",
|
|
707
|
+
min_h1=0,
|
|
708
|
+
max_h1=100,
|
|
709
|
+
min_h2=0,
|
|
710
|
+
max_h2=100,
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
# Assert with PBCs and prdictive tracking the cells should cross the border
|
|
714
|
+
assert output_predict_pbc["cell"].tolist() == [1, 2, 1, 2, 1, 2, 1, 2]
|