Note
Go to the end to download the full example code
bbox annotator#
import numpy as np
import pandas as pd
from magicgui.widgets import ComboBox, Container
from skimage import data
import napari
# set up the categorical annotation values and text display properties
box_annotations = ['person', 'sky', 'camera']
text_feature = 'box_label'
features = pd.DataFrame({
text_feature: pd.Series([], dtype=pd.CategoricalDtype(box_annotations))
})
text_color = 'green'
text_size = 20
# create the GUI for selecting the values
def create_label_menu(shapes_layer, label_feature, labels):
"""Create a label menu widget that can be added to the napari viewer dock
Parameters
----------
shapes_layer : napari.layers.Shapes
a napari shapes layer
label_feature : str
the name of the shapes feature to use the displayed text
labels : List[str]
list of the possible text labels values.
Returns
-------
label_widget : magicgui.widgets.Container
the container widget with the label combobox
"""
# Create the label selection menu
label_menu = ComboBox(label='text label', choices=labels)
label_widget = Container(widgets=[label_menu])
def update_label_menu():
"""This is a callback function that updates the label menu when
the default features of the Shapes layer change
"""
new_label = str(shapes_layer.feature_defaults[label_feature][0])
if new_label != label_menu.value:
label_menu.value = new_label
shapes_layer.events.feature_defaults.connect(update_label_menu)
def set_selected_features_to_default():
"""This is a callback that updates the feature values of the currently
selected shapes. This is a side-effect of the deprecated current_properties
setter, but does not occur when modifying feature_defaults."""
indices = list(shapes_layer.selected_data)
default_value = shapes_layer.feature_defaults[label_feature][0]
shapes_layer.features[label_feature][indices] = default_value
shapes_layer.events.features()
shapes_layer.events.feature_defaults.connect(set_selected_features_to_default)
shapes_layer.events.features.connect(shapes_layer.refresh_text)
def label_changed(value: str):
"""This is a callback that update the default features on the Shapes layer
when the label menu selection changes
"""
shapes_layer.feature_defaults[label_feature] = value
shapes_layer.events.feature_defaults()
label_menu.changed.connect(label_changed)
return label_widget
# create a stack with the camera image shifted in each slice
n_slices = 5
base_image = data.camera()
image = np.zeros((n_slices, base_image.shape[0], base_image.shape[1]), dtype=base_image.dtype)
for slice_idx in range(n_slices):
shift = 1 + 10 * slice_idx
image[slice_idx, ...] = np.pad(base_image, ((0, 0), (shift, 0)), mode='constant')[:, :-shift]
# create a viewer with a fake t+2D image
viewer = napari.view_image(image)
# create an empty shapes layer initialized with
# text set to display the box label
text_kwargs = {
'string': text_feature,
'size': text_size,
'color': text_color
}
shapes = viewer.add_shapes(
face_color='black',
features=features,
text=text_kwargs,
ndim=3
)
# create the label section gui
label_widget = create_label_menu(
shapes_layer=shapes,
label_feature=text_feature,
labels=box_annotations
)
# add the label selection gui to the viewer as a dock widget
viewer.window.add_dock_widget(label_widget, area='right', name='label_widget')
# set the shapes layer mode to adding rectangles
shapes.mode = 'add_rectangle'
if __name__ == '__main__':
napari.run()