micro_sam.sam_annotator.annotator_tracking

  1from typing import Optional, Tuple, Union, List
  2
  3import numpy as np
  4
  5import torch
  6
  7import napari
  8from magicgui.widgets import ComboBox, Container
  9
 10from .. import util
 11from . import util as vutil
 12from . import _widgets as widgets
 13from ._tooltips import get_tooltip
 14from ._state import AnnotatorState
 15from ._annotator import _AnnotatorBase
 16
 17
 18# Cyan (track) and Magenta (division)
 19STATE_COLOR_CYCLE = ["#00FFFF", "#FF00FF", ]
 20"""@private"""
 21
 22
 23# This solution is a bit hacky, so I won't move it to _widgets.py yet.
 24def create_tracking_menu(points_layer, box_layer, states, track_ids, tracking_widget=None):
 25    """@private"""
 26    state = AnnotatorState()
 27
 28    def _get_widget_menu(container, label):
 29        for w in container:
 30            if isinstance(w, ComboBox) and w.label == label:
 31                return w
 32        raise ValueError(f"ComboBox with label '{label}' not found.")
 33
 34    if tracking_widget is None:
 35        state_menu = ComboBox(
 36            label="track_state", choices=states, tooltip=get_tooltip("annotator_tracking", "track_state")
 37        )
 38        track_id_menu = ComboBox(
 39            label="track_id", choices=list(map(str, track_ids)), tooltip=get_tooltip("annotator_tracking", "track_id")
 40        )
 41        tracking_widget = Container(widgets=[state_menu, track_id_menu])
 42    else:
 43        state_menu = _get_widget_menu(tracking_widget, "track_state")
 44        track_id_menu = _get_widget_menu(tracking_widget, "track_id")
 45
 46    def update_state(event):
 47        if "state" in points_layer.current_properties:
 48            new_state = str(points_layer.current_properties["state"][0])
 49            if new_state != state_menu.value:
 50                state_menu.value = new_state
 51
 52    def update_track_id(event):
 53        if "track_id" in points_layer.current_properties:
 54            new_id = str(points_layer.current_properties["track_id"][0])
 55            if new_id != track_id_menu.value:
 56                track_id_menu.value = new_id
 57                state.current_track_id = int(new_id)
 58
 59    # def update_state_boxes(event):
 60    #     new_state = str(box_layer.current_properties["state"][0])
 61    #     if new_state != state_menu.value:
 62    #         state_menu.value = new_state
 63
 64    # napari 0.9 no longer syncs 'current_properties' with the selection on shape layers (napari/napari#9221).
 65    def update_track_id_boxes(*args):
 66        if "track_id" not in box_layer.properties:
 67            return
 68        selected = list(box_layer.selected_data)
 69        track_ids = set(box_layer.properties["track_id"][selected])
 70        if len(track_ids) != 1:  # A mixed selection would relabel every selected box.
 71            return
 72        new_id = str(track_ids.pop())
 73        if new_id != track_id_menu.value:
 74            track_id_menu.value = new_id
 75            state.current_track_id = int(new_id)
 76
 77    points_layer.events.current_properties.connect(update_state)
 78    points_layer.events.current_properties.connect(update_track_id)
 79    # box_layer.events.current_properties.connect(update_state_boxes)
 80    box_layer.selected_data.events.items_changed.connect(update_track_id_boxes)
 81
 82    def state_changed(new_state):
 83        current_properties = points_layer.current_properties
 84        current_properties["state"] = np.array([new_state])
 85        points_layer.current_properties = current_properties
 86        points_layer.refresh_colors()
 87
 88    def track_id_changed(new_track_id):
 89        current_properties = points_layer.current_properties
 90        current_properties["track_id"] = np.array([new_track_id])
 91        # Note: this fails with a key error after committing a lineage with multiple tracks.
 92        # I think this does not cause any further errors, so we just skip this.
 93        try:
 94            points_layer.current_properties = current_properties
 95        except KeyError:
 96            pass
 97        state.current_track_id = int(new_track_id)
 98
 99    # def state_changed_boxes(new_state):
100    #     current_properties = box_layer.current_properties
101    #     current_properties["state"] = np.array([new_state])
102    #     box_layer.current_properties = current_properties
103    #     box_layer.refresh_colors()
104
105    def track_id_changed_boxes(new_track_id):
106        current_properties = box_layer.current_properties
107        current_properties["track_id"] = np.array([new_track_id])
108        box_layer.current_properties = current_properties
109        state.current_track_id = int(new_track_id)
110
111    state_menu.changed.connect(state_changed)
112    track_id_menu.changed.connect(track_id_changed)
113    # state_menu.changed.connect(state_changed_boxes)
114    track_id_menu.changed.connect(track_id_changed_boxes)
115
116    state_menu.set_choice("track")
117    return tracking_widget
118
119
120class AnnotatorTracking(_AnnotatorBase):
121
122    # The tracking annotator needs different settings for the prompt layers
123    # to support the additional tracking state.
124    # That's why we over-ride this function.
125    def _require_layers(self, layer_choices: Optional[List[str]] = None):
126
127        # Check whether the image is initialized already. And use the image shape and scale for the layers.
128        state = AnnotatorState()
129        shape = self._shape if state.image_shape is None else state.image_shape
130
131        # Add the label layers for the current object, the automatic segmentation and the committed segmentation.
132        dummy_data = np.zeros(shape, dtype="uint32")
133        image_scale = state.image_scale
134
135        # Before adding new layers, we always check whether a layer with this name already exists or not.
136        if "current_object" not in self._viewer.layers:
137            if layer_choices and "current_object" in layer_choices:  # Check at 'commit' call button.
138                widgets._validation_window_for_missing_layer("current_object")
139            self._viewer.add_labels(data=dummy_data, name="current_object")
140            if image_scale is not None:
141                self._viewer.layers["current_object"].scale = image_scale
142
143        if "auto_segmentation" not in self._viewer.layers:
144            if layer_choices and "auto_segmentation" in layer_choices:  # Check at 'commit' call button.
145                widgets._validation_window_for_missing_layer("auto_segmentation")
146            self._viewer.add_labels(data=dummy_data, name="auto_segmentation")
147            if image_scale is not None:
148                self._viewer.layers["auto_segmentation"].scale = image_scale
149
150        if "committed_objects" not in self._viewer.layers:
151            if layer_choices and "committed_objects" in layer_choices:  # Check at 'commit' call button.
152                widgets._validation_window_for_missing_layer("committed_objects")
153            self._viewer.add_labels(data=dummy_data, name="committed_objects")
154            # Randomize colors so it is easy to see when object committed.
155            self._viewer.layers["committed_objects"].new_colormap()
156            if image_scale is not None:
157                self._viewer.layers["committed_objects"].scale = image_scale
158
159        # Add the point prompts layer.
160        self._point_labels = ["positive", "negative"]
161        self._track_state_labels = ["track", "division"]
162        _point_prompt_property_choices = {
163            "label": self._point_labels,
164            "state": self._track_state_labels,
165            "track_id": ["1"],  # we use string to avoid pandas warning
166        }
167
168        point_layer_mismatch = True
169        if "point_prompts" in self._viewer.layers:
170            # Check whether the 'property_choices' match or not.
171            curr_property_choices = self._viewer.layers["point_prompts"].property_choices
172            point_layer_mismatch = set(curr_property_choices.keys()) != set(_point_prompt_property_choices.keys())
173
174        if point_layer_mismatch and "point_prompts" not in self._viewer.layers:
175            self._point_prompt_layer = self._viewer.add_points(
176                name="point_prompts",
177                property_choices=_point_prompt_property_choices,
178                border_color="label",
179                border_color_cycle=vutil.LABEL_COLOR_CYCLE,
180                symbol="o",
181                face_color="state",
182                face_color_cycle=STATE_COLOR_CYCLE,
183                border_width=0.4,
184                size=12,
185                ndim=self._ndim,
186            )
187            self._point_prompt_layer.border_color_mode = "cycle"
188            self._point_prompt_layer.face_color_mode = "cycle"
189            _new_point_layer = True
190        else:
191            self._point_prompt_layer = self._viewer.layers["point_prompts"]
192            _new_point_layer = False
193
194        # Add the point prompts layer.
195        _box_prompt_property_choices = {"track_id": ["1"]}
196
197        box_layer_mismatch = True
198        if "prompts" in self._viewer.layers:
199            # Check whether the 'property_choices' match or not.
200            curr_property_choices = self._viewer.layers["prompts"].property_choices
201            box_layer_mismatch = set(curr_property_choices.keys()) != set(_box_prompt_property_choices.keys())
202
203        if box_layer_mismatch and "prompts" not in self._viewer.layers:
204            # Using the box layer to set divisions currently doesn't work.
205            # That's why some of the code below is commented out.
206            self._box_prompt_layer = self._viewer.add_shapes(
207                shape_type="rectangle",
208                edge_width=4,
209                ndim=self._ndim,
210                face_color="transparent",
211                name="prompts",
212                edge_color="green",
213                property_choices=_box_prompt_property_choices,
214                # property_choices={"track_id": ["1"], "state": self._track_state_labels},
215                # edge_color_cycle=STATE_COLOR_CYCLE,
216            )
217            # self._box_prompt_layer.edge_color_mode = "cycle"
218            _new_box_layer = True
219        else:
220            self._box_prompt_layer = self._viewer.layers["prompts"]
221            _new_box_layer = False
222
223        # Trigger a new connection for the tracking state menu only when a new layer is (re)created.
224        if _new_point_layer or _new_box_layer:
225            self._tracking_widget = create_tracking_menu(
226                points_layer=self._point_prompt_layer,
227                box_layer=self._box_prompt_layer,
228                states=self._track_state_labels,
229                track_ids=list(state.lineage.keys()),
230                tracking_widget=state.widgets.get("tracking"),
231            )
232            state.widgets["tracking"] = self._tracking_widget
233
234    def _get_widgets(self):
235        state = AnnotatorState()
236        self._require_layers()
237
238        # Create the tracking state menu.
239        # NOTE: Check whether it exists already from `_require_layers` or needs to be created.
240        if state.widgets.get("tracking") is None:
241            self._tracking_widget = create_tracking_menu(
242                points_layer=self._point_prompt_layer,
243                box_layer=self._box_prompt_layer,
244                states=self._track_state_labels,
245                track_ids=list(state.lineage.keys()),
246            )
247        else:
248            self._tracking_widget = state.widgets.get("tracking")
249
250        segment_nd = widgets.SegmentNDWidget(self._viewer, tracking=True)
251        autotrack = widgets.AutoTrackWidget(self._viewer, with_decoder=self._with_decoder, volumetric=True)
252        return {
253            "tracking": self._tracking_widget,
254            "segment": widgets.segment_frame(),
255            "segment_nd": segment_nd,
256            "autosegment": autotrack,
257            "commit": widgets.commit_track(),
258            "clear": widgets.clear_track(),
259        }
260
261    def __init__(self, viewer: "napari.viewer.Viewer", reset_state: bool = True) -> None:
262        # Initialize the state for tracking.
263        self._init_track_state()
264        self._with_decoder = AnnotatorState().decoder is not None
265        super().__init__(viewer=viewer, ndim=3)
266        # Go to t=0.
267        self._viewer.dims.current_step = (0, 0, 0) + tuple(sh // 2 for sh in self._shape[1:])
268
269        # Set the expected annotator class to the state.
270        state = AnnotatorState()
271
272        # Reset the state.
273        if reset_state:
274            state.reset_state()
275
276        state.annotator = self
277
278    def _init_track_state(self):
279        state = AnnotatorState()
280        state.current_track_id = 1
281        state.lineage = {1: []}
282        state.committed_lineages = []
283
284    def _update_image(self):
285        super()._update_image()
286        self._init_track_state()
287        state = AnnotatorState()
288        if self._with_decoder:
289            state.amg_state = vutil._load_is_state(state.embedding_path)
290        else:
291            state.amg_state = vutil._load_amg_state(state.embedding_path)
292
293
294def annotator_tracking(
295    image: np.ndarray,
296    embedding_path: Optional[str] = None,
297    # tracking_result: Optional[str] = None,
298    model_type: str = util._DEFAULT_MODEL,
299    tile_shape: Optional[Tuple[int, int]] = None,
300    halo: Optional[Tuple[int, int]] = None,
301    return_viewer: bool = False,
302    viewer: Optional["napari.viewer.Viewer"] = None,
303    precompute_amg_state: bool = False,
304    checkpoint_path: Optional[str] = None,
305    decoder_path: Optional[str] = None,
306    device: Optional[Union[str, torch.device]] = None,
307) -> Optional["napari.viewer.Viewer"]:
308    """Start the tracking annotation tool fora given timeseries.
309
310    Args:
311        image: The image data.
312        embedding_path: Filepath for saving the precomputed embeddings.
313        model_type: The Segment Anything model to use. For details on the available models check out
314            https://computational-cell-analytics.github.io/micro-sam/micro_sam.html#finetuned-models.
315        tile_shape: Shape of tiles for tiled embedding prediction.
316            If `None` then the whole image is passed to Segment Anything.
317        halo: Shape of the overlap between tiles, which is needed to segment objects on tile borders.
318        return_viewer: Whether to return the napari viewer to further modify it before starting the tool.
319            By default, does not return the napari viewer.
320        viewer: The viewer to which the Segment Anything functionality should be added.
321            This enables using a pre-initialized viewer.
322        precompute_amg_state: Whether to precompute the state for automatic mask generation.
323            This will take more time when precomputing embeddings, but will then make
324            automatic mask generation much faster. By default, set to 'False'.
325        checkpoint_path: Path to a custom checkpoint from which to load the SAM model.
326        decoder_path: Path to a custom decoder checkpoint from which to load the 'micro-sam` decoder.
327        device: The computational device to use for the SAM model.
328            By default, automatically chooses the best available device.
329
330    Returns:
331        The napari viewer, only returned if `return_viewer=True`.
332    """
333
334    # Initialize the predictor state.
335    state = AnnotatorState()
336    state.initialize_predictor(
337        image, model_type=model_type, save_path=embedding_path,
338        halo=halo, tile_shape=tile_shape, prefer_decoder=True,
339        ndim=3, checkpoint_path=checkpoint_path, device=device,
340        precompute_amg_state=precompute_amg_state, use_cli=True,
341        decoder_path=decoder_path,
342    )
343    state.image_shape = image.shape[:-1] if image.ndim == 4 else image.shape
344
345    if viewer is None:
346        viewer = napari.Viewer()
347
348    viewer.add_image(image, name="image")
349    annotator = AnnotatorTracking(viewer, reset_state=False)
350
351    # Trigger layer update of the annotator so that layers have the correct shape.
352    annotator._update_image()
353
354    # Add the annotator widget to the viewer and sync widgets.
355    viewer.window.add_dock_widget(annotator)
356    vutil._sync_embedding_widget(
357        widget=state.widgets["embeddings"],
358        model_type=model_type if checkpoint_path is None else state.predictor.model_type,
359        save_path=embedding_path,
360        checkpoint_path=checkpoint_path,
361        device=device,
362        tile_shape=tile_shape,
363        halo=halo,
364    )
365
366    if return_viewer:
367        return viewer
368
369    napari.run()
370
371
372def main():
373    """@private"""
374    parser = vutil._initialize_parser(
375        description="Run interactive segmentation for an image volume.",
376        with_segmentation_result=False,
377        with_instance_segmentation=False,
378    )
379
380    # Tracking result is not yet supported, we need to also deserialize the lineage.
381    # parser.add_argument(
382    #     "-t", "--tracking_result",
383    #     help="Optional filepath to a precomputed tracking result. If passed this will be used to initialize the "
384    #     "'committed_tracks' layer. This can be useful if you want to correct an existing tracking result or if you "
385    #     "have saved intermediate results from the annotator and want to continue. "
386    #     "Supports the same file formats as 'input'."
387    # )
388    # parser.add_argument(
389    #     "-tk", "--tracking_key",
390    #     help="The key for opening the tracking result. Same rules as for 'key' apply."
391    # )
392
393    args = parser.parse_args()
394    image = util.load_image_data(args.input, key=args.key)
395
396    annotator_tracking(
397        image, embedding_path=args.embedding_path, model_type=args.model_type,
398        tile_shape=args.tile_shape, halo=args.halo,
399        checkpoint_path=args.checkpoint, decoder_path=args.decoder_path, device=args.device,
400    )
class AnnotatorTracking(micro_sam.sam_annotator._annotator._AnnotatorBase):
121class AnnotatorTracking(_AnnotatorBase):
122
123    # The tracking annotator needs different settings for the prompt layers
124    # to support the additional tracking state.
125    # That's why we over-ride this function.
126    def _require_layers(self, layer_choices: Optional[List[str]] = None):
127
128        # Check whether the image is initialized already. And use the image shape and scale for the layers.
129        state = AnnotatorState()
130        shape = self._shape if state.image_shape is None else state.image_shape
131
132        # Add the label layers for the current object, the automatic segmentation and the committed segmentation.
133        dummy_data = np.zeros(shape, dtype="uint32")
134        image_scale = state.image_scale
135
136        # Before adding new layers, we always check whether a layer with this name already exists or not.
137        if "current_object" not in self._viewer.layers:
138            if layer_choices and "current_object" in layer_choices:  # Check at 'commit' call button.
139                widgets._validation_window_for_missing_layer("current_object")
140            self._viewer.add_labels(data=dummy_data, name="current_object")
141            if image_scale is not None:
142                self._viewer.layers["current_object"].scale = image_scale
143
144        if "auto_segmentation" not in self._viewer.layers:
145            if layer_choices and "auto_segmentation" in layer_choices:  # Check at 'commit' call button.
146                widgets._validation_window_for_missing_layer("auto_segmentation")
147            self._viewer.add_labels(data=dummy_data, name="auto_segmentation")
148            if image_scale is not None:
149                self._viewer.layers["auto_segmentation"].scale = image_scale
150
151        if "committed_objects" not in self._viewer.layers:
152            if layer_choices and "committed_objects" in layer_choices:  # Check at 'commit' call button.
153                widgets._validation_window_for_missing_layer("committed_objects")
154            self._viewer.add_labels(data=dummy_data, name="committed_objects")
155            # Randomize colors so it is easy to see when object committed.
156            self._viewer.layers["committed_objects"].new_colormap()
157            if image_scale is not None:
158                self._viewer.layers["committed_objects"].scale = image_scale
159
160        # Add the point prompts layer.
161        self._point_labels = ["positive", "negative"]
162        self._track_state_labels = ["track", "division"]
163        _point_prompt_property_choices = {
164            "label": self._point_labels,
165            "state": self._track_state_labels,
166            "track_id": ["1"],  # we use string to avoid pandas warning
167        }
168
169        point_layer_mismatch = True
170        if "point_prompts" in self._viewer.layers:
171            # Check whether the 'property_choices' match or not.
172            curr_property_choices = self._viewer.layers["point_prompts"].property_choices
173            point_layer_mismatch = set(curr_property_choices.keys()) != set(_point_prompt_property_choices.keys())
174
175        if point_layer_mismatch and "point_prompts" not in self._viewer.layers:
176            self._point_prompt_layer = self._viewer.add_points(
177                name="point_prompts",
178                property_choices=_point_prompt_property_choices,
179                border_color="label",
180                border_color_cycle=vutil.LABEL_COLOR_CYCLE,
181                symbol="o",
182                face_color="state",
183                face_color_cycle=STATE_COLOR_CYCLE,
184                border_width=0.4,
185                size=12,
186                ndim=self._ndim,
187            )
188            self._point_prompt_layer.border_color_mode = "cycle"
189            self._point_prompt_layer.face_color_mode = "cycle"
190            _new_point_layer = True
191        else:
192            self._point_prompt_layer = self._viewer.layers["point_prompts"]
193            _new_point_layer = False
194
195        # Add the point prompts layer.
196        _box_prompt_property_choices = {"track_id": ["1"]}
197
198        box_layer_mismatch = True
199        if "prompts" in self._viewer.layers:
200            # Check whether the 'property_choices' match or not.
201            curr_property_choices = self._viewer.layers["prompts"].property_choices
202            box_layer_mismatch = set(curr_property_choices.keys()) != set(_box_prompt_property_choices.keys())
203
204        if box_layer_mismatch and "prompts" not in self._viewer.layers:
205            # Using the box layer to set divisions currently doesn't work.
206            # That's why some of the code below is commented out.
207            self._box_prompt_layer = self._viewer.add_shapes(
208                shape_type="rectangle",
209                edge_width=4,
210                ndim=self._ndim,
211                face_color="transparent",
212                name="prompts",
213                edge_color="green",
214                property_choices=_box_prompt_property_choices,
215                # property_choices={"track_id": ["1"], "state": self._track_state_labels},
216                # edge_color_cycle=STATE_COLOR_CYCLE,
217            )
218            # self._box_prompt_layer.edge_color_mode = "cycle"
219            _new_box_layer = True
220        else:
221            self._box_prompt_layer = self._viewer.layers["prompts"]
222            _new_box_layer = False
223
224        # Trigger a new connection for the tracking state menu only when a new layer is (re)created.
225        if _new_point_layer or _new_box_layer:
226            self._tracking_widget = create_tracking_menu(
227                points_layer=self._point_prompt_layer,
228                box_layer=self._box_prompt_layer,
229                states=self._track_state_labels,
230                track_ids=list(state.lineage.keys()),
231                tracking_widget=state.widgets.get("tracking"),
232            )
233            state.widgets["tracking"] = self._tracking_widget
234
235    def _get_widgets(self):
236        state = AnnotatorState()
237        self._require_layers()
238
239        # Create the tracking state menu.
240        # NOTE: Check whether it exists already from `_require_layers` or needs to be created.
241        if state.widgets.get("tracking") is None:
242            self._tracking_widget = create_tracking_menu(
243                points_layer=self._point_prompt_layer,
244                box_layer=self._box_prompt_layer,
245                states=self._track_state_labels,
246                track_ids=list(state.lineage.keys()),
247            )
248        else:
249            self._tracking_widget = state.widgets.get("tracking")
250
251        segment_nd = widgets.SegmentNDWidget(self._viewer, tracking=True)
252        autotrack = widgets.AutoTrackWidget(self._viewer, with_decoder=self._with_decoder, volumetric=True)
253        return {
254            "tracking": self._tracking_widget,
255            "segment": widgets.segment_frame(),
256            "segment_nd": segment_nd,
257            "autosegment": autotrack,
258            "commit": widgets.commit_track(),
259            "clear": widgets.clear_track(),
260        }
261
262    def __init__(self, viewer: "napari.viewer.Viewer", reset_state: bool = True) -> None:
263        # Initialize the state for tracking.
264        self._init_track_state()
265        self._with_decoder = AnnotatorState().decoder is not None
266        super().__init__(viewer=viewer, ndim=3)
267        # Go to t=0.
268        self._viewer.dims.current_step = (0, 0, 0) + tuple(sh // 2 for sh in self._shape[1:])
269
270        # Set the expected annotator class to the state.
271        state = AnnotatorState()
272
273        # Reset the state.
274        if reset_state:
275            state.reset_state()
276
277        state.annotator = self
278
279    def _init_track_state(self):
280        state = AnnotatorState()
281        state.current_track_id = 1
282        state.lineage = {1: []}
283        state.committed_lineages = []
284
285    def _update_image(self):
286        super()._update_image()
287        self._init_track_state()
288        state = AnnotatorState()
289        if self._with_decoder:
290            state.amg_state = vutil._load_is_state(state.embedding_path)
291        else:
292            state.amg_state = vutil._load_amg_state(state.embedding_path)

Base class for micro_sam annotation plugins.

Implements the logic for the 2d, 3d and tracking annotator. The annotators differ in their data dimensionality and the widgets.

AnnotatorTracking(viewer: napari.viewer.Viewer, reset_state: bool = True)
262    def __init__(self, viewer: "napari.viewer.Viewer", reset_state: bool = True) -> None:
263        # Initialize the state for tracking.
264        self._init_track_state()
265        self._with_decoder = AnnotatorState().decoder is not None
266        super().__init__(viewer=viewer, ndim=3)
267        # Go to t=0.
268        self._viewer.dims.current_step = (0, 0, 0) + tuple(sh // 2 for sh in self._shape[1:])
269
270        # Set the expected annotator class to the state.
271        state = AnnotatorState()
272
273        # Reset the state.
274        if reset_state:
275            state.reset_state()
276
277        state.annotator = self

Create the annotator GUI.

Arguments:
  • viewer: The napari viewer.
  • ndim: The number of spatial dimension of the image data (2 or 3).
def annotator_tracking( image: numpy.ndarray, embedding_path: Optional[str] = None, model_type: str = 'vit_b_lm', tile_shape: Optional[Tuple[int, int]] = None, halo: Optional[Tuple[int, int]] = None, return_viewer: bool = False, viewer: Optional[napari.viewer.Viewer] = None, precompute_amg_state: bool = False, checkpoint_path: Optional[str] = None, decoder_path: Optional[str] = None, device: Union[str, torch.device, NoneType] = None) -> Optional[napari.viewer.Viewer]:
295def annotator_tracking(
296    image: np.ndarray,
297    embedding_path: Optional[str] = None,
298    # tracking_result: Optional[str] = None,
299    model_type: str = util._DEFAULT_MODEL,
300    tile_shape: Optional[Tuple[int, int]] = None,
301    halo: Optional[Tuple[int, int]] = None,
302    return_viewer: bool = False,
303    viewer: Optional["napari.viewer.Viewer"] = None,
304    precompute_amg_state: bool = False,
305    checkpoint_path: Optional[str] = None,
306    decoder_path: Optional[str] = None,
307    device: Optional[Union[str, torch.device]] = None,
308) -> Optional["napari.viewer.Viewer"]:
309    """Start the tracking annotation tool fora given timeseries.
310
311    Args:
312        image: The image data.
313        embedding_path: Filepath for saving the precomputed embeddings.
314        model_type: The Segment Anything model to use. For details on the available models check out
315            https://computational-cell-analytics.github.io/micro-sam/micro_sam.html#finetuned-models.
316        tile_shape: Shape of tiles for tiled embedding prediction.
317            If `None` then the whole image is passed to Segment Anything.
318        halo: Shape of the overlap between tiles, which is needed to segment objects on tile borders.
319        return_viewer: Whether to return the napari viewer to further modify it before starting the tool.
320            By default, does not return the napari viewer.
321        viewer: The viewer to which the Segment Anything functionality should be added.
322            This enables using a pre-initialized viewer.
323        precompute_amg_state: Whether to precompute the state for automatic mask generation.
324            This will take more time when precomputing embeddings, but will then make
325            automatic mask generation much faster. By default, set to 'False'.
326        checkpoint_path: Path to a custom checkpoint from which to load the SAM model.
327        decoder_path: Path to a custom decoder checkpoint from which to load the 'micro-sam` decoder.
328        device: The computational device to use for the SAM model.
329            By default, automatically chooses the best available device.
330
331    Returns:
332        The napari viewer, only returned if `return_viewer=True`.
333    """
334
335    # Initialize the predictor state.
336    state = AnnotatorState()
337    state.initialize_predictor(
338        image, model_type=model_type, save_path=embedding_path,
339        halo=halo, tile_shape=tile_shape, prefer_decoder=True,
340        ndim=3, checkpoint_path=checkpoint_path, device=device,
341        precompute_amg_state=precompute_amg_state, use_cli=True,
342        decoder_path=decoder_path,
343    )
344    state.image_shape = image.shape[:-1] if image.ndim == 4 else image.shape
345
346    if viewer is None:
347        viewer = napari.Viewer()
348
349    viewer.add_image(image, name="image")
350    annotator = AnnotatorTracking(viewer, reset_state=False)
351
352    # Trigger layer update of the annotator so that layers have the correct shape.
353    annotator._update_image()
354
355    # Add the annotator widget to the viewer and sync widgets.
356    viewer.window.add_dock_widget(annotator)
357    vutil._sync_embedding_widget(
358        widget=state.widgets["embeddings"],
359        model_type=model_type if checkpoint_path is None else state.predictor.model_type,
360        save_path=embedding_path,
361        checkpoint_path=checkpoint_path,
362        device=device,
363        tile_shape=tile_shape,
364        halo=halo,
365    )
366
367    if return_viewer:
368        return viewer
369
370    napari.run()

Start the tracking annotation tool fora given timeseries.

Arguments:
  • image: The image data.
  • embedding_path: Filepath for saving the precomputed embeddings.
  • model_type: The Segment Anything model to use. For details on the available models check out https://computational-cell-analytics.github.io/micro-sam/micro_sam.html#finetuned-models.
  • tile_shape: Shape of tiles for tiled embedding prediction. If None then the whole image is passed to Segment Anything.
  • halo: Shape of the overlap between tiles, which is needed to segment objects on tile borders.
  • return_viewer: Whether to return the napari viewer to further modify it before starting the tool. By default, does not return the napari viewer.
  • viewer: The viewer to which the Segment Anything functionality should be added. This enables using a pre-initialized viewer.
  • precompute_amg_state: Whether to precompute the state for automatic mask generation. This will take more time when precomputing embeddings, but will then make automatic mask generation much faster. By default, set to 'False'.
  • checkpoint_path: Path to a custom checkpoint from which to load the SAM model.
  • decoder_path: Path to a custom decoder checkpoint from which to load the 'micro-sam` decoder.
  • device: The computational device to use for the SAM model. By default, automatically chooses the best available device.
Returns:

The napari viewer, only returned if return_viewer=True.