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