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.
Files changed (70) hide show
  1. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/PKG-INFO +2 -2
  2. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/__init__.py +1 -1
  3. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/detector/detections.py +57 -2
  4. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/features_extractor.py +65 -1
  5. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/dataset/ctc.py +5 -3
  6. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/base.py +336 -25
  7. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/kalman_linker.py +46 -10
  8. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/koft.py +42 -11
  9. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/nearest_neighbor.py +29 -9
  10. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/metrics/ctc.py +14 -2
  11. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/PKG-INFO +2 -2
  12. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/requires.txt +1 -1
  13. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/setup.cfg +1 -1
  14. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/LICENSE +0 -0
  15. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/README.md +0 -0
  16. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/__init__.py +0 -0
  17. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/detector/__init__.py +0 -0
  18. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/detector/detector.py +0 -0
  19. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/linker.py +0 -0
  20. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/optical_flow/__init__.py +0 -0
  21. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/optical_flow/optical_flow.py +0 -0
  22. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/parameters.py +0 -0
  23. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/refiner.py +0 -0
  24. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/tracker.py +0 -0
  25. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/api/tracks.py +0 -0
  26. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/dataset/__init__.py +0 -0
  27. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/example_data.py +0 -0
  28. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/fiji/__init__.py +0 -0
  29. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/fiji/io.py +0 -0
  30. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/fiji/run.py +0 -0
  31. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/icy/__init__.py +0 -0
  32. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/icy/io.py +0 -0
  33. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/icy/run.py +0 -0
  34. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/__init__.py +0 -0
  35. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/detector/__init__.py +0 -0
  36. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/detector/stardist.py +0 -0
  37. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/detector/wavelet.py +0 -0
  38. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/__init__.py +0 -0
  39. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/__init__.py +0 -0
  40. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/frame_by_frame/greedy_lap.py +0 -0
  41. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/icy_emht/__init__.py +0 -0
  42. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/icy_emht/emht_protocol.xml +0 -0
  43. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/icy_emht/emht_protocol_with_full_specs.xml +0 -0
  44. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/icy_emht/icy_emht.py +0 -0
  45. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/trackmate/__init__.py +0 -0
  46. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/trackmate/_trackmate.py +0 -0
  47. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/linker/trackmate/trackmate.py +0 -0
  48. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/optical_flow/__init__.py +0 -0
  49. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/optical_flow/opencv.py +0 -0
  50. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/optical_flow/skimage.py +0 -0
  51. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/__init__.py +0 -0
  52. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/cleaner.py +0 -0
  53. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/interpolater.py +0 -0
  54. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/propagation.py +0 -0
  55. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/stitching/__init__.py +0 -0
  56. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/stitching/dist_stitcher.py +0 -0
  57. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/implementation/refiner/stitching/emc2.py +0 -0
  58. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/metrics/__init__.py +0 -0
  59. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/py.typed +0 -0
  60. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/utils.py +0 -0
  61. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/video/__init__.py +0 -0
  62. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/video/reader.py +0 -0
  63. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/video/transforms.py +0 -0
  64. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/video/video.py +0 -0
  65. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack/visualize.py +0 -0
  66. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/SOURCES.txt +0 -0
  67. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/dependency_links.txt +0 -0
  68. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/byotrack.egg-info/top_level.txt +0 -0
  69. {byotrack-1.2.0.dev0 → byotrack-1.2.0.dev2}/pyproject.toml +0 -0
  70. {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.dev0
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: torch_tps
31
+ Requires-Dist: torch-tps
32
32
  Requires-Dist: tqdm
33
33
  Provides-Extra: full
34
34
  Requires-Dist: matplotlib; extra == "full"
@@ -83,4 +83,4 @@ from byotrack.api.tracks import Track
83
83
  from byotrack.video import Video, VideoTransformConfig
84
84
 
85
85
 
86
- __version__ = "1.2.0.dev0"
86
+ __version__ = "1.2.0.dev2"
@@ -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 centre of each instance in the segmentation"""
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
- # TODO: Add some examples
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 = []