Skip to content

Fix/stage offset calibration#281

Merged
beniroquai merged 72 commits into
masterfrom
fix/stageOffsetCalibration
May 28, 2026
Merged

Fix/stage offset calibration#281
beniroquai merged 72 commits into
masterfrom
fix/stageOffsetCalibration

Conversation

@beniroquai
Copy link
Copy Markdown
Collaborator

This pull request removes the old standalone calibration plugin UIs and centralizes stage offset calibration directly in the WellSelectorCanvas context menu, making the workflow safer and more consistent. It also introduces Ashlar stitching as a selectable mode in the experiment designer, with automatic triggering when experiments finish, and adds new backend API bindings to support these workflows.

Calibration workflow improvements:

  • Removed the StageOffsetCalibration and StageCenterCalibrationWizard plugin components and their corresponding UI entries in App.jsx, consolidating all calibration into the WellSelectorCanvas right-click context menu. [1] [2] [3] [4]
  • Added a confirmation dialog for "We are here (calibrate offset)" in WellSelectorCanvas, ensuring users confirm before overwriting the stage offset. The dialog snapshots the raw device position before writing, making the calibration atomic and robust. [1] [2] [3]
  • Updated the offset calibration API contract and logic to use explicit device positions for deterministic calibration, and added a new API binding to fetch the raw device position (apiPositionerControllerGetDevicePositionAxis). [1] [2]

Experiment stitching enhancements:

  • Added Ashlar stitching as a new selectable mode in the tiling UI, with state wiring to enable/disable it. When enabled, Ashlar stitching is triggered automatically when an experiment finishes, with user feedback in the UI. [1] [2] [3] [4] [5]

API and code cleanup:

  • Removed the unused apiStageCenterCalibrationGetStatus.js and added a new API binding for fetching the latest heatmap for center calibration (apiStageCenterCalibrationGetLatestHeatmap). [1] [2]

These changes streamline calibration workflows, reduce user error, and add robust experiment stitching options.

beniroquai added 30 commits May 1, 2026 18:55
Refactor pixel calibration end-to-end: backend, detector managers, and frontend. Adds a pending-calibration approval flow (getPendingCalibration / applyPendingCalibration / discardPendingCalibration) and switches storage to per-detector calibration entries instead of objective-only keys. Removes the overview/AprilTag calibrators and related stream UI, simplifies PixelCalibrationController initialization so it always loads (even without config), and applies flips/pixel-size to detectors via a new _applyCalibrationToDetector helper. Backend: major controller changes, pending state, config persistence, and removal/archiving of apriltag/overview modules. Detector managers (e.g. HikCam) stop reading flipX/flipY and authoritative pixel-size from setup and default to neutral values so PixelCalibrationController is the single source of truth. Frontend: new API wrappers (get/apply/discard pending), calibrate API updated to use detectorName, PixelCalibrationTab reworked to remove overview stream and add a review/edit/apply UI for pending results. Also adds a large CLAUDE_CODE_INSTRUCTIONS_Pixelcalibration.md with refactor guidance and archives some tests/docs and old requirement files.
Introduce objective-aware pixel calibration throughout frontend and backend. Frontend: expose an Objective selector in pixel calibration tabs, pass objectiveId as a query param to PixelCalibration API calls and respect FastAPI query/body mapping. Backend: migrate PixelCalibrationController to key calibrations by "<detector>::<objective>", support objectiveId on calibrate/apply/get/discard/delete/set operations, and only apply calibrations for the currently selected objective while still recognising legacy detector-only keys. Add getPixelSizeUm API, react to objective changes to re-apply matching calibrations, and store objective metadata with pending/persisted entries. Misc: add overviewCameraName config in setup/ExperimentManager and resolve overview camera in ExperimentController; move pixel size/flip defaults out of camera managers (GXPIPY/Tucsen/Virtual) so PixelCalibrationController is the single source of truth.
Make ashlar stitching pick up pixel size from a parent *_protocol.json (falls back to 1.0 µm if not found) by scanning the parent directory and extracting post_params.pixel_size; pass that value to build_ashlar_stitched. Also switch script to run main() by default instead of the test helper. In PixelCalibrationController, remove the transitive cv2 import and simplify imports from affine_stage_calibration (remove re-export of validate_calibration), plus minor whitespace/usage example cleanups.
Introduce detector-aware pixel calibration endpoints and UI changes. Frontend: add API helpers (getAvailableDetectors, getCalibrationData, setCalibrationData), surface detector dropdowns in PixelCalibrationTab and ManualPixelCalibrationTab, send previewSubsamplingFactor and raw preview coords from manual calibration, and add an editable per-(detector,objective) save UI. Backend: PixelCalibrationController gains getAvailableDetectors, previewSubsamplingFactor handling in manual calibration, richer setCalibrationData response, and JSON sanitization to convert numpy types and replace NaN/Inf with nulls. ObjectiveController now logs a one-time warning when falling back to deprecated static pixelsizes. ArkitektManager gets a device-code hook to handle/display device codes. Also bump NanoImagingPack to 2.1.5 in pyproject.toml and setup.py.
Introduce an Opentrons-compatible labware subsystem and UI integration. Adds a new imswitch/imcontrol/model/labware package (models, loader, manager, selector, generators, schema_v2 and built-in definitions), OME plate metadata writer, and unit tests for labware loading/validation/selection. Frontend: add LabwareSelectionPanel and backend API stubs under frontend/backendapi; update WellSelectorComponent and Redux slices to integrate wellplate selection. Backend: update ExperimentController and related experiment controller modules to expose labware/well-selection functionality. Misc: add Windows launch configuration in .vscode/launch.json, remove PixelCalibration instruction doc and add WellplateOpentrons spec.
Migrate ImSwitch to an Opentrons-style labware layer: add docs (docs/labware_opentrons.md), vendored labware JSON definitions, and update labware generator/models/schema to the new format. Remove the legacy wellplate_layouts module and associated frontend API shims; ExperimentController now forwards to the LabwareManager and exposes compatibility wrappers that return the legacy dict shape. Frontend changes: add confirmation dialogs when switching layouts/labware (to avoid accidental point loss), add per-well sub-position grid generation with an SVG preview, and wire apply/replace behaviour to expand sub-positions. Small controller/fallback tweaks keep backward compatibility while encouraging use of the new endpoints and JSON definitions.
Implements autonomous overview scanning, registration persistence, overlay JPEG storage, a new Overview Scan UI tab, and freehand drawing for region-to-points conversion.

Key changes:
- Added CLAUDE_CODE_INSTRUCTIONS.md with implementation notes and design spec.
- Backend: extended overview registration to persist stagePosition/warp, save/load registration config and overlay JPEGs; added endpoints in ExperimentController for running autonomous scans, getting/updating registration config, and serving overlay images.
- Frontend: new OverviewScanTab component and API wrappers (apiGetOverviewRegistrationConfigData, apiUpdateOverviewRegistrationConfig, apiRunAutonomousOverviewScan, apiGetOverviewOverlayImage); moved overlay controls into the new tab and added tabbed UI in WellSelectorComponent.
- Freehand drawing: introduced FREEHAND_DRAW mode in WellSelectorCanvas (drawing, throttling, polygon fill), added generateFreehandScanPositions and clearFreehand exposed via ref, and added convert button in WellSelectorComponent to create experiment points from the polygon.
- State: updated OverviewRegistrationSlice to hold registrationConfig, autonomous scan state and progress, and new reducers used by the tab.

Files added/modified include frontend components (OverviewScanTab, WellSelectorCanvas, WellSelectorComponent, OverviewRegistrationWizard), frontend backendapi wrappers, state slice updates, and backend overview_registration/ExperimentController updates to support persistence and autonomous operation.
Persist overview-camera registration configs into the setup file and load them on startup so registrations survive process restarts. ExperimentController: import config tools, load persisted registrations at init, persist registrations after register/save operations, and add helpers for setup keying. Add overviewRegistration field to SetupInfo. Frontend: expose Overview Scan tab in the main Axon UI, remove the separate tab from WellSelector, and adjust OverviewRegistrationWizard to start the MJPEG stream as soon as the wizard opens. Add areaId/groupId/wellId metadata to points (freehand and well sub-position flows) so related tiles can be grouped by downstream writers, and surface these groups in a new PerWellPointsOverview component. Also persist overview overlay preferences to localStorage and rename/refactor a labware layout helper for clarity.
Add editable objective metadata and related state/handlers, plus various UI and status improvements. Key changes:
- Add new API: apiObjectiveControllerSetObjectiveParameters to set objective metadata on the backend.
- Import and wire the new API in ObjectiveController; add per-slot editable metadata fields, a save handler (handleSaveObjectiveMeta) and UI for name/NA/magnification/pixelsize.
- Populate new objective metadata in Redux via fetchObjectiveControllerGetStatus and add corresponding actions/state in ObjectiveSlice (availableObjectivesNames, availableObjectiveMagnifications, availableObjectiveNAs, availableObjectivePixelSizes).
- Small behavior tweaks: make skipZ default to false in apiObjectiveControllerMoveToObjective; remove an early return in the move handler so flow continues after the positions check (previous return commented out).
- UI/label improvements: rename X0/X1/Z0/Z1 labels to more descriptive Position/Focus 1/2 and adjust related button labels/placeholders.
- Fix FRAMESettingsController tab indices (shift TestHoming/ObjectiveController tabs) and add polling in WizardStep4 so Z updates while user adjusts focus.

These changes enable editing/saving objective parameters from the UI, improve clarity of objective/position labels, and ensure objective metadata is synchronized into application state.
Frontend: Refactor detector parameter fetching to a useCallback and add polling while auto-exposure is active; show a warning Alert when mode is auto. In the StreamControlOverlay add detector discovery, a detector selector (when multiple detectors present), include the selected detector_name in stream parameter payloads, and update labels/tooltips to use RAW (16-bit) wording.

Backend: Add per-detector stream parameter storage (_perDetectorStreamParams). setStreamParams now accepts an optional detectorName and stores per-detector overrides. getStreamParams accepts detectorName (defaults to the first available detector) and merges per-detector overrides into the response. These changes enable selecting and persisting stream settings per detector.
Fetch detector exposure/gain on mount and add a button to apply current camera settings (frontend/src/axon/ExperimentComponent.js). Add z_speed to the experiment model and defaults (models.py, ExperimentSlice.js), expose setZSpeed in the Redux slice, and wire a Z Speed selector into the tiling UI (TilingDimension.js). Improve FocusMap controls to disable/annotate compute buttons in manual-map mode (FocusMapDimension.js). Simplify stream overlay UI by renaming RAW -> Binary, removing detector-name fetching/selector and excluding detector_name from stream requests, and adjust related labels (StreamControlOverlay.js). Server-side ExperimentController now respects z_speed when setting Z motion speed and tightens laser-enable logic to handle falsy values safely (ExperimentController.py). These changes align UI defaults with hardware settings and add explicit Z speed handling across model, controller, and UI.
Replace legacy PixelCalibrationController overview stream usage with LiveViewController endpoints. Components (OverviewRegistrationWizard, SetLasersTab, TestHomingTab) now build MJPEG URLs using /LiveViewController/mjpeg_stream?detectorName=ObservationCamera and call apiLiveViewControllerStartLiveView / apiLiveViewControllerStopLiveView to start/stop streams (subsampling_factor: 1). apiPixelCalibrationControllerOverviewStream was updated to start/stop the ObservationCamera via LiveViewController. Also include stream lifecycle handling in the wizard (start on open, stop on close) and small UI changes in FocusMapDimension (removed disabled on manual measure button and updated button labels).
Switch FRAMESettings tabs to reuse the central WebSocket live stream instead of creating per-component MJPEG endpoints. Added LiveStreamSlice and LiveViewSlice selectors and use apiLiveViewControllerStartLiveView / apiLiveViewControllerStopLiveView to switch the active detector to ObservationCamera (jpeg, 1x subsampling), and restore the previously active detector/protocol when stopping. Replaced component-local MJPEG URLs and img refs with the shared base64 image from liveStreamState.liveViewImage and updated UI to show "Waiting for image..." while active but no image yet. Changes applied to ObjectiveControllerTab, SetLasersTab and TestHomingTab, and include small state/prop cleanup (prevDetectorRef / prevProtocolRef) and error/status messaging updates.
Expose configurable Move Camera speeds and wire them through the UI and state. Added moveCameraSpeedXY and moveCameraSpeedZ to the well selector slice with defaults (20000 µm/s and 1000 µm/s) and validation (parseFloat fallback to defaults). Created actions and handlers to update these values from the component. The WellSelectorComponent now shows XY/Z speed inputs when mode === MOVE_CAMERA and dispatches changes. WellSelectorCanvas uses moveCameraSpeedXY when issuing X/Y positioner move calls. This enables users to adjust camera movement speeds from the UI and persists values in the slice.
Move OverviewRegistrationWizard import and mounting from WellSelectorComponent to AxonTabComponent so the wizard is always available regardless of which tab is active. Remove the redundant import/instance from WellSelectorComponent. Adjust TestHomingTab layout/styles: switch from minHeight to a fixed 500px height, add overflow:hidden, and constrain the live preview image with maxHeight, objectFit: 'contain', and display:block to keep the image scaled and centered without overflow.
Introduce a raster-based stage-center calibration workflow and per-slot recapture support.

Frontend:
- Add StageOffsetCalibrationTab (ui + logic) to FRAME Settings: runs an XY raster scan, polls heatmap, renders heatmap canvas, shows brightest spot and allows manual override + storing offsets via PositionerController.
- Add API clients: apiStageCenterCalibrationGetHeatmap, apiStageCenterCalibrationGetIsRunning, apiExperimentControllerRecaptureSlot.
- Wire a "Retake" button into OverviewRegistrationWizard to call ExperimentController.recaptureSlot and refresh overlay.
- Register the new Stage Offset Calibration tab in FRAMESettingsController.

Backend:
- Add ExperimentController.recaptureSlot API: computes slot center (from layout_data / labware / Heidstar fallback), moves stage to center, and reuses snapOverviewImage to replace a single slot snapshot.
- Rework StageCenterCalibrationController: replace legacy spiral worker with a deterministic raster scan that collects (x,y,mean_intensity) samples, exposes performCalibration/getCalibrationHeatmap/getIsCalibrationRunning/stopCalibration, returns structured results including brightest spot and metadata, emits progress, and saves CSVs. Minor MovementController cleanup.

Purpose: enable accurate stage offset calibration via a heatmap-driven raster scan and let users selectively retake individual overview slots without re-running the full registration.
Add a redesigned two-step Stage Offset Calibration flow and supporting APIs.

Frontend: - New backend API wrappers: getKnownCalibrationLayouts, getKnownCalibrationPoint, getOverviewAsyncStatus, getRecommendedScanParameters. - Major rewrite of StageOffsetCalibrationTab: introduces LayoutMapMini and HeatmapCanvas, adaptive recommended scan defaults, polling-driven run state, click-to-move stage, merge of backend-known layouts with fallbacks, removal of exposure input (managed by detector), improved UX for known vs measured points, and persisting offsets via setStageOffsetAxis using known+current positions. - Integrates position state and positioner move API calls.

Backend: - StageCenterCalibrationController: added getRecommendedScanParameters to compute pixel-size/FOV-based defaults; updated raster worker to use explicit keyword args for stage.move; minor cleanup. - VirtualCameraManager: change initial _running flag to False.

These changes provide adaptive scan defaults, a visual layout map and heatmap for easier calibration, and the API endpoints required by the new UI.
Prevent long-running overview endpoints from blocking FastAPI workers by running recapture and autonomous scan work in background threads and exposing a polling endpoint. Frontend: import and poll apiExperimentControllerGetOverviewAsyncStatus in OverviewRegistrationWizard and OverviewScanTab, wait for completion (with timeouts) before refreshing overlay/using results. Backend: add async bookkeeping (_overview_async_lock, thread, _overview_async_status), _tryStartOverviewAsync/_finishOverviewAsync helpers, getOverviewAsyncStatus API, convert recaptureSlot and runAutonomousOverviewScan to spawn daemon worker threads (with corresponding _...Blocking and worker methods), and surface errors/results via the shared status. Also add known calibration point APIs for use by the frontend.
Refactor frontend objective handling and backend status reporting.

- Removed legacy FRAMESettings/ObjectiveControllerTab and wired the settings tab to the ExtendedObjectiveController component.
- ObjectiveController UI: generalized movePositioner to accept an axis, added Z-axis controls and larger step buttons, consolidated repeated set-position handlers into a single helper with numeric validation and user confirmation, added confirmation when saving objective metadata.
- ObjectiveSwitcher: show objective names and magnifications in labels, improved spinner styling.
- Wizard steps (2-6): use apiObjectiveControllerSetObjectiveParameters to persist metadata, use fetchObjectiveControllerGetStatus where appropriate, reuse PositionSlice state, add periodic polling of positions, and shift objective slot indexing to match backend (0/1). Added confirmation dialogs before saving X/Z positions.
- WebSocketHandler: enhanced parsing of sigObjectiveChanged payload and robustly dispatches many objective-related fields (positions, available objective lists, magnifications, NAs, pixel sizes, current slot).
- fetchObjectiveControllerGetStatus: hardened null checks, dispatch additional fields and available objective arrays safely.
- Backend ObjectiveController: stop forcing currentObjective after homing, centralize per-slot pixel-size lookup (integrates PixelCalibrationController when available), and include per-slot pixel sizes and other availability lists in getstatus response for frontend compatibility.

These changes tighten frontend/backend synchronization for objective state, improve UX (confirmation/prompts, bigger moves, clearer labels), and make the status updates more robust.
Add per-detector stream parameter persistence and front-end/back-end syncing. Frontend: new API helper apiLiveViewControllerSetDetectorStreamParameters, LiveView now passes saved per-detector overrides when starting streams and stores effective params returned by the backend into Redux. StreamControlOverlay loads per-detector settings from backend, updates Redux when the user changes stream settings, and fire-and-forgets saves them to the backend. DetectorParameters polling useEffect was commented out to avoid unnecessary overhead. LiveStreamSlice: added perDetectorSettings state and reducers (setPerDetectorSettings, updateDetectorSettings). Backend (LiveViewController): added _detectorParams storage, use saved per-detector params when starting streams, persist effective params, include per_detector in settings responses, and new API method setDetectorStreamParameters to update (and optionally restart) active detector streams. These changes allow detector-specific tuning to survive detector switches and keep UI and controller state in sync.
Major refactor to centralize and harden stage-offset calibration flows. Adds frontend APIs apiPositionerControllerGetDevicePositionAxis and apiStageCenterCalibrationGetLatestHeatmap and updates apiPositionerControllerSetStageOffsetAxis to accept an explicit currentDevicePosition parameter. WellSelectorCanvas now exposes a confirmed "We are here" context action that shows a dialog, snapshots raw device positions and writes stage offsets atomically to avoid accidental overwrites. StageOffsetCalibrationTab was rewritten (FRAMESettings) to provide a 2-step scan/review/accept workflow with homing handshake, heatmap restore on reload, pick-vs-move semantics, and confirm dialogs; multiple legacy wizard/components (StageCenter wizard, joystick, stage map, etc.) were removed. Minor UI and canvas improvements (HeatmapCanvas, LayoutMapMini), added calibration image asset, and corresponding backend/controller/manager updates to support the new behavior and make offset writes deterministic.
Copilot AI review requested due to automatic review settings May 27, 2026 08:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 centralizes stage offset calibration into the well/FRAME calibration workflows, removes the old standalone calibration UI, and adds Ashlar stitching as an experiment output option with backend execution support.

Changes:

  • Reworked stage offset calibration APIs and frontend flows to use raw device-position snapshots and persisted offsets.
  • Added latest-heatmap persistence/restoration and a richer FRAME calibration tab.
  • Added Ashlar stitching support in the experiment designer and backend conversion script integration.

Reviewed changes

Copilot reviewed 38 out of 40 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
scripts/convert_experiment_tiffs.py Adds Ashlar stitching mode, reader fallback, compatibility patches, and pixel-size lookup.
imswitch/imcontrol/view/guitools/ViewSetupInfo.py Removes obsolete stage-offset setup helpers.
imswitch/imcontrol/model/managers/rs232/VirtualMicroscopeManager.py Adds virtual microscope image mode for stage offset calibration.
imswitch/imcontrol/model/managers/positioners/VirtualStageManager.py Applies persisted offsets and exposes raw device position.
imswitch/imcontrol/model/managers/positioners/PositionerManager.py Defines canonical stage-offset API behavior.
imswitch/imcontrol/model/managers/positioners/ESP32StageManager.py Implements raw device position and updated offset contract.
imswitch/imcontrol/model/managers/detectors/HikCamManager.py Whitespace cleanup.
imswitch/imcontrol/controller/controllers/StageCenterCalibrationController.py Persists/restores heatmap data and parks at brightest sample.
imswitch/imcontrol/controller/controllers/PositionerController.py Adds raw-position API and updated offset persistence.
imswitch/imcontrol/controller/controllers/ExperimentController.py Adds backend Ashlar stitching task API.
frontend/src/state/store.js Removes deleted calibration reducers.
frontend/src/state/slices/StageOffsetCalibrationSlice.js Removes legacy stage offset calibration state.
frontend/src/state/slices/StageCenterCalibrationSlice.js Removes legacy stage center calibration state.
frontend/src/state/slices/ExperimentSlice.js Adds Ashlar stitching experiment flag.
frontend/src/constants/appRegistry.js Removes legacy calibration plugin entries.
frontend/src/components/wizard-steps/StageCenterStep1.js Removes legacy wizard step.
frontend/src/components/wizard-steps/StageCenterStep2.js Removes legacy wizard step.
frontend/src/components/wizard-steps/StageCenterStep3.js Removes legacy wizard step.
frontend/src/components/wizard-steps/StageCenterStep4.js Removes legacy wizard step.
frontend/src/components/wizard-steps/StageCenterStep5.js Removes legacy wizard step.
frontend/src/components/wizard-steps/StageCenterStep6.js Removes legacy wizard step.
frontend/src/components/StageOffsetCalibrationController.jsx Removes legacy calibration UI.
frontend/src/components/StageOffsetCalibrationController.js Removes legacy calibration UI.
frontend/src/components/StageMapVisualization.js Removes legacy stage map component.
frontend/src/components/StageMapCanvas.js Removes legacy stage map wrapper.
frontend/src/components/StageCenterCalibrationWizard.js Removes legacy stage center wizard.
frontend/src/components/LiveStreamTile.js Removes legacy live stream tile used by deleted wizard.
frontend/src/components/JoystickController.js Removes legacy joystick component.
frontend/src/components/FRAMESettings/StageOffsetCalibrationTab.js Replaces calibration workflow with homing, scan, heatmap, and confirmed offset storage.
frontend/src/backendapi/apiStageCenterCalibrationGetStatus.js Removes obsolete status API wrapper.
frontend/src/backendapi/apiStageCenterCalibrationGetLatestHeatmap.js Adds latest-heatmap API wrapper.
frontend/src/backendapi/apiPositionerControllerSetStageOffsetAxis.js Updates offset API wrapper to use currentDevicePosition.
frontend/src/backendapi/apiPositionerControllerGetDevicePositionAxis.js Adds raw device position API wrapper.
frontend/src/backendapi/apiExperimentControllerRunAshlarStitching.js Adds frontend API wrapper to start Ashlar stitching.
frontend/src/axon/WellSelectorComponent.js Updates calibration guidance copy.
frontend/src/axon/WellSelectorCanvas.js Adds confirmed right-click “We are here” offset calibration.
frontend/src/axon/experiment-designer/TilingDimension.js Adds Ashlar as a stitching mode.
frontend/src/axon/experiment-designer/ExperimentDesigner.js Auto-starts Ashlar stitching after experiment status transitions to idle.
frontend/src/App.jsx Removes legacy calibration plugin rendering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1701 to +1704
target_dir = experimentDir.strip() if experimentDir else ""
if not target_dir:
target_dir = getattr(self, "_last_experiment_dir", "")
if not target_dir or not os.path.isdir(target_dir):
Comment on lines +111 to +115
if (
prev === Status.RUNNING &&
curr === Status.IDLE &&
experimentState.parameterValue.ome_write_ashlar_stitch
) {
Comment on lines 433 to 434
const stageX = xMin + (cx / (w - cellW)) * xRange;
const stageY = yMin + ((h - cellH - cy) / (h - cellH)) * yRange;
beniroquai and others added 13 commits May 27, 2026 14:57
Co-authored-by: Copilot <copilot@github.com>
Add explicit support for single-objective systems by tracking per-slot configuration and conditionally hiding UI and actions.

Frontend: introduce slotConfigured in ObjectiveSlice (default [true,true]) and add setSlotConfigured action; middleware now dispatches slotConfigured from controller status. Components (ObjectiveSwitcher, ObjectiveController, ObjectiveCalibrationWizard) read slotConfigured to hide or alter controls and build the calibration wizard steps dynamically (skipping slot-1 steps in single-objective mode). UI also shows explanatory captions when slot 1 is unavailable.

Backend: ObjectiveController now computes which slots are actually configured (name present && magnification > 0), exposes an isSlotConfigured API, includes slotConfigured in the status payload, recomputes configured state when parameters change, and refuses axis-A motions for unconfigured slots to avoid invalid commands.

These changes prevent presenting controls for absent objectives and stop issuing motions to unconfigured slots, improving robustness for single-objective hardware.
Detect LED matrix hardware and expose synthetic illumination channels ("LED Matrix Ring" and "LED Matrix DPC") throughout the experiment controller. Key changes:

- ExperimentController: detect first LED matrix device, add channel name constants, extend ExperimentWorkflowParams with illuSourceKinds and append synthetic channels (ring/dpc) when present; set isDPCpossible flag; add set_led_matrix_pattern() helper to drive ring/halves/off patterns with settle timing; ensure performanceMode falls back when synthetic channels are used.
- ExecutionContext: add illumination_kinds and illumination_params fields and include them in the serialised context.
- ExperimentModeBase: allow passing channel_names into OME writer config.
- ExperimentNormalMode: thread illumination_kinds/illumination_params through workflow creation; compute effective OME channel counts and human-readable channel_names (expand DPC into 4 sub-channels); build workflow steps per-kind (default lasers, ring snapshots, 4-frame DPC halves) with proper per-frame LED-matrix control, writer indices, and channel_index bookkeeping; exclude synthetic channels from keep-illumination-on optimisation and ensure finalization turns LED matrix off once.
- Models: add IlluminationKind type, ParameterValue.illuminationParams for kind-specific params, and illuSourceKinds to ExperimentWorkflowParams; export IlluminationKind.

Backward compatibility: legacy clients still work (default kind assumed when missing); synthetic channels are handled best-effort and hardware errors are logged rather than failing runs.
Introduce illumination (lasers & LEDs) into stream presets and bump schema to v2.

- Add IlluminationIcon import and new STORAGE_KEY for v2 presets, keep STORAGE_KEY_V1 for fallback.
- Add migrateV1ToV2 to upgrade v1 shapes (adds empty illumination block and schemaVersion=2).
- readPresets now prefers v2, falls back to v1 and migrates in-memory (does not write v2 until user saves).
- Implement fetchIlluminationState to capture laser names/values/active and LED names (values may be null when getters are unavailable).
- Save now records illumination and schemaVersion when creating a preset.
- Applying a preset now sets laser and LED power/active via controller endpoints, collecting per-device errors and surfacing a combined warning if some illumination updates fail; backend stream params are still re-confirmed.
- UI: show illumination badge in preset menu entries and add a chip indicating illumination will be fetched on save.

This change lets users capture and re-apply illumination state with graceful degradation when controllers are missing or calls fail.
Add a full-featured Edit dialog for stream presets: new UI (Edit button, dialog form, Capture Current, Save, Save As New) and an overwrite-on-collision confirmation. Introduce state for editDraft, editOpen, editError and overwriteConfirm, plus helper functions to open/close the dialog, patch draft fields (including per-laser/LED helpers), capture live hardware into the draft, validate and normalize inputs, and persist updates or clones to the presets list. Also add necessary UI imports and icons (DialogContentText, Switch, Divider, EditIcon, RefreshIcon) and show validation errors in the dialog.
Add support for LED-matrix synthetic channels end-to-end and a debounced live-preview in the Wellplate designer. Frontend: clamp ring-radius slider to backend-provided ledMatrixInfo.maxRingRadius, add debounced preview calls to /setRing and /setHalves (coalesced to avoid flooding ESP32), and strengthen API helpers to forward optional RGB params via URLSearchParams. State: add ledMatrixInfo to ParameterRangeSlice and populate it from fetchExperimentControllerGetCurrentExperimentParams. Backend: ExperimentController now publishes ledMatrixInfo (nLedsX/nLedsY/maxRingRadius), promotes synthetic-channel RGB max values into illuIntensities so synthetic channels count as active, and hardens experiment_normal_mode by using name-based kind lookups (and a module DPC_SUB_DIRS constant) to avoid bugs from parallel-array misalignment. Add ledMatrixInfo field to the experiment workflow model. These changes make synthetic illumination UX safer and more robust against array alignment issues.
Introduce OpticalFlowController: a new controller that measures camera<->stage rotation alignment using optical flow, runs the stage move asynchronously, streams live angle samples via signals, and exposes start/abort/getstatus APIs (requires OpenCV). Register the controller in the example_virtual_microscope.json setup. Also update PositionerController to call positioner.move with explicit keyword arguments (value, axis, is_absolute, is_blocking, speed) and adjust the fallback call to the new signature to match the positioner API.
Replace the previous Lucas-Kanade / Farneback optical-flow pipeline with FFT-based phase correlation (cv2.phaseCorrelate). Add frame preprocessing (center crop to 1024px, float conversion, optional Hanning window), rename _toGray to _processFrame, and enable DEBUG dumps (phase_corr_debug.png/.tif). Harden movement abort/stop logic with multiple stop attempts, redundant stage stop calls, and a fallback reissue of current-position moves; abort response now includes the previous state. Misc: set OpticalFlowController.DEBUG = True, lower grabCameraFrame frameSync to 1 and add debug logging, adjust autofocus defaults (rangeZ/resolutionZ), and add ParameterValue.channelEnabledForExperiment to control per-channel inclusion in experiments.
Introduce an Optical Flow alignment feature and improve synthetic-channel intensity handling.

- Add OpticalFlowTab component (UI) and OpticalFlowSlice (Redux) and wire the reducer into the store; FRAMESettingsController exposes the new tab.
- WebSocketHandler now consumes sigUpdateFlowAngle, sigFlowStateChanged and sigFlowResult to drive the live plot and FSM state.
- ChannelsDimension: when toggling or editing synthetic channels (kinds 'ring'/'dpc'), mirror max(R,G,B) into illuIntensities for enabled channels so the backend active-channel gate/passthrough logic behaves correctly; keep intensity in sync when RGB params change.
- ExperimentController: backend now honors optional channelEnabledForExperiment — only promote synthetic RGB -> illuminationIntensities when the channel is explicitly enabled, and zero stale intensities for disabled channels while preserving legacy behavior for older clients.
- StreamPresets: add per-section save toggles and an option to apply objective Z when saving a preset.

These changes add the new optical-flow tooling and ensure frontend/backend agree about whether synthetic illumination channels are active, preventing silent passthrough collapse and mismatched illumination state.
Resolved conflicts:
- IlluminationController.js: kept HEAD's fetchLaserRuntimeState (handles
  synthetic LED-matrix channels via illuSourceKinds); adopted master's
  multi-line fetch URL formatting.
- ObjectiveSwitcher.js: adopted master's caption block; removed HEAD's
  redundant '+ Z' buttons (switchTo only accepts one arg).
- StreamPresets.js: kept HEAD entirely (preset edit dialog, save-as,
  validation, illumination capture); master's changes were cosmetic.
@beniroquai beniroquai enabled auto-merge May 28, 2026 14:35
@beniroquai beniroquai added this pull request to the merge queue May 28, 2026
Merged via the queue into master with commit ab63e4e May 28, 2026
14 checks passed
@beniroquai beniroquai deleted the fix/stageOffsetCalibration branch May 28, 2026 14:49
Franzili pushed a commit that referenced this pull request May 30, 2026
This pull request removes the old standalone calibration plugin UIs and
centralizes stage offset calibration directly in the
`WellSelectorCanvas` context menu, making the workflow safer and more
consistent. It also introduces Ashlar stitching as a selectable mode in
the experiment designer, with automatic triggering when experiments
finish, and adds new backend API bindings to support these workflows.

**Calibration workflow improvements:**

* Removed the `StageOffsetCalibration` and
`StageCenterCalibrationWizard` plugin components and their corresponding
UI entries in `App.jsx`, consolidating all calibration into the
`WellSelectorCanvas` right-click context menu.
[[1]](diffhunk://#diff-a0eba768ed9d2a17091f82d46efb5e1c988f08185cc1e9989366995cdb4e3ba9L28-R28)
[[2]](diffhunk://#diff-a0eba768ed9d2a17091f82d46efb5e1c988f08185cc1e9989366995cdb4e3ba9L44)
[[3]](diffhunk://#diff-a0eba768ed9d2a17091f82d46efb5e1c988f08185cc1e9989366995cdb4e3ba9L536-L538)
[[4]](diffhunk://#diff-a0eba768ed9d2a17091f82d46efb5e1c988f08185cc1e9989366995cdb4e3ba9L627-L629)
* Added a confirmation dialog for "We are here (calibrate offset)" in
`WellSelectorCanvas`, ensuring users confirm before overwriting the
stage offset. The dialog snapshots the raw device position before
writing, making the calibration atomic and robust.
[[1]](diffhunk://#diff-f9970f763f41caebe7bc5a295307ad98326a6de46dd85b11c15ae77b97fdb37fR68-R74)
[[2]](diffhunk://#diff-f9970f763f41caebe7bc5a295307ad98326a6de46dd85b11c15ae77b97fdb37fL1627-L1655)
[[3]](diffhunk://#diff-f9970f763f41caebe7bc5a295307ad98326a6de46dd85b11c15ae77b97fdb37fR1723-R1788)
* Updated the offset calibration API contract and logic to use explicit
device positions for deterministic calibration, and added a new API
binding to fetch the raw device position
(`apiPositionerControllerGetDevicePositionAxis`).
[[1]](diffhunk://#diff-9f0061e8db6d5946e7e4a00f4b00f05761dafd2cd0297480f0c2a4d7ecf3467eL2-R39)
[[2]](diffhunk://#diff-1651e146e302b8aec2ee0e93bb454aa1f9979e027e53ec7b37b0bfe4e31ca87eR1-R24)

**Experiment stitching enhancements:**

* Added Ashlar stitching as a new selectable mode in the tiling UI, with
state wiring to enable/disable it. When enabled, Ashlar stitching is
triggered automatically when an experiment finishes, with user feedback
in the UI.
[[1]](diffhunk://#diff-398755f7efc456e680e858235568936d178193e314b370b2d52d0180d9605a53L222-R231)
[[2]](diffhunk://#diff-398755f7efc456e680e858235568936d178193e314b370b2d52d0180d9605a53R251-R258)
[[3]](diffhunk://#diff-b04ccf6b6d7a6afaf25775f6c193446d8c5b87715fff7b650a1b22bca45dc988R55)
[[4]](diffhunk://#diff-b04ccf6b6d7a6afaf25775f6c193446d8c5b87715fff7b650a1b22bca45dc988R104-R132)
[[5]](diffhunk://#diff-92dde5a733fda5309ba396ffa8e5dedfe72530b4f5b8854fc100d78f29e14221R1-R28)

**API and code cleanup:**

* Removed the unused `apiStageCenterCalibrationGetStatus.js` and added a
new API binding for fetching the latest heatmap for center calibration
(`apiStageCenterCalibrationGetLatestHeatmap`).
[[1]](diffhunk://#diff-497310f2faca9db18e2055868da8f1ccc1f8e1b3ed6389579383a377bcd2ec4dL1-L29)
[[2]](diffhunk://#diff-f18f085a2774b63f41b19d9a2af0ef13212105b4c90f5b91183c395df64b1a2eR1-R23)

These changes streamline calibration workflows, reduce user error, and
add robust experiment stitching options.

---------

Co-authored-by: Copilot <copilot@github.com>
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