Skip to content

Commit 1d222a1

Browse files
authored
Merge pull request #324 from funkelab/322-rendering-of-contour-labels-in-3d
322 rendering of contour labels in 3d
2 parents 78878a2 + 832937f commit 1d222a1

File tree

3 files changed

+91
-21
lines changed

3 files changed

+91
-21
lines changed

src/motile_tracker/data_views/views/layers/track_labels.py

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,7 @@ def __init__(
123123
# Connect click events to node selection
124124
def click(self, _, event):
125125
if (
126-
event.type == "mouse_press"
127-
and self.mode == "pan_zoom"
128-
and not (
129-
self.tracks_viewer.mode == "lineage" and self.viewer.dims.ndisplay == 3
130-
)
126+
event.type == "mouse_press" and self.mode == "pan_zoom"
131127
): # disable selecting in lineage mode in 3D
132128
# differentiate between click and drag
133129
was_click = yield from detect_click(event)
@@ -140,18 +136,37 @@ def assign_new_label(self, event):
140136

141137
new_label(self)
142138

143-
def process_click(self, event: Event, label: int):
144-
"""Process the click event to update the selected nodes"""
139+
def process_click(
140+
self, event: Event, label: int, layer: ContourLabels | None = None
141+
):
142+
"""Process the click event to update the selected nodes.
145143
146-
if (
147-
label is not None and label != 0 and self.colormap.map(label)[-1] != 0
148-
): # check opacity (=visibility) in the colormap
149-
append = "Shift" in event.modifiers
150-
jump = "Control" in event.modifiers
151-
if jump:
152-
self.tracks_viewer.center_on_node(label)
144+
Args:
145+
event (Event): The click event.
146+
label (int): The label value at the clicked position.
147+
layer (ContourLabels | None): The (ortho view) layer from which the click originated.
148+
If provided, it is used to check label visibility in that layer's colormap.
149+
"""
150+
151+
if label is not None and label != 0:
152+
# check visibility in the respective colormap. If a label is not visible, it
153+
# is not allowed to be selected from this view
154+
if layer is not None:
155+
is_visible = layer.colormap.color_dict.get(label)[3] > 0
156+
else:
157+
is_visible = self.colormap.color_dict.get(label)[3] > 0
158+
if is_visible:
159+
append = "Shift" in event.modifiers
160+
jump = "Control" in event.modifiers
161+
if jump:
162+
self.tracks_viewer.center_on_node(label)
163+
else:
164+
self.tracks_viewer.selected_nodes.add(label, append)
153165
else:
154-
self.tracks_viewer.selected_nodes.add(label, append)
166+
warnings.warn(
167+
f"Node {label} is not visible in this view and cannot be selected.",
168+
stacklevel=2,
169+
)
155170

156171
def _get_colormap(self) -> DirectLabelColormap:
157172
"""Get a DirectLabelColormap that maps node ids to their track ids, and then
@@ -315,7 +330,7 @@ def update_label_colormap(self, visible: list[int] | str) -> None:
315330

316331
highlighted = set(self.tracks_viewer.selected_nodes)
317332
foreground = self.colormap.color_dict.keys() if visible == "all" else visible
318-
background = (
333+
self.background = (
319334
[]
320335
if visible == "all"
321336
else self.colormap.color_dict.keys() - visible - highlighted
@@ -328,7 +343,13 @@ def update_label_colormap(self, visible: list[int] | str) -> None:
328343
if not self.foreground_contour:
329344
self.filled_labels.extend(foreground)
330345

331-
self.set_opacity(background, self.background_opacity)
346+
# special case: 3D rendering + partially filled contours -> set background opacity
347+
# to 0
348+
if self._slice.slice_input.ndisplay == 3 and self.contour > 0:
349+
self.set_opacity(self.background, 0)
350+
else:
351+
# set normal background opacity
352+
self.set_opacity(self.background, self.background_opacity)
332353
self.set_opacity(foreground, self.foreground_opacity)
333354
self.set_opacity(highlighted, self.highlight_opacity)
334355
self.refresh_colormap()

src/motile_tracker/data_views/views/layers/track_points.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,20 @@ def add(self, coords: list[float]):
122122
with self.events.current_size.blocker():
123123
super().add(coords)
124124

125-
def process_click(self, event: Event, point_index: int | None):
126-
"""Select the clicked point(s)"""
125+
def process_click(
126+
self,
127+
event: Event,
128+
point_index: int | None,
129+
_layer: napari.layers.Points | None = None,
130+
):
131+
"""Select the clicked point(s)
132+
133+
Args:
134+
event (Event): The mouse event
135+
point_index (int | None): The index of the clicked point, or None if no point
136+
was clicked
137+
_layer (napari.layers.Points | None): Optional, unused. The (ortho view) layer on which the click occurred, which is forwarded by default.
138+
"""
127139

128140
if point_index is None:
129141
self.tracks_viewer.selected_nodes.reset()

src/motile_tracker/data_views/views/ortho_views.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import inspect
23

34
import napari_orthogonal_views.ortho_view_widget as ov_widget
@@ -100,13 +101,14 @@ def get_property_names_from_class(layer_cls):
100101
- {"mode", "size", "current_size"},
101102
},
102103
TrackLabels: {
104+
"forward_exclude": {"colormap"},
103105
"reverse_exclude": set(get_property_names_from_class(Labels))
104106
- {
105107
"mode",
106108
"selected_label",
107109
"n_edit_dimensions",
108110
"brush_size",
109-
} # Let TrackLabels handle these properties on its own because it is listening to
111+
}, # Let TrackLabels handle these properties on its own because it is listening to
110112
# them and we do not want to overwrite through reverse syncing.
111113
},
112114
}
@@ -217,6 +219,40 @@ def paint_wrapper(event: Event):
217219
copied_layer.events.paint.connect(paint_wrapper)
218220

219221

222+
def colormap_hook(orig_layer: TrackLabels, copied_layer: Labels) -> None:
223+
"""Hook to sync colormap changes from the original TrackLabels layer to the copied
224+
layers. We need a hook for the special case in which one of the views is showing a 3D
225+
rendering in combination with partially filled contour labels. Since contours are not
226+
rendered in 3D, we want to display the non-filled labels with full opacity instead.
227+
228+
Args:
229+
orig_layer (TrackLabels): TracksLabels layer from which the copied layer is
230+
derived.
231+
copied_layer (ContourLabels): ContourLabels equivalent of the TracksLabels layer.
232+
"""
233+
234+
def sync_colormap(orig_layer: TrackLabels, copied_layer: Labels, event: Event):
235+
"""Sync the colormap from the original TrackLabels instance to the copied
236+
ContourLabels instance. Check the slice ndisplay and contour settings to adjust
237+
background opacity accordingly."""
238+
239+
copied_layer.colormap = copy.deepcopy(orig_layer.colormap)
240+
if copied_layer._slice.slice_input.ndisplay == 3 and orig_layer.contour > 0:
241+
copied_layer.set_opacity(orig_layer.background, 0)
242+
else:
243+
copied_layer.set_opacity(
244+
orig_layer.background, orig_layer.background_opacity
245+
)
246+
copied_layer.refresh_colormap()
247+
248+
def update_colormap_wrapper(event: Event):
249+
"""Wrap paint event and send to original layer."""
250+
251+
return sync_colormap(orig_layer, copied_layer, event)
252+
253+
orig_layer.events.colormap.connect(update_colormap_wrapper)
254+
255+
220256
def track_layers_hook(
221257
orig_layer: TrackLabels | TrackPoints, copied_layer: Labels | Points
222258
) -> None:
@@ -239,7 +275,7 @@ def click(
239275
was_click = yield from detect_click(event)
240276
if was_click:
241277
value = get_click_value(layer, event)
242-
orig_layer.process_click(event, value)
278+
orig_layer.process_click(event, value, layer)
243279

244280
# Wrap and attach click callback
245281
def click_wrapper(layer, event):
@@ -265,6 +301,7 @@ def initialize_ortho_views(viewer: Viewer) -> OrthoViewManager:
265301
orth_view_manager.register_layer_hook((TrackLabels, TrackPoints), track_layers_hook)
266302
orth_view_manager.register_layer_hook((TrackLabels), paint_event_hook)
267303
orth_view_manager.register_layer_hook((TrackPoints), point_data_hook)
304+
orth_view_manager.register_layer_hook((TrackLabels), colormap_hook)
268305
orth_view_manager.set_sync_filters(sync_filters)
269306
orth_view_manager.activate_checkboxes = True
270307

0 commit comments

Comments
 (0)