From ffbff727ce32ac72caef2de2dd4cee757cd69f8f Mon Sep 17 00:00:00 2001 From: Andy Sweet Date: Tue, 9 Jan 2024 16:33:38 -0800 Subject: [PATCH 1/2] Use property choices --- docs/tutorials/annotation/annotate_points.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/tutorials/annotation/annotate_points.md b/docs/tutorials/annotation/annotate_points.md index 49bccf027..3dda9f1c6 100644 --- a/docs/tutorials/annotation/annotate_points.md +++ b/docs/tutorials/annotation/annotate_points.md @@ -99,8 +99,7 @@ def point_annotator( viewer = napari.view_image(stack) points_layer = viewer.add_points( - features={"label": np.array([], dtype=str)}, # we need to set empty features to set defaults - feature_defaults={'label': labels[:1]}, # set the first label as default feature + property_choices={"label": labels}, edge_color='label', edge_color_cycle=COLOR_CYCLE, symbol='o', @@ -209,9 +208,8 @@ As discussed above, we will be storing which feature of interest each point corr To visualize the feature each point represents, we set the edge color as a color cycle mapped to the `label` property (`edge_color='label'`). ```python -properties = {'label': labels} points_layer = viewer.add_points( - properties=properties, + property_choices={'label': labels}, edge_color='label', edge_color_cycle=COLOR_CYCLE, symbol='o', @@ -291,7 +289,7 @@ We then need to connect the dropdown menu (`label_menu`) to the points layer to First, we define a function to update the label dropdown menu GUI when the value of the selected point or next point to be added is changed. On the points layer, the property values of the next point to be added are stored in the `current_properties` property. The points layer has an event that gets emitted when the `current_properties` property is changed (`points_layer.events.current_properties`). -We connect the function we created to the event so that `update_label_menu()` is called whenever `Points.current_property` is changed. +We connect the function we created to the event so that `update_label_menu()` is called whenever `Points.current_properties` is changed. ```python def update_label_menu(event): From 21ee6ffb2c2e97e713a00fe6b92fc72cdab0f418 Mon Sep 17 00:00:00 2001 From: Andy Sweet Date: Thu, 11 Jan 2024 15:18:02 -0800 Subject: [PATCH 2/2] Update based on other fixes --- docs/tutorials/annotation/annotate_points.md | 57 +++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/docs/tutorials/annotation/annotate_points.md b/docs/tutorials/annotation/annotate_points.md index 3dda9f1c6..f2205ed34 100644 --- a/docs/tutorials/annotation/annotate_points.md +++ b/docs/tutorials/annotation/annotate_points.md @@ -24,11 +24,10 @@ We will walk through the code in the following sections. from typing import List from dask_image.imread import imread -import napari from magicgui.widgets import ComboBox, Container +import napari import numpy as np - COLOR_CYCLE = [ '#1f77b4', '#ff7f0e', @@ -46,15 +45,15 @@ COLOR_CYCLE = [ def create_label_menu(points_layer, labels): """Create a label menu widget that can be added to the napari viewer dock - Parameters: - ----------- + Parameters + ---------- points_layer : napari.layers.Points a napari points layer labels : List[str] list of the labels for each keypoint to be annotated (e.g., the body parts to be labeled). - Returns: - -------- + Returns + ------- label_menu : Container the magicgui Container with our dropdown menu widget """ @@ -62,6 +61,7 @@ def create_label_menu(points_layer, labels): label_menu = ComboBox(label='feature_label', choices=labels) label_widget = Container(widgets=[label_menu]) + def update_label_menu(event): """Update the label menu when the point selection changes""" new_label = str(points_layer.current_properties['label'][0]) @@ -99,14 +99,14 @@ def point_annotator( viewer = napari.view_image(stack) points_layer = viewer.add_points( - property_choices={"label": labels}, + ndim=3, + property_choices={'label': labels}, edge_color='label', edge_color_cycle=COLOR_CYCLE, symbol='o', face_color='transparent', edge_width=0.5, # fraction of point size size=12, - ndim=3 ) points_layer.edge_color_mode = 'cycle' @@ -129,12 +129,13 @@ def point_annotator( def next_on_click(layer, event): """Mouse click binding to advance the label when a point is added""" if layer.mode == 'add': + # By default, napari selects the point that was just added. + # Disable that behavior, as the highlight gets in the way + # and also causes next_label to change the color of the + # point that was just added. + layer.selected_data = set() next_label() - # by default, napari selects the point that was just added - # disable that behavior, as the highlight gets in the way - layer.selected_data = {} - points_layer.mode = 'add' points_layer.mouse_drag_callbacks.append(next_on_click) @@ -150,6 +151,8 @@ def point_annotator( current_properties['label'] = np.array([new_label]) points_layer.current_properties = current_properties points_layer.refresh_colors() + + napari.run() ``` ## `point_annotator()` @@ -160,8 +163,8 @@ See below for the function definition. ```python def point_annotator( - im_path: str, - labels: List[str], + im_path: str, + labels: List[str], ): """Create a GUI for annotating points in a series of images. @@ -209,14 +212,14 @@ To visualize the feature each point represents, we set the edge color as a color ```python points_layer = viewer.add_points( + ndim=3, property_choices={'label': labels}, edge_color='label', edge_color_cycle=COLOR_CYCLE, symbol='o', face_color='transparent', - edge_width=8, + edge_width=0.5, # fraction of point size size=12, - ndim=3 ) ``` @@ -260,14 +263,14 @@ GUI interactive. def create_label_menu(points_layer, labels): """Create a label menu widget that can be added to the napari viewer dock - Parameters: + Parameters ----------- points_layer : napari.layers.Points a napari points layer labels : List[str] list of the labels for each keypoint to be annotated (e.g., the body parts to be labeled). - Returns: + Returns -------- label_menu : Container the magicgui Container with our dropdown menu widget @@ -306,12 +309,12 @@ Similar to the points layer, the magicgui object has an event that gets emitted To ensure the points layer is updated whenever the GUI selection is changed, we connect `label_changed()` to the `label_menu.changed` event. ```python -def label_changed(event): +def label_changed(new_label): """Update the Points layer when the label menu selection changes""" - selected_label = event.value current_properties = points_layer.current_properties - current_properties['label'] = np.asarray([selected_label]) + current_properties['label'] = np.asarray([new_label]) points_layer.current_properties = current_properties + points_layer.refresh_colors() label_menu.changed.connect(label_changed) ``` @@ -351,6 +354,7 @@ def next_label(event=None): new_label = labels[new_ind] current_properties['label'] = np.array([new_label]) points_layer.current_properties = current_properties + points_layer.refresh_colors() ``` We can do the same with another function that instead decrements the label with wraparound. @@ -367,6 +371,7 @@ def prev_label(event): new_label = labels[new_ind] current_properties['label'] = np.array([new_label]) points_layer.current_properties = current_properties + points_layer.refresh_colors() ``` ## Mousebinding to iterate through labels @@ -381,13 +386,13 @@ Finally, ```python def next_on_click(layer, event): """Mouse click binding to advance the label when a point is added""" - # only do something if we are adding points if layer.mode == 'add': + # By default, napari selects the point that was just added. + # Disable that behavior, as the highlight gets in the way + # and also causes next_label to change the color of the + # point that was just added. + layer.selected_data = set() next_label() - - # by default, napari selects the point that was just added - # disable that behavior, as the highlight gets in the way - layer.selected_data = [] ``` After creating the function, we then add it to the `points_layer` mouse drag callbacks.