Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

3. Custom Widgets & Interactions

Goal: Add interactive GUI widgets, custom keybindings, layer event callbacks, and mouse drag interactions to napari — turning analysis functions into interactive tools.

Setup

Let’s load the spots and nuclei data from Block 2 and get a fresh viewer:

<Image layer 'spots' at 0x7f33e59d6ad0>
Loading...

1. Writing analysis functions (10 min)

First, let’s write the analysis function we’ll turn into a widget. The spots image has some background autofluorescence — we can clean it up with a gaussian high-pass filter: subtract a blurred version of the image from the original, keeping only the sharp, spot-like features.

Let’s test it on our spots data with sigma=2:

<Image layer 'filtered spots' at 0x7f340c390910>
Loading...

The spots stand out much more clearly against the background! But what if we want to try a different sigma value? We’d have to re-run the cell manually each time — not exactly an interactive exploration.

2. Interactive filtering with magicgui (25 min)

In Block 2 we wrote a gaussian_high_pass function to clean up the spots image — but changing the sigma parameter meant re-running a cell each time. Let’s make it interactive with magicgui.

The @magicgui decorator reads type annotations on your function parameters and automatically generates corresponding GUI widgets:

<napari._qt.widgets.qt_viewer_dock_widget.QtViewerDockWidget at 0x7f33e5762cb0>
Loading...

Notice what just happened: magicgui read the ImageData type annotation and automatically created a dropdown that lists only image layers. The sigma parameter became a spin box. And because the return type is also ImageData, the result is automatically added as a new image layer.

Press the Run button — the filtered result appears. Change the sigma value and press Run again: the layer updates in place.

The gaussian_high_pass object is both a widget and a callable function:

2.0
Output shape: (492, 494)

This means you can use the same function in a script or as a widget in napari — no code duplication.

Adding sliders and auto-call

Let’s make it even more interactive: replace the spin box with a slider and have the function run automatically whenever we move it.

First, remove the old widget:

Now recreate it with widget configuration:

<napari._qt.widgets.qt_viewer_dock_widget.QtViewerDockWidget at 0x7f33e564e0d0>
Loading...

Now drag the slider — the filter updates instantly! auto_call=True means the function runs whenever any parameter changes, no Run button needed.

You can also set widget values programmatically:

A more complete example: spot detection

Let’s build a widget for the full spot detection workflow. We’ll use skimage.feature.blob_log to detect spots and return them as a Points layer with custom styling.

When a function returns a LayerDataTuple, napari creates a new layer using whatever data and visualization settings you provide:

<napari._qt.widgets.qt_viewer_dock_widget.QtViewerDockWidget at 0x7f33e41c9810>
Loading...

Try adjusting the sliders. The spots update in real time — change spot_threshold to detect more or fewer spots, adjust blob_sigma to match the spot size in your image.

3. Custom keybindings (15 min)

Keybindings let you trigger actions with keyboard shortcuts. napari makes this remarkably easy with the bind_key decorator.

Let’s bind Shift-D to report how many spots were detected in the current Points layer. We’ll attach it to the Points layer type so it only fires when a Points layer is active:

Now select the Points layer created by detect_spots and press Shift-D. You should see a notification pop up in the viewer!

Keybindings can also be attached to the viewer (fires regardless of which layer is active):

4. Layer events (15 min)

napari layers emit events when their properties change — data, colormap, opacity, even individual point positions. You can connect custom functions (callbacks) to these events.

Let’s demonstrate with a cool example: warping an image when control points are moved. This has been adopted from the scikit-image Use thin-plate splines for image warping example.

Loading...

The warp function

We’ll use thin-plate splines to warp the image based on point positions:

Connecting to the data event

We want the warp to happen whenever a point moves. We connect a callback to the layer’s data event:

<function __main__.warp_on_point_changed(event)>

Now select the moving_points layer, switch to the Select points tool, and drag a point. The image warps when you release the mouse.

5. Mouse callbacks (15 min)

Layer events fire when a change completes. But what if you want to react while the user is dragging? That’s where mouse callbacks come in.

Mouse callbacks use a generator pattern — they yield to separate the logic for mouse press, drag, and release:

def some_mouse_callback(layer, event):
    # --- Mouse press ---
    print("Mouse pressed")
    yield  # ← this pauses; execution resumes on drag

    # --- Mouse drag ---
    while event.type == 'mouse_move':
        print("Dragging...")
        yield  # ← yields control each frame

    # --- Mouse release ---
    print("Mouse released")

Warping on drag

Let’s replace the layer event callback with a mouse drag callback that warps the image as you drag a point:

Now select the moving_points layer and drag a point — the image warps in real time as you move the mouse!

Recap

In this block you learned to:

TechniqueWhat it doesHow to attach
magicguiAuto-generate GUI widgets from functions@magicgui + viewer.window.add_dock_widget()
Custom keybindingsTrigger actions with keyboard shortcuts@Points.bind_key('Shift-D') / @viewer.bind_key('key')
Layer eventsReact to property changeslayer.events.data.connect(callback)
Mouse callbacksReact to mouse drag in real timelayer.mouse_drag_callbacks.append(callback)

In Block 4, we’ll package our detect_spots widget into a pip-installable napari plugin that anyone can use.