byotrack 1.2.0.dev0__tar.gz → 1.2.0.dev2__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.
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/PKG-INFO +2 -2
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/__init__.py +1 -1
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/detector/detections.py +57 -2
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/features_extractor.py +65 -1
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/dataset/ctc.py +5 -3
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/base.py +336 -25
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/kalman_linker.py +46 -10
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/koft.py +42 -11
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/nearest_neighbor.py +29 -9
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/metrics/ctc.py +14 -2
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/PKG-INFO +2 -2
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/requires.txt +1 -1
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/setup.cfg +1 -1
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/LICENSE +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/README.md +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/detector/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/detector/detector.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/linker.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/optical_flow/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/optical_flow/optical_flow.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/parameters.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/refiner.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/tracker.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/tracks.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/dataset/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/example_data.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/fiji/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/fiji/io.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/fiji/run.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/icy/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/icy/io.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/icy/run.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/detector/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/detector/stardist.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/detector/wavelet.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/greedy_lap.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/icy_emht/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/icy_emht/emht_protocol.xml +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/icy_emht/emht_protocol_with_full_specs.xml +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/icy_emht/icy_emht.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/trackmate/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/trackmate/_trackmate.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/trackmate/trackmate.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/optical_flow/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/optical_flow/opencv.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/optical_flow/skimage.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/cleaner.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/interpolater.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/propagation.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/stitching/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/stitching/dist_stitcher.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/stitching/emc2.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/metrics/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/py.typed +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/utils.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/video/__init__.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/video/reader.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/video/transforms.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/video/video.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/visualize.py +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/SOURCES.txt +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/dependency_links.txt +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/top_level.txt +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/pyproject.toml +0 -0
- {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: byotrack
|
|
3
|
-
Version: 1.2.0.
|
|
3
|
+
Version: 1.2.0.dev2
|
|
4
4
|
Summary: Biological particle tracking with Python
|
|
5
5
|
Home-page: https://github.com/raphaelreme/byotrack
|
|
6
6
|
Author: Raphael Reme
|
|
@@ -28,7 +28,7 @@ Requires-Dist: platformdirs
|
|
|
28
28
|
Requires-Dist: pylapy[scipy]
|
|
29
29
|
Requires-Dist: tifffile[all]
|
|
30
30
|
Requires-Dist: torch
|
|
31
|
-
Requires-Dist:
|
|
31
|
+
Requires-Dist: torch-tps
|
|
32
32
|
Requires-Dist: tqdm
|
|
33
33
|
Provides-Extra: full
|
|
34
34
|
Requires-Dist: matplotlib; extra == "full"
|
|
@@ -38,7 +38,7 @@ def _check_confidence(confidence: torch.Tensor) -> None:
|
|
|
38
38
|
|
|
39
39
|
@numba.njit(parallel=False)
|
|
40
40
|
def _position_from_segmentation(segmentation: np.ndarray) -> np.ndarray:
|
|
41
|
-
"""Return the
|
|
41
|
+
"""Return the center (mean) of each instance in the segmentation"""
|
|
42
42
|
# A bit slower than previous version in 2D, but still fine
|
|
43
43
|
|
|
44
44
|
n = segmentation.max()
|
|
@@ -57,6 +57,42 @@ def _position_from_segmentation(segmentation: np.ndarray) -> np.ndarray:
|
|
|
57
57
|
return m_1.astype(np.float32) / m_0.reshape(-1, 1)
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
@numba.njit(parallel=False)
|
|
61
|
+
def _median_from_segmentation(segmentation: np.ndarray) -> np.ndarray:
|
|
62
|
+
"""Return the center (median) of each instance in the segmentation"""
|
|
63
|
+
|
|
64
|
+
# Flatten space axes
|
|
65
|
+
flat_segmentation = segmentation.reshape(-1)
|
|
66
|
+
|
|
67
|
+
n = segmentation.max()
|
|
68
|
+
counts = np.zeros(n, dtype=np.uint)
|
|
69
|
+
|
|
70
|
+
for i in range(flat_segmentation.shape[0]):
|
|
71
|
+
instance = flat_segmentation[i] - 1
|
|
72
|
+
if instance != -1:
|
|
73
|
+
counts[instance] += 1
|
|
74
|
+
|
|
75
|
+
m = np.max(counts)
|
|
76
|
+
|
|
77
|
+
# Reset counts and allocate position
|
|
78
|
+
counts[:] = 0
|
|
79
|
+
positions = np.empty((n, m, len(segmentation.shape)), dtype=np.uint)
|
|
80
|
+
|
|
81
|
+
for index in np.ndindex(*segmentation.shape):
|
|
82
|
+
instance = segmentation[index] - 1
|
|
83
|
+
if instance != -1:
|
|
84
|
+
positions[instance, counts[instance]] = index
|
|
85
|
+
counts[instance] += 1
|
|
86
|
+
|
|
87
|
+
# Compute medians
|
|
88
|
+
median = np.zeros((n, len(segmentation.shape)), dtype=np.float32)
|
|
89
|
+
for instance in range(n):
|
|
90
|
+
for axis in range(len(segmentation.shape)):
|
|
91
|
+
median[instance, axis] = np.median(positions[instance, : counts[instance], axis])
|
|
92
|
+
|
|
93
|
+
return median
|
|
94
|
+
|
|
95
|
+
|
|
60
96
|
@numba.njit
|
|
61
97
|
def _bbox_from_segmentation(segmentation: np.ndarray) -> np.ndarray:
|
|
62
98
|
# A bit slower than previous version in 2D, but still fine
|
|
@@ -242,12 +278,15 @@ class Detections:
|
|
|
242
278
|
Shape: ([D, ]H, W), dtype: int32
|
|
243
279
|
confidence (torch.Tensor): Confidence for each instance
|
|
244
280
|
Shape: (N,), dtype: float32
|
|
281
|
+
use_median_position (bool): Use median instead of mean to compute positions from segmentation.
|
|
282
|
+
Default: True (Usually more robust)
|
|
245
283
|
|
|
246
284
|
"""
|
|
247
285
|
|
|
248
|
-
def __init__(self, data: Dict[str, torch.Tensor], frame_id: int = -1) -> None:
|
|
286
|
+
def __init__(self, data: Dict[str, torch.Tensor], frame_id: int = -1, use_median_position=True) -> None:
|
|
249
287
|
self.length = -1
|
|
250
288
|
self.dim = -1
|
|
289
|
+
self._use_median_position = use_median_position
|
|
251
290
|
|
|
252
291
|
if "position" in data:
|
|
253
292
|
_check_position(data["position"])
|
|
@@ -321,6 +360,20 @@ class Detections:
|
|
|
321
360
|
|
|
322
361
|
return confidence
|
|
323
362
|
|
|
363
|
+
@property
|
|
364
|
+
def use_median_position(self) -> bool:
|
|
365
|
+
return self._use_median_position
|
|
366
|
+
|
|
367
|
+
@use_median_position.setter
|
|
368
|
+
def use_median_position(self, value: bool) -> None:
|
|
369
|
+
if value is self._use_median_position:
|
|
370
|
+
return
|
|
371
|
+
|
|
372
|
+
self._use_median_position = value
|
|
373
|
+
|
|
374
|
+
# Invalidate computed positions
|
|
375
|
+
self._lazy_extrapolated_data.pop("position")
|
|
376
|
+
|
|
324
377
|
def _extrapolate_shape(self) -> Tuple[int, ...]:
|
|
325
378
|
"""Extrapolate shape from data
|
|
326
379
|
|
|
@@ -354,6 +407,8 @@ class Detections:
|
|
|
354
407
|
|
|
355
408
|
"""
|
|
356
409
|
if "segmentation" in self.data:
|
|
410
|
+
if self.use_median_position:
|
|
411
|
+
return torch.tensor(_median_from_segmentation(self.data["segmentation"].numpy()))
|
|
357
412
|
return torch.tensor(_position_from_segmentation(self.data["segmentation"].numpy()))
|
|
358
413
|
|
|
359
414
|
return self.data["bbox"][:, : self.dim] + (self.data["bbox"][:, self.dim :] - 1) / 2
|
|
@@ -4,6 +4,7 @@ from abc import ABC, abstractmethod
|
|
|
4
4
|
from typing import Iterable
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
|
+
import numba # type: ignore
|
|
7
8
|
import torch
|
|
8
9
|
|
|
9
10
|
import byotrack # pylint: disable=cyclic-import
|
|
@@ -56,4 +57,67 @@ class MultiFeaturesExtractor(FeaturesExtractor):
|
|
|
56
57
|
return torch.cat(features, dim=-1)
|
|
57
58
|
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
class MassExtractor(FeaturesExtractor):
|
|
61
|
+
"""Extract the mass of each detection (number of pixels)"""
|
|
62
|
+
|
|
63
|
+
def __call__(self, frame: np.ndarray, detections: byotrack.Detections):
|
|
64
|
+
torch.tensor(compute_mass(detections.segmentation.numpy()), dtype=torch.float32)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class IntensityExtractor(FeaturesExtractor):
|
|
68
|
+
"""Extract the sum of intensities of each detection"""
|
|
69
|
+
|
|
70
|
+
def __call__(self, frame: np.ndarray, detections: byotrack.Detections):
|
|
71
|
+
torch.tensor(compute_intensity(detections.segmentation.numpy(), frame.sum(axis=-1)), dtype=torch.float32)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@numba.njit
|
|
75
|
+
def compute_mass(segmentation: np.ndarray) -> np.ndarray:
|
|
76
|
+
"""Extract the number of pixels of each detection
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
segmentation (np.ndarray): Segmentation mask
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
np.ndarray: Mass for each object
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
n = segmentation.max()
|
|
86
|
+
mass = np.zeros(n, dtype=np.uint)
|
|
87
|
+
|
|
88
|
+
# Ravel in 1D
|
|
89
|
+
segmentation = segmentation.reshape(-1)
|
|
90
|
+
|
|
91
|
+
for i in range(segmentation.shape[0]):
|
|
92
|
+
instance = segmentation[i] - 1
|
|
93
|
+
if instance != -1:
|
|
94
|
+
mass[instance] += 1
|
|
95
|
+
|
|
96
|
+
return mass
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@numba.njit
|
|
100
|
+
def compute_intensity(segmentation: np.ndarray, frame: np.ndarray) -> np.ndarray:
|
|
101
|
+
"""Extract the cumulated intensity of each detection
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
segmentation (np.ndarray): Segmentation mask
|
|
105
|
+
frame (np.ndarray): Video frame (should have the same number of pixels than segmentation)
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
np.ndarray: Sum of intensity for each object
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
n = segmentation.max()
|
|
112
|
+
intensity = np.zeros(n, dtype=frame.dtype)
|
|
113
|
+
|
|
114
|
+
# Ravel in 1D
|
|
115
|
+
segmentation = segmentation.reshape(-1)
|
|
116
|
+
frame = frame.reshape(-1)
|
|
117
|
+
|
|
118
|
+
for i in range(segmentation.shape[0]):
|
|
119
|
+
instance = segmentation[i] - 1
|
|
120
|
+
if instance != -1:
|
|
121
|
+
intensity[instance] += frame[i]
|
|
122
|
+
|
|
123
|
+
return intensity
|
|
@@ -40,6 +40,8 @@ class GroundTruthDetector(byotrack.BatchDetector):
|
|
|
40
40
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
|
+
progress_bar_description = "Detections (Load from CTC format)"
|
|
44
|
+
|
|
43
45
|
def detect(self, batch: np.ndarray) -> List[byotrack.Detections]:
|
|
44
46
|
assert batch.shape[-1] == 1, "Multichannel segmentation are not supported"
|
|
45
47
|
assert np.issubdtype(batch.dtype, np.integer)
|
|
@@ -131,7 +133,7 @@ def load_tracks( # pylint: disable=too-many-locals,too-many-branches,too-many-s
|
|
|
131
133
|
segmentation_paths = path.glob("mask*.tif") if is_res else path.glob("man_*.tif")
|
|
132
134
|
loader = byotrack.video.reader.FrameTiffLoader()
|
|
133
135
|
|
|
134
|
-
for path_ in tqdm.tqdm(byotrack.utils.sorted_alphanumeric(segmentation_paths)):
|
|
136
|
+
for path_ in tqdm.tqdm(byotrack.utils.sorted_alphanumeric(segmentation_paths), desc="Loading CTC tracks"):
|
|
135
137
|
if is_res:
|
|
136
138
|
frame_id = int(path_.stem[len("mask") :])
|
|
137
139
|
elif "seg" in path.stem:
|
|
@@ -225,7 +227,7 @@ def save_detections(
|
|
|
225
227
|
|
|
226
228
|
os.makedirs(path, exist_ok=True)
|
|
227
229
|
|
|
228
|
-
for frame_id, detections in enumerate(tqdm.tqdm(detections_sequence)):
|
|
230
|
+
for frame_id, detections in enumerate(tqdm.tqdm(detections_sequence, desc="Saving Detections to CTC")):
|
|
229
231
|
segmentation = detections.segmentation.numpy().astype(np.uint16)
|
|
230
232
|
|
|
231
233
|
if as_res:
|
|
@@ -472,7 +474,7 @@ def save_tracks( # pylint: disable=too-many-branches,too-many-locals,too-many-s
|
|
|
472
474
|
else:
|
|
473
475
|
assert shape is not None, "Without detections_sequence, you need to provide the shape argument"
|
|
474
476
|
|
|
475
|
-
for frame_id in tqdm.trange(last + 1):
|
|
477
|
+
for frame_id in tqdm.trange(last + 1, desc="Saving tracks to CTC"):
|
|
476
478
|
has_detections = len(detections_sequence) > frame_id
|
|
477
479
|
|
|
478
480
|
disk_positions = []
|