Skip to content

Commit de4d894

Browse files
Better preservation for committed objects (#967)
Implement different preservation mode for commit and add tooltips
1 parent a71f6c1 commit de4d894

File tree

2 files changed

+52
-12
lines changed

2 files changed

+52
-12
lines changed

micro_sam/sam_annotator/_tooltips.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@
6969
"n_epochs": "Define the number of training epochs for your model.",
7070
"configuration": "Specifiy the hardware configuration to use for training.",
7171
},
72+
"commit": {
73+
"layer": "The layer to commit. Either 'current_object' to commit results from prompt-based segmentation or 'auto_segmentation' to commit results from automatic segmentation.", # noqa
74+
"preserve_mode": "The mode for preserving already committed objects. Either 'objects' to preserve on a per-object level, 'pixels' to preserve on a per-pixel level, or 'none' to not preserve.", # noqa
75+
"commit_path": "The path to a zarr file for saving committed objects, prompts and other segmentation settings.",
76+
}
7277
}
7378

7479

micro_sam/sam_annotator/_widgets.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import z5py
1515
import napari
1616
import numpy as np
17+
import nifty.ground_truth as ngt
1718

1819
import elf.parallel
1920

@@ -358,7 +359,22 @@ def clear_track(viewer: "napari.viewer.Viewer", all_frames: bool = True) -> None
358359
gc.collect()
359360

360361

361-
def _commit_impl(viewer, layer, preserve_committed):
362+
def _mask_matched_objects(seg, prev_seg, preservation_threshold):
363+
prev_ids = np.unique(prev_seg)
364+
ovlp = ngt.overlap(prev_seg, seg)
365+
366+
mask_ids, prev_mask_ids = [], []
367+
for prev_id in prev_ids:
368+
seg_ids, overlaps = ovlp.overlapArrays(prev_id, True)
369+
if seg_ids[0] != 0 and overlaps[0] >= preservation_threshold:
370+
mask_ids.append(seg_ids[0])
371+
prev_mask_ids.append(prev_id)
372+
373+
preserve_mask = np.logical_or(np.isin(seg, mask_ids), np.isin(prev_seg, prev_mask_ids))
374+
return preserve_mask
375+
376+
377+
def _commit_impl(viewer, layer, preserve_mode, preservation_threshold):
362378
# Check if we have a z_range. If yes, use it to set a bounding box.
363379
state = AnnotatorState()
364380
if state.z_range is None:
@@ -373,7 +389,7 @@ def _commit_impl(viewer, layer, preserve_committed):
373389
seg = viewer.layers[layer].data[bb].astype(dtype)
374390
shape = seg.shape
375391

376-
# We parallelize these operatios because they take quite long for large volumes.
392+
# We parallelize these operations because they take quite long for large volumes.
377393

378394
# Compute the max id in the commited objects.
379395
# id_offset = int(viewer.layers["committed_objects"].data.max())
@@ -388,9 +404,16 @@ def _commit_impl(viewer, layer, preserve_committed):
388404
mask = elf.parallel.apply_operation(
389405
seg, 0, np.not_equal, out=mask, block_shape=util.get_block_shape(shape)
390406
)
391-
if preserve_committed:
407+
if preserve_mode != "none":
392408
prev_seg = viewer.layers["committed_objects"].data[bb]
393-
mask[prev_seg != 0] = 0
409+
# The mode 'pixels' corresponds to a naive implementation where only committed pixels are preserved.
410+
if preserve_mode == "pixels":
411+
preserve_mask = prev_seg != 0
412+
# In the other mode 'objects' we preserve committed objects instead, by comparing the overlaps
413+
# of already committed and newly committed objects.
414+
else:
415+
preserve_mask = _mask_matched_objects(seg, prev_seg, preservation_threshold)
416+
mask[preserve_mask] = 0
394417

395418
# Write the current object to committed objects.
396419
seg[mask] += id_offset
@@ -580,13 +603,15 @@ def write_prompts(object_id, prompts, point_prompts, point_labels, track_state=N
580603

581604
@magic_factory(
582605
call_button="Commit [C]",
583-
layer={"choices": ["current_object", "auto_segmentation"]},
584-
commit_path={"mode": "d"}, # choose a directory
606+
layer={"choices": ["current_object", "auto_segmentation"], "tooltip": get_tooltip("commit", "layer")},
607+
preserve_mode={"choices": ["objects", "pixels", "none"], "tooltip": get_tooltip("commit", "preserve_mode")},
608+
commit_path={"mode": "d", "tooltip": get_tooltip("commit", "commit_path")},
585609
)
586610
def commit(
587611
viewer: "napari.viewer.Viewer",
588612
layer: str = "current_object",
589-
preserve_committed: bool = True,
613+
preserve_mode: str = "objects",
614+
preservation_threshold: float = 0.75,
590615
commit_path: Optional[Path] = None,
591616
) -> None:
592617
"""Widget for committing the segmented objects from automatic or interactive segmentation.
@@ -595,11 +620,15 @@ def commit(
595620
viewer: The napari viewer.
596621
layer: Select the layer to commit. Can be either 'current_object' to commit interacitve segmentation results.
597622
Or 'auto_segmentation' to commit automatic segmentation results.
598-
preserve_committed: If active already committted objects are not over-written by new commits.
623+
preserve_mode: The mode for preserving already committed objects, in order to prevent over-writing
624+
them by a new commit. Supports the modes 'objects', which preserves on the object level and is the default,
625+
'pixels', which preserves on the pixel-level, or 'none', which does not preserve commited objects.
626+
preservation_threshold: The overlap threshold for preserving objects. This is only used if
627+
preservation_mode is set to 'objects'.
599628
commit_path: Select a file path where the committed results and prompts will be saved.
600629
This feature is still experimental.
601630
"""
602-
_, seg, mask, bb = _commit_impl(viewer, layer, preserve_committed)
631+
_, seg, mask, bb = _commit_impl(viewer, layer, preserve_mode, preservation_threshold)
603632

604633
if commit_path is not None:
605634
_commit_to_file(commit_path, viewer, layer, seg, mask, bb)
@@ -620,12 +649,14 @@ def commit(
620649
@magic_factory(
621650
call_button="Commit [C]",
622651
layer={"choices": ["current_object", "auto_segmentation"]},
652+
preserve_mode={"choices": ["objects", "pixels", "none"]},
623653
commit_path={"mode": "d"}, # choose a directory
624654
)
625655
def commit_track(
626656
viewer: "napari.viewer.Viewer",
627657
layer: str = "current_object",
628-
preserve_committed: bool = True,
658+
preserve_mode: str = "objects",
659+
preservation_threshold: float = 0.75,
629660
commit_path: Optional[Path] = None,
630661
) -> None:
631662
"""Widget for committing the objects from interactive tracking.
@@ -634,12 +665,16 @@ def commit_track(
634665
viewer: The napari viewer.
635666
layer: Select the layer to commit. Can be either 'current_object' to commit interacitve segmentation results.
636667
Or 'auto_segmentation' to commit automatic segmentation results.
637-
preserve_committed: If active already committted objects are not over-written by new commits.
668+
preserve_mode: The mode for preserving already committed objects, in order to prevent over-writing
669+
them by a new commit. Supports the modes 'objects', which preserves on the object level and is the default,
670+
'pixels', which preserves on the pixel-level, or 'none', which does not preserve commited objects.
671+
preservation_threshold: The overlap threshold for preserving objects. This is only used if
672+
preservation_mode is set to 'objects'.
638673
commit_path: Select a file path where the committed results and prompts will be saved.
639674
This feature is still experimental.
640675
"""
641676
# Commit the segmentation layer.
642-
id_offset, seg, mask, bb = _commit_impl(viewer, layer, preserve_committed)
677+
id_offset, seg, mask, bb = _commit_impl(viewer, layer, preserve_mode, preservation_threshold)
643678

644679
# Update the lineages.
645680
state = AnnotatorState()

0 commit comments

Comments
 (0)