Skip to content

feat: add lock label visibility checkbox for multi-label cases - #25

Open
zapaishchykova wants to merge 3 commits into
mainfrom
fix/persistent-label-visibility
Open

feat: add lock label visibility checkbox for multi-label cases#25
zapaishchykova wants to merge 3 commits into
mainfrom
fix/persistent-label-visibility

Conversation

@zapaishchykova

Copy link
Copy Markdown
Owner

Summary

  • Fixes Multi-label segmentation masks - make the visibility toggle persistent throughout cases #16 — adds a "Lock label visibility across cases" checkbox so users reviewing multi-label masks can hide unwanted labels once and have that setting persist across every subsequent case
  • Fixes the silent no-op: visibility was previously stored by Slicer's auto-generated segment ID (e.g. Segment_1_20240901_123456) which differs between NIfTI files — every get() silently fell back to True (fully visible), making the existing store/restore code a no-op; now keyed by position index which is stable across cases with the same label set
  • Fixes centroid fiducial filter in set_segmentation_and_mask_for_segmentation_editor which had the same segment-ID key bug

Changes

__init__ — new flag:

self.lock_visibility = False

setup() — new checkbox (unchecked by default, so existing behaviour is unchanged):

self.lockVisibilityCheckbox = qt.QCheckBox("Lock label visibility across cases")
self.lockVisibilityCheckbox.stateChanged.connect(lambda state: setattr(self, 'lock_visibility', bool(state)))
self.layout.addWidget(self.lockVisibilityCheckbox)

store_segment_visiblity_states — key by index:

for i, seg_id in enumerate(self.segmentation_node.GetSegmentation().GetSegmentIDs()):
    self.segment_visiblity_states[i] = ...GetSegmentVisibility(seg_id)

restore_segment_visiblity_states — conditional restore:

if not self.lock_visibility:
    return
for i, seg_id in enumerate(...GetSegmentIDs()):
    ...SetSegmentVisibility(seg_id, self.segment_visiblity_states.get(i, True))

onAtlasDirectoryChanged — reset visibility state on directory switch:

self.segment_visiblity_states = {}

Test plan

  • Load a directory with multi-label masks (e.g. 14 labels)
  • Hide all but 2 labels on the first case
  • Checkbox unchecked (default): advance to next case — all 14 labels should reappear
  • Check "Lock label visibility across cases": advance — only the 2 visible labels should remain visible
  • Switch to a different directory — visibility lock should reset (all labels visible again)
  • Single-label masks: no change in behaviour

Adds a "Lock label visibility across cases" checkbox that lets users
persist per-label visibility settings when reviewing multi-label masks.

Previously the restore logic was always a no-op because visibility was
keyed by Slicer's auto-generated segment ID (e.g. "Segment_1_20240901_…")
which differs across NIfTI files, so every get() fell back to True
(fully visible). Visibility is now keyed by position index which is
stable across cases with the same label set.

Also fixes the centroid fiducial filter in
set_segmentation_and_mask_for_segmentation_editor to use the same
index-based lookup.

Closes #16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in UI control to persist segmentation label visibility across multiple reviewed cases, and updates visibility/centroid logic to use a stable per-segment index key rather than Slicer’s per-file auto-generated segment IDs.

Changes:

  • Introduces a “Lock label visibility across cases” checkbox and a lock_visibility flag.
  • Stores/restores segment visibility keyed by segment position index (instead of segment ID) and clears stored state on directory change.
  • Updates centroid jump filtering logic to reference index-based visibility state.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 661 to 665
seg_ids = list(self.segmentation_node.GetSegmentation().GetSegmentIDs())
for segmentId in stats["SegmentIDs"]:
if self.segment_visiblity_states.get(segmentId, True):
idx = seg_ids.index(segmentId) if segmentId in seg_ids else None
if self.segment_visiblity_states.get(idx, True):
centroid_ras = stats[segmentId, "LabelmapSegmentStatisticsPlugin.centroid_ras"]
Comment on lines +661 to +664
seg_ids = list(self.segmentation_node.GetSegmentation().GetSegmentIDs())
for segmentId in stats["SegmentIDs"]:
if self.segment_visiblity_states.get(segmentId, True):
idx = seg_ids.index(segmentId) if segmentId in seg_ids else None
if self.segment_visiblity_states.get(idx, True):
When lock_visibility=False, restore_segment_visiblity_states returns
early so the new case renders with all labels visible. The centroid
filter in set_segmentation_and_mask_for_segmentation_editor was still
reading stale stored states from the previous case, causing centroids
to be hidden for labels that are actually visible.

Skip the stored-state filter entirely when the lock is off so centroid
markers always match actual display state.
Replace the stored-state + index-lookup approach with a direct call to
GetSegmentVisibility on the display node. This fixes both Copilot issues:
- the filter now reads the true display state (already correct after
  restore_segment_visiblity_states runs), so it works regardless of
  whether the lock checkbox is on or off
- eliminates the O(n²) seg_ids.index() loop in favour of a single
  O(1) display-node call per segment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi-label segmentation masks - make the visibility toggle persistent throughout cases

2 participants