Conversation
Contributor
There was a problem hiding this comment.
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.
Alpaca233
force-pushed
the
feature/remove-napari-live-view
branch
from
September 1, 2026 01:34
dd2cfd7 to
b01c620
Compare
…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
force-pushed
the
feature/remove-napari-live-view
branch
from
September 15, 2026 00:03
0a41e1e to
2538cfa
Compare
…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
Contributor
There was a problem hiding this comment.
🟡 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The napari live viewer (
NapariLiveWidget) was unreachable in practice —USE_NAPARI_FOR_LIVE_VIEW,USE_NAPARI_FOR_LIVE_CONTROLandUSE_NAPARI_WELL_SELECTIONwere hardcodedFalsewith 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 pyqtgraphImageDisplayWindow, 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)show_alignment_reference(image)/hide_alignment_reference()add a secondImageItemin 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)_set_statehelper.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.pymeasure_translation_px(reference, live, live_crop_fraction)— center-crops the reference by the live display's resolution scaling (via the newcrop_to_fraction, also used byStreamHandler), 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 frommove_from_click_image, now shared by click-to-move, auto-align andWellplateCalibration.viewerClicked.to_grayscale— shared by registration, the overlay andcalculate_focus_measure.Removed
NapariLiveWidget(~580 lines) and the three flags plus every gated branch ingui_hcs.py; well selector always docks normally; the alignment widget is now set up unconditionally (outside live-only mode);napari_connectionsis a flat list.NapariLiveWidgetintensity-cap test (theLiveControlWidgettwin remains).Testing
tests/control/test_alignment.py(registration incl. sign conventions, display crop, binning rescale and color reference; stage mapping under bothINVERTED_OBJECTIVEsettings; the widget workflow against a realImageDisplayWindow; the GUI auto-align handler and its guards; the calibration click) andtests/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).fluidics_protocol/test_library_port.py) is pre-existing onmasterfrom feat(fluidics): protocol engine phases 0–1 — plumbing, demolition, engine seams, headless ProtocolRunner #625.test_HighContentScreeningGui.py: 5 passed (exit 0 withSQUID_PYTEST_HARD_EXIT=1as in CI).blackclean.🤖 Generated with Claude Code
https://claude.ai/code/session_01C54w5TgbZet5DP3GyCZHTN