itasc 0.2.0.dev0__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 (162) hide show
  1. itasc/__init__.py +16 -0
  2. itasc/cellpose/__init__.py +31 -0
  3. itasc/cellpose/cellpose_runner.py +334 -0
  4. itasc/cellpose/divergence_maps.py +380 -0
  5. itasc/cellpose/flow_following.py +283 -0
  6. itasc/cellpose/joint.py +98 -0
  7. itasc/cellpose/napari.yaml +10 -0
  8. itasc/cellpose/native_masks.py +313 -0
  9. itasc/cellpose/retrack.py +232 -0
  10. itasc/cellpose/shape.py +51 -0
  11. itasc/cellpose/track_laptrack.py +279 -0
  12. itasc/contact_analysis/__init__.py +61 -0
  13. itasc/contact_analysis/_provenance.py +29 -0
  14. itasc/contact_analysis/catalog.py +393 -0
  15. itasc/contact_analysis/config.py +155 -0
  16. itasc/contact_analysis/contacts/__init__.py +24 -0
  17. itasc/contact_analysis/contacts/batch.py +158 -0
  18. itasc/contact_analysis/contacts/build.py +837 -0
  19. itasc/contact_analysis/contacts/contact_labels.py +136 -0
  20. itasc/contact_analysis/contacts/neighborhood.py +146 -0
  21. itasc/contact_analysis/contacts/reader.py +58 -0
  22. itasc/contact_analysis/contacts/signed_contact_length.py +143 -0
  23. itasc/contact_analysis/curation.py +185 -0
  24. itasc/contact_analysis/dynamics/__init__.py +43 -0
  25. itasc/contact_analysis/dynamics/collective.py +206 -0
  26. itasc/contact_analysis/dynamics/kinematics.py +278 -0
  27. itasc/contact_analysis/dynamics/msd.py +165 -0
  28. itasc/contact_analysis/dynamics/store.py +251 -0
  29. itasc/contact_analysis/dynamics/trajectories.py +100 -0
  30. itasc/contact_analysis/frame_interval.py +85 -0
  31. itasc/contact_analysis/napari.yaml +10 -0
  32. itasc/contact_analysis/pipeline.py +313 -0
  33. itasc/contact_analysis/pixel_size.py +166 -0
  34. itasc/contact_analysis/quantifier.py +204 -0
  35. itasc/contact_analysis/quantifiers/__init__.py +7 -0
  36. itasc/contact_analysis/quantifiers/_contacts_derived.py +42 -0
  37. itasc/contact_analysis/quantifiers/cell_density.py +65 -0
  38. itasc/contact_analysis/quantifiers/cell_dynamics.py +81 -0
  39. itasc/contact_analysis/quantifiers/cell_shape.py +32 -0
  40. itasc/contact_analysis/quantifiers/contacts.py +72 -0
  41. itasc/contact_analysis/quantifiers/neighbor_count.py +26 -0
  42. itasc/contact_analysis/quantifiers/nucleus_dynamics.py +77 -0
  43. itasc/contact_analysis/quantifiers/nucleus_shape.py +35 -0
  44. itasc/contact_analysis/quantifiers/shape_relational.py +34 -0
  45. itasc/contact_analysis/quantifiers/signed_contact_length.py +44 -0
  46. itasc/contact_analysis/records.py +129 -0
  47. itasc/contact_analysis/shape/__init__.py +19 -0
  48. itasc/contact_analysis/shape/core.py +141 -0
  49. itasc/contact_analysis/shape/relational.py +134 -0
  50. itasc/contact_analysis/shape_tables.py +387 -0
  51. itasc/core/__init__.py +0 -0
  52. itasc/core/cancellation.py +15 -0
  53. itasc/core/commit.py +61 -0
  54. itasc/core/imageops.py +34 -0
  55. itasc/core/label_store.py +53 -0
  56. itasc/core/lineage.py +113 -0
  57. itasc/core/logging.py +51 -0
  58. itasc/core/paths.py +34 -0
  59. itasc/core/tiff.py +13 -0
  60. itasc/correction/__init__.py +25 -0
  61. itasc/correction/labels.py +983 -0
  62. itasc/napari/__init__.py +7 -0
  63. itasc/napari/_experiments_panel.py +1091 -0
  64. itasc/napari/_flow_layout.py +98 -0
  65. itasc/napari/_icons.py +126 -0
  66. itasc/napari/_napari_compat.py +118 -0
  67. itasc/napari/_paths.py +231 -0
  68. itasc/napari/_preview_cache.py +60 -0
  69. itasc/napari/_spotlight.py +46 -0
  70. itasc/napari/_stage_loader.py +146 -0
  71. itasc/napari/_stage_status.py +110 -0
  72. itasc/napari/_standalone_paths.py +89 -0
  73. itasc/napari/_state.py +146 -0
  74. itasc/napari/_status_rail.py +128 -0
  75. itasc/napari/_track_render.py +114 -0
  76. itasc/napari/_widget_helpers.py +236 -0
  77. itasc/napari/aggregate_widget.py +339 -0
  78. itasc/napari/candidate_gallery_controller.py +285 -0
  79. itasc/napari/cell_workflow_widget.py +1405 -0
  80. itasc/napari/cellpose_segment_track_widget.py +1253 -0
  81. itasc/napari/cellpose_widget.py +1034 -0
  82. itasc/napari/contact_analysis/__init__.py +6 -0
  83. itasc/napari/contact_analysis/curation_controller.py +94 -0
  84. itasc/napari/contact_analysis/plugins/__init__.py +102 -0
  85. itasc/napari/contact_analysis/plugins/catalog_summary.py +56 -0
  86. itasc/napari/contact_analysis/plugins/curation.py +247 -0
  87. itasc/napari/contact_analysis/plugins/visualize_contacts.py +64 -0
  88. itasc/napari/contact_analysis_widget.py +951 -0
  89. itasc/napari/contact_visualization.py +895 -0
  90. itasc/napari/correction/__init__.py +8 -0
  91. itasc/napari/correction/_correction_anchor.py +54 -0
  92. itasc/napari/correction/_correction_candidate_panel.py +259 -0
  93. itasc/napari/correction/_correction_candidates.py +169 -0
  94. itasc/napari/correction/_correction_centroids.py +132 -0
  95. itasc/napari/correction/_correction_commit.py +71 -0
  96. itasc/napari/correction/_correction_events.py +39 -0
  97. itasc/napari/correction/_correction_keymap.py +80 -0
  98. itasc/napari/correction/_correction_layer_lifecycle.py +137 -0
  99. itasc/napari/correction/_correction_layer_loader.py +86 -0
  100. itasc/napari/correction/_correction_navigation.py +97 -0
  101. itasc/napari/correction/_correction_paint.py +46 -0
  102. itasc/napari/correction/_correction_playback.py +68 -0
  103. itasc/napari/correction/_correction_protection.py +44 -0
  104. itasc/napari/correction/_correction_takeover.py +64 -0
  105. itasc/napari/correction/_correction_track_accordion.py +566 -0
  106. itasc/napari/correction/_correction_track_path.py +388 -0
  107. itasc/napari/correction/_correction_ui.py +367 -0
  108. itasc/napari/correction/_correction_ui_nucleus.py +393 -0
  109. itasc/napari/correction/_correction_utils.py +204 -0
  110. itasc/napari/correction/_correction_validation.py +83 -0
  111. itasc/napari/correction/cell_correction_widget.py +1190 -0
  112. itasc/napari/correction/correction_widget.py +1363 -0
  113. itasc/napari/correction/nucleus_correction_widget.py +2185 -0
  114. itasc/napari/divergence_maps_widget.py +795 -0
  115. itasc/napari/lineage_canvas_controller.py +351 -0
  116. itasc/napari/main_widget.py +1184 -0
  117. itasc/napari/nucleus_atom_extraction_widget.py +648 -0
  118. itasc/napari/nucleus_db_browser_widget.py +1442 -0
  119. itasc/napari/nucleus_pipeline_widget.py +579 -0
  120. itasc/napari/nucleus_segmentation_inputs_widget.py +21 -0
  121. itasc/napari/nucleus_tracking_inputs_widget.py +248 -0
  122. itasc/napari/nucleus_workflow_widget.py +963 -0
  123. itasc/napari/track_path_controller.py +387 -0
  124. itasc/napari/ui_gate.py +266 -0
  125. itasc/napari/ui_style.py +503 -0
  126. itasc/napari/validated_overlay_controller.py +254 -0
  127. itasc/napari/widgets.py +616 -0
  128. itasc/napari.yaml +45 -0
  129. itasc/segmentation/__init__.py +60 -0
  130. itasc/segmentation/cell_divergence_segmentation.py +433 -0
  131. itasc/segmentation/cell_label_icm.py +637 -0
  132. itasc/segmentation/contour_filtering.py +128 -0
  133. itasc/segmentation/lineage.py +18 -0
  134. itasc/segmentation/nucleus_segmentation.py +35 -0
  135. itasc/tracking_ultrack/__init__.py +12 -0
  136. itasc/tracking_ultrack/_node_geometry.py +191 -0
  137. itasc/tracking_ultrack/atoms.py +470 -0
  138. itasc/tracking_ultrack/config.py +76 -0
  139. itasc/tracking_ultrack/corrections.py +1213 -0
  140. itasc/tracking_ultrack/db_build.py +492 -0
  141. itasc/tracking_ultrack/db_query.py +952 -0
  142. itasc/tracking_ultrack/export.py +133 -0
  143. itasc/tracking_ultrack/extend.py +243 -0
  144. itasc/tracking_ultrack/ingest.py +83 -0
  145. itasc/tracking_ultrack/linking.py +286 -0
  146. itasc/tracking_ultrack/multi_threshold.py +46 -0
  147. itasc/tracking_ultrack/napari.yaml +10 -0
  148. itasc/tracking_ultrack/reseed.py +456 -0
  149. itasc/tracking_ultrack/retracker.py +153 -0
  150. itasc/tracking_ultrack/scoring.py +74 -0
  151. itasc/tracking_ultrack/seed_prior.py +175 -0
  152. itasc/tracking_ultrack/solve.py +52 -0
  153. itasc/tracking_ultrack/swap_candidate.py +171 -0
  154. itasc/tracking_ultrack/track_quality.py +142 -0
  155. itasc/tracking_ultrack/validation_nodes.py +315 -0
  156. itasc/tracking_ultrack/validation_state.py +430 -0
  157. itasc-0.2.0.dev0.data/data/itasc/napari.yaml +45 -0
  158. itasc-0.2.0.dev0.dist-info/METADATA +186 -0
  159. itasc-0.2.0.dev0.dist-info/RECORD +162 -0
  160. itasc-0.2.0.dev0.dist-info/WHEEL +4 -0
  161. itasc-0.2.0.dev0.dist-info/entry_points.txt +2 -0
  162. itasc-0.2.0.dev0.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,254 @@
1
+ """Validated-track overlay state for the nucleus correction workflow."""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from collections.abc import Callable
6
+
7
+ import numpy as np
8
+ from napari.utils.colormaps import direct_colormap
9
+
10
+ from itasc.tracking_ultrack.validation_state import (
11
+ invalidate_track,
12
+ read_corrections,
13
+ read_validated_tracks,
14
+ )
15
+
16
+ VALIDATED_OVERLAY = "[Correction] Validated: Nucleus"
17
+ LEGACY_VALIDATED_OVERLAY = "Validated: Nucleus"
18
+ ANCHOR_OVERLAY = "[Correction] Anchors: Nucleus"
19
+ LEGACY_ANCHOR_OVERLAY = "Anchors: Nucleus"
20
+ SPOTLIGHT_LAYER = "[Correction] CellSpotlight"
21
+ VALIDATED_OVERLAY_OPACITY = 0.4
22
+ VALIDATED_OVERLAY_COLOR = "#00ff00"
23
+ ANCHOR_OVERLAY_COLOR = "#b39400"
24
+ # In the filled-by-ID view (images hidden) a translucent wash would hide the
25
+ # per-ID colours, so the overlay is drawn as an opaque border of this thickness.
26
+ VALIDATED_OVERLAY_CONTOUR = 2
27
+
28
+
29
+ class ValidatedOverlayController:
30
+ """Own validated-cell overlay rendering and validation counter updates."""
31
+
32
+ def __init__(
33
+ self,
34
+ viewer,
35
+ *,
36
+ tracked_layer_provider: Callable[[], object | None],
37
+ pos_dir_provider: Callable[[], Path | None],
38
+ owned_layers: set[str],
39
+ ) -> None:
40
+ self.viewer = viewer
41
+ self._tracked_layer_provider = tracked_layer_provider
42
+ self._pos_dir_provider = pos_dir_provider
43
+ self._owned_layers = owned_layers
44
+ # When True, overlays render as an opaque border instead of a wash.
45
+ self._border_mode = False
46
+
47
+ def set_border_mode(self, enabled: bool) -> None:
48
+ """Switch overlays between translucent wash and opaque border.
49
+
50
+ Used by the filled-by-ID viewer mode: a wash would obscure the per-ID
51
+ label colours, so validated/anchor cells are shown as a coloured border.
52
+ Applies to existing overlay layers immediately and is remembered so
53
+ later overlay rebuilds keep the chosen style.
54
+ """
55
+ self._border_mode = bool(enabled)
56
+ for name in (VALIDATED_OVERLAY, ANCHOR_OVERLAY):
57
+ if name in self.viewer.layers:
58
+ self._apply_overlay_style(self.viewer.layers[name])
59
+
60
+ def _apply_overlay_style(self, layer) -> None:
61
+ try:
62
+ if self._border_mode:
63
+ layer.contour = VALIDATED_OVERLAY_CONTOUR
64
+ layer.opacity = 1.0
65
+ else:
66
+ layer.contour = 0
67
+ layer.opacity = VALIDATED_OVERLAY_OPACITY
68
+ except Exception:
69
+ pass
70
+
71
+ def refresh_overlay(self, frame_view_2d=None) -> None:
72
+ # ``frame_view_2d`` is accepted (and ignored) for call-site compatibility:
73
+ # the overlays are whole-stack masks now, so napari slices the current
74
+ # frame for us — no per-frame rebuild needed. See refresh_validated_overlay.
75
+ self.refresh_validated_overlay()
76
+ self.refresh_anchor_overlay()
77
+
78
+ def refresh_validated_overlay(self, frame_view_2d=None) -> None:
79
+ """Paint validated cells across *every* frame they're validated in.
80
+
81
+ Builds the whole ``(T, H, W)`` mask once from the validated-tracks store
82
+ rather than just the current frame, so scrubbing the time slider does not
83
+ trigger a rebuild — napari shows the right slice itself. Re-run only when
84
+ the validations or the labels actually change.
85
+ """
86
+ tracked = self._tracked_layer_provider()
87
+ pos_dir = self._pos_dir_provider()
88
+ if pos_dir is None or tracked is None:
89
+ self.remove_overlay_layers()
90
+ return
91
+ data = getattr(tracked, "data", None)
92
+ if data is None or data.ndim < 3:
93
+ return
94
+ validated_tracks = read_validated_tracks(pos_dir)
95
+ overlay_exists = VALIDATED_OVERLAY in self.viewer.layers
96
+ if not validated_tracks and not overlay_exists:
97
+ return
98
+ full = self._mask_stack(np.asarray(data), validated_tracks)
99
+ if overlay_exists:
100
+ self.viewer.layers[VALIDATED_OVERLAY].data = full
101
+ else:
102
+ self.add_overlay(full)
103
+
104
+ def refresh_anchor_overlay(self, frame_view_2d=None) -> None:
105
+ """Paint anchored cells across every frame they're anchored in (whole stack)."""
106
+ tracked = self._tracked_layer_provider()
107
+ pos_dir = self._pos_dir_provider()
108
+ if pos_dir is None or tracked is None:
109
+ self.remove_overlay_layers()
110
+ return
111
+ data = getattr(tracked, "data", None)
112
+ if data is None or data.ndim < 3:
113
+ return
114
+ anchor_tracks: dict[int, set[int]] = {}
115
+ for correction in read_corrections(pos_dir):
116
+ if correction.kind == "anchor":
117
+ anchor_tracks.setdefault(int(correction.cell_id), set()).add(
118
+ int(correction.t)
119
+ )
120
+ overlay_exists = ANCHOR_OVERLAY in self.viewer.layers
121
+ if not anchor_tracks and not overlay_exists:
122
+ return
123
+ full = self._mask_stack(np.asarray(data), anchor_tracks)
124
+ if overlay_exists:
125
+ self.viewer.layers[ANCHOR_OVERLAY].data = full
126
+ else:
127
+ self.add_anchor_overlay(full)
128
+
129
+ @staticmethod
130
+ def _mask_stack(data: np.ndarray, tracks: dict[int, set[int]]) -> np.ndarray:
131
+ """A ``(T, H, W)`` uint8 mask: 1 where ``cell_id`` lives in its frames."""
132
+ full = np.zeros(data.shape, dtype=np.uint8)
133
+ n_frames = data.shape[0]
134
+ for cell_id, frames in tracks.items():
135
+ for frame in frames:
136
+ frame = int(frame)
137
+ if 0 <= frame < n_frames:
138
+ full[frame][data[frame] == int(cell_id)] = 1
139
+ return full
140
+
141
+ def add_overlay(self, data: np.ndarray) -> None:
142
+ if VALIDATED_OVERLAY in self.viewer.layers:
143
+ layer = self.viewer.layers[VALIDATED_OVERLAY]
144
+ layer.data = data
145
+ layer.colormap = direct_colormap(
146
+ {None: (0, 0, 0, 0), 1: VALIDATED_OVERLAY_COLOR}
147
+ )
148
+ self._apply_overlay_style(layer)
149
+ self._owned_layers.add(VALIDATED_OVERLAY)
150
+ self.place_below_spotlight()
151
+ return
152
+ if LEGACY_VALIDATED_OVERLAY in self.viewer.layers:
153
+ self.viewer.layers.remove(self.viewer.layers[LEGACY_VALIDATED_OVERLAY])
154
+ layer = self.viewer.add_labels(
155
+ data,
156
+ name=VALIDATED_OVERLAY,
157
+ opacity=VALIDATED_OVERLAY_OPACITY,
158
+ colormap=direct_colormap(
159
+ {None: (0, 0, 0, 0), 1: VALIDATED_OVERLAY_COLOR}
160
+ ),
161
+ )
162
+ self._apply_overlay_style(layer)
163
+ self._owned_layers.add(VALIDATED_OVERLAY)
164
+ self.place_below_spotlight()
165
+ tracked = self._tracked_layer_provider()
166
+ if tracked is not None:
167
+ self.viewer.layers.selection.active = tracked
168
+
169
+ def add_anchor_overlay(self, data: np.ndarray) -> None:
170
+ if ANCHOR_OVERLAY in self.viewer.layers:
171
+ layer = self.viewer.layers[ANCHOR_OVERLAY]
172
+ layer.data = data
173
+ layer.colormap = direct_colormap(
174
+ {None: (0, 0, 0, 0), 1: ANCHOR_OVERLAY_COLOR}
175
+ )
176
+ self._apply_overlay_style(layer)
177
+ self._owned_layers.add(ANCHOR_OVERLAY)
178
+ self.place_below_spotlight()
179
+ return
180
+ if LEGACY_ANCHOR_OVERLAY in self.viewer.layers:
181
+ self.viewer.layers.remove(self.viewer.layers[LEGACY_ANCHOR_OVERLAY])
182
+ layer = self.viewer.add_labels(
183
+ data,
184
+ name=ANCHOR_OVERLAY,
185
+ opacity=VALIDATED_OVERLAY_OPACITY,
186
+ colormap=direct_colormap({None: (0, 0, 0, 0), 1: ANCHOR_OVERLAY_COLOR}),
187
+ )
188
+ self._apply_overlay_style(layer)
189
+ self._owned_layers.add(ANCHOR_OVERLAY)
190
+ self.place_below_spotlight()
191
+ tracked = self._tracked_layer_provider()
192
+ if tracked is not None:
193
+ self.viewer.layers.selection.active = tracked
194
+
195
+ def place_below_spotlight(self) -> None:
196
+ if SPOTLIGHT_LAYER not in self.viewer.layers:
197
+ return
198
+ for name in (VALIDATED_OVERLAY, ANCHOR_OVERLAY):
199
+ if name not in self.viewer.layers:
200
+ continue
201
+ overlay_index = self.viewer.layers.index(name)
202
+ spotlight_index = self.viewer.layers.index(SPOTLIGHT_LAYER)
203
+ if overlay_index > spotlight_index:
204
+ self.viewer.layers.move(overlay_index, spotlight_index)
205
+
206
+ def remove_overlay_layers(self) -> None:
207
+ for name in (
208
+ VALIDATED_OVERLAY,
209
+ LEGACY_VALIDATED_OVERLAY,
210
+ ANCHOR_OVERLAY,
211
+ LEGACY_ANCHOR_OVERLAY,
212
+ ):
213
+ if name in self.viewer.layers:
214
+ self.viewer.layers.remove(self.viewer.layers[name])
215
+ self._owned_layers.discard(name)
216
+
217
+ def refresh_counter(self, label) -> None:
218
+ pos_dir = self._pos_dir_provider()
219
+ if pos_dir is None or self._tracked_layer_provider() is None:
220
+ label.setText("")
221
+ return
222
+ validated_tracks = read_validated_tracks(pos_dir)
223
+ n_tracks = len(validated_tracks)
224
+ n_cell_frames = sum(len(frames) for frames in validated_tracks.values())
225
+ label.setText(
226
+ f"{n_tracks} track(s) validated, {n_cell_frames} cell-frame(s) covered"
227
+ )
228
+
229
+ def on_cells_edited(
230
+ self,
231
+ t: int,
232
+ changed_ids: set[int],
233
+ *,
234
+ frame_view_2d: Callable[[np.ndarray, int], np.ndarray | None],
235
+ counter_label,
236
+ ) -> None:
237
+ pos_dir = self._pos_dir_provider()
238
+ if pos_dir is None:
239
+ return
240
+ for cell_id in changed_ids:
241
+ invalidate_track(pos_dir, cell_id)
242
+ self.refresh_overlay(frame_view_2d)
243
+ self.refresh_counter(counter_label)
244
+
245
+ def frames_with_cell(self, cell_id: int) -> list[int]:
246
+ layer = self._tracked_layer_provider()
247
+ if cell_id == 0 or layer is None:
248
+ return []
249
+ data = getattr(layer, "data", None)
250
+ if data is None or data.ndim < 3:
251
+ return []
252
+ spatial_axes = tuple(range(1, data.ndim))
253
+ present = np.any(data == cell_id, axis=spatial_axes)
254
+ return [int(t) for t in np.where(present)[0]]