Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ icon: lucide/history

Current development version for the next ConfUSIus release.

### :sparkles: Enhancements

- [`register_volumewise`][confusius.registration.register_volumewise] can now show live
motion diagnostics, including motion estimates, framewise displacement, and optimizer
summaries ([#352](https://github.com/confusius-tools/confusius/pull/352)).

### :bug: Fixes

- [`register_volumewise`][confusius.registration.register_volumewise] now warns when
lazy dask inputs use multi-volume time chunks, which can repeatedly read the same
chunk and slow down volume-by-volume registration
([#352](https://github.com/confusius-tools/confusius/pull/352)).

### :frame_photo: Napari plugin

- The registration panel now shows live volumewise motion diagnostics in
a floating plot window during motion correction
([#352](https://github.com/confusius-tools/confusius/pull/352)).

## 0.7.1

Released 2026-09-16.
Expand Down Expand Up @@ -269,9 +288,6 @@ Released 2026-08-31.

- **[Napari plugin]** ConfUSIus now requires napari 0.9.0 or newer
([#413](https://github.com/confusius-tools/confusius/pull/413)).

### :wrench: Maintenance

- Bumped the `brainglobe-atlasapi` dependency to v3
([#412](https://github.com/confusius-tools/confusius/pull/412)).

Expand Down
2 changes: 1 addition & 1 deletion src/confusius/_napari/_registration/_metric_plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def add_metric(self, value: float) -> None:
"""Append a metric value and schedule a redraw.

Called from the GUI thread via the
`NapariRegistrationProgressPlotterBridge.metric_updated` signal. Rapid iteration
`NapariVolumeRegistrationProgressPlotterBridge.metric_updated` signal. Rapid iteration
events are coalesced through a single-shot timer so the canvas is redrawn at
most once per ~16 ms regardless of the worker-side event rate.

Expand Down
17 changes: 13 additions & 4 deletions src/confusius/_napari/_registration/_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,16 @@
)
from confusius._napari._registration._panel_worker_state import on_registration_failed
from confusius._napari._registration._progress import (
NapariRegistrationProgressPlotterBridge,
NapariRegistrationProgressReporterBridge,
NapariVolumeRegistrationProgressPlotterBridge,
NapariVolumewiseRegistrationProgressReporterBridge,
)
from confusius._napari._registration._transform_payloads import (
OutputGridPayload,
TransformPayload,
)
from confusius._napari._registration._volumewise_diagnostics_plotter import (
VolumewiseRegistrationDiagnosticsPlotter,
)
from confusius.registration import register_volume, register_volumewise

if TYPE_CHECKING:
Expand Down Expand Up @@ -267,13 +270,15 @@ def __init__(self, viewer: napari.Viewer) -> None:
self._loaded_transform_payload: TransformPayload | None = None
self._optimizer_weight_spins: list[QDoubleSpinBox] = []
# Per-run progress state. Set on the GUI thread before the worker starts.
self._progress_bridge: NapariRegistrationProgressPlotterBridge | None = None
self._progress_bridge: NapariVolumeRegistrationProgressPlotterBridge | None = (
None
)
self._progress_layer: Image | None = None
self._progress_fixed_layer: Image | None = None
self._progress_moving_layer: Image | None = None
self._manual_transform_event_layers: list[Layer] = []
self._volumewise_progress_bridge: (
NapariRegistrationProgressReporterBridge | None
NapariVolumewiseRegistrationProgressReporterBridge | None
) = None
self._volumewise_progress_layer: Image | None = None
self._volumewise_moving_preview_layer: Image | None = None
Expand All @@ -283,6 +288,10 @@ def __init__(self, viewer: napari.Viewer) -> None:
# across subsequent runs, and torn down with the progress state.
self._metric_plotter: RegistrationMetricPlotter | None = None
self._metric_dock: QDockWidget | None = None
self._volumewise_diagnostics_plotter: (
VolumewiseRegistrationDiagnosticsPlotter | None
) = None
self._volumewise_diagnostics_dock: QDockWidget | None = None
self._active_operation: Literal["register_volume", "register_volumewise"] = (
"register_volume"
)
Expand Down
87 changes: 77 additions & 10 deletions src/confusius/_napari/_registration/_panel_progress.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
_preserve_view,
)
from confusius._napari._registration._progress import (
NapariRegistrationProgressPlotterBridge,
NapariRegistrationProgressReporter,
NapariRegistrationProgressReporterBridge,
NapariVolumeRegistrationProgressPlotterBridge,
NapariVolumewiseRegistrationProgressReporter,
NapariVolumewiseRegistrationProgressReporterBridge,
make_napari_progress_factory,
)
from confusius.plotting.napari import plot_napari
Expand All @@ -35,7 +35,10 @@
RegistrationMetricPlotter,
)
from confusius._napari._registration._panel import RegistrationPanel
from confusius.registration import RegistrationProgress
from confusius._napari._registration._volumewise_diagnostics_plotter import (
VolumewiseRegistrationDiagnosticsPlotter,
)
from confusius.registration import VolumeRegistrationProgress


def setup_volumewise_progress(
Expand All @@ -44,7 +47,7 @@ def setup_volumewise_progress(
moving_layer: Image,
moving: xr.DataArray,
layer_name: str,
) -> NapariRegistrationProgressReporter:
) -> NapariVolumewiseRegistrationProgressReporter:
"""Create volumewise preview layers and a progress reporter.

Parameters
Expand All @@ -60,7 +63,7 @@ def setup_volumewise_progress(

Returns
-------
NapariRegistrationProgressReporter
NapariVolumewiseRegistrationProgressReporter
Worker-side reporter that forwards completed-frame updates back to the
panel.
"""
Expand Down Expand Up @@ -126,7 +129,7 @@ def setup_volumewise_progress(
except Exception as exc:
panel._set_error(f"Could not create progress layer: {exc}")
raise
bridge = NapariRegistrationProgressReporterBridge()
bridge = NapariVolumewiseRegistrationProgressReporterBridge()
bridge.frame_progress.connect(
lambda completed_frames, total_frames: update_volumewise_progress_bar(
panel, completed_frames, total_frames
Expand All @@ -137,6 +140,13 @@ def setup_volumewise_progress(
panel, frame_index, frame_data
)
)
plotter = ensure_volumewise_diagnostics_plotter(
panel,
moving=moving,
reference=moving.isel({TIME_DIM: 0}),
)
if plotter is not None:
bridge.diagnostics_updated.connect(plotter.add_frame)

panel._volumewise_progress_bridge = bridge
panel._volumewise_progress_layer = cast("Image", layer)
Expand All @@ -145,7 +155,7 @@ def setup_volumewise_progress(
panel._volumewise_progress_total = moving.sizes[TIME_DIM]
panel._progress.setRange(0, panel._volumewise_progress_total)
panel._progress.setValue(0)
return NapariRegistrationProgressReporter(
return NapariVolumewiseRegistrationProgressReporter(
bridge,
n_frames=panel._volumewise_progress_total,
)
Expand Down Expand Up @@ -234,6 +244,63 @@ def teardown_volumewise_progress(
panel._volumewise_progress_layer = None
panel._volumewise_progress_time_axis = None
panel._volumewise_progress_total = None
if panel._volumewise_diagnostics_dock is not None:
try:
panel.viewer.window.remove_dock_widget(panel._volumewise_diagnostics_dock)
except (KeyError, ValueError, RuntimeError):
pass
panel._volumewise_diagnostics_dock = None
panel._volumewise_diagnostics_plotter = None


def ensure_volumewise_diagnostics_plotter(
panel: RegistrationPanel,
*,
moving: xr.DataArray,
reference: xr.DataArray,
) -> VolumewiseRegistrationDiagnosticsPlotter | None:
"""Return the floating volumewise diagnostics plotter, creating it if needed.

Parameters
----------
panel : RegistrationPanel
Registration panel whose viewer receives the diagnostics widget.
moving : xarray.DataArray
Volumewise data carrying the time coordinate.
reference : xarray.DataArray
Spatial reference used for motion diagnostics.

Returns
-------
VolumewiseRegistrationDiagnosticsPlotter or None
Floating diagnostics widget.
"""
from confusius._napari._registration._volumewise_diagnostics_plotter import (
VolumewiseRegistrationDiagnosticsPlotter,
)

time_coords = moving.coords[TIME_DIM].values if TIME_DIM in moving.coords else None
time_units = (
str(moving.coords[TIME_DIM].attrs.get("units"))
if TIME_DIM in moving.coords and "units" in moving.coords[TIME_DIM].attrs
else None
)
panel._volumewise_diagnostics_plotter = VolumewiseRegistrationDiagnosticsPlotter(
panel.viewer,
n_frames=moving.sizes[TIME_DIM],
reference=reference,
time_coords=time_coords,
time_units=time_units,
)
dock = panel.viewer.window.add_dock_widget(
panel._volumewise_diagnostics_plotter,
name="Volumewise Registration Diagnostics",
area="right",
)
panel._volumewise_diagnostics_dock = dock
dock.setFloating(True)
dock.resize(900, 820)
return panel._volumewise_diagnostics_plotter


def create_volume_progress_plotter(
Expand All @@ -245,7 +312,7 @@ def create_volume_progress_plotter(
fixed: xr.DataArray,
layer_name: str,
initial_transform: npt.NDArray[np.floating] | None = None,
) -> Callable[..., RegistrationProgress]:
) -> Callable[..., VolumeRegistrationProgress]:
"""Create between-scan preview layers and a progress-plotter factory.

Parameters
Expand Down Expand Up @@ -369,7 +436,7 @@ def create_volume_progress_plotter(
panel._set_error(f"Could not create progress layer: {exc}")
raise

bridge = NapariRegistrationProgressPlotterBridge()
bridge = NapariVolumeRegistrationProgressPlotterBridge()
bridge.iterated.connect(lambda arr: update_progress_layer(panel, arr))
panel._progress_bridge = bridge
panel._progress_layer = cast("Image", layer)
Expand Down
Loading
Loading