Skip to content

Remove napari live view; port overexposure indicator and alignment (with auto-align) to the pyqtgraph viewer - #627

Open
Alpaca233 wants to merge 6 commits into
masterfrom
feature/remove-napari-live-view
Open

Alpaca233 wants to merge 6 commits into
masterfrom
feature/remove-napari-live-view

Conversation

@Alpaca233

@Alpaca233 Alpaca233 commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

The napari live viewer (NapariLiveWidget) was unreachable in practice — USE_NAPARI_FOR_LIVE_VIEW, USE_NAPARI_FOR_LIVE_CONTROL and USE_NAPARI_WELL_SELECTION were hardcoded False with no config override — which also made the alignment feature unreachable, since it required the napari viewer. This PR removes the napari live view path and ports its two napari-only features into the pyqtgraph ImageDisplayWindow, which has been the actual live viewer all along.

Mosaic View and the Multichannel Acquisition tab still use napari and are untouched.

Changes

ImageDisplayWindow (core/core.py)

  • Overexposure toggle on the status bar next to Line Profiler, off by default. Implemented as a red mask overlay item (1 where any channel is at or above the upper contrast limit), so one mechanism covers both display modes and both mono and RGB frames without touching the histogram-owned LUT or the frame data. The mask is recomputed only when the frame or the levels change.
  • Alignment reference overlay: show_alignment_reference(image) / hide_alignment_reference() add a second ImageItem in additive magenta whose levels follow the live image (per frame and on histogram changes). Color references are reduced to intensity here, since pyqtgraph ignores lookup tables on H×W×3 data.
  • current_image() returns the last received frame.

AlignmentWidget (widgets.py)

  • Takes the live display instead of a napari viewer; the napari layer-management section is replaced by the two calls above. State transitions go through one _set_state helper.
  • New Auto button (enabled while a reference is loaded): emits the reference image; the GUI registers the current live frame against it with skimage.registration.phase_cross_correlation (already used by PDAF and the stitcher), converts the shift with the shared pixel→stage helper, and moves the stage to cancel it. The user then clicks Confirm Offset as before, so the human check stays in the loop and no correlation-confidence threshold is needed — a failed registration is obvious in the overlay. Auto requires a running live view (the last frame is stale once the stage has moved).

control/utils.py

  • measure_translation_px(reference, live, live_crop_fraction) — center-crops the reference by the live display's resolution scaling (via the new crop_to_fraction, also used by StreamHandler), resamples to the live resolution (binning mismatch), handles color inputs, returns the displacement in live pixels.
  • image_delta_to_stage_delta_mm(...) — the pixel→stage conversion extracted from move_from_click_image, now shared by click-to-move, auto-align and WellplateCalibration.viewerClicked.
  • to_grayscale — shared by registration, the overlay and calculate_focus_measure.

Removed

  • NapariLiveWidget (~580 lines) and the three flags plus every gated branch in gui_hcs.py; well selector always docks normally; the alignment widget is now set up unconditionally (outside live-only mode); napari_connections is a flat list.
  • The NapariLiveWidget intensity-cap test (the LiveControlWidget twin remains).

Testing

  • New tests in tests/control/test_alignment.py (registration incl. sign conventions, display crop, binning rescale and color reference; stage mapping under both INVERTED_OBJECTIVE settings; the widget workflow against a real ImageDisplayWindow; the GUI auto-align handler and its guards; the calibration click) and tests/control/test_image_display_window.py (overexposure overlay in both display modes, for RGB frames and on histogram level changes; reference overlay add/reuse/remove, level sync and grayscale reduction).
  • Full suite: 1749 passed, 9 skipped, 1 xfailed; the one failure (fluidics_protocol/test_library_port.py) is pre-existing on master from feat(fluidics): protocol engine phases 0–1 — plumbing, demolition, engine seams, headless ProtocolRunner #625. test_HighContentScreeningGui.py: 5 passed (exit 0 with SQUID_PYTEST_HARD_EXIT=1 as in CI).
  • black clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01C54w5TgbZet5DP3GyCZHTN

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

Removes the unreachable napari live-view path and ports alignment and overexposure features to the pyqtgraph viewer.

Changes:

  • Adds pyqtgraph overexposure and alignment overlays.
  • Adds automatic image registration and stage alignment.
  • Removes napari live-view flags, implementation, and tests.

Reviewed changes

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

Show a summary per file
File Description
software/control/_def.py Removes obsolete napari live flags.
software/control/core/core.py Adds display overlays and current-frame access.
software/control/gui_hcs.py Integrates pyqtgraph alignment and removes napari branches.
software/control/utils.py Adds registration and stage-delta helpers.
software/control/widgets.py Ports alignment workflow and removes NapariLiveWidget.
software/tests/control/test_alignment.py Tests registration and alignment workflows.
software/tests/control/test_image_display_window.py Tests new display features.
software/tests/control/test_intensity_cap.py Removes obsolete napari test coverage.

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

Comment thread software/control/utils.py Outdated
Comment thread software/control/core/core.py Outdated
Comment thread software/control/gui_hcs.py
Comment thread software/control/core/core.py Outdated
@Alpaca233
Alpaca233 force-pushed the feature/remove-napari-live-view branch from dd2cfd7 to b01c620 Compare September 1, 2026 01:34
Alpaca233 and others added 5 commits September 14, 2026 16:57
…lignment to the pyqtgraph viewer

- ImageDisplayWindow gains an "Overexposure" toggle on the status bar (napari's
  grayclip mapping: pixels at/above the upper contrast limit render red; off by
  default), an additive magenta reference overlay that tracks the live contrast
  range, and current_image()
- AlignmentWidget takes the live display instead of a napari viewer and gains an
  Auto button: the live frame is registered against the reference with
  skimage phase_cross_correlation and the stage is nudged to cancel the
  displacement; Confirm Offset then computes the offset exactly as before
- Pixel->stage conversion extracted into control.utils and shared by
  click-to-move and auto-align
- Remove NapariLiveWidget and the USE_NAPARI_FOR_LIVE_VIEW / _LIVE_CONTROL /
  _WELL_SELECTION flags (hardcoded False with no config override, so the napari
  live view was unreachable and the alignment feature with it)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C54w5TgbZet5DP3GyCZHTN
…e frames, RGB overexposure

- measure_translation_px takes the display crop fraction: below 100% display
  resolution the live frame is the center crop of the FOV (StreamHandler), so
  the same part of the reference is compared instead of resizing the full FOV
  into the crop
- AlignmentWidget reduces color references to intensity before overlaying;
  pyqtgraph ignores lookup tables on H x W x 3 data
- Auto align requires a running live view: current_image() keeps the last
  frame after live stops, which is stale once the stage has moved
- ImageDisplayWindow marks saturated RGB pixels red in the data when the
  overexposure indicator is on (LUTs do not apply to RGB frames), and
  current_image() returns the unmodified frame

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C54w5TgbZet5DP3GyCZHTN
…, grayscale and stage-move helpers

- Overexposure indicator is now a single red mask overlay item, replacing the
  three mode-specific implementations (histogram gradient / item LUT / RGB
  data marking). Pixel readouts no longer see marked data, and the mask only
  recomputes when the frame or levels change; the reference overlay's levels
  are only reapplied when they differ
- Auto align reads the display crop fraction from LiveController; the
  QtStreamHandler wrapper does not expose it (the stub-based test encoded the
  wrong attribute path and now uses the real one)
- crop_to_fraction shared by StreamHandler and measure_translation_px;
  to_grayscale uses the codebase's RGB convention and is reused by
  calculate_focus_measure; grayscale reduction moves into
  show_alignment_reference where the LUT constraint lives
- _move_stage_by_image_delta shared by click-to-move and auto align;
  WellplateCalibration.viewerClicked uses the same helpers instead of its own
  copy of the pixel->stage math
- napari_connections is a flat list; AlignmentWidget clear reuses reset();
  shared image_display_window fixture; hoisted test imports

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C54w5TgbZet5DP3GyCZHTN
…g templates

_def.py only maps an ini key onto an existing module variable, so
use_napari_for_live_view has been silently ignored since the flag was
removed, and use_napari_for_multipoint / use_napari_for_tiled_display have
had no counterpart for longer. Also give LIVE_ONLY_MODE its own label in
_def.py instead of sitting under "Napari integration".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C54w5TgbZet5DP3GyCZHTN
…ver-exposed Pixels"

The Auto button is hidden outside the confirm step instead of greyed out.
The floating Align/Auto widget uses a fixed-size layout so it shrinks and
grows with the button, and NavigationViewer repositions it on resize so it
stays beside the Clear Scan Grid button.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C54w5TgbZet5DP3GyCZHTN
@Alpaca233
Alpaca233 force-pushed the feature/remove-napari-live-view branch from 0a41e1e to 2538cfa Compare September 15, 2026 00:03
…e button tooltips

The reference overlay was added in additive magenta over a plain grayscale
live image, so overlap came out pink-white and nothing on screen was ever
green (the napari version had switched the live layer to a green colormap).
The live image is now multiplied by a one-pixel green overlay item while a
reference is shown: gray x green keeps only the green channel, so fringes
show as green and magenta and aligned content adds up to white. The live
item itself (levels, lookup table, histogram gradient) is never touched, so
the mechanism is the same in both display modes and a histogram edit made
during alignment is not clobbered on hide. Overlay stacking is explicit by
z-value (tint, reference, overexposure mask). Measured full-scene paint on
2084x2084 frames: 13.0 ms with tint + reference vs 10.7 ms reference only.

The Align button now carries a tooltip per workflow state ("Load a previous
acquisition to align to" / "Line up the two images, then record the offset"
/ "Remove the alignment offset"), says "Start Live first" while disabled,
and the Auto tip notes it needs Live. Label, tooltip and Auto visibility are
derived in one place from the state and enabled flag.

Tests: rendered-pixel check that a white frame paints green under a
reference and white again after hide, in both display modes; tint follows
the live image size; overlay z-order; tooltips follow the state machine.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0121bRk7XXsNhwDtqEE3C6by

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.

🟡 Changes recommended

Reference geometry, stale-frame synchronization, and BGR/RGB handling can produce incorrect visual or automatic alignment.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 19/19 changed files
  • Comments generated: 3
  • Review effort level: Balanced

Comment on lines +1152 to +1153
self.alignment_reference_item.setImage(utils.to_grayscale(image), autoLevels=False)
self._update_overlays()
Comment on lines +1302 to +1305
live_image = self.imageDisplayWindow.current_image()
pixel_size_um = self.microscope.get_image_pixel_size_um()
if not self.liveController.is_live or live_image is None or pixel_size_um is None:
QMessageBox.warning(self, "Alignment Error", "Auto align needs a running live view and a known pixel size.")
Comment on lines +10683 to +10684
self._reference_image = ref_image
self._display.show_alignment_reference(ref_image)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants