Skip to content

feat(napari): responsive mouse source + pinnable signals - #429

Open
sdiebolt wants to merge 8 commits into
mainfrom
feat/signal-plotter-mouse-source
Open

feat(napari): responsive mouse source + pinnable signals#429
sdiebolt wants to merge 8 commits into
mainfrom
feat/signal-plotter-mouse-source

Conversation

@sdiebolt

@sdiebolt sdiebolt commented Aug 31, 2026

Copy link
Copy Markdown
Member

Summary

  • Throttle mouse-driven signal-plotter updates (leading-edge, no added latency in the common case) and let Shift-press-without-moving plot the resting cursor's signal.
  • Rename ImportedSignal/imported_signals to StoredSignal/stored_signals across the signal store, manager, and plotter, since the store no longer only holds file imports.
  • Add SignalStore.pin_signal + a "Pin" button on the plotter to snapshot a currently plotted live signal (mouse voxel, point, or label region) so it survives a source-mode switch instead of being dropped.
  • Add an "Export Selected" button to the Signal Manager dialog.

Closes #428.

@sdiebolt sdiebolt self-assigned this Aug 31, 2026
@sdiebolt sdiebolt added the enhancement New feature or request label Aug 31, 2026
@sdiebolt sdiebolt changed the title napari: responsive mouse source + pinnable signals feat(napari): responsive mouse source + pinnable signals Aug 31, 2026
@sdiebolt
sdiebolt requested review from FelipeCybis and a lite review from Copilot August 31, 2026 23:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the napari Signal Plotter’s interactivity and persistence by throttling mouse-driven updates, enabling Shift-press plotting without cursor motion, and introducing pinnable (persisted) signals via a renamed “stored signals” concept across the plugin.

Changes:

  • Rename the signal store API/types from “imported” to “stored” to reflect both imported and pinned signals.
  • Add pinning support: SignalStore.pin_signal, a “Pin” toolbar button, and plot-time dedup so pinned copies don’t duplicate currently-live sources.
  • Add “Export Selected” to the Signal Manager dialog, plus supporting tests and icon/theming updates.

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/unit/test_napari/test_signal_store.py Updates tests for stored-signal rename and adds pinning behavior tests.
tests/unit/test_napari/test_signal_plotter.py Updates integration tests for stored signals and adds pin/dedup + pin-button behavior tests.
tests/unit/test_napari/test_signal_manager.py Updates stored-signal rename usage and adds export-selected tests.
src/confusius/_napari/assets/pin.svg Adds a pin icon asset used by the new Pin button.
src/confusius/_napari/_theme.py Generalizes toolbar button creation/styling to support Pin + configurable icon/tooltip.
src/confusius/_napari/_signals/_store.py Introduces StoredSignal + pin_signal, renames store APIs, and updates docs/errors.
src/confusius/_napari/_signals/_plotter.py Implements mouse throttling, Shift-press plotting, pinning workflow, and stored-signal overlay changes.
src/confusius/_napari/_signals/_manager.py Renames UI to “stored” and adds “Export Selected” for stored signals.
Suppressed comments (1)

src/confusius/_napari/_signals/_plotter.py:1507

  • In _stored_plot_signals(), the loop variable is named signals even though it represents a single stored signal. This makes the subsequent attribute access (signals.x, signals.y, etc.) harder to read and easy to confuse with the surrounding list variables.
                    s.y_values,
                    linewidth=linewidth,
                    alpha=alpha,
                    label=s.label,
                    color=s.color,

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/confusius/_napari/_signals/_store.py Outdated
Comment thread src/confusius/_napari/_signals/_store.py
Comment thread src/confusius/_napari/_signals/_plotter.py
@github-actions

Copy link
Copy Markdown
Contributor

📖 Doc preview: https://confusius.tools/pr-preview/pr-429/

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Shift-hover previously required movement to trigger a plot (pressing
Shift while already resting on a voxel did nothing) and every raw
mouse-move event ran the full extract-and-redraw pipeline
synchronously, causing backlogs and jitter on large recordings.

- Bind Shift in the napari keymap so pressing it alone plots the
  signal at the resting cursor, restoring any prior binding on close.
- Throttle mouse-source updates to ~60/s with a leading-edge scheme:
  process immediately when enough time has elapsed (zero added latency
  in the common case), otherwise collapse a fast burst of events into
  one trailing update instead of queuing every intermediate position.
- Replace the fast-path's relim()/autoscale_view() (which rescans
  every plotted line) with a direct numpy min/max over the arrays
  already held for the current frame.

Claude-Session: https://claude.ai/code/session_01SHcGjS2Loy7KoNXStz4Xin
Rename ImportedSignal/SignalStore.imported_signals to StoredSignal/
stored_signals, since the store no longer only holds file imports:
add SignalStore.pin_signal to snapshot a currently plotted live
signal (mouse voxel, a point, or a label region) into a persistent
stored signal, since a live signal previously only existed while its
source mode was active and was dropped on switching modes.

- SignalPlotter tracks the currently plotted live signal(s) and
  exposes a "Pin" toolbar button (reusing/generalizing
  create_export_button and style_export_button) to snapshot them.
- A pinned signal is hidden from the plot while a live signal with a
  matching origin is still active, so re-entering the same source
  mode doesn't show a duplicate overlapping line; mouse pins are
  exempt since the live mouse id is a fixed "mouse-0", not per-voxel.
  Pinned entries get a "(pinned)" name suffix so they're never a
  silent duplicate of the live one in the legend/manager.
- Re-pinning the same origin updates that entry's data in place
  instead of duplicating (e.g. after repainting a label mask).
- Add an "Export Selected" button to the Signal Manager dialog to
  export only the selected stored signals.

Fixes #428.

Claude-Session: https://claude.ai/code/session_01SHcGjS2Loy7KoNXStz4Xin
The dedup that hid a pinned signal while a live signal with the same
origin was still active consistently read as "pinning silently did
nothing" (confirmed for labels and points, not just visually but at
the API level too) rather than as intended duplicate-avoidance. Drop
it: the "(pinned)" name suffix already keeps the two distinguishable
in the legend/manager, and two overlapping identical lines is a much
smaller cost than a signal appearing to vanish.

Also always show the legend in the stored-only render path (a lone
line there has no other identifying context, unlike the mouse-hover
title which already names the voxel).

Claude-Session: https://claude.ai/code/session_01SHcGjS2Loy7KoNXStz4Xin
Promotes SignalStore to the top-level widget and threads it into the
Signals and QC panels, so a computed DVARS trace is added there
directly, making it selectable elsewhere (e.g. as a Preprocessing
panel confound/sample mask) without importing it from a file.

Claude-Session: https://claude.ai/code/session_01SHcGjS2Loy7KoNXStz4Xin
The plotter previously held the bare Shift keybinding for its whole
lifetime, displacing any other Shift binding even while in points/
labels mode where it has no effect. Bind/unbind now follows
set_source_mode instead of widget open/close.
@sdiebolt
sdiebolt force-pushed the feat/signal-plotter-mouse-source branch from 358d768 to 9af4bd6 Compare September 1, 2026 09:48
AGENTS.md now routes all napari-plugin changelog entries to a
dedicated :frame_photo: Napari plugin section (always last) instead
of a **[Napari plugin]** prefix inside the type-based sections.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Signal plotter: live signals can be slow and aren't persistent across source modes

2 participants