Simple-Track 2.0.4__tar.gz → 2.0.6__tar.gz
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.
- {simple_track-2.0.4 → simple_track-2.0.6}/PKG-INFO +9 -7
- {simple_track-2.0.4 → simple_track-2.0.6}/README.md +8 -6
- {simple_track-2.0.4 → simple_track-2.0.6}/pyproject.toml +1 -1
- {simple_track-2.0.4 → simple_track-2.0.6}/src/Simple_Track.egg-info/PKG-INFO +9 -7
- {simple_track-2.0.4 → simple_track-2.0.6}/src/run_simple_track.py +3 -1
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/feature.py +21 -8
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/frame.py +7 -1
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/track.py +1 -1
- {simple_track-2.0.4 → simple_track-2.0.6}/tests/test_frame.py +61 -3
- {simple_track-2.0.4 → simple_track-2.0.6}/LICENSE +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/setup.cfg +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/Simple_Track.egg-info/SOURCES.txt +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/Simple_Track.egg-info/dependency_links.txt +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/Simple_Track.egg-info/entry_points.txt +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/Simple_Track.egg-info/requires.txt +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/Simple_Track.egg-info/top_level.txt +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/__init__.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/exceptions.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/flow_solver.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/frame_output.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/frame_tracker.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/load.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/src/simpletrack/utils.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/tests/test_feature.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/tests/test_flow_solver.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/tests/test_frame_tracker.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/tests/test_mwe_output.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/tests/test_simple_track_and_load.py +0 -0
- {simple_track-2.0.4 → simple_track-2.0.6}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Simple-Track
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
4
4
|
Summary: Threshold-based object tracking algorithm for 2D data
|
|
5
5
|
Author-email: Adam Gainford <adam.gainford@reading.ac.uk>, Thorwald Stein <t.h.m.stein@reading.ac.uk>
|
|
6
6
|
License-Expression: MPL-2.0
|
|
@@ -33,13 +33,15 @@ Features are tracked between consecutive frames of data by projecting feature fi
|
|
|
33
33
|
|
|
34
34
|
# Installation
|
|
35
35
|
|
|
36
|
-
Simple-Track can be installed using PyPi:
|
|
36
|
+
Simple-Track can be installed using PyPi or conda-forge:
|
|
37
37
|
|
|
38
38
|
```
|
|
39
39
|
python3 -m pip install simple-track
|
|
40
40
|
```
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
```
|
|
42
|
+
conda install conda-forge::simple-track
|
|
43
|
+
```
|
|
44
|
+
Coming soon to uv
|
|
43
45
|
|
|
44
46
|
# User Guide
|
|
45
47
|
|
|
@@ -177,10 +179,10 @@ Fields (`.field` files):
|
|
|
177
179
|
|
|
178
180
|
Features (`.csv` or `.txt` files):
|
|
179
181
|
* ID: Unique feature identifier that persists between frames (i.e., a feature retains the same id across all frames that it is tracked).
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
+
* centroid: (y, x) tuple containing central location of feature.
|
|
183
|
+
* size: Number of pixels spanned by the feature.
|
|
182
184
|
* dydx: (dy, dx) tuple containing motion vector that translated feature to its location in the current frame from the previous frame.
|
|
183
|
-
*
|
|
185
|
+
* max: Maximum value contained within the feature in the input data.
|
|
184
186
|
* lifetime: Number of timesteps the feature has existed for.
|
|
185
187
|
* accreted: List of IDs of features that were accreted by this feature, if applicable.
|
|
186
188
|
* parent: ID of parent feature that this feature split from, if applicable
|
|
@@ -11,13 +11,15 @@ Features are tracked between consecutive frames of data by projecting feature fi
|
|
|
11
11
|
|
|
12
12
|
# Installation
|
|
13
13
|
|
|
14
|
-
Simple-Track can be installed using PyPi:
|
|
14
|
+
Simple-Track can be installed using PyPi or conda-forge:
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
python3 -m pip install simple-track
|
|
18
18
|
```
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
```
|
|
20
|
+
conda install conda-forge::simple-track
|
|
21
|
+
```
|
|
22
|
+
Coming soon to uv
|
|
21
23
|
|
|
22
24
|
# User Guide
|
|
23
25
|
|
|
@@ -155,10 +157,10 @@ Fields (`.field` files):
|
|
|
155
157
|
|
|
156
158
|
Features (`.csv` or `.txt` files):
|
|
157
159
|
* ID: Unique feature identifier that persists between frames (i.e., a feature retains the same id across all frames that it is tracked).
|
|
158
|
-
*
|
|
159
|
-
*
|
|
160
|
+
* centroid: (y, x) tuple containing central location of feature.
|
|
161
|
+
* size: Number of pixels spanned by the feature.
|
|
160
162
|
* dydx: (dy, dx) tuple containing motion vector that translated feature to its location in the current frame from the previous frame.
|
|
161
|
-
*
|
|
163
|
+
* max: Maximum value contained within the feature in the input data.
|
|
162
164
|
* lifetime: Number of timesteps the feature has existed for.
|
|
163
165
|
* accreted: List of IDs of features that were accreted by this feature, if applicable.
|
|
164
166
|
* parent: ID of parent feature that this feature split from, if applicable
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Simple-Track
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
4
4
|
Summary: Threshold-based object tracking algorithm for 2D data
|
|
5
5
|
Author-email: Adam Gainford <adam.gainford@reading.ac.uk>, Thorwald Stein <t.h.m.stein@reading.ac.uk>
|
|
6
6
|
License-Expression: MPL-2.0
|
|
@@ -33,13 +33,15 @@ Features are tracked between consecutive frames of data by projecting feature fi
|
|
|
33
33
|
|
|
34
34
|
# Installation
|
|
35
35
|
|
|
36
|
-
Simple-Track can be installed using PyPi:
|
|
36
|
+
Simple-Track can be installed using PyPi or conda-forge:
|
|
37
37
|
|
|
38
38
|
```
|
|
39
39
|
python3 -m pip install simple-track
|
|
40
40
|
```
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
```
|
|
42
|
+
conda install conda-forge::simple-track
|
|
43
|
+
```
|
|
44
|
+
Coming soon to uv
|
|
43
45
|
|
|
44
46
|
# User Guide
|
|
45
47
|
|
|
@@ -177,10 +179,10 @@ Fields (`.field` files):
|
|
|
177
179
|
|
|
178
180
|
Features (`.csv` or `.txt` files):
|
|
179
181
|
* ID: Unique feature identifier that persists between frames (i.e., a feature retains the same id across all frames that it is tracked).
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
+
* centroid: (y, x) tuple containing central location of feature.
|
|
183
|
+
* size: Number of pixels spanned by the feature.
|
|
182
184
|
* dydx: (dy, dx) tuple containing motion vector that translated feature to its location in the current frame from the previous frame.
|
|
183
|
-
*
|
|
185
|
+
* max: Maximum value contained within the feature in the input data.
|
|
184
186
|
* lifetime: Number of timesteps the feature has existed for.
|
|
185
187
|
* accreted: List of IDs of features that were accreted by this feature, if applicable.
|
|
186
188
|
* parent: ID of parent feature that this feature split from, if applicable
|
|
@@ -8,7 +8,9 @@ def run_tracking():
|
|
|
8
8
|
# Need to make sure that changes don't affect pyproject.toml entry points
|
|
9
9
|
# easiest just to pass the parser in to run_tracking.
|
|
10
10
|
parser = argparse.ArgumentParser(description="Run Simple-Track")
|
|
11
|
-
parser.add_argument(
|
|
11
|
+
parser.add_argument(
|
|
12
|
+
"configs", nargs="+", help="Path to one or more yaml config files."
|
|
13
|
+
)
|
|
12
14
|
args = parser.parse_args()
|
|
13
15
|
|
|
14
16
|
for config_path in args.configs:
|
|
@@ -9,7 +9,7 @@ from simpletrack.utils import check_arrays, check_valid_ids, native
|
|
|
9
9
|
class Feature:
|
|
10
10
|
"""
|
|
11
11
|
Object containing details about a specific feature, including its id, time,
|
|
12
|
-
centroid,
|
|
12
|
+
centroid, maximum value, lifetime, and whether it has undergone any
|
|
13
13
|
mergers of splits in the current timestep.
|
|
14
14
|
"""
|
|
15
15
|
|
|
@@ -30,7 +30,8 @@ class Feature:
|
|
|
30
30
|
self._parent = None
|
|
31
31
|
self._children = []
|
|
32
32
|
self._dydx = ()
|
|
33
|
-
self.
|
|
33
|
+
self._max = None
|
|
34
|
+
self._mean = None
|
|
34
35
|
|
|
35
36
|
def __repr__(self) -> str:
|
|
36
37
|
repr_str = f"Feature id: {self._id} (provisionally {self._provisional_id}), "
|
|
@@ -140,11 +141,18 @@ class Feature:
|
|
|
140
141
|
return native(self._dydx)
|
|
141
142
|
|
|
142
143
|
@property
|
|
143
|
-
def
|
|
144
|
+
def max(self) -> float:
|
|
144
145
|
"""
|
|
145
146
|
Maximum value of the Feature in the raw input data
|
|
146
147
|
"""
|
|
147
|
-
return self.
|
|
148
|
+
return self._max
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def mean(self) -> float:
|
|
152
|
+
"""
|
|
153
|
+
Mean value of the Feature in the raw input data
|
|
154
|
+
"""
|
|
155
|
+
return self._mean
|
|
148
156
|
|
|
149
157
|
@coords.setter
|
|
150
158
|
def coords(self, new_coords: NDArray[np.integer]) -> None:
|
|
@@ -184,9 +192,13 @@ class Feature:
|
|
|
184
192
|
id_of_accreting_feature = check_valid_ids(id_of_accreting_feature)
|
|
185
193
|
self._accreted_in_next_frame_by = id_of_accreting_feature
|
|
186
194
|
|
|
187
|
-
@
|
|
188
|
-
def
|
|
189
|
-
self.
|
|
195
|
+
@max.setter
|
|
196
|
+
def max(self, max_val: float) -> None:
|
|
197
|
+
self._max = max_val
|
|
198
|
+
|
|
199
|
+
@mean.setter
|
|
200
|
+
def mean(self, mean_val: float) -> None:
|
|
201
|
+
self._mean = mean_val
|
|
190
202
|
|
|
191
203
|
def calculate_centroid(self) -> tuple:
|
|
192
204
|
"""
|
|
@@ -279,7 +291,8 @@ class Feature:
|
|
|
279
291
|
"size": self.get_size(),
|
|
280
292
|
# native() does not convert dydx to python type for some reason
|
|
281
293
|
"dydx": tuple([val.item() for val in self._dydx]),
|
|
282
|
-
"
|
|
294
|
+
"max": self._max,
|
|
295
|
+
"mean": self._mean,
|
|
283
296
|
"lifetime": self._lifetime,
|
|
284
297
|
"accreted": self._accreted,
|
|
285
298
|
# This will not be output properly in the current workflow, since each
|
|
@@ -176,6 +176,8 @@ class Frame:
|
|
|
176
176
|
# Provisionally set the lifetime field to 1 anywhere there is a feature
|
|
177
177
|
self._lifetime_field = np.zeros_like(self._feature_field)
|
|
178
178
|
self._lifetime_field[self._feature_field > 0] = 1
|
|
179
|
+
if np.max(self._feature_field) == 0:
|
|
180
|
+
return
|
|
179
181
|
self.max_id = int(np.max(self._feature_field))
|
|
180
182
|
self.populate_features()
|
|
181
183
|
|
|
@@ -211,7 +213,8 @@ class Frame:
|
|
|
211
213
|
)
|
|
212
214
|
# If raw field is not None, use this to find max value within Feature
|
|
213
215
|
if self.raw_field is not None:
|
|
214
|
-
feature.
|
|
216
|
+
feature.max = np.max(self.raw_field[feature_mask])
|
|
217
|
+
feature.mean = np.mean(self.raw_field[feature_mask])
|
|
215
218
|
self._features[feature_id] = feature
|
|
216
219
|
|
|
217
220
|
def assign_displacements(self, y_flow: NDArray, x_flow: NDArray) -> None:
|
|
@@ -387,6 +390,9 @@ class Timeline:
|
|
|
387
390
|
def __init__(self):
|
|
388
391
|
self.timeline = {}
|
|
389
392
|
|
|
393
|
+
def __len__(self) -> int:
|
|
394
|
+
return len(self.timeline)
|
|
395
|
+
|
|
390
396
|
def add_to_timelime(self, frame: Frame) -> None:
|
|
391
397
|
"""
|
|
392
398
|
Add the input frame to the timeline, using the frame.get_time() to
|
|
@@ -58,7 +58,7 @@ class Tracker:
|
|
|
58
58
|
self.frame_tracker = FrameTracker()
|
|
59
59
|
|
|
60
60
|
if "OUTPUT" in self.config:
|
|
61
|
-
self.skip_tracking = self.config["
|
|
61
|
+
self.skip_tracking = self.config["OUTPUT"].get("skip_tracking", False)
|
|
62
62
|
output_path = self.config["OUTPUT"].get("path", "./output")
|
|
63
63
|
expt_name = self.config["OUTPUT"].get(
|
|
64
64
|
"experiment_name", "Simple-Track Experiment"
|
|
@@ -103,7 +103,7 @@ def test_populate_features_valid_feature_field():
|
|
|
103
103
|
assert test_frame.features == expected_dict
|
|
104
104
|
|
|
105
105
|
|
|
106
|
-
def
|
|
106
|
+
def test_populate_features_sets_max_property():
|
|
107
107
|
test_time = dt.datetime.now()
|
|
108
108
|
test_frame = Frame()
|
|
109
109
|
test_frame.time = test_time
|
|
@@ -123,8 +123,33 @@ def test_populate_features_sets_extreme_property():
|
|
|
123
123
|
test_frame.raw_field = test_raw_field
|
|
124
124
|
test_frame.populate_features()
|
|
125
125
|
|
|
126
|
-
assert test_frame.get_feature(1).
|
|
127
|
-
assert test_frame.get_feature(2).
|
|
126
|
+
assert test_frame.get_feature(1).max == 10
|
|
127
|
+
assert test_frame.get_feature(2).max == 20
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_populate_features_sets_mean_property():
|
|
131
|
+
test_time = dt.datetime.now()
|
|
132
|
+
test_frame = Frame()
|
|
133
|
+
test_frame.time = test_time
|
|
134
|
+
|
|
135
|
+
test_feature_field = test_field.copy()
|
|
136
|
+
test_feature_field[2:6, 2:6] = 1
|
|
137
|
+
test_feature_field[6:9, 6:9] = 2
|
|
138
|
+
|
|
139
|
+
test_raw_field = test_field.copy()
|
|
140
|
+
test_raw_field[2:6, 2:6] = 10
|
|
141
|
+
test_raw_field[2:6, 4:6] = 50
|
|
142
|
+
test_raw_field[6:9, 6:9] = 20
|
|
143
|
+
# Set another higher maximum within the field to check mean
|
|
144
|
+
# only picks up the values within the feature mask
|
|
145
|
+
test_raw_field[0:2, 0:2] = 100
|
|
146
|
+
|
|
147
|
+
test_frame.feature_field = test_feature_field
|
|
148
|
+
test_frame.raw_field = test_raw_field
|
|
149
|
+
test_frame.populate_features()
|
|
150
|
+
|
|
151
|
+
assert test_frame.get_feature(1).mean == 30
|
|
152
|
+
assert test_frame.get_feature(2).mean == 20
|
|
128
153
|
|
|
129
154
|
|
|
130
155
|
def test_populate_features_invalid_negative_features():
|
|
@@ -175,6 +200,39 @@ def test_populate_features_with_no_feature_field():
|
|
|
175
200
|
)
|
|
176
201
|
|
|
177
202
|
|
|
203
|
+
def test_populate_features_with_feature_field_but_no_identifiable_features():
|
|
204
|
+
test_time = dt.datetime.now()
|
|
205
|
+
test_frame = Frame()
|
|
206
|
+
test_frame.time = test_time
|
|
207
|
+
|
|
208
|
+
test_feature_field = np.zeros((10, 10))
|
|
209
|
+
test_frame.feature_field = test_feature_field
|
|
210
|
+
|
|
211
|
+
test_frame.populate_features()
|
|
212
|
+
|
|
213
|
+
if len(test_frame.features) != 0:
|
|
214
|
+
raise TypeError(
|
|
215
|
+
f"No features expected for this test, got {len(test_frame.get_features())}"
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_identify_and_populate_features_with_raw_field_but_no_identifiable_features():
|
|
220
|
+
test_time = dt.datetime.now()
|
|
221
|
+
test_frame = Frame()
|
|
222
|
+
test_frame.time = test_time
|
|
223
|
+
|
|
224
|
+
test_raw_field = np.zeros((10, 10))
|
|
225
|
+
test_frame.raw_field = test_raw_field
|
|
226
|
+
|
|
227
|
+
test_frame.identify_features(threshold=1, min_size=1)
|
|
228
|
+
# test_frame.populate_features() automatically happens within identify_features
|
|
229
|
+
|
|
230
|
+
if len(test_frame.features) != 0:
|
|
231
|
+
raise TypeError(
|
|
232
|
+
f"No features expected for this test, got {len(test_frame.get_features())}"
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
178
236
|
def test_assign_displacements_valid_inputs():
|
|
179
237
|
test_frame = Frame()
|
|
180
238
|
y_flow = np.ones((10, 10))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|