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,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]