-
Notifications
You must be signed in to change notification settings - Fork 10
connect napari-sided object selection in plotter widget #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
connect napari-sided object selection in plotter widget #441
Conversation
for more information, see https://pre-commit.ci
…include SELECTED_DATA_LAYER_CLUSTER_ID
for more information, see https://pre-commit.ci
def _get_selected_objects(self, layer: napari.layers.Layer) -> List[int]: | ||
""" | ||
Retrieve id of selected object on napari canvas" | ||
""" | ||
if isinstance(layer, napari.layers.Points): | ||
return list(layer.selected_data) | ||
elif isinstance(layer, napari.layers.Labels): | ||
return [layer.selected_label] | ||
else: | ||
raise TypeError( | ||
f"Layer type {type(layer)} is not supported for selection." | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't necessarily have to be a member of the PlotterWidget
- none of the functionality relies on self
. Originally, some other functionality in here (e.g., _set_layer_color
) weren't member functions but I think that was lost somewhere along the way.
Still - moving it to _utilities.py
could help to keep this widget a bit cleaner here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I will move it!
def _update_layer_selected_data_feature( | ||
self, layer: napari.layers.Layer | ||
) -> None: | ||
""" | ||
Update the layer selected_data to feature. | ||
""" | ||
selected_data = self._get_selected_objects(layer) | ||
cluster = np.zeros(len(layer.features)) | ||
cluster[list(selected_data)] = 1 | ||
# set categorical to be selectable in "Hue" dropdown | ||
layer.features["SELECTED_DATA_LAYER_CLUSTER_ID"] = pd.Categorical( | ||
cluster | ||
) | ||
self.plot_needs_update.emit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure - does it make sense to iterate over all the layers and collect the selected objects inside this function? That way, you could save yourself the lambda
callback connection above. Somethhing like this:
for layer in self.layers:
selected_data = self._get_selected_objects(layer)
cluster = np.zeros(len(layer.features))
cluster[list(selected_data)] = 1
# set categorical to be selectable in "Hue" dropdown
layer.features["SELECTED_DATA_LAYER_CLUSTER_ID"] = pd.Categorical(cluster)
edit: Avoiding the lambda function would probably also make pre-commit happy :)
Hi @ClementCaporal , just gave it a try, it looks pretty much how I envisioned it to work for selectable objects :)
I was wondering why it was so slow for labels 😅
Maybe we could just mute/block this event while updating the colormap? I just tried to add the following into the elif isinstance(layer, napari.layers.Labels):
from napari.utils import DirectLabelColormap
from ._utilities import _get_unique_values
# Ensure the first color is transparent for the background
colors = np.insert(colors, 0, [0, 0, 0, 0], axis=0)
color_dict = dict(zip(_get_unique_values(layer), colors))
layer.events.selected_label.block() # avoid triggering the infinite loop
layer.colormap = DirectLabelColormap(color_dict=color_dict)
layer.events.selected_label.unblock() # restore functionality for whatever purpose it serves |
Thank you for this workaround! I think I will still open an issue on the napari side because I don't understand why this event is needed
Co-authored-by: Johannes Soltwedel <[email protected]>
Follow-up on #366
Apologies for the delay in getting back to you!
Since I couldn't copy-paste your comments directly, here's a summary of what I’ve addressed based on your feedback:
your _is_selectable
and_get_selected_objects
as private functions and applied them where needed._VIEWPORT_CLUSTER_ID
: I kept the original nameSELECTED_DATA_LAYER_CLUSTER_ID
to stay consistent with the naming used in the napari layer selected_data. I don't have a strong opinion on this, this is just a proposition, I can change it.This PR is not ready yet (and it doesn't pass all the tests) because :
I’m still facing an issue related to selection on the labels layer that I think are out of scope of this PR.
To handle label selection, we need to listen to the
selected_label
event:https://github.com/napari/napari/blob/86e6d27c547f3fad22ddfe8835c4869d3f0931ed/src/napari/layers/labels/labels.py#L382
From there, we update the layer features and emit
self.plot_needs_update
. This eventually leads to updatinglayer.colormap
, which unfortunately triggers theselected_label
event again:https://github.com/napari/napari/blob/86e6d27c547f3fad22ddfe8835c4869d3f0931ed/src/napari/layers/labels/labels.py#L571
This creates an unintended recursive loop.
I think this might be a bug on napari’s side (it's unclear to me why changing the colormap should emit a selected_label event). Unless I’ve missed something, I’m will open an issue on their repo based on your review.
Thanks again for your time and for the excellent plugin!