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,747 @@
1
+ """
2
+ Audit of the testing functions that produce our test data.
3
+ Who's watching the watchmen, basically.
4
+ """
5
+
6
+ import pytest
7
+ from tobac.testing import (
8
+ generate_single_feature,
9
+ get_single_pbc_coordinate,
10
+ make_sample_data_3D_1blob,
11
+ )
12
+ import tobac.testing as tbtest
13
+ from collections import Counter
14
+ import pandas as pd
15
+ from pandas.testing import assert_frame_equal
16
+ import datetime
17
+ import numpy as np
18
+ from xarray import DataArray
19
+
20
+
21
+ def test_make_feature_blob():
22
+ """Tests ```tobac.testing.make_feature_blob```
23
+ Currently runs the following tests:
24
+ Creates a blob in the right location and cuts off without PBCs
25
+ Blob extends off PBCs for all dimensions when appropriate
26
+ """
27
+
28
+ # Test without PBCs first, make sure that a blob is generated in the first place.
29
+ # 2D test
30
+ out_blob = tbtest.make_feature_blob(
31
+ np.zeros((10, 10)),
32
+ h1_loc=5,
33
+ h2_loc=5,
34
+ h1_size=2,
35
+ h2_size=2,
36
+ shape="rectangle",
37
+ amplitude=1,
38
+ PBC_flag="none",
39
+ )
40
+ assert np.all(out_blob[4:6, 4:6] == 1)
41
+ # There should be exactly 4 points of value 1.
42
+ assert np.sum(out_blob) == 4 and np.min(out_blob) == 0
43
+
44
+ # 3D test
45
+ out_blob = tbtest.make_feature_blob(
46
+ np.zeros((10, 10, 10)),
47
+ h1_loc=5,
48
+ h2_loc=5,
49
+ v_loc=5,
50
+ h1_size=2,
51
+ h2_size=2,
52
+ v_size=2,
53
+ shape="rectangle",
54
+ amplitude=1,
55
+ PBC_flag="none",
56
+ )
57
+ assert np.all(out_blob[4:6, 4:6, 4:6] == 1)
58
+ # There should be exactly 8 points of value 1.
59
+ assert np.sum(out_blob) == 8 and np.min(out_blob) == 0
60
+
61
+ # Test that it cuts things off along a boundary.
62
+ # 2D test
63
+ out_blob = tbtest.make_feature_blob(
64
+ np.zeros((10, 10)),
65
+ h1_loc=5,
66
+ h2_loc=9,
67
+ h1_size=2,
68
+ h2_size=4,
69
+ shape="rectangle",
70
+ amplitude=1,
71
+ PBC_flag="none",
72
+ )
73
+ assert np.all(out_blob[4:6, 7:10] == 1)
74
+ assert np.all(out_blob[4:6, 0:1] == 0)
75
+ # There should be exactly 4 points of value 1.
76
+ assert np.sum(out_blob) == 6 and np.min(out_blob) == 0
77
+
78
+ # 3D test
79
+ out_blob = tbtest.make_feature_blob(
80
+ np.zeros((10, 10, 10)),
81
+ h1_loc=5,
82
+ h2_loc=9,
83
+ v_loc=5,
84
+ h1_size=2,
85
+ h2_size=4,
86
+ v_size=2,
87
+ shape="rectangle",
88
+ amplitude=1,
89
+ PBC_flag="none",
90
+ )
91
+ assert np.all(out_blob[4:6, 4:6, 7:10] == 1)
92
+ assert np.all(out_blob[4:6, 4:6, 0:1] == 0)
93
+ # There should be exactly 4 points of value 1.
94
+ assert np.sum(out_blob) == 12 and np.min(out_blob) == 0
95
+
96
+ for PBC_condition in ["hdim_1", "hdim_2", "both"]:
97
+ # Now test simple cases with PBCs
98
+ # 2D test
99
+ out_blob = tbtest.make_feature_blob(
100
+ np.zeros((10, 10)),
101
+ h1_loc=5,
102
+ h2_loc=5,
103
+ h1_size=2,
104
+ h2_size=2,
105
+ shape="rectangle",
106
+ amplitude=1,
107
+ PBC_flag=PBC_condition,
108
+ )
109
+ assert np.all(out_blob[4:6, 4:6] == 1)
110
+ # There should be exactly 4 points of value 1.
111
+ assert np.sum(out_blob) == 4 and np.min(out_blob) == 0
112
+
113
+ # 3D test
114
+ out_blob = tbtest.make_feature_blob(
115
+ np.zeros((10, 10, 10)),
116
+ h1_loc=5,
117
+ h2_loc=5,
118
+ v_loc=5,
119
+ h1_size=2,
120
+ h2_size=2,
121
+ v_size=2,
122
+ shape="rectangle",
123
+ amplitude=1,
124
+ PBC_flag=PBC_condition,
125
+ )
126
+ assert np.all(out_blob[4:6, 4:6, 4:6] == 1)
127
+ # There should be exactly 8 points of value 1.
128
+ assert np.sum(out_blob) == 8 and np.min(out_blob) == 0
129
+
130
+ # Test that it wraps around on the hdim_1 positive side
131
+ for PBC_condition in ["hdim_2", "both"]:
132
+ out_blob = tbtest.make_feature_blob(
133
+ np.zeros((10, 10)),
134
+ h1_loc=5,
135
+ h2_loc=9,
136
+ h1_size=2,
137
+ h2_size=4,
138
+ shape="rectangle",
139
+ amplitude=1,
140
+ PBC_flag=PBC_condition,
141
+ )
142
+ assert np.all(out_blob[4:6, 7:10] == 1)
143
+ assert np.all(out_blob[4:6, 0:1] == 1)
144
+ # There should be exactly 4 points of value 1.
145
+ assert np.sum(out_blob) == 8 and np.min(out_blob) == 0
146
+
147
+ # 3D test
148
+ out_blob = tbtest.make_feature_blob(
149
+ np.zeros((10, 10, 10)),
150
+ h1_loc=5,
151
+ h2_loc=9,
152
+ v_loc=5,
153
+ h1_size=2,
154
+ h2_size=4,
155
+ v_size=2,
156
+ shape="rectangle",
157
+ amplitude=1,
158
+ PBC_flag=PBC_condition,
159
+ )
160
+ assert np.all(out_blob[4:6, 4:6, 7:10] == 1)
161
+ assert np.all(out_blob[4:6, 4:6, 0:1] == 1)
162
+ # There should be exactly 4 points of value 1.
163
+ assert np.sum(out_blob) == 16 and np.min(out_blob) == 0
164
+
165
+
166
+ def test_make_sample_data_3D_1blob():
167
+ """Tests `make_sample_data_3D_1blob`
168
+ Currently runs the following tests:
169
+ - Creates a 3D dataset with a blob moving diagonally.
170
+ - Tests the shape of the returned data.
171
+ - Verifies blob presence at specific time steps.
172
+ - Tests `invert_xy` functionality.
173
+ """
174
+ # Test for default 3D data (without inversion)
175
+ data_xarray = make_sample_data_3D_1blob(data_type="xarray", invert_xy=False)
176
+
177
+ # Assert the data type is xarray DataArray
178
+ assert isinstance(
179
+ data_xarray, DataArray
180
+ ), f"Expected iris Cube or xarray DataArray, got {type(data_xarray)}"
181
+
182
+ # For xarray DataArray, check directly with .coords
183
+ assert "time" in data_xarray.coords, "Time coordinate missing"
184
+ assert "z" in data_xarray.coords, "Z coordinate missing"
185
+ assert "y" in data_xarray.coords, "Y coordinate missing"
186
+ assert "x" in data_xarray.coords, "X coordinate missing"
187
+
188
+ # Test the shape of the returned data
189
+ assert data_xarray.shape == (
190
+ 25,
191
+ 50,
192
+ 50,
193
+ 100,
194
+ ), f"Unexpected data shape: {data_xarray.shape}"
195
+
196
+ # Test for the presence of the blob at the first time step
197
+ data_at_time_0 = data_xarray[0] # Get the data at the first time step
198
+ blob = data_at_time_0[
199
+ 0, 10:20, 10:20
200
+ ] # Checking a small region in the blob's location
201
+ assert np.any(blob.data > 0), "No blob detected at time 0"
202
+
203
+ # Test when `invert_xy=True`
204
+ data_inverted = make_sample_data_3D_1blob(data_type="xarray", invert_xy=True)
205
+
206
+ # Assert the shape has changed due to inversion
207
+ assert data_inverted.shape == (
208
+ 25,
209
+ 50,
210
+ 100,
211
+ 50,
212
+ ), f"Unexpected shape with invert_xy=True: {data_inverted.shape}"
213
+
214
+ # Check if the blob's presence is still correct after inversion
215
+ data_inverted_at_time_0 = data_inverted[0]
216
+ inverted_blob = data_inverted_at_time_0[0, 10:20, 10:20]
217
+ assert np.any(inverted_blob.data > 0), "No blob detected at time 0 after inversion"
218
+
219
+
220
+ def test_get_single_pbc_coordinate():
221
+ """Tests ```tobac.testing.get_single_pbc_coordinate```.
222
+ Currently runs the following tests:
223
+ Point within bounds with all PBC conditions
224
+ Point off bounds on each side
225
+ Invalid point
226
+ """
227
+
228
+ # Test points that do not need to be adjusted for all PBC conditions
229
+ for PBC_condition in ["none", "hdim_1", "hdim_2", "both"]:
230
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 3, 3, PBC_condition) == (3, 3)
231
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 0, 0, PBC_condition) == (0, 0)
232
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 9, 9, PBC_condition) == (9, 9)
233
+
234
+ # Test points off bounds on each side
235
+ # First points off min/max of hdim_1 for the two that allow it
236
+ for PBC_condition in ["hdim_1", "both"]:
237
+ assert get_single_pbc_coordinate(0, 10, 0, 10, -3, 3, PBC_condition) == (7, 3)
238
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 12, 3, PBC_condition) == (2, 3)
239
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 10, 3, PBC_condition) == (0, 3)
240
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 20, 3, PBC_condition) == (0, 3)
241
+ assert get_single_pbc_coordinate(0, 10, 0, 10, -30, 3, PBC_condition) == (0, 3)
242
+
243
+ # Now test points off min/max of hdim_1 for the two that don't allow it (expect raise error)
244
+ for PBC_condition in ["none", "hdim_2"]:
245
+ with pytest.raises(ValueError):
246
+ get_single_pbc_coordinate(0, 10, 0, 10, -3, 3, PBC_condition)
247
+ with pytest.raises(ValueError):
248
+ get_single_pbc_coordinate(0, 10, 0, 10, 12, 3, PBC_condition)
249
+ with pytest.raises(ValueError):
250
+ get_single_pbc_coordinate(0, 10, 0, 10, 10, 3, PBC_condition)
251
+
252
+ # Now test points off min/max of hdim_2 for the two that allow it
253
+ for PBC_condition in ["hdim_2", "both"]:
254
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 3, -3, PBC_condition) == (3, 7)
255
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 3, 12, PBC_condition) == (3, 2)
256
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 3, 10, PBC_condition) == (3, 0)
257
+
258
+ # Now test hdim_2 min/max for the two that don't allow it
259
+ for PBC_condition in ["none", "hdim_1"]:
260
+ with pytest.raises(ValueError):
261
+ get_single_pbc_coordinate(0, 10, 0, 10, 3, -3, PBC_condition)
262
+ with pytest.raises(ValueError):
263
+ get_single_pbc_coordinate(0, 10, 0, 10, 3, 12, PBC_condition)
264
+ with pytest.raises(ValueError):
265
+ get_single_pbc_coordinate(0, 10, 0, 10, 3, 10, PBC_condition)
266
+
267
+ # Now test hdim_1 and hdim_2 min/max for 'both'
268
+ assert get_single_pbc_coordinate(0, 11, 0, 10, -3, -3, "both") == (8, 7)
269
+ assert get_single_pbc_coordinate(0, 10, 0, 10, 12, 12, "both") == (2, 2)
270
+
271
+ # Now test hdim_1 and hdim/2 min/max for the three that don't allow it
272
+ for PBC_condition in ["none", "hdim_1", "hdim_2"]:
273
+ with pytest.raises(ValueError):
274
+ get_single_pbc_coordinate(0, 11, 0, 10, -3, -3, PBC_condition)
275
+ with pytest.raises(ValueError):
276
+ get_single_pbc_coordinate(0, 10, 0, 10, 12, 12, PBC_condition)
277
+
278
+
279
+ def test_generate_single_feature():
280
+ """Tests the `generate_single_feature` function.
281
+ Currently runs the following tests:
282
+ A single feature is generated
283
+
284
+ """
285
+
286
+ # Testing a simple 3D case
287
+ expected_df = pd.DataFrame.from_dict(
288
+ [
289
+ {
290
+ "hdim_1": 1,
291
+ "hdim_2": 1,
292
+ "vdim": 1,
293
+ "frame": 0,
294
+ "feature": 1,
295
+ "idx": 0,
296
+ "time": datetime.datetime(2022, 1, 1, 0, 0),
297
+ }
298
+ ]
299
+ )
300
+
301
+ assert_frame_equal(
302
+ generate_single_feature(
303
+ 1, 1, start_v=1, frame_start=0, max_h1=1000, max_h2=1000
304
+ ).sort_index(axis=1),
305
+ expected_df.sort_index(axis=1),
306
+ )
307
+
308
+ # Testing a simple 2D case
309
+ expected_df = pd.DataFrame.from_dict(
310
+ [
311
+ {
312
+ "hdim_1": 1,
313
+ "hdim_2": 1,
314
+ "frame": 0,
315
+ "feature": 1,
316
+ "idx": 0,
317
+ "time": datetime.datetime(2022, 1, 1, 0, 0),
318
+ }
319
+ ]
320
+ )
321
+ assert_frame_equal(
322
+ generate_single_feature(
323
+ 1, 1, frame_start=0, max_h1=1000, max_h2=1000
324
+ ).sort_index(axis=1),
325
+ expected_df.sort_index(axis=1),
326
+ )
327
+
328
+ # Testing a simple 2D case with movement
329
+ expected_df = pd.DataFrame.from_dict(
330
+ [
331
+ {
332
+ "hdim_1": 1,
333
+ "hdim_2": 1,
334
+ "frame": 0,
335
+ "feature": 1,
336
+ "idx": 0,
337
+ "time": datetime.datetime(2022, 1, 1, 0, 0),
338
+ },
339
+ {
340
+ "hdim_1": 2,
341
+ "hdim_2": 2,
342
+ "frame": 1,
343
+ "feature": 2,
344
+ "idx": 0,
345
+ "time": datetime.datetime(2022, 1, 1, 0, 5),
346
+ },
347
+ {
348
+ "hdim_1": 3,
349
+ "hdim_2": 3,
350
+ "frame": 2,
351
+ "feature": 3,
352
+ "idx": 0,
353
+ "time": datetime.datetime(2022, 1, 1, 0, 10),
354
+ },
355
+ {
356
+ "hdim_1": 4,
357
+ "hdim_2": 4,
358
+ "frame": 3,
359
+ "feature": 4,
360
+ "idx": 0,
361
+ "time": datetime.datetime(2022, 1, 1, 0, 15),
362
+ },
363
+ ]
364
+ )
365
+ assert_frame_equal(
366
+ generate_single_feature(
367
+ 1,
368
+ 1,
369
+ frame_start=0,
370
+ num_frames=4,
371
+ spd_h1=1,
372
+ spd_h2=1,
373
+ max_h1=1000,
374
+ max_h2=1000,
375
+ ).sort_index(axis=1),
376
+ expected_df.sort_index(axis=1),
377
+ )
378
+
379
+ # Testing a simple 3D case with movement
380
+ expected_df = pd.DataFrame.from_dict(
381
+ [
382
+ {
383
+ "hdim_1": 1,
384
+ "hdim_2": 1,
385
+ "vdim": 1,
386
+ "frame": 0,
387
+ "feature": 1,
388
+ "idx": 0,
389
+ "time": datetime.datetime(2022, 1, 1, 0, 0),
390
+ },
391
+ {
392
+ "hdim_1": 2,
393
+ "hdim_2": 2,
394
+ "vdim": 2,
395
+ "frame": 1,
396
+ "feature": 2,
397
+ "idx": 0,
398
+ "time": datetime.datetime(2022, 1, 1, 0, 5),
399
+ },
400
+ {
401
+ "hdim_1": 3,
402
+ "hdim_2": 3,
403
+ "vdim": 3,
404
+ "frame": 2,
405
+ "feature": 3,
406
+ "idx": 0,
407
+ "time": datetime.datetime(2022, 1, 1, 0, 10),
408
+ },
409
+ {
410
+ "hdim_1": 4,
411
+ "hdim_2": 4,
412
+ "vdim": 4,
413
+ "frame": 3,
414
+ "feature": 4,
415
+ "idx": 0,
416
+ "time": datetime.datetime(2022, 1, 1, 0, 15),
417
+ },
418
+ ]
419
+ )
420
+ assert_frame_equal(
421
+ generate_single_feature(
422
+ 1,
423
+ 1,
424
+ start_v=1,
425
+ frame_start=0,
426
+ num_frames=4,
427
+ spd_h1=1,
428
+ spd_h2=1,
429
+ spd_v=1,
430
+ max_h1=1000,
431
+ max_h2=1000,
432
+ ).sort_index(axis=1),
433
+ expected_df.sort_index(axis=1),
434
+ )
435
+
436
+ # Testing a simple 3D case with movement that passes the hdim_1 boundary
437
+ expected_df = pd.DataFrame.from_dict(
438
+ [
439
+ {
440
+ "hdim_1": 1,
441
+ "hdim_2": 1,
442
+ "vdim": 1,
443
+ "frame": 0,
444
+ "feature": 1,
445
+ "idx": 0,
446
+ "time": datetime.datetime(2022, 1, 1, 0, 0),
447
+ },
448
+ {
449
+ "hdim_1": 5,
450
+ "hdim_2": 2,
451
+ "vdim": 2,
452
+ "frame": 1,
453
+ "feature": 2,
454
+ "idx": 0,
455
+ "time": datetime.datetime(2022, 1, 1, 0, 5),
456
+ },
457
+ {
458
+ "hdim_1": 9,
459
+ "hdim_2": 3,
460
+ "vdim": 3,
461
+ "frame": 2,
462
+ "feature": 3,
463
+ "idx": 0,
464
+ "time": datetime.datetime(2022, 1, 1, 0, 10),
465
+ },
466
+ {
467
+ "hdim_1": 3,
468
+ "hdim_2": 4,
469
+ "vdim": 4,
470
+ "frame": 3,
471
+ "feature": 4,
472
+ "idx": 0,
473
+ "time": datetime.datetime(2022, 1, 1, 0, 15),
474
+ },
475
+ ]
476
+ )
477
+ assert_frame_equal(
478
+ generate_single_feature(
479
+ 1,
480
+ 1,
481
+ start_v=1,
482
+ min_h1=0,
483
+ max_h1=10,
484
+ min_h2=0,
485
+ max_h2=10,
486
+ frame_start=0,
487
+ num_frames=4,
488
+ spd_h1=4,
489
+ spd_h2=1,
490
+ spd_v=1,
491
+ PBC_flag="hdim_1",
492
+ ).sort_index(axis=1),
493
+ expected_df.sort_index(axis=1),
494
+ )
495
+
496
+ # Testing a simple 3D case with movement that passes the hdim_1 boundary
497
+ expected_df = pd.DataFrame.from_dict(
498
+ [
499
+ {
500
+ "hdim_1": 1,
501
+ "hdim_2": 1,
502
+ "vdim": 1,
503
+ "frame": 0,
504
+ "feature": 1,
505
+ "idx": 0,
506
+ "time": datetime.datetime(2022, 1, 1, 0, 0),
507
+ },
508
+ {
509
+ "hdim_1": 5,
510
+ "hdim_2": 2,
511
+ "vdim": 2,
512
+ "frame": 1,
513
+ "feature": 2,
514
+ "idx": 0,
515
+ "time": datetime.datetime(2022, 1, 1, 0, 5),
516
+ },
517
+ {
518
+ "hdim_1": 9,
519
+ "hdim_2": 3,
520
+ "vdim": 3,
521
+ "frame": 2,
522
+ "feature": 3,
523
+ "idx": 0,
524
+ "time": datetime.datetime(2022, 1, 1, 0, 10),
525
+ },
526
+ {
527
+ "hdim_1": 3,
528
+ "hdim_2": 4,
529
+ "vdim": 4,
530
+ "frame": 3,
531
+ "feature": 4,
532
+ "idx": 0,
533
+ "time": datetime.datetime(2022, 1, 1, 0, 15),
534
+ },
535
+ ]
536
+ )
537
+ assert_frame_equal(
538
+ generate_single_feature(
539
+ 1,
540
+ 1,
541
+ start_v=1,
542
+ min_h1=0,
543
+ max_h1=10,
544
+ min_h2=0,
545
+ max_h2=10,
546
+ frame_start=0,
547
+ num_frames=4,
548
+ spd_h1=4,
549
+ spd_h2=1,
550
+ spd_v=1,
551
+ PBC_flag="hdim_1",
552
+ ).sort_index(axis=1),
553
+ expected_df.sort_index(axis=1),
554
+ )
555
+
556
+ # Testing a simple 3D case with movement that passes the hdim_2 boundary
557
+ expected_df = pd.DataFrame.from_dict(
558
+ [
559
+ {
560
+ "hdim_1": 1,
561
+ "hdim_2": 1,
562
+ "vdim": 1,
563
+ "frame": 0,
564
+ "feature": 1,
565
+ "idx": 0,
566
+ "time": datetime.datetime(2022, 1, 1, 0, 0),
567
+ },
568
+ {
569
+ "hdim_1": 2,
570
+ "hdim_2": 5,
571
+ "vdim": 2,
572
+ "frame": 1,
573
+ "feature": 2,
574
+ "idx": 0,
575
+ "time": datetime.datetime(2022, 1, 1, 0, 5),
576
+ },
577
+ {
578
+ "hdim_1": 3,
579
+ "hdim_2": 9,
580
+ "vdim": 3,
581
+ "frame": 2,
582
+ "feature": 3,
583
+ "idx": 0,
584
+ "time": datetime.datetime(2022, 1, 1, 0, 10),
585
+ },
586
+ {
587
+ "hdim_1": 4,
588
+ "hdim_2": 3,
589
+ "vdim": 4,
590
+ "frame": 3,
591
+ "feature": 4,
592
+ "idx": 0,
593
+ "time": datetime.datetime(2022, 1, 1, 0, 15),
594
+ },
595
+ ]
596
+ )
597
+ assert_frame_equal(
598
+ generate_single_feature(
599
+ 1,
600
+ 1,
601
+ start_v=1,
602
+ min_h1=0,
603
+ max_h1=10,
604
+ min_h2=0,
605
+ max_h2=10,
606
+ frame_start=0,
607
+ num_frames=4,
608
+ spd_h1=1,
609
+ spd_h2=4,
610
+ spd_v=1,
611
+ PBC_flag="hdim_2",
612
+ ).sort_index(axis=1),
613
+ expected_df.sort_index(axis=1),
614
+ )
615
+
616
+ # Testing a simple 3D case with movement that passes the hdim_1 and hdim_2 boundaries
617
+ expected_df = pd.DataFrame.from_dict(
618
+ [
619
+ {
620
+ "hdim_1": 1,
621
+ "hdim_2": 1,
622
+ "vdim": 1,
623
+ "frame": 0,
624
+ "feature": 1,
625
+ "idx": 0,
626
+ "time": datetime.datetime(2022, 1, 1, 0, 0),
627
+ },
628
+ {
629
+ "hdim_1": 6,
630
+ "hdim_2": 5,
631
+ "vdim": 2,
632
+ "frame": 1,
633
+ "feature": 2,
634
+ "idx": 0,
635
+ "time": datetime.datetime(2022, 1, 1, 0, 5),
636
+ },
637
+ {
638
+ "hdim_1": 1,
639
+ "hdim_2": 9,
640
+ "vdim": 3,
641
+ "frame": 2,
642
+ "feature": 3,
643
+ "idx": 0,
644
+ "time": datetime.datetime(2022, 1, 1, 0, 10),
645
+ },
646
+ {
647
+ "hdim_1": 6,
648
+ "hdim_2": 3,
649
+ "vdim": 4,
650
+ "frame": 3,
651
+ "feature": 4,
652
+ "idx": 0,
653
+ "time": datetime.datetime(2022, 1, 1, 0, 15),
654
+ },
655
+ ]
656
+ )
657
+ assert_frame_equal(
658
+ generate_single_feature(
659
+ 1,
660
+ 1,
661
+ start_v=1,
662
+ min_h1=0,
663
+ max_h1=10,
664
+ min_h2=0,
665
+ max_h2=10,
666
+ frame_start=0,
667
+ num_frames=4,
668
+ spd_h1=5,
669
+ spd_h2=4,
670
+ spd_v=1,
671
+ PBC_flag="both",
672
+ ).sort_index(axis=1),
673
+ expected_df.sort_index(axis=1),
674
+ )
675
+
676
+
677
+ @pytest.mark.parametrize(
678
+ "in_pt,in_sz,axis_size,out_pts",
679
+ [
680
+ (3, 0, (0, 5), (3, 3)),
681
+ (3, 3, (0, 5), (2, 5)),
682
+ ],
683
+ )
684
+ def test_get_start_end_of_feat_nopbc(in_pt, in_sz, axis_size, out_pts):
685
+ """Tests ```tobac.testing.get_start_end_of_feat```"""
686
+ assert (
687
+ tbtest.get_start_end_of_feat(in_pt, in_sz, axis_size[0], axis_size[1])
688
+ == out_pts
689
+ )
690
+
691
+
692
+ """
693
+ I acknowledge that this is a little confusing for the expected outputs, especially for the 3D.
694
+ """
695
+
696
+
697
+ @pytest.mark.parametrize(
698
+ "min_max_coords, lengths, expected_outs",
699
+ [
700
+ ((0, 3), (4,), [0, 1, 2, 3]),
701
+ (
702
+ (0, 3, 0, 3),
703
+ (4, 4),
704
+ [
705
+ [
706
+ [
707
+ 0,
708
+ ]
709
+ * 4,
710
+ [1] * 4,
711
+ [2] * 4,
712
+ [3] * 4,
713
+ ],
714
+ [[0, 1, 2, 3]] * 4,
715
+ ],
716
+ ),
717
+ (
718
+ (0, 1, 0, 1, 0, 1),
719
+ (2, 2, 2),
720
+ [
721
+ [
722
+ [[0] * 2] * 2,
723
+ [[1] * 2] * 2,
724
+ ],
725
+ [[[0, 0], [1, 1]], [[0, 0], [1, 1]]],
726
+ [[[0, 1], [0, 1]], [[0, 1], [0, 1]]],
727
+ ],
728
+ ),
729
+ ],
730
+ )
731
+ def test_generate_grid_coords(min_max_coords, lengths, expected_outs):
732
+ """Tests ```tobac.testing.generate_grid_coords```
733
+ Parameters
734
+ ----------
735
+ min_max_coords: array-like, either length 2, length 4, or length 6.
736
+ The minimum and maximum values in each dimension as:
737
+ (min_dim1, max_dim1, min_dim2, max_dim2, min_dim3, max_dim3) to use
738
+ all 3 dimensions. You can omit any dimensions that you aren't using.
739
+ lengths: array-like, either length 1, 2, or 3.
740
+ The lengths of values in each dimension. Length must equal 1/2 the length
741
+ of min_max_coords.
742
+ expected_outs: array-like, either 1D, 2D, or 3D
743
+ The expected output
744
+ """
745
+
746
+ out_grid = tbtest.generate_grid_coords(min_max_coords, lengths)
747
+ assert np.all(np.isclose(out_grid, np.array(expected_outs)))