fix: stop live mode + pump Qt event loop during run_experiment#9
Draft
hinderling wants to merge 3 commits into
Draft
fix: stop live mode + pump Qt event loop during run_experiment#9hinderling wants to merge 3 commits into
hinderling wants to merge 3 commits into
Conversation
Two related fixes inside _run_mda_with_events: 1. Auto-stop continuous sequence acquisition before MDA. If live mode is still running when the MDA's first snapImage fires, napari-micromanager's _core_link._image_snapped handler reads the snap buffer with getImage() before the engine can, and the engine raises "Camera image buffer read failed". Stopping the sequence unconditionally before MDA starts removes that contention. 2. Pump Qt events instead of plain time.sleep / thread.join. _run_mda_with_events runs on the main thread, the same thread napari paints from. The backpressure throttle (time.sleep(0.1)) and the trailing mda_thread.join() block the Qt event loop, so napari freezes for the duration of the run and any ensure_main_thread-queued callbacks accumulate until the cell exits. Replace with _pump_qt_and_sleep / _qt_join helpers that call QCoreApplication.processEvents() between waits. Both fall back to plain blocking if Qt isn't loaded, so headless/test runs are unaffected.
The previous try/except swallowed three distinct failure modes — missing ``mmc`` attribute, RPC failure on proxy microscopes, and real core/hardware errors from ``isSequenceRunning`` or ``stopSequenceAcquisition`` — all silently. Surface the latter two so genuine failures aren't hidden behind a pre-emptive cleanup.
Controller._build_stim_slm (main thread) calls Analyzer.get_stim_mask, which blocks on FrameDispenser.wait_for_frame -- a plain threading.Condition.wait() with no Qt awareness. The actual stim-mask compute is already off-main (it runs in the Analyzer's ThreadPoolExecutor alongside cellpose / DINOv3 / tracking), but the main thread's wait for that result still freezes the Qt event loop, so napari hangs for the GPU's worth of seconds on every stim frame. Slice the wait into 50 ms chunks and call QCoreApplication.processEvents() between attempts, preserving the caller-supplied total timeout. Falls back to a plain wait_for_frame if Qt isn't loaded, so headless / test runs keep their original behaviour.
Collaborator
Author
|
resolved with #10 (running acquisition in separate thread) |
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
Two related fixes inside
Controller._run_mda_with_events:Auto-stop continuous sequence acquisition before MDA. If live mode is still running when the MDA's first
snapImagefires, napari-micromanager's_core_link._image_snappedhandler reads the snap buffer withgetImage()before the engine can, and the engine raisesRuntimeError: Camera image buffer read failed.. Stopping the sequence unconditionally before MDA starts removes that contention without requiring the user to remember to press Stop Live.Pump Qt events instead of plain
time.sleep/thread.join._run_mda_with_eventsruns on the main thread — the same thread napari paints from. The backpressure throttle (time.sleep(0.1)while the MDA worker drains the queue) and the trailingmda_thread.join()both block the Qt event loop, so napari freezes for the entire run and anyensure_main_thread-queued callbacks (notably napari-micromanager's own per-frame preview update) pile up until the cell exits. Two helpers,_pump_qt_and_sleepand_qt_join, callQCoreApplication.processEvents()between waits so napari stays interactive throughout. Both fall back to plain blocking if Qt isn't loaded, so non-GUI test runs are unaffected.Why
With the two fixes together:
ctrl.run_experiment(events, ...)runs end-to-end without theCamera image buffer read failedrace, napari stays responsive, and napari-mm's existingpreviewlayer updates live as frames arrive — no controller-side viewer wrapper needed.Verification
Tested on the Moench rig against
random_stim_per_cell_circle.ipynb:Camera image buffer read failed.whenever the user had pressed Live during the focus-check cells.Test plan
ctrl.run_experimentruns to completion with napari-micromanager open and live-mode either on or off at the start.