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()