From 36ab7272c680cd49ba4338fafa245cebfa872d68 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Fri, 15 May 2026 15:58:28 +0200 Subject: [PATCH 01/41] feat: async run_experiment via RunHandle + cancellation + status widget Move the MDA feed loop onto a worker thread, expose live status through a RunHandle + psygnal Signal, and add a minimal napari widget that mirrors the current run. Breaking change: ctrl.run_experiment(events, ...) and ctrl.continue_experiment(...) now return a RunHandle immediately instead of blocking until the run is done. Existing notebooks that did `ctrl.run_experiment(events, ...)` must be updated to either `handle = ctrl.run_experiment(events, ...); handle.wait()` for the old blocking semantics, or to use the new non-blocking flow (poll handle.status(), subscribe to handle.statusChanged, call handle.cancel() to stop early). What's in this commit: - faro/core/run_status.py (new): * RunStatus -- immutable snapshot dataclass with state, event/FOV indices, frame count, lag_ms, error info. * RunHandle -- owns the worker thread + cooperative cancel event, exposes status()/wait()/cancel()/is_running() + a psygnal statusChanged signal that emits the latest RunStatus on each update. Subscribers on the main thread see queued-connection delivery via psygnal's Qt integration. - faro/core/controller.py: * Controller exposes a class-level runStarted = Signal(object). Fires on every new run/continue so widgets can re-bind. * run_experiment / continue_experiment spawn a worker thread, return the handle, emit runStarted. Validation still happens synchronously so a bad event list raises on the calling thread. * _run_worker centralises pre-flight setup (writer init -- including the potentially-slow zarr rmtree on overwrite -- and Analyzer construction) and wraps the feed loop in try/except so worker-side failures land in handle.fatal_error rather than crashing the user. * _run_mda_with_events accepts the handle, checks handle.cancel_event at each loop iteration and in the backpressure throttle, asks the engine to cancel the in-flight event when set, and emits status updates on each RTMEvent dequeue. * _on_frame_ready (and ControllerSimulated._on_frame_ready) call a shared _bump_status_for_frame helper that increments n_frames_received and computes lag_ms vs event.min_start_time. * Now off the main thread, all the prior Qt-pumping helpers (_pump_qt_and_sleep, _qt_join, _wait_for_frame_pumping_qt) and the superqt ensure_main_thread import are obsolete and removed. The preview-layer machinery (viewer=, _on_preview_frame, _apply_preview, PREVIEW_LAYER_NAME) is also removed -- napari-micromanager's own _NapariMDAHandler already routes generator events into the preview layer. * finish_experiment now waits for the current handle before shutting down the Analyzer. * _pending_sentinels guarded by a Lock since extend_experiment now runs on the calling thread while the feed loop runs on the worker. - faro/widgets/experiment_status.py (new): * ExperimentStatusWidget -- read-out of state, FOV, event index, frame count, lag, elapsed time, error count. Has a Stop button that calls handle.cancel(). Subscribes to controller.runStarted so it automatically re-binds when a new run begins; cleans up the previous handle's signal subscription on each rebind. Verified end-to-end via a Qt smoke test: - Live updates flow from the worker thread to the widget on the main thread (psygnal+Qt queued delivery). - Stop button triggers handle.cancel(); the worker's cancel-check fires within one iteration and the run exits at the next event boundary. - Starting a new run re-binds the widget to the new handle and resets the progress bar / counters. --- faro/core/controller.py | 337 ++++++++++++++++++++++-------- faro/core/run_status.py | 141 +++++++++++++ faro/widgets/__init__.py | 8 + faro/widgets/experiment_status.py | 190 +++++++++++++++++ 4 files changed, 593 insertions(+), 83 deletions(-) create mode 100644 faro/core/run_status.py create mode 100644 faro/widgets/__init__.py create mode 100644 faro/widgets/experiment_status.py diff --git a/faro/core/controller.py b/faro/core/controller.py index b3e92a0..105c730 100644 --- a/faro/core/controller.py +++ b/faro/core/controller.py @@ -1,5 +1,6 @@ from faro.core.pipeline import store_img, ImageProcessingPipeline from faro.core.data_structures import FovState, ImgType, StimMode +from faro.core.run_status import RunHandle, RunStatus from faro.core.writers import ( Writer, TiffWriter, @@ -12,11 +13,13 @@ ) from faro.stimulation.base import Stim, StimWithImage, StimWithPipeline +import contextlib import threading import traceback from dataclasses import dataclass from typing import Literal from faro.core._useq_compat import SLMImage +from psygnal import Signal from useq import MDAEvent from queue import Queue, Empty as QueueEmpty import numpy as np @@ -570,6 +573,11 @@ class Controller: STOP_EVENT = object() + # Emitted on each new ``run_experiment`` / ``continue_experiment`` call, + # carrying the freshly-created RunHandle. Widgets subscribe to this so + # they can re-bind to whichever run is current. + runStarted = Signal(object) + def __init__(self, mic, pipeline, *, writer: Writer | None = None): """ Args: @@ -577,6 +585,15 @@ def __init__(self, mic, pipeline, *, writer: Writer | None = None): pipeline: ImageProcessingPipeline instance. writer: Storage backend. If None, Analyzer uses TiffWriter (default). Pass an OmeZarrWriter for OME-Zarr output. + + Note: + ``run_experiment`` and ``continue_experiment`` are *non-blocking* + in this version: they spawn a worker thread and return a + :class:`RunHandle` immediately. Call ``handle.wait()`` to block + until the run finishes, ``handle.cancel()`` to abort, or + subscribe to ``handle.statusChanged`` for live updates. The + ``runStarted`` signal on the controller fires for every new run + so widgets can re-bind. """ self._mic = mic self._pipeline = pipeline @@ -592,6 +609,7 @@ def __init__(self, mic, pipeline, *, writer: Writer | None = None): self._experiment_start: float | None = None self._event_queue: Queue | None = None # for extend_experiment self._pending_sentinels: int = 0 # number of None sentinels yet to consume + self._pending_sentinels_lock = threading.Lock() self._fov_positions: dict[int, tuple[float, float, float]] = {} self._pre_loop_hook: callable | None = None # testing hook self._all_events: list = [] # accumulated events for JSON persistence @@ -600,10 +618,14 @@ def __init__(self, mic, pipeline, *, writer: Writer | None = None): # Survives finish_experiment() so tests/notebooks can inspect it. self.background_errors: list[BackgroundError] = [] - # Fatal condition raised from the signal-callback thread, re-raised - # after the MDA drains (see _on_frame_ready + _run_mda_with_events). + # Fatal condition raised from the signal-callback thread, surfaced + # through the handle's RunStatus.fatal_error. self._fatal_error: BaseException | None = None + # Current run handle (None when no run is in progress / between runs). + # The worker thread owns it; status update sites use it via this attr. + self._current_handle: RunHandle | None = None + # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ @@ -623,23 +645,35 @@ def validate_events(self, events) -> bool: ok = self._mic.validate_hardware(events) and ok return ok - def run_experiment(self, events, *, stim_mode="current", validate=True): - """Run an acquisition from a list of RTMEvents. + def run_experiment( + self, events, *, stim_mode="current", validate=True + ) -> RunHandle: + """Start an acquisition asynchronously. Returns immediately. + + The MDA feed loop runs in a worker thread; ``RunHandle`` exposes + ``wait()`` / ``cancel()`` / ``status()`` and a ``statusChanged`` + signal for live observation. There is **no** synchronous fallback — + callers that previously did ``ctrl.run_experiment(events)`` must + now do ``ctrl.run_experiment(events).wait()`` if they want the old + blocking semantics. ``handle.wait()`` re-raises any worker-side + ``fatal_error`` so the prior raise-on-failure behaviour is + preserved when callers explicitly opt in. Args: - events: Iterable of RTMEvent. Will be materialised to a list - when ``validate=True`` so it can be iterated twice. - stim_mode: How stim masks are resolved when the stimulator needs - labels or images. - - * ``"current"`` -- acquire the imaging frame, wait for the - pipeline to segment it and produce the mask, then stimulate - within the same timepoint. - * ``"previous"`` -- stimulate using the mask produced from the - *previous* timepoint (for the same FOV). - validate: Run :meth:`validate_events` before starting. Set to - ``False`` to skip (e.g. if you already validated manually). + events: Iterable of RTMEvent. Materialised to a list. + stim_mode: How stim masks are resolved (``"current"`` / + ``"previous"``). See previous docstring for the gritty + semantics. + validate: Run :meth:`validate_events` before starting. Validation + still happens *synchronously* before the worker spawns so + bad event lists surface as exceptions on the calling thread. + + Raises: + RuntimeError: If a previous run is still in progress. Call + ``handle.wait()`` or ``handle.cancel()`` first. + ValueError: If ``validate=True`` and events fail validation. """ + self._require_no_active_run() events = list(events) if validate: if not self.validate_events(events): @@ -648,58 +682,35 @@ def run_experiment(self, events, *, stim_mode="current", validate=True): "Fix the issues or pass validate=False to skip." ) - if self._experiment_start is None: - self._experiment_start = time.monotonic() - - # Pre-compute offset so extend_experiment can use it during the run - if events: - self._t_offset = max(e.index.get("t", 0) for e in events) + 1 - - # Persist events to storage as JSON - self._all_events = list(events) - if self._writer is not None: - self._writer.save_events(self._all_events) - - # Initialize writer stream with values derived from events + microscope - if ( - isinstance(self._writer, OmeZarrWriter) - and self._writer._stream is None - and self._writer._raw_array is None - ): - self._writer.init_stream( - position_names=_extract_positions_from_events(events), - channel_names=_extract_channel_names_from_events(events), - image_height=self._mic.mmc.getImageHeight(), - image_width=self._mic.mmc.getImageWidth(), - n_timepoints=_extract_n_timepoints_from_events(events), - n_stim_channels=_extract_n_stim_channels_from_events(events), - ) + handle = RunHandle(n_events_total=len(events)) + self._current_handle = handle - self._analyzer = Analyzer(self._pipeline, writer=self._writer) - self._analyzer.stim_mode = stim_mode - self._validate_fov_positions(events) - self._run_mda_with_events(events, stim_mode=stim_mode) - - # Update wall-clock offset for continuation - self._time_offset = time.monotonic() - self._experiment_start - - def continue_experiment(self, events, *, stim_mode="current", validate=True): - """Continue acquisition with new events, preserving all pipeline state. + handle._thread = threading.Thread( + target=self._run_worker, + args=(events, stim_mode, handle), + kwargs={"is_continue": False}, + name="FaroRunWorker", + daemon=True, + ) + handle._thread.start() + self.runStarted.emit(handle) + return handle - Reuses the existing ``Analyzer`` (and its ``FovState`` objects) so - that tracking, timestep counters, and filenames continue seamlessly - from the previous ``run_experiment()`` or ``continue_experiment()`` - call. + def continue_experiment( + self, events, *, stim_mode="current", validate=True + ) -> RunHandle: + """Continue acquisition with new events, preserving Analyzer state. - Args: - events: Iterable of RTMEvent. Timesteps and metadata will be - offset automatically. - stim_mode: Same as :meth:`run_experiment`. - validate: Same as :meth:`run_experiment`. + Same async semantics as :meth:`run_experiment`. Reuses the existing + ``Analyzer`` and per-FOV state so tracking and timestep counters + continue seamlessly across runs. Raises: - RuntimeError: If no previous experiment exists to continue. + RuntimeError: If no previous experiment exists to continue, or + if a run is still in progress. + ValueError: If ``validate=True`` and events fail validation. """ + self._require_no_active_run() if self._analyzer is None: raise RuntimeError( "No experiment to continue. Call run_experiment() first." @@ -722,20 +733,93 @@ def continue_experiment(self, events, *, stim_mode="current", validate=True): offset_events = self._offset_events(events) - # Pre-compute offset so extend_experiment can use it during the run - if offset_events: - self._t_offset = max(e.index.get("t", 0) for e in offset_events) + 1 + handle = RunHandle(n_events_total=len(offset_events)) + self._current_handle = handle - # Append to accumulated events and persist - self._all_events.extend(offset_events) - if self._writer is not None: - self._writer.save_events(self._all_events) + handle._thread = threading.Thread( + target=self._run_worker, + args=(offset_events, stim_mode, handle), + kwargs={"is_continue": True}, + name="FaroRunWorker", + daemon=True, + ) + handle._thread.start() + self.runStarted.emit(handle) + return handle + + def _require_no_active_run(self) -> None: + """Raise if a run is still in progress.""" + if self._current_handle is not None and self._current_handle.is_running(): + raise RuntimeError( + "An experiment is already running. Call handle.wait() or " + "handle.cancel() first." + ) + + def _run_worker( + self, events, stim_mode: str, handle: RunHandle, /, *, is_continue: bool + ) -> None: + """Background-thread entry point for an experiment run. - self._validate_fov_positions(offset_events) - self._run_mda_with_events(offset_events, stim_mode=stim_mode) + Owns: writer init (incl. potentially-slow zarr ``rmtree`` on overwrite), + ``Analyzer`` construction (or reuse for continue), the feed loop, and + the final wall-clock offset update. All status updates flow through + ``handle.update`` so listeners see the progression. + """ + handle.update(state="running", started_at=time.monotonic()) + try: + # ---- pre-loop setup ----------------------------------------- + if self._experiment_start is None: + self._experiment_start = time.monotonic() + + if events: + self._t_offset = max(e.index.get("t", 0) for e in events) + 1 + + if is_continue: + self._all_events.extend(events) + else: + self._all_events = list(events) + + if self._writer is not None: + self._writer.save_events(self._all_events) + + if ( + not is_continue + and isinstance(self._writer, OmeZarrWriter) + and self._writer._stream is None + and self._writer._raw_array is None + ): + # NB: on a network drive an overwrite-existing init can do a + # multi-minute rmtree. With the feed loop on a worker thread + # this no longer freezes napari; status stays "running" until + # the actual MDA starts. + self._writer.init_stream( + position_names=_extract_positions_from_events(events), + channel_names=_extract_channel_names_from_events(events), + image_height=self._mic.mmc.getImageHeight(), + image_width=self._mic.mmc.getImageWidth(), + n_timepoints=_extract_n_timepoints_from_events(events), + n_stim_channels=_extract_n_stim_channels_from_events(events), + ) - # Update wall-clock offset for continuation - self._time_offset = time.monotonic() - self._experiment_start + if not is_continue: + self._analyzer = Analyzer(self._pipeline, writer=self._writer) + self._analyzer.stim_mode = stim_mode + + self._validate_fov_positions(events) + + # ---- the feed loop ------------------------------------------ + self._run_mda_with_events(events, stim_mode=stim_mode, handle=handle) + + except BaseException as exc: + traceback.print_exc() + handle.update( + state="error", fatal_error=exc, finished_at=time.monotonic() + ) + else: + # Update wall-clock offset for continuation + if self._experiment_start is not None: + self._time_offset = time.monotonic() - self._experiment_start + handle.update(state="done", finished_at=time.monotonic()) def extend_experiment(self, events): """Add more events to a running experiment (non-blocking). @@ -751,8 +835,11 @@ def extend_experiment(self, events): events = list(events) offset_events = self._offset_events(events) - # Add events + sentinel; bump counter so the loop keeps going - self._pending_sentinels += 1 + # Add events + sentinel; bump counter so the loop keeps going. Lock + # because the feed loop now reads _pending_sentinels from the worker + # thread while extend_experiment runs from the caller's thread. + with self._pending_sentinels_lock: + self._pending_sentinels += 1 for ev in offset_events: self._event_queue.put(ev) self._event_queue.put(None) # sentinel for this batch @@ -770,7 +857,14 @@ def finish_experiment(self, *, drain_timeout: float = 300.0): pipeline queues to drain before raising ``TimeoutError``. On timeout no teardown happens, so the call is safe to retry with a larger value. + + If a run is still in progress this blocks until it finishes — + cancel it first via ``handle.cancel()`` if you want to abort. """ + if self._current_handle is not None and self._current_handle.is_running(): + self._current_handle.wait() + self._current_handle = None + if self._analyzer is not None: # shutdown is the gate — only snapshot background_errors and # drop the Analyzer once it succeeds. On TimeoutError the @@ -787,6 +881,9 @@ def finish_experiment(self, *, drain_timeout: float = 300.0): self._frame_buffers.clear() def stop_run(self): + """Hard-stop the run path (legacy). Prefer ``handle.cancel()``.""" + if self._current_handle is not None: + self._current_handle.cancel() self._queue.put(self.STOP_EVENT) self._mic.cancel_mda() if self._analyzer is not None: @@ -834,15 +931,33 @@ def _validate_fov_positions(self, events): ) self._fov_positions[fov] = pos - def _run_mda_with_events(self, events, *, stim_mode): - """Run the MDA event loop — shared by run/continue_experiment.""" + def _run_mda_with_events(self, events, *, stim_mode, handle: RunHandle): + """Run the MDA event loop on the worker thread. + + Called from :meth:`_run_worker`. The whole body runs off the main + thread, so blocking primitives (``time.sleep``, ``thread.join``, + ``FrameDispenser.wait_for_frame``) no longer freeze napari. The + feed loop checks ``handle.cancel_event`` at each iteration so + ``handle.cancel()`` returns control without a Ctrl-C. + """ + # Live mode (continuous sequence acquisition) and MDA both drive the + # camera. If live is still running when the MDA's first snapImage + # fires, the snap buffer is consumed by the live-poll listener (in + # napari-micromanager: _core_link._image_snapped) before the engine + # calls getImage, and the engine raises "Camera image buffer read + # failed". Stop it unconditionally before MDA starts. + mmc = getattr(self._mic, "mmc", None) + if mmc is not None and mmc.isSequenceRunning(): + mmc.stopSequenceAcquisition() + self._mic.connect_frame(self._on_frame_ready) # Set up event queue for extend_experiment support. # _pending_sentinels tracks how many extra batches (from # extend_experiment) still need to be drained. self._event_queue = Queue() - self._pending_sentinels = 0 + with self._pending_sentinels_lock: + self._pending_sentinels = 0 events = sorted( events, key=lambda e: (e.min_start_time or 0, e.index.get("p", 0)) ) @@ -858,16 +973,43 @@ def _run_mda_with_events(self, events, *, stim_mode): try: while True: - rtm_event = self._event_queue.get() + if handle.cancel_event.is_set(): + break + + # Short timeout so cancellation is responsive even when + # the queue is empty (waiting for extend_experiment). + try: + rtm_event = self._event_queue.get(timeout=0.1) + except QueueEmpty: + continue + if rtm_event is None: # Sentinel consumed — stop only if no extension pending - if self._pending_sentinels > 0: - self._pending_sentinels -= 1 - continue + with self._pending_sentinels_lock: + if self._pending_sentinels > 0: + self._pending_sentinels -= 1 + continue break + # Status update: the feed loop committed to this RTMEvent. + prev = handle.status() + fov = rtm_event.index.get("p") + handle.update( + current_event_index=dict(rtm_event.index), + current_fov=fov, + n_events_consumed=prev.n_events_consumed + 1, + ) + + # Backpressure: don't get too far ahead of the MDA engine. + # Plain time.sleep is fine here -- this is a worker thread, + # not the main thread, so napari's event loop is untouched. while self._queue.qsize() >= 3: - time.sleep(0.1) + if handle.cancel_event.is_set(): + break + time.sleep(0.05) + if handle.cancel_event.is_set(): + break + self._n_channels = len(rtm_event.channels) # In "previous" mode at t=0 there is no predecessor @@ -909,9 +1051,17 @@ def _run_mda_with_events(self, events, *, stim_mode): self._event_queue = None self._queue.put(self.STOP_EVENT) if mda_thread is not None: + if handle.cancel_event.is_set(): + # Ask the engine to drop the in-flight event so the + # worker thread can exit promptly. + with contextlib.suppress(Exception): + self._mic.cancel_mda() mda_thread.join() self._mic.disconnect_frame(self._on_frame_ready) + # _fatal_error from a signal-callback thread surfaces through the + # handle's RunStatus.fatal_error -- _run_worker reads it after we + # return. Re-raise so the worker's try/except can record it. if self._fatal_error is not None: fatal = self._fatal_error self._fatal_error = None @@ -921,11 +1071,31 @@ def _run_mda_with_events(self, events, *, stim_mode): # Frame handling # ------------------------------------------------------------------ + def _bump_status_for_frame(self, event: MDAEvent) -> None: + """Update RunHandle counters for the current frame; no-op if no handle.""" + handle = self._current_handle + if handle is None: + return + prev = handle.status() + wallclock = time.time() + lag_ms: float | None = None + min_start = getattr(event, "min_start_time", None) + if min_start is not None and prev.started_at is not None: + elapsed = time.monotonic() - prev.started_at + lag_ms = (elapsed - min_start) * 1000.0 + handle.update( + n_frames_received=prev.n_frames_received + 1, + last_frame_wallclock=wallclock, + lag_ms=lag_ms, + ) + def _on_frame_ready(self, img: np.ndarray, event: MDAEvent) -> None: # Drop subsequent frames after a fatal error — the MDA is winding down. if self._fatal_error is not None: return + self._bump_status_for_frame(event) + meta = event.metadata or {} img_type = meta.get("img_type", ImgType.IMG_RAW) @@ -1081,6 +1251,7 @@ def _read_zarr_raw(self, timestep: int, fov: int) -> np.ndarray: def _on_frame_ready(self, img: np.ndarray, event: MDAEvent) -> None: """Override to load images from disk for simulated controller.""" + self._bump_status_for_frame(event) meta = event.metadata or {} img_type = meta.get("img_type", ImgType.IMG_RAW) diff --git a/faro/core/run_status.py b/faro/core/run_status.py new file mode 100644 index 0000000..ef21663 --- /dev/null +++ b/faro/core/run_status.py @@ -0,0 +1,141 @@ +"""Status reporting + cancellation handle for asynchronous experiment runs. + +``Controller.run_experiment`` returns a ``RunHandle``. The handle owns: + +* the worker thread driving the MDA feed loop, +* a cooperative cancellation event, +* an immutable ``RunStatus`` snapshot that the controller's threads update + via :meth:`RunHandle.update`, +* a ``statusChanged`` :mod:`psygnal` signal that UI widgets (and any other + observer) can subscribe to for live updates. + +The handle is thread-safe: callers and the worker can both read ``status()`` +and the worker can call ``update(...)`` without coordination from the +caller. Status updates emit ``statusChanged`` synchronously; with ``qtpy`` +loaded :mod:`psygnal` routes the emission to listeners' threads through +Qt's queued connections, so a napari widget connected on the main thread +sees a queued slot call from the worker without extra plumbing. +""" +from __future__ import annotations + +import threading +from dataclasses import dataclass, field, replace +from typing import TYPE_CHECKING, Any, Literal + +from psygnal import Signal + +if TYPE_CHECKING: + pass + + +RunState = Literal["pending", "running", "cancelling", "done", "error"] + + +@dataclass(frozen=True) +class RunStatus: + """Immutable snapshot of an experiment run.""" + + state: RunState = "pending" + # Latest RTMEvent index the feed loop committed to the MDA queue. + current_event_index: dict[str, int] | None = None + current_fov: int | None = None + # Counts. + n_events_total: int = 0 # how many RTMEvents the run was started with + n_events_consumed: int = 0 # RTMEvents pulled by the feed loop so far + n_frames_received: int = 0 # frames acknowledged via frameReady + # Timing. + started_at: float | None = None # time.monotonic() when the worker began + finished_at: float | None = None # time.monotonic() when the worker exited + last_frame_wallclock: float | None = None + # Lag: (time.monotonic() - started_at) - event.min_start_time, in ms, + # at the most recent frame_ready. Positive == we're behind schedule. + lag_ms: float | None = None + # Pipeline / storage backpressure visibility (best-effort). + pipeline_inflight: int = 0 + storage_queue_depth: int = 0 + # Errors. ``background_errors`` accumulates analyzer-side issues; ``fatal_error`` + # is set when the worker itself raises. + background_errors: tuple[Any, ...] = field(default_factory=tuple) + fatal_error: BaseException | None = None + + +class RunHandle: + """Handle returned by ``Controller.run_experiment`` / ``continue_experiment``. + + Use ``wait()`` to block until the run finishes, ``cancel()`` to request a + graceful stop, ``status()`` for a snapshot, or subscribe to + ``statusChanged`` for live updates. ``statusChanged`` is a per-instance + :mod:`psygnal` signal that emits the latest ``RunStatus`` after every + update. + """ + + # psygnal class-level Signal is a descriptor; access via ``handle.statusChanged`` + # gives an instance-bound signal. Different handles have independent signals. + statusChanged = Signal(RunStatus) + + def __init__(self, n_events_total: int = 0) -> None: + self._lock = threading.RLock() + self._status = RunStatus(state="pending", n_events_total=n_events_total) + self._cancel_event = threading.Event() + self._thread: threading.Thread | None = None + + # -- public API --------------------------------------------------------- + + def status(self) -> RunStatus: + """Return the current immutable status snapshot.""" + with self._lock: + return self._status + + def wait(self, timeout: float | None = None) -> RunStatus: + """Block until the worker thread finishes (or ``timeout`` elapses). + + Returns the final ``RunStatus``. Re-raises ``fatal_error`` if the + worker crashed -- mirroring the previous synchronous-run behaviour. + """ + if self._thread is not None: + self._thread.join(timeout) + status = self.status() + if status.fatal_error is not None: + raise status.fatal_error + return status + + def cancel(self) -> None: + """Request graceful cancellation. Idempotent. Does not block. + + Sets the cancel event the feed loop polls on each iteration; on the + next poll the loop stops feeding new events, asks the MDA engine to + abort the in-flight event, and exits. Use ``wait()`` afterwards to + block until the worker actually stops. + """ + self._cancel_event.set() + with self._lock: + if self._status.state == "running": + self._status = replace(self._status, state="cancelling") + new_status = self._status + else: + return + self.statusChanged.emit(new_status) + + def is_running(self) -> bool: + """True if the worker thread is alive.""" + return self._thread is not None and self._thread.is_alive() + + # -- worker-side helpers ------------------------------------------------ + + @property + def cancel_event(self) -> threading.Event: + """The cooperative-cancel event the feed loop polls. Worker-side.""" + return self._cancel_event + + def update(self, **updates: Any) -> RunStatus: + """Atomically apply ``updates`` to the status snapshot and emit. + + Called from worker / pipeline / storage threads. ``statusChanged`` + listeners on the main thread see queued-connection delivery via + psygnal's Qt integration. + """ + with self._lock: + self._status = replace(self._status, **updates) + new_status = self._status + self.statusChanged.emit(new_status) + return new_status diff --git a/faro/widgets/__init__.py b/faro/widgets/__init__.py new file mode 100644 index 0000000..f57b352 --- /dev/null +++ b/faro/widgets/__init__.py @@ -0,0 +1,8 @@ +"""Optional UI widgets for visualising / controlling faro runs. + +These imports are guarded so that headless deployments don't pay the Qt +import cost just from ``import faro``. +""" +from faro.widgets.experiment_status import ExperimentStatusWidget + +__all__ = ["ExperimentStatusWidget"] diff --git a/faro/widgets/experiment_status.py b/faro/widgets/experiment_status.py new file mode 100644 index 0000000..e3e604b --- /dev/null +++ b/faro/widgets/experiment_status.py @@ -0,0 +1,190 @@ +"""Minimal Qt widget that mirrors a Controller's current ``RunHandle``. + +Construct with a :class:`faro.core.controller.Controller` instance: + + from faro.widgets import ExperimentStatusWidget + widget = ExperimentStatusWidget(ctrl) + viewer.window.add_dock_widget(widget, name="Experiment") + +The widget subscribes to ``ctrl.runStarted``, so it automatically re-binds +to whichever run is current. Each ``RunHandle.statusChanged`` emission +updates the labels and progress bar; the Stop button calls +``handle.cancel()``. Designed to be cheap to construct, cheap to update, +and trivially extensible (subclass and override ``_refresh`` to add +fields). +""" +from __future__ import annotations + +import time +from typing import TYPE_CHECKING + +from qtpy.QtCore import Qt +from qtpy.QtWidgets import ( + QFormLayout, + QHBoxLayout, + QLabel, + QProgressBar, + QPushButton, + QVBoxLayout, + QWidget, +) + +from faro.core.run_status import RunHandle, RunStatus + +if TYPE_CHECKING: + from faro.core.controller import Controller + + +_STATE_COLORS = { + "pending": "#888888", + "running": "#2e7d32", + "cancelling": "#ef6c00", + "done": "#1565c0", + "error": "#c62828", +} + + +class ExperimentStatusWidget(QWidget): + """A read-out + Stop button for the controller's currently-bound run.""" + + def __init__(self, controller: "Controller", parent: QWidget | None = None) -> None: + super().__init__(parent) + self._controller = controller + self._handle: RunHandle | None = None + + self._build_ui() + self._refresh(None) + + controller.runStarted.connect(self._on_run_started) + + # -- UI construction ---------------------------------------------------- + + def _build_ui(self) -> None: + self.setWindowTitle("Experiment status") + + self._state_label = QLabel("idle") + self._state_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self._state_label.setStyleSheet( + "font-weight: bold; padding: 4px; border-radius: 4px;" + ) + + self._progress = QProgressBar() + self._progress.setRange(0, 0) # indeterminate until we have totals + self._progress.setTextVisible(True) + + form = QFormLayout() + self._fov_label = QLabel("—") + self._event_label = QLabel("—") + self._frames_label = QLabel("0") + self._lag_label = QLabel("—") + self._elapsed_label = QLabel("—") + self._errors_label = QLabel("0") + form.addRow("Current FOV:", self._fov_label) + form.addRow("Event index:", self._event_label) + form.addRow("Frames received:", self._frames_label) + form.addRow("Lag (ms):", self._lag_label) + form.addRow("Elapsed:", self._elapsed_label) + form.addRow("Background errors:", self._errors_label) + + self._stop_btn = QPushButton("Stop") + self._stop_btn.clicked.connect(self._on_stop_clicked) + self._stop_btn.setEnabled(False) + + button_row = QHBoxLayout() + button_row.addStretch(1) + button_row.addWidget(self._stop_btn) + + layout = QVBoxLayout(self) + layout.addWidget(self._state_label) + layout.addWidget(self._progress) + layout.addLayout(form) + layout.addLayout(button_row) + layout.addStretch(1) + + # -- run binding -------------------------------------------------------- + + def _on_run_started(self, handle: RunHandle) -> None: + """Re-bind whenever the controller emits ``runStarted``.""" + if self._handle is not None: + try: + self._handle.statusChanged.disconnect(self._refresh) + except Exception: + pass + + self._handle = handle + handle.statusChanged.connect(self._refresh) + self._refresh(handle.status()) + + def _on_stop_clicked(self) -> None: + # cancel() is idempotent and a no-op when the run isn't running; + # rely on it rather than re-checking is_running here, so the button + # works even if state is "done"/"error" by the time the click lands. + if self._handle is not None: + self._handle.cancel() + + # -- refresh ------------------------------------------------------------ + + def _refresh(self, status: RunStatus | None) -> None: + """Slot connected to ``handle.statusChanged``. + + Also called once with ``None`` at construction time (no handle yet). + """ + if status is None: + self._state_label.setText("idle (no run yet)") + self._state_label.setStyleSheet( + "font-weight: bold; padding: 4px; border-radius: 4px; " + f"background-color: {_STATE_COLORS['pending']}; color: white;" + ) + self._progress.setRange(0, 0) + self._progress.setValue(0) + self._progress.setFormat("") + self._stop_btn.setEnabled(False) + return + + color = _STATE_COLORS.get(status.state, "#888888") + self._state_label.setText(status.state.upper()) + self._state_label.setStyleSheet( + "font-weight: bold; padding: 4px; border-radius: 4px; " + f"background-color: {color}; color: white;" + ) + + if status.n_events_total > 0: + self._progress.setRange(0, status.n_events_total) + self._progress.setValue(status.n_events_consumed) + self._progress.setFormat( + f"{status.n_events_consumed} / {status.n_events_total} events" + ) + else: + self._progress.setRange(0, 0) + self._progress.setValue(0) + self._progress.setFormat("") + + self._fov_label.setText( + "—" if status.current_fov is None else str(status.current_fov) + ) + self._event_label.setText( + "—" if status.current_event_index is None + else ", ".join(f"{k}={v}" for k, v in status.current_event_index.items()) + ) + self._frames_label.setText(str(status.n_frames_received)) + if status.lag_ms is None: + self._lag_label.setText("—") + else: + self._lag_label.setText(f"{status.lag_ms:+.0f}") + + if status.started_at is not None: + end = status.finished_at or time.monotonic() + self._elapsed_label.setText(f"{end - status.started_at:.1f}s") + else: + self._elapsed_label.setText("—") + + n_errors = len(status.background_errors) + if status.fatal_error is not None: + self._errors_label.setText( + f"{n_errors} background + fatal: {type(status.fatal_error).__name__}" + ) + else: + self._errors_label.setText(str(n_errors)) + + # Stop is only meaningful while the run is actually running. + self._stop_btn.setEnabled(status.state in ("running",)) From fa22eb43cc3501295e2f81ecb3bc3c1f2da9fa4a Mon Sep 17 00:00:00 2001 From: Hinderling Date: Fri, 15 May 2026 16:17:18 +0200 Subject: [PATCH 02/41] refactor: route image-size lookup through AbstractMicroscope, not mmc The OmeZarrWriter init in _run_worker still pulled image height/width via self._mic.mmc.getImageHeight/Width -- a pymmcore-plus-specific call that breaks any non-pymmcore microscope. Use the AbstractMicroscope-level convention: subclasses populate self.image_height / self.image_width on the microscope instance (Moench already does this in init_scope). Fall back to mmc if the attributes aren't present but mmc is, so existing pymmcore-only microscopes keep working without code changes. Raise a clear error when neither path is available. --- faro/core/controller.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/faro/core/controller.py b/faro/core/controller.py index 105c730..bc048d7 100644 --- a/faro/core/controller.py +++ b/faro/core/controller.py @@ -755,6 +755,28 @@ def _require_no_active_run(self) -> None: "handle.cancel() first." ) + def _get_image_size(self) -> tuple[int, int]: + """Return (height, width) of the microscope's camera frames. + + Prefers ``self._mic.image_height`` / ``image_width`` (the + ``AbstractMicroscope``-level convention; ``Moench.init_scope`` and + peers populate them on the microscope instance). Falls back to a + pymmcore-plus core call when the microscope exposes one. Raises if + neither is available. + """ + h = getattr(self._mic, "image_height", None) + w = getattr(self._mic, "image_width", None) + if h is not None and w is not None: + return h, w + mmc = getattr(self._mic, "mmc", None) + if mmc is not None: + return mmc.getImageHeight(), mmc.getImageWidth() + raise RuntimeError( + "Microscope does not expose image dimensions. Set " + "self.image_height / self.image_width on the microscope, or " + "provide a CMMCorePlus instance via self.mmc." + ) + def _run_worker( self, events, stim_mode: str, handle: RunHandle, /, *, is_continue: bool ) -> None: @@ -792,11 +814,12 @@ def _run_worker( # multi-minute rmtree. With the feed loop on a worker thread # this no longer freezes napari; status stays "running" until # the actual MDA starts. + img_h, img_w = self._get_image_size() self._writer.init_stream( position_names=_extract_positions_from_events(events), channel_names=_extract_channel_names_from_events(events), - image_height=self._mic.mmc.getImageHeight(), - image_width=self._mic.mmc.getImageWidth(), + image_height=img_h, + image_width=img_w, n_timepoints=_extract_n_timepoints_from_events(events), n_stim_channels=_extract_n_stim_channels_from_events(events), ) From 8bf38dd032d5c10d312dc3ed2b923d2228357604 Mon Sep 17 00:00:00 2001 From: hinderling Date: Fri, 15 May 2026 18:04:48 +0200 Subject: [PATCH 03/41] fix: make ExperimentStatusWidget work under napari/Qt Three independent bugs surfaced when running the new async run_experiment + ExperimentStatusWidget against a napari viewer (reproduced with the optogenetic virtual_microscope backend): 1. pymmcore-plus's signals_backend() auto-selects the *qt* backend whenever a QApplication is loaded. core.mda.events.frameReady then becomes a QtCore.SignalInstance and cross-thread emits land in Qt.QueuedConnection, where they're delivered only when the main thread pumps events. With Controller.run_experiment now spawning a worker and RunHandle.wait() joining on it, the main thread is typically idle-blocked exactly when the engine is firing frames -- so the controller's _on_frame_ready never ran, the engine completed "successfully" with zero frames received, and the pipeline never saw any data. Force PYMM_SIGNALS_BACKEND=psygnal in faro/microscope/base.py so the data path stays direct/synchronous on the engine thread regardless of whether Qt is loaded. The widget-side path (RunHandle.statusChanged) still uses psygnal's own queued delivery -- see fix #2. 2. ExperimentStatusWidget connected handle.statusChanged with the default (direct) connection. Status updates emitted from the worker thread therefore ran the widget's _refresh slot synchronously off-main, calling QLabel.setText / QProgressBar.setValue from a non-GUI thread. Under napari that lands in vispy's OpenGL compositor and aborts with "Cannot make QOpenGLContext current in a different thread" -> SIGABRT (kernel hard-crash in VSCode Jupyter). Switch to connect(..., thread="main") so psygnal queues the call into its main-thread queue. 3. psygnal's queued callbacks live in QueuedCallback._GLOBAL_QUEUE, which nothing drains by default -- the widget would be invoked on the main thread, but only when something explicitly calls psygnal.emit_queued(). RunHandle's docstring claims auto-Qt delivery; that's not how psygnal actually works. Call psygnal.qt.start_emitting_from_queue() in the widget's __init__, which installs a main-thread QTimer that fires emit_queued() on every Qt event-loop tick. Idempotent and global, so multiple widgets / multiple runs are safe. Lockfile: bump pymmcore-widgets (8c8f76e -> 48ff414) so the unrelated upstream crash in pymmcore_widgets._presets_widget._on_property_changed when handed an empty device label (virtual_microscope's shutter) is included. Without that bump, the MDA engine itself aborts on the first setShutterOpen() once frames actually start flowing. Verified end-to-end against virtual_microscope's optogenetic backend: - headless async run: 5/5 frames (regression check, unchanged) - napari.Viewer() + handle.wait(): 5/5 frames (was 0/5) - napari + napari-micromanager + widget: 5/5 frames, no crash, exit 0 - widget visibly updates progress / frames / state mid-experiment (sampled QLabel.text() while pumping Qt events) - 87 unit tests still pass --- faro/microscope/base.py | 16 ++++++ faro/widgets/experiment_status.py | 20 ++++++- uv.lock | 88 ++++++++++++++++++++----------- 3 files changed, 91 insertions(+), 33 deletions(-) diff --git a/faro/microscope/base.py b/faro/microscope/base.py index 0c4309b..f4d29ce 100644 --- a/faro/microscope/base.py +++ b/faro/microscope/base.py @@ -12,6 +12,22 @@ category=FutureWarning, ) +# Force pymmcore-plus to use the psygnal signal backend regardless of whether +# a QApplication is loaded. With "auto" (the default), pymmcore-plus picks +# the qt backend whenever Qt is around, which routes core.mda.events.frameReady +# through Qt.AutoConnection -- queued delivery to the main thread for any +# non-QObject listener. The Controller's frame handler runs on the MDA engine +# thread (no Qt objects touched); since Controller.run_experiment now spawns +# a worker thread and Controller.RunHandle.wait() joins on that worker, +# the main thread is typically blocked while the engine emits frames. +# A queued Qt connection on top of that means frame callbacks pile up +# undelivered -- the engine completes happily, but no frames reach the +# pipeline. Forcing 'psygnal' keeps the data path direct and synchronous, +# decoupled from any GUI loop. Widgets that subscribe to RunHandle's own +# psygnal signals still get Qt-routed updates because *those* receivers +# are QObjects. +os.environ.setdefault("PYMM_SIGNALS_BACKEND", "psygnal") + import numpy as np from useq import MDAEvent diff --git a/faro/widgets/experiment_status.py b/faro/widgets/experiment_status.py index e3e604b..67ef698 100644 --- a/faro/widgets/experiment_status.py +++ b/faro/widgets/experiment_status.py @@ -57,6 +57,18 @@ def __init__(self, controller: "Controller", parent: QWidget | None = None) -> N controller.runStarted.connect(self._on_run_started) + # psygnal's queued connections (used by statusChanged below) park + # callbacks in a per-thread queue that nothing drains by default. + # start_emitting_from_queue installs a QTimer on the main thread + # that calls psygnal.emit_queued() on every Qt event-loop tick, so + # worker-thread emits actually reach the widget. Idempotent: safe + # to call from multiple widgets / multiple constructions. + try: + from psygnal.qt import start_emitting_from_queue + start_emitting_from_queue() + except ImportError: + pass + # -- UI construction ---------------------------------------------------- def _build_ui(self) -> None: @@ -112,7 +124,13 @@ def _on_run_started(self, handle: RunHandle) -> None: pass self._handle = handle - handle.statusChanged.connect(self._refresh) + # thread="main" routes worker-thread emits through Qt's queued + # connection to the main thread. Without it, the slot runs + # synchronously on the emitter (worker / engine) thread and + # touches QWidgets from off-main -- under napari that lands + # in vispy/OpenGL with "Cannot make QOpenGLContext current in + # a different thread" -> SIGABRT. + handle.statusChanged.connect(self._refresh, thread="main") self._refresh(handle.status()) def _on_stop_clicked(self) -> None: diff --git a/uv.lock b/uv.lock index 0c5d8e9..62bd20e 100644 --- a/uv.lock +++ b/uv.lock @@ -868,6 +868,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, ] +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + [[package]] name = "executing" version = "2.2.1" @@ -926,6 +935,7 @@ stardist = [ ] test = [ { name = "pytest" }, + { name = "pytest-xdist" }, ] virtual-microscope = [ { name = "imageio", extra = ["ffmpeg"] }, @@ -956,6 +966,7 @@ requires-dist = [ { name = "pymmcore-plus", specifier = ">=0.17.3" }, { name = "pymmcore-widgets", git = "https://github.com/pymmcore-plus/pymmcore-widgets.git" }, { name = "pytest", marker = "extra == 'test'" }, + { name = "pytest-xdist", marker = "extra == 'test'" }, { name = "scikit-image" }, { name = "scipy", marker = "extra == 'convpaint'" }, { name = "stardist", marker = "extra == 'stardist'" }, @@ -3806,8 +3817,8 @@ cli = [ [[package]] name = "pymmcore-widgets" -version = "0.12.2.dev8+g8c8f76e5d" -source = { git = "https://github.com/pymmcore-plus/pymmcore-widgets.git#8c8f76e5d0778af14b63b3e03206a93eac4664b3" } +version = "0.12.2.dev12+g48ff41416" +source = { git = "https://github.com/pymmcore-plus/pymmcore-widgets.git#48ff4141619304fe5a58827ead62cc8d01e5f134" } dependencies = [ { name = "pymmcore-plus", extra = ["cli"] }, { name = "pyopengl", marker = "sys_platform == 'darwin' or (extra == 'extra-4-faro-api74' and extra == 'extra-4-faro-api75')" }, @@ -3914,6 +3925,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -4890,21 +4914,21 @@ dependencies = [ { name = "typing-extensions" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313t-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313t-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313t-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314-win_amd64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314t-manylinux_2_28_aarch64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314t-manylinux_2_28_x86_64.whl" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314t-win_amd64.whl" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:252f237d417fac3ba59b1635815c1f035a8241f2af038f2c076ed430932d89f1", upload-time = "2026-04-27T20:01:46Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:96911323dcfcd42028c7e8edde7bdf25bb187753234e8775f0f3f112e86a22db", upload-time = "2026-04-27T20:02:14Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp312-cp312-win_amd64.whl", hash = "sha256:ef8beae16d781c3244ef28dc7bee6d8871c26bbde65d5bf66e902cb61972c4ab", upload-time = "2026-04-27T20:03:28Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c3d60f79666b9101e3914a2e5dec2e81eac834e13cae0bcf59e94dc1a465f756", upload-time = "2026-04-27T20:04:49Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:554461b76f21211927c776056bcb0b00fb42972364794b686d768ebb0b586366", upload-time = "2026-04-27T20:05:21Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313-win_amd64.whl", hash = "sha256:339801f2163698a53c7fb3c91883e7f44331d22c34d45acfbce4eff71f2332fa", upload-time = "2026-04-27T20:06:44Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:a33905bc3e093b25d2b019181cf834f7f7d4c562739e13dd36a798ecb2e411b0", upload-time = "2026-04-27T20:08:23Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:6fd10ed484eb695312ae829719888bb9f6c7f5e8503528e3e8ad1b98a45296c2", upload-time = "2026-04-27T20:08:56Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp313-cp313t-win_amd64.whl", hash = "sha256:21d2734fd02af45d19bb88c0ff2e86b238ce73f7bde6003ade7f1454ae299198", upload-time = "2026-04-27T20:10:20Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:efcdfe08ec2c9db28b50cc7329fed0c90bb74fa6fbce0f7eb12e20db2279a40f", upload-time = "2026-04-27T20:11:48Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:6ccc36928fd17c86011b46fb81bd2c85475f1fbf967dde758672d6a8d83a212a", upload-time = "2026-04-27T20:12:18Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314-win_amd64.whl", hash = "sha256:d886f1c2f4406d7ad0c59f254ceb0a9c47a03e97a7c704b778a2066d752dde29", upload-time = "2026-04-27T20:13:41Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:bdb20f8b04e9fcaba2f354c3026667bebb74de8a92526b706aa735e2df334c24", upload-time = "2026-04-27T20:15:02Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:28f952cd4a927616ad9d77644a93237d1ca50bf30d0cf26962b9162d8a00ffa0", upload-time = "2026-04-27T20:15:30Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torch-2.11.0%2Bcu130-cp314-cp314t-win_amd64.whl", hash = "sha256:d0a857adc487f275bfc9e7cdc51d12940613ba18b6362da214e20e9e3871f817", upload-time = "2026-04-27T20:16:48Z" }, ] [[package]] @@ -4917,21 +4941,21 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e2b39db78be674ee4ce7e921f54b70e5c281594c9267d981c061684ed38df936" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f030a9bd8ada1a31b7111ea1589c1ecb5fa0884fee700a203e731b4cf378a98" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp312-cp312-win_amd64.whl", hash = "sha256:a3578f7c8e8a2724306c68c56873a1675fa7ce45471e18235c720a2ed242fe44" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:3af2c699719cc0e2518bf317664200e5a987fb75a25b9b3bf3817a4796ddd64f" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:441a98bed4fff1d54b8450499e377e1a605bec31f2ecb1a38a340f95dcc83897" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313-win_amd64.whl", hash = "sha256:64de855465d6de60583e776889fad9412480f9f9e04fdd8d17ae96fa93864e9a" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c3ac485da79552b4f579c525c826f7a63288b0d1cafc1201b16e1148bfdea69a" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:110659ff38cd1d2ca0ac6e6a0f2c842fcb5fe739dfe65ff7456a12b2c4dce775" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313t-win_amd64.whl", hash = "sha256:a7e19c3ab5c6d8e3c9f8c6d427f6b8862dfb8227ea4a758ea7a709951daf2f0d" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:13fe3dee74a9ee31b551b10a8b4113d9bc5212bb0572a07af88b34a5d25d9701" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:8d6a83a639d14f7e84f6b838a17e26f9ce41cdbe3dfe0c29ef74b32eb398ba28" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314-win_amd64.whl", hash = "sha256:ab671ffe837aff470baad6af97133ea5a49f8ea2383832550e510a63caf711e4" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cd89effa98de436ec22ccbbd278cdadc0fdec8eb81a396150f50b321c2230866" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:aacac4d990ac794f3abeca66cc26affb42fbeba9789e5c351183665bab4902d2" }, - { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314t-win_amd64.whl", hash = "sha256:23b9666084c72d07fc715001880b648e6a410796d1925c10f054f1ee034f5cc7" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e2b39db78be674ee4ce7e921f54b70e5c281594c9267d981c061684ed38df936", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f030a9bd8ada1a31b7111ea1589c1ecb5fa0884fee700a203e731b4cf378a98", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp312-cp312-win_amd64.whl", hash = "sha256:a3578f7c8e8a2724306c68c56873a1675fa7ce45471e18235c720a2ed242fe44", upload-time = "2026-04-09T23:21:53Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:3af2c699719cc0e2518bf317664200e5a987fb75a25b9b3bf3817a4796ddd64f", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:441a98bed4fff1d54b8450499e377e1a605bec31f2ecb1a38a340f95dcc83897", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313-win_amd64.whl", hash = "sha256:64de855465d6de60583e776889fad9412480f9f9e04fdd8d17ae96fa93864e9a", upload-time = "2026-04-09T23:21:54Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c3ac485da79552b4f579c525c826f7a63288b0d1cafc1201b16e1148bfdea69a", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:110659ff38cd1d2ca0ac6e6a0f2c842fcb5fe739dfe65ff7456a12b2c4dce775", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp313-cp313t-win_amd64.whl", hash = "sha256:a7e19c3ab5c6d8e3c9f8c6d427f6b8862dfb8227ea4a758ea7a709951daf2f0d", upload-time = "2026-04-09T23:21:55Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:13fe3dee74a9ee31b551b10a8b4113d9bc5212bb0572a07af88b34a5d25d9701", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:8d6a83a639d14f7e84f6b838a17e26f9ce41cdbe3dfe0c29ef74b32eb398ba28", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314-win_amd64.whl", hash = "sha256:ab671ffe837aff470baad6af97133ea5a49f8ea2383832550e510a63caf711e4", upload-time = "2026-04-09T23:21:56Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cd89effa98de436ec22ccbbd278cdadc0fdec8eb81a396150f50b321c2230866", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:aacac4d990ac794f3abeca66cc26affb42fbeba9789e5c351183665bab4902d2", upload-time = "2026-03-23T15:36:26Z" }, + { url = "https://download-r2.pytorch.org/whl/cu130/torchvision-0.26.0%2Bcu130-cp314-cp314t-win_amd64.whl", hash = "sha256:23b9666084c72d07fc715001880b648e6a410796d1925c10f054f1ee034f5cc7", upload-time = "2026-04-09T23:21:57Z" }, ] [[package]] From c8b7d553e9c84bdecf51688406366d8869958667 Mon Sep 17 00:00:00 2001 From: hinderling Date: Fri, 15 May 2026 18:05:19 +0200 Subject: [PATCH 04/41] example: async optogenetic demo notebook with napari + status widget Sibling of demo_sim_optogenetic.ipynb that exercises the new async run_experiment + RunHandle + ExperimentStatusWidget end-to-end against virtual_microscope's optogenetic backend, with a live napari viewer dock-attached. Walks through: handle = ctrl.run_experiment(...) is non-blocking, the kernel is free; poll handle.status() while it runs; subscribe to handle.statusChanged from the kernel side; cancel via the widget Stop button or handle.cancel(); handle.wait() blocks if you want the old synchronous semantics; continue_experiment() re-binds the widget automatically via runStarted. Phases are concatenated with combine(..., axis="t") per the new RTMSequence API. --- .../demo_sim_optogenetic_napari_async.ipynb | 1034 +++++++++++++++++ 1 file changed, 1034 insertions(+) create mode 100644 experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb diff --git a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb new file mode 100644 index 0000000..6a6b7b9 --- /dev/null +++ b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb @@ -0,0 +1,1034 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Async optogenetic demo with napari + ExperimentStatusWidget\n", + "\n", + "Same virtual-microscope optogenetic pipeline as `demo_sim_optogenetic.ipynb`, but:\n", + "\n", + "- A live **napari** viewer with the **napari-micromanager** GUI is opened first, so frames stream in as they are acquired.\n", + "- The new **`ExperimentStatusWidget`** is docked into the viewer. It mirrors the controller's current `RunHandle`: state, current FOV / event index, frames received, lag, elapsed time, and a **Stop** button.\n", + "- `ctrl.run_experiment(events)` is now **non-blocking** and returns a `RunHandle`. The notebook returns control to the kernel immediately so you can interact with the viewer, poll status, cancel the run, or queue a `continue_experiment(...)`.\n", + "\n", + "Requirements:\n", + "\n", + "```\n", + "uv sync --extra virtual-microscope\n", + "```\n", + "\n", + "Run cells one at a time in a Jupyter kernel that has Qt running (start the Jupyter kernel from a terminal so the napari window can pop up).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Start the virtual microscope" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Config Groups\n",
+       "└── Channel\n",
+       "    ├── DAPI\n",
+       "    ├── membrane\n",
+       "    └── phase-contrast\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mConfig Groups\u001b[0m\n", + "└── \u001b[1;36mChannel\u001b[0m\n", + " ├── DAPI\n", + " ├── membrane\n", + " └── phase-contrast\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from virtual_microscope.backends.optogenetic import setup_optogenetic\n", + "from faro.microscope.simulation import UniMMCoreSimulation\n", + "import faro.core.utils as utils\n", + "\n", + "core, sim = setup_optogenetic()\n", + "utils.print_configs(core)\n", + "\n", + "mic = UniMMCoreSimulation(mmc=core)\n", + "mic.init_scope() # detect SLM device for optogenetic stimulation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Open napari + napari-micromanager\n", + "\n", + "We attach the simulated `core` to a `napari-micromanager` `MainWindow` so frames stream into the viewer as they are acquired." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import napari\n", + "from napari_micromanager import MainWindow\n", + "\n", + "viewer = napari.Viewer()\n", + "mm_wdg = MainWindow(viewer)\n", + "mm_wdg._mmc = core\n", + "viewer.window.add_dock_widget(mm_wdg, name=\"napari-micromanager\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Build the pipeline (segmentation + tracking + features + stimulation)\n", + "\n", + "Same components as the original demo: Otsu/watershed segmentation, trackpy linker, simple region-props feature extractor, and the `StimUpDown` stimulator that pushes even-particle cells up and odd-particle cells down." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "from skimage.filters import gaussian, threshold_otsu\n", + "from skimage.feature import peak_local_max\n", + "from skimage.segmentation import watershed\n", + "from skimage.measure import label, regionprops\n", + "from skimage.morphology import disk, dilation\n", + "from scipy import ndimage\n", + "import skimage.measure\n", + "import pandas as pd\n", + "\n", + "from faro.segmentation.base import Segmentator\n", + "from faro.feature_extraction.base import FeatureExtractor\n", + "from faro.stimulation.base import StimWithPipeline\n", + "from faro.tracking.trackpy import TrackerTrackpy\n", + "from faro.core.data_structures import SegmentationMethod\n", + "\n", + "\n", + "class WatershedSegmentator(Segmentator):\n", + " \"\"\"Gaussian blur -> Otsu -> distance transform -> watershed.\"\"\"\n", + "\n", + " def segment(self, image: np.ndarray) -> np.ndarray:\n", + " blurred = gaussian(image, sigma=3)\n", + " thresh = threshold_otsu(blurred)\n", + " binary = blurred > thresh\n", + " distance = ndimage.distance_transform_edt(binary)\n", + " coords = peak_local_max(distance, min_distance=20, labels=binary)\n", + " markers = np.zeros(distance.shape, dtype=bool)\n", + " markers[tuple(coords.T)] = True\n", + " markers = label(markers)\n", + " return watershed(-distance, markers, mask=binary)\n", + "\n", + "\n", + "class SimpleFE(FeatureExtractor):\n", + " def __init__(self, used_mask):\n", + " self.used_mask = used_mask\n", + " super().__init__()\n", + "\n", + " def extract_features(self, labels, image, df_tracked=None, metadata=None):\n", + " table = skimage.measure.regionprops_table(\n", + " labels[self.used_mask], properties=[\"label\", \"area\"]\n", + " )\n", + " return pd.DataFrame.from_dict(table), None\n", + "\n", + "\n", + "class StimUpDown(StimWithPipeline):\n", + " \"\"\"Even particles -> illuminate top edge; odd particles -> bottom edge.\"\"\"\n", + "\n", + " def __init__(self, fraction=0.2):\n", + " self.fraction = fraction\n", + "\n", + " def get_stim_mask(self, label_images, metadata=None, img=None, tracks=None):\n", + " labels = label_images[\"labels\"]\n", + " stim_mask = np.zeros(labels.shape, dtype=np.uint8)\n", + " selem = disk(3)\n", + "\n", + " if tracks is None or tracks.empty:\n", + " return stim_mask, None\n", + "\n", + " current = tracks[tracks[\"timestep\"] == tracks[\"timestep\"].max()]\n", + " label_to_particle = dict(zip(current[\"label\"], current[\"particle\"]))\n", + "\n", + " for prop in regionprops(labels):\n", + " minr, minc, maxr, maxc = prop.bbox\n", + " pid = label_to_particle.get(prop.label, 0)\n", + "\n", + " if pid % 2 == 0:\n", + " y_cutoff = minr + self.fraction * (maxr - minr)\n", + " select = lambda rows, c=y_cutoff: rows < c\n", + " else:\n", + " y_cutoff = maxr - self.fraction * (maxr - minr)\n", + " select = lambda rows, c=y_cutoff: rows > c\n", + "\n", + " cell_mask = labels == prop.label\n", + " rows, cols = np.where(cell_mask)\n", + " edge_pixels = select(rows)\n", + " if not edge_pixels.any():\n", + " continue\n", + "\n", + " local = np.zeros_like(labels, dtype=np.uint8)\n", + " local[rows[edge_pixels], cols[edge_pixels]] = 1\n", + " local = dilation(local, footprint=selem)\n", + " stim_mask = np.maximum(stim_mask, local)\n", + "\n", + " return stim_mask, None\n", + "\n", + "\n", + "segmentators = [\n", + " SegmentationMethod(\n", + " name=\"labels\",\n", + " segmentation_class=WatershedSegmentator(),\n", + " use_channel=0,\n", + " save_tracked=True,\n", + " )\n", + "]\n", + "\n", + "tracker = TrackerTrackpy(search_range=15)\n", + "feature_extractor = SimpleFE(\"labels\")\n", + "stimulator = StimUpDown(fraction=0.2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Assemble the pipeline + controller" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Directory /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_9zg31hbr/tracks created \n", + "Storage path: /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_9zg31hbr\n" + ] + } + ], + "source": [ + "import os, shutil, tempfile\n", + "from faro.core.pipeline import ImageProcessingPipeline\n", + "from faro.core.controller import Controller\n", + "from faro.core.writers import TiffWriter\n", + "\n", + "path = tempfile.mkdtemp(prefix=\"napari_async_optogenetic_\")\n", + "if os.path.exists(path):\n", + " shutil.rmtree(path)\n", + "\n", + "pipeline = ImageProcessingPipeline(\n", + " storage_path=path,\n", + " segmentators=segmentators,\n", + " feature_extractor=feature_extractor,\n", + " tracker=tracker,\n", + " stimulator=stimulator,\n", + ")\n", + "\n", + "writer = TiffWriter(storage_path=path)\n", + "ctrl = Controller(mic, pipeline, writer=writer)\n", + "print(f\"Storage path: {path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Dock the `ExperimentStatusWidget`\n", + "\n", + "The widget subscribes to `ctrl.runStarted`, so it automatically re-binds whenever a new run begins. The **Stop** button calls `handle.cancel()` on the currently bound run." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from faro.widgets import ExperimentStatusWidget\n", + "\n", + "status_widget = ExperimentStatusWidget(ctrl)\n", + "viewer.window.add_dock_widget(status_widget, name=\"Experiment status\", area=\"right\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Build a multi-phase event sequence\n", + "\n", + "Baseline → stimulation → recovery, all on a single FOV. Phases are joined with `combine(..., axis=\"t\")`, which offsets each subsequent sequence's `t` indices and `min_start_time` past the previous one. Slow enough that you can comfortably interact with the napari viewer while it runs." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "40 events: 5 baseline + 30 stim + 5 recovery\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "fov", + "rawType": "int64", + "type": "integer" + }, + { + "name": "timestep", + "rawType": "int64", + "type": "integer" + }, + { + "name": "time", + "rawType": "float64", + "type": "float" + }, + { + "name": "x_pos", + "rawType": "float64", + "type": "float" + }, + { + "name": "y_pos", + "rawType": "float64", + "type": "float" + }, + { + "name": "z_pos", + "rawType": "float64", + "type": "float" + }, + { + "name": "channels", + "rawType": "object", + "type": "unknown" + }, + { + "name": "stim_channels", + "rawType": "object", + "type": "unknown" + }, + { + "name": "ref_channels", + "rawType": "object", + "type": "unknown" + }, + { + "name": "stim", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "ref", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "phase", + "rawType": "object", + "type": "string" + }, + { + "name": "stim_power", + "rawType": "float64", + "type": "float" + }, + { + "name": "stim_exposure", + "rawType": "float64", + "type": "float" + } + ], + "ref": "4f1c44aa-2500-4534-a1ba-06471cda1a66", + "rows": [ + [ + "0", + "0", + "0", + "0.0", + "0.0", + "0.0", + "0.0", + "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", + "()", + "()", + "False", + "False", + "baseline", + null, + null + ], + [ + "1", + "0", + "1", + "1.5", + "0.0", + "0.0", + "0.0", + "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", + "()", + "()", + "False", + "False", + "baseline", + null, + null + ], + [ + "2", + "0", + "2", + "3.0", + "0.0", + "0.0", + "0.0", + "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", + "()", + "()", + "False", + "False", + "baseline", + null, + null + ], + [ + "3", + "0", + "3", + "4.5", + "0.0", + "0.0", + "0.0", + "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", + "()", + "()", + "False", + "False", + "baseline", + null, + null + ], + [ + "4", + "0", + "4", + "6.0", + "0.0", + "0.0", + "0.0", + "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", + "()", + "()", + "False", + "False", + "baseline", + null, + null + ] + ], + "shape": { + "columns": 14, + "rows": 5 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
fovtimesteptimex_posy_posz_poschannelsstim_channelsref_channelsstimrefphasestim_powerstim_exposure
0000.00.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
1011.50.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
2023.00.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
3034.50.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
4046.00.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
\n", + "
" + ], + "text/plain": [ + " fov timestep time x_pos y_pos z_pos \\\n", + "0 0 0 0.0 0.0 0.0 0.0 \n", + "1 0 1 1.5 0.0 0.0 0.0 \n", + "2 0 2 3.0 0.0 0.0 0.0 \n", + "3 0 3 4.5 0.0 0.0 0.0 \n", + "4 0 4 6.0 0.0 0.0 0.0 \n", + "\n", + " channels stim_channels \\\n", + "0 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", + "1 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", + "2 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", + "3 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", + "4 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", + "\n", + " ref_channels stim ref phase stim_power stim_exposure \n", + "0 () False False baseline NaN NaN \n", + "1 () False False baseline NaN NaN \n", + "2 () False False baseline NaN NaN \n", + "3 () False False baseline NaN NaN \n", + "4 () False False baseline NaN NaN " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from faro.core.data_structures import RTMSequence, combine\n", + "from faro.core.utils import events_to_dataframe\n", + "\n", + "n_baseline = 5\n", + "n_stim = 30\n", + "n_recovery = 5\n", + "interval_s = 1.5 # seconds between frames; raise if your machine struggles\n", + "\n", + "baseline = RTMSequence(\n", + " time_plan={\"interval\": interval_s, \"loops\": n_baseline},\n", + " stage_positions=[(0.0, 0.0, 0.0)],\n", + " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", + " rtm_metadata={\"phase\": \"baseline\"},\n", + ")\n", + "\n", + "stim_phase = RTMSequence(\n", + " time_plan={\"interval\": interval_s, \"loops\": n_stim},\n", + " stage_positions=[(0.0, 0.0, 0.0)],\n", + " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", + " stim_channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", + " stim_frames=range(n_stim),\n", + " rtm_metadata={\"phase\": \"stimulation\"},\n", + ")\n", + "\n", + "recovery = RTMSequence(\n", + " time_plan={\"interval\": interval_s, \"loops\": n_recovery},\n", + " stage_positions=[(0.0, 0.0, 0.0)],\n", + " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", + " rtm_metadata={\"phase\": \"recovery\"},\n", + ")\n", + "\n", + "events = combine(baseline, stim_phase, recovery, axis=\"t\")\n", + "df_events = events_to_dataframe(events)\n", + "print(f\"{len(events)} events: {n_baseline} baseline + {n_stim} stim + {n_recovery} recovery\")\n", + "df_events.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Launch the run (non-blocking)\n", + "\n", + "`run_experiment` now returns a `RunHandle` immediately. The MDA feed loop is on a worker thread; the kernel is free.\n", + "\n", + "Watch the **napari viewer** for live frames and the **Experiment status** dock for state / FOV / event index / lag. Try clicking **Stop** mid-run — the worker exits at the next event boundary." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "run started, handle.is_running()=True\n", + "current status: running\n" + ] + } + ], + "source": [ + "handle = ctrl.run_experiment(events, stim_mode=\"current\")\n", + "print(f\"run started, handle.is_running()={handle.is_running()}\")\n", + "print(f\"current status: {handle.status().state}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Poll the status from the kernel\n", + "\n", + "Re-run the cell below as often as you like while the experiment is running." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "state : done\n", + "events consumed : 40 / 40\n", + "frames received : 70\n", + "current FOV : 0\n", + "current event : {'t': 39, 'p': 0}\n", + "lag (ms) : 116.06683302670717\n", + "bg errors : 0\n", + "fatal error : None\n" + ] + } + ], + "source": [ + "s = handle.status()\n", + "print(f\"state : {s.state}\")\n", + "print(f\"events consumed : {s.n_events_consumed} / {s.n_events_total}\")\n", + "print(f\"frames received : {s.n_frames_received}\")\n", + "print(f\"current FOV : {s.current_fov}\")\n", + "print(f\"current event : {s.current_event_index}\")\n", + "print(f\"lag (ms) : {s.lag_ms}\")\n", + "print(f\"bg errors : {len(s.background_errors)}\")\n", + "print(f\"fatal error : {s.fatal_error!r}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Subscribe to live updates from the kernel\n", + "\n", + "Anything you do here is in *addition* to the dock widget — the widget already listens. Useful if you want to pipe status into your own logging." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[notify] state=running consumed=28/40 frames=50 lag_ms=102.20704099629074\n", + "[notify] state=running consumed=28/40 frames=50 lag_ms=102.20704099629074\n", + "[notify] state=running consumed=29/40 frames=50 lag_ms=102.20704099629074\n", + "[notify] state=running consumed=29/40 frames=50 lag_ms=102.20704099629074\n", + "[notify] state=running consumed=29/40 frames=51 lag_ms=451.81912498082966\n", + "[notify] state=running consumed=29/40 frames=51 lag_ms=451.81912498082966\n", + "[notify] state=running consumed=29/40 frames=52 lag_ms=120.75233302311972\n", + "[notify] state=running consumed=29/40 frames=52 lag_ms=120.75233302311972\n", + "[notify] state=running consumed=30/40 frames=52 lag_ms=120.75233302311972\n", + "[notify] state=running consumed=30/40 frames=52 lag_ms=120.75233302311972\n", + "[notify] state=running consumed=30/40 frames=53 lag_ms=473.3314160257578\n", + "[notify] state=running consumed=30/40 frames=53 lag_ms=473.3314160257578\n", + "[notify] state=running consumed=30/40 frames=54 lag_ms=125.35141600528732\n", + "[notify] state=running consumed=30/40 frames=54 lag_ms=125.35141600528732\n", + "[notify] state=running consumed=31/40 frames=54 lag_ms=125.35141600528732\n", + "[notify] state=running consumed=31/40 frames=54 lag_ms=125.35141600528732\n", + "[notify] state=running consumed=31/40 frames=55 lag_ms=472.52970800036564\n", + "[notify] state=running consumed=31/40 frames=55 lag_ms=472.52970800036564\n", + "[notify] state=running consumed=31/40 frames=56 lag_ms=124.23370801843703\n", + "[notify] state=running consumed=31/40 frames=56 lag_ms=124.23370801843703\n", + "[notify] state=running consumed=32/40 frames=56 lag_ms=124.23370801843703\n", + "[notify] state=running consumed=32/40 frames=56 lag_ms=124.23370801843703\n", + "[notify] state=running consumed=32/40 frames=57 lag_ms=472.7424160228111\n", + "[notify] state=running consumed=32/40 frames=57 lag_ms=472.7424160228111\n", + "[notify] state=running consumed=32/40 frames=58 lag_ms=107.98183298902586\n", + "[notify] state=running consumed=32/40 frames=58 lag_ms=107.98183298902586\n", + "[notify] state=running consumed=33/40 frames=58 lag_ms=107.98183298902586\n", + "[notify] state=running consumed=33/40 frames=58 lag_ms=107.98183298902586\n", + "[notify] state=running consumed=33/40 frames=59 lag_ms=458.4957080078311\n", + "[notify] state=running consumed=33/40 frames=59 lag_ms=458.4957080078311\n", + "[notify] state=running consumed=33/40 frames=60 lag_ms=122.33808299060911\n", + "[notify] state=running consumed=33/40 frames=60 lag_ms=122.33808299060911\n", + "[notify] state=running consumed=34/40 frames=60 lag_ms=122.33808299060911\n", + "[notify] state=running consumed=34/40 frames=60 lag_ms=122.33808299060911\n", + "[notify] state=running consumed=34/40 frames=61 lag_ms=477.04116598470137\n", + "[notify] state=running consumed=34/40 frames=61 lag_ms=477.04116598470137\n", + "[notify] state=running consumed=34/40 frames=62 lag_ms=120.07075001019984\n", + "[notify] state=running consumed=34/40 frames=62 lag_ms=120.07075001019984\n", + "[notify] state=running consumed=35/40 frames=62 lag_ms=120.07075001019984\n", + "[notify] state=running consumed=35/40 frames=62 lag_ms=120.07075001019984\n", + "[notify] state=running consumed=35/40 frames=63 lag_ms=471.21175000211224\n", + "[notify] state=running consumed=35/40 frames=63 lag_ms=471.21175000211224\n", + "[notify] state=running consumed=35/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=35/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=36/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=36/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=37/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=37/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=38/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=38/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=39/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=39/40 frames=64 lag_ms=105.58366600889713\n", + "[notify] state=running consumed=39/40 frames=65 lag_ms=464.6262080059387\n", + "[notify] state=running consumed=39/40 frames=65 lag_ms=464.6262080059387\n", + "[notify] state=running consumed=40/40 frames=65 lag_ms=464.6262080059387\n", + "[notify] state=running consumed=40/40 frames=65 lag_ms=464.6262080059387\n", + "[notify] state=running consumed=40/40 frames=66 lag_ms=119.60500001441687\n", + "[notify] state=running consumed=40/40 frames=66 lag_ms=119.60500001441687\n", + "[notify] state=running consumed=40/40 frames=67 lag_ms=123.85783297941089\n", + "[notify] state=running consumed=40/40 frames=67 lag_ms=123.85783297941089\n", + "[notify] state=running consumed=40/40 frames=68 lag_ms=121.0207500262186\n", + "[notify] state=running consumed=40/40 frames=68 lag_ms=121.0207500262186\n", + "[notify] state=running consumed=40/40 frames=69 lag_ms=113.7447910150513\n", + "[notify] state=running consumed=40/40 frames=69 lag_ms=113.7447910150513\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[31;20m2026-05-15 17:48:18,561 - pymmcore-plus - ERROR - (_logger.py:154) SimCameraDevice does not support setting ROI.\u001b[0m\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[notify] state=running consumed=40/40 frames=70 lag_ms=116.06683302670717\n", + "[notify] state=running consumed=40/40 frames=70 lag_ms=116.06683302670717\n", + "[notify] state=done consumed=40/40 frames=70 lag_ms=116.06683302670717\n", + "[notify] state=done consumed=40/40 frames=70 lag_ms=116.06683302670717\n" + ] + } + ], + "source": [ + "def _print_status(status):\n", + " print(f\"[notify] state={status.state} consumed={status.n_events_consumed}\"\n", + " f\"/{status.n_events_total} frames={status.n_frames_received}\"\n", + " f\" lag_ms={status.lag_ms}\")\n", + "\n", + "handle.statusChanged.connect(_print_status)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Cancel from the kernel (alternative to the Stop button)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# handle.cancel() # uncomment to abort" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Block until the run finishes\n", + "\n", + "Equivalent to the old synchronous behavior. Re-raises any worker-side `fatal_error`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "OMP: Info #276: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.\n" + ] + } + ], + "source": [ + "final_status = handle.wait()\n", + "print(f\"final state : {final_status.state}\")\n", + "print(f\"frames received : {final_status.n_frames_received}\")\n", + "if final_status.fatal_error is not None:\n", + " print(f\"fatal : {final_status.fatal_error!r}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 8. Continue the experiment (optional)\n", + "\n", + "`ctrl.continue_experiment(...)` reuses the existing Analyzer and tracking state. Same async semantics: returns a fresh `RunHandle`, the widget re-binds automatically via `runStarted`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "extra = RTMSequence(\n", + " time_plan={\"interval\": interval_s, \"loops\": 10},\n", + " stage_positions=[(0.0, 0.0, 0.0)],\n", + " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", + " rtm_metadata={\"phase\": \"extra-recovery\"},\n", + ")\n", + "\n", + "handle2 = ctrl.continue_experiment(list(extra), stim_mode=\"current\")\n", + "print(f\"continuation started, handle2.is_running()={handle2.is_running()}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "handle2.wait()\n", + "print(handle2.status().state)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 9. Finish + tear down" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mic.post_experiment()\n", + "ctrl.finish_experiment()\n", + "print(f\"All data written to: {path}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 10. Quick look at the results" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import tifffile\n", + "from glob import glob\n", + "\n", + "tracks = pd.read_parquet(os.path.join(path, \"tracks\", \"0_latest.parquet\"))\n", + "particles = tracks[\"particle\"].unique()\n", + "\n", + "fig, ax = plt.subplots(figsize=(5, 5), dpi=150)\n", + "raw_files = sorted(glob(os.path.join(path, \"raw\", \"*.tiff\")))\n", + "if raw_files:\n", + " last_img = tifffile.imread(raw_files[-1])\n", + " if last_img.ndim == 3:\n", + " last_img = last_img[0]\n", + " ax.imshow(last_img, cmap=\"gray_r\")\n", + "\n", + "cmap = plt.cm.tab20\n", + "for i, pid in enumerate(particles):\n", + " t = tracks[tracks[\"particle\"] == pid].sort_values(\"timestep\")\n", + " ax.plot(t[\"y\"], t[\"x\"], color=cmap(i % 20), lw=1.2, alpha=0.85)\n", + "ax.set_title(f\"Tracks ({len(particles)} particles)\")\n", + "ax.set_xticks([]); ax.set_yticks([])\n", + "plt.tight_layout()\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 8b0f3318eb135f58ea1e504e2ef7ba19cadef388 Mon Sep 17 00:00:00 2001 From: hinderling Date: Sat, 16 May 2026 11:20:50 +0200 Subject: [PATCH 05/41] feat: observable + controllable async runs (events, pause, restart fix) Backend changes that make an async run inspectable and steerable -- the data the new ExperimentStatusWidget renders, plus two bug fixes surfaced while building it. run_status.py - RunHandle.events: optional snapshot of the (sorted) RTMEvents the handle is driving, so widgets can render per-event visualisations (event strip, FOV map) that need the full plan up front. - Pause/resume: RunState gains "pausing"/"paused"; RunHandle gains pause()/resume()/is_paused() and a pause_event the feed loop polls. cancel() now also clears the pause event so a cancel while paused still releases the feed loop. controller.py - run_experiment / continue_experiment sort events once (by min_start_time, then position) and stash the sorted list on the handle, so the order the worker processes matches what the widget displays. - Feed loop honors pause_event: before pulling the next RTMEvent it checks the flag, flips state to "paused", and idles until resume() -- the MDA engine drains whatever is already queued, then waits. - fix: the engine queue (self._queue) is recreated per run. The finally-block feeds a STOP_EVENT sentinel to stop the engine; on a *cancelled* run cancel_mda() aborts the engine, which may stop without draining the queue, leaving stale events + the sentinel behind. Reusing that queue made the next run's engine consume the stale sentinel and exit after a few events ("stuck at 3/80"). A fresh queue per run fixes it. - fix: _bump_status_for_frame skips IMG_STIM frames. A stim emission is the SLM-illuminated snap paired with its imaging frame; counting it double-updated the status (lag/elapsed refreshing twice per stim event) and made n_frames_received drift away from the RTMEvent count. Imaging + ref frames are the meaningful data frames. Verified end-to-end against the optogenetic virtual-microscope backend: cancel mid-run then restart reaches steady state (no stall); pause halts feeding after the backpressure window drains and resume continues to completion; frame count tracks RTMEvents 1:1 for single-channel plans. --- faro/core/controller.py | 56 +++++++++++++++++++++++++++++++++++-- faro/core/run_status.py | 62 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 112 insertions(+), 6 deletions(-) diff --git a/faro/core/controller.py b/faro/core/controller.py index bc048d7..c482304 100644 --- a/faro/core/controller.py +++ b/faro/core/controller.py @@ -682,7 +682,15 @@ def run_experiment( "Fix the issues or pass validate=False to skip." ) - handle = RunHandle(n_events_total=len(events)) + # Sort once here so the order the worker processes them matches + # the order the widget displays. _run_mda_with_events also sorts + # (idempotent on an already-sorted list); doing it here lets us + # stash the canonical sequence on the handle for status widgets. + events = sorted( + events, key=lambda e: (e.min_start_time or 0, e.index.get("p", 0)) + ) + + handle = RunHandle(n_events_total=len(events), events=events) self._current_handle = handle handle._thread = threading.Thread( @@ -732,8 +740,14 @@ def continue_experiment( ) offset_events = self._offset_events(events) + offset_events = sorted( + offset_events, + key=lambda e: (e.min_start_time or 0, e.index.get("p", 0)), + ) - handle = RunHandle(n_events_total=len(offset_events)) + handle = RunHandle( + n_events_total=len(offset_events), events=offset_events + ) self._current_handle = handle handle._thread = threading.Thread( @@ -975,6 +989,17 @@ def _run_mda_with_events(self, events, *, stim_mode, handle: RunHandle): self._mic.connect_frame(self._on_frame_ready) + # Recreate the engine queue for this run. The finally-block below + # puts a STOP_EVENT sentinel into self._queue to stop the engine; + # on a *cancelled* run the engine is aborted via cancel_mda() and + # may stop without draining the queue, leaving stale events + the + # STOP sentinel behind. Reusing that queue for the next run makes + # the new engine consume the stale sentinel and exit almost + # immediately -- the feed loop keeps pushing but nothing snaps + # (the run "sticks" after a few events). A fresh queue per run + # avoids that entirely. + self._queue = Queue() + # Set up event queue for extend_experiment support. # _pending_sentinels tracks how many extra batches (from # extend_experiment) still need to be drained. @@ -999,6 +1024,21 @@ def _run_mda_with_events(self, events, *, stim_mode, handle: RunHandle): if handle.cancel_event.is_set(): break + # Pause: stop feeding new events before pulling the next + # one. The MDA engine drains whatever is already queued + # (in-flight event + backpressure window), then idles. + # No new events are fed until resume() clears the event. + if handle.pause_event.is_set(): + handle.update(state="paused") + while handle.pause_event.is_set(): + if handle.cancel_event.is_set(): + break + time.sleep(0.05) + if handle.cancel_event.is_set(): + break + handle.update(state="running") + continue + # Short timeout so cancellation is responsive even when # the queue is empty (waiting for extend_experiment). try: @@ -1095,10 +1135,20 @@ def _run_mda_with_events(self, events, *, stim_mode, handle: RunHandle): # ------------------------------------------------------------------ def _bump_status_for_frame(self, event: MDAEvent) -> None: - """Update RunHandle counters for the current frame; no-op if no handle.""" + """Update RunHandle counters for the current frame; no-op if no handle. + + Stim emissions are skipped: a stim frame is the SLM-illuminated + snap that fires alongside its imaging frame, so counting it would + double-update the widget per stim event (lag/elapsed appearing + to refresh twice in quick succession). Imaging + ref frames are + the meaningful "data frames" the user cares about. + """ handle = self._current_handle if handle is None: return + img_type = (event.metadata or {}).get("img_type", ImgType.IMG_RAW) + if img_type == ImgType.IMG_STIM: + return prev = handle.status() wallclock = time.time() lag_ms: float | None = None diff --git a/faro/core/run_status.py b/faro/core/run_status.py index ef21663..27f5da2 100644 --- a/faro/core/run_status.py +++ b/faro/core/run_status.py @@ -28,7 +28,9 @@ pass -RunState = Literal["pending", "running", "cancelling", "done", "error"] +RunState = Literal[ + "pending", "running", "pausing", "paused", "cancelling", "done", "error" +] @dataclass(frozen=True) @@ -73,11 +75,20 @@ class RunHandle: # gives an instance-bound signal. Different handles have independent signals. statusChanged = Signal(RunStatus) - def __init__(self, n_events_total: int = 0) -> None: + def __init__( + self, n_events_total: int = 0, events: list | None = None + ) -> None: self._lock = threading.RLock() self._status = RunStatus(state="pending", n_events_total=n_events_total) self._cancel_event = threading.Event() + self._pause_event = threading.Event() self._thread: threading.Thread | None = None + # Optional snapshot of the (sorted) RTMEvents this handle is driving. + # Widgets use this to render per-event visualisations (e.g. an event + # strip + FOV map) that need to know the full run plan up front. + # ``None`` keeps backward-compat with callers that construct + # RunHandle directly without an events list. + self.events: list | None = list(events) if events is not None else None # -- public API --------------------------------------------------------- @@ -108,18 +119,58 @@ def cancel(self) -> None: block until the worker actually stops. """ self._cancel_event.set() + # A cancel during pause must also release the feed loop's pause-wait. + self._pause_event.clear() with self._lock: - if self._status.state == "running": + if self._status.state in ("running", "pausing", "paused"): self._status = replace(self._status, state="cancelling") new_status = self._status else: return self.statusChanged.emit(new_status) + def pause(self) -> None: + """Request a graceful pause. Idempotent. Does not block. + + Sets the pause event the feed loop polls before pulling each new + RTMEvent. The loop finishes feeding the current event, then halts + at the next iteration -- the MDA engine drains whatever is already + queued, and no further events are fed until :meth:`resume`. State + goes ``running -> pausing`` here; the feed loop flips it to + ``paused`` once it actually stops. + """ + if self._pause_event.is_set(): + return + self._pause_event.set() + with self._lock: + if self._status.state == "running": + self._status = replace(self._status, state="pausing") + new_status = self._status + else: + return + self.statusChanged.emit(new_status) + + def resume(self) -> None: + """Resume a paused run. Idempotent. Does not block.""" + if not self._pause_event.is_set(): + return + self._pause_event.clear() + with self._lock: + if self._status.state in ("pausing", "paused"): + self._status = replace(self._status, state="running") + new_status = self._status + else: + return + self.statusChanged.emit(new_status) + def is_running(self) -> bool: """True if the worker thread is alive.""" return self._thread is not None and self._thread.is_alive() + def is_paused(self) -> bool: + """True if a pause has been requested (pausing or paused).""" + return self._pause_event.is_set() + # -- worker-side helpers ------------------------------------------------ @property @@ -127,6 +178,11 @@ def cancel_event(self) -> threading.Event: """The cooperative-cancel event the feed loop polls. Worker-side.""" return self._cancel_event + @property + def pause_event(self) -> threading.Event: + """The cooperative-pause event the feed loop polls. Worker-side.""" + return self._pause_event + def update(self, **updates: Any) -> RunStatus: """Atomically apply ``updates`` to the status snapshot and emit. From ccfdb694684238539568e82767b9aaa809401f13 Mon Sep 17 00:00:00 2001 From: hinderling Date: Sat, 16 May 2026 11:21:06 +0200 Subject: [PATCH 06/41] feat: rich ExperimentStatusWidget with event strip + FOV map Rework the minimal status widget into a full run dashboard, driven by the RunHandle data exposed in the previous commit. Components (top to bottom): - State chip -- RUNNING / PAUSED / DONE / ... as plain text in a translucent-neutral rounded chip (no per-state fill: a colored banner competed with the imaging/stim/ref legend colors). - Legend chips -- imaging / stim / ref; the chip matching the current event type is fully opaque, the others dimmed. - EventStrip -- one cell per RTMEvent, color-coded by type. Past + current cells opaque (progress fill), future cells dimmed. Same-type runs are coalesced into single fills so thousands of events render with correct alpha instead of over-stacking at sub-pixel widths. Empty state draws a "(no events loaded)" placeholder. - FovMap -- one dot per unique FOV position, equal-aspect (a straight line of FOVs stays a line), grey visit-order path, active dot recolored to the current event type. Pinned square via resizeEvent. Paints its own rounded panel background; "FOV X/Y" counter in the corner. - Stats form -- event N/M, elapsed, scheduled, lag, remaining, errors. Times formatted hh:mm:ss with the leading unit suffixed and dropped when zero; lag turns red past 5 s. Wrapped in a shaded panel echoing napari's layer-controls boxes. - Pause/Resume + Stop buttons. Threading / theming details: - statusChanged is connected with thread="main" and the widget calls psygnal.qt.start_emitting_from_queue() so worker-thread emits are delivered on the GUI thread (drives QWidgets safely under napari). - A 250 ms QTimer ticks the elapsed/remaining clocks between status emissions so time fields don't freeze between frames. - The strip cursor tracks n_frames_received (actual snaps), not n_events_consumed (the feed loop runs 3-4 ahead via backpressure, which made the strip jump several cells at run start). - Colors/fonts derive from the Qt palette so the widget adapts to napari's light/dark theme; corner radii match napari widgets. --- faro/widgets/experiment_status.py | 774 ++++++++++++++++++++++++++---- 1 file changed, 679 insertions(+), 95 deletions(-) diff --git a/faro/widgets/experiment_status.py b/faro/widgets/experiment_status.py index 67ef698..a95f727 100644 --- a/faro/widgets/experiment_status.py +++ b/faro/widgets/experiment_status.py @@ -1,122 +1,593 @@ -"""Minimal Qt widget that mirrors a Controller's current ``RunHandle``. +"""Qt status widget for the controller's currently-bound run. -Construct with a :class:`faro.core.controller.Controller` instance: +Construct with a :class:`faro.core.controller.Controller` instance:: from faro.widgets import ExperimentStatusWidget widget = ExperimentStatusWidget(ctrl) viewer.window.add_dock_widget(widget, name="Experiment") +Layout (top to bottom): + + - State label (RUNNING / DONE / CANCELLING / ERROR, color-coded) + - Legend chips (imaging / stim / ref) -- the chip matching the *current* + event type is fully opaque; the others are dimmed + - Event strip (one cell per RTMEvent, color-coded by type; past cells + are fully opaque, future cells are dimmed; the current cell has a + darker border) + - FOV map (one dot per unique FOV position, equal-aspect, with a + grey path drawn in visit order; the dot for the current FOV is + re-colored in the active event-type's color) + - Stats form (event N/M, elapsed, scheduled, lag, remaining, frames, + background errors) + - Stop button (calls ``handle.cancel()``) + The widget subscribes to ``ctrl.runStarted``, so it automatically re-binds to whichever run is current. Each ``RunHandle.statusChanged`` emission -updates the labels and progress bar; the Stop button calls -``handle.cancel()``. Designed to be cheap to construct, cheap to update, -and trivially extensible (subclass and override ``_refresh`` to add -fields). +updates the labels / strip / map; a small QTimer also refreshes the +``elapsed`` / ``remaining`` fields between status updates so the clock +doesn't appear frozen between frames. """ from __future__ import annotations import time -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Sequence -from qtpy.QtCore import Qt +from qtpy.QtCore import Qt, QTimer, QPointF, QRectF +from qtpy.QtGui import ( + QBrush, QColor, QFontDatabase, QPainter, QPalette, QPen, +) from qtpy.QtWidgets import ( QFormLayout, + QFrame, QHBoxLayout, QLabel, - QProgressBar, QPushButton, QVBoxLayout, QWidget, ) +from faro.core.data_structures import ImgType from faro.core.run_status import RunHandle, RunStatus if TYPE_CHECKING: from faro.core.controller import Controller -_STATE_COLORS = { - "pending": "#888888", - "running": "#2e7d32", - "cancelling": "#ef6c00", - "done": "#1565c0", - "error": "#c62828", +# ───────────────────────────────────────────────────────────────────────── +# Design tokens +# ───────────────────────────────────────────────────────────────────────── + +EVENT_COLORS: dict[str, str] = { + "imaging": "#2e7d32", + "stim": "#1565c0", + "ref": "#ef6c00", } +DEFAULT_EVENT_COLOR = "#888888" + +# Corner radius -- matches napari's own widgets (buttons, layer controls). +_RADIUS_PX = 3 + +# Translucent neutral overlay for the grouped panels (FOV map / stats). +# A 50%-grey at low alpha lightens a dark theme and darkens a light one, +# so the grouping reads on both without hardcoding a theme color. +_PANEL_BG = "rgba(128, 128, 128, 28)" + +# Event strip +_FUTURE_ALPHA = 90 +_PAST_ALPHA = 255 +_BORDER_PX = 2 +_GAP_PX = 1 +_MIN_GAP_AT_CELL_W = 3.0 + +# FOV map +_DOT_RADIUS_PX = 5 +_PATH_WIDTH_PX = 2 +_MAP_PADDING_PX = 24 +_MIN_WORLD_EXTENT = 1e-6 + +# Lag warn threshold (red is recognizable on both light and dark themes) +_LAG_WARN_S = 5.0 +_LAG_BAD_COLOR = "#e53935" + + +# ───────────────────────────────────────────────────────────────────────── +# Helpers (event-list introspection + small formatters) +# ───────────────────────────────────────────────────────────────────────── + +def _event_type_token(ev) -> str: + """Map an RTMEvent to one of {"ref", "stim", "imaging"} for visualisation. + + Order of precedence matches what the user sees as the *dominant* effect + for that timepoint: ref > stim > imaging. + + RTMEvent doesn't expose ``stim`` / ``ref`` booleans directly -- + ``events_to_dataframe`` derives them from the channel tuple lengths, + so we do the same here. + """ + if getattr(ev, "ref_channels", ()): + return "ref" + if getattr(ev, "stim_channels", ()): + return "stim" + # Fallback for plain MDAEvents: peek at metadata['img_type'] + md = getattr(ev, "metadata", None) or {} + img_type = md.get("img_type") + if img_type == ImgType.IMG_REF: + return "ref" + if img_type == ImgType.IMG_STIM: + return "stim" + return "imaging" + + +def _extract_plan(events: Sequence) -> tuple[list[str], list[int], list[tuple[float, float]], list[float]]: + """Walk events once and return (types, fovs, positions_by_fov, scheduled). + + - ``types``: per-event type token + - ``fovs``: per-event FOV index + - ``positions_by_fov``: list indexed by FOV index, holding ``(x, y)`` + of that FOV's stage position (taken from the first event that visits + each FOV). + - ``scheduled``: per-event ``min_start_time`` in seconds (0.0 if missing) + """ + types: list[str] = [] + fovs: list[int] = [] + scheduled: list[float] = [] + seen_fov: dict[int, tuple[float, float]] = {} + + for ev in events: + types.append(_event_type_token(ev)) + p = ev.index.get("p", 0) + fovs.append(p) + if p not in seen_fov: + seen_fov[p] = (float(ev.x_pos or 0.0), float(ev.y_pos or 0.0)) + mst = getattr(ev, "min_start_time", None) + scheduled.append(float(mst) if mst is not None else 0.0) + + if seen_fov: + max_p = max(seen_fov) + positions = [seen_fov.get(i, (0.0, 0.0)) for i in range(max_p + 1)] + else: + positions = [] + return types, fovs, positions, scheduled + + +def format_duration(seconds: float, *, show_ms: bool = False) -> str: + """``hh:mm:ss h`` / ``mm:ss min`` / ``s s`` with optional .mmm. + + Leading components are dropped when zero, and the largest displayed + component is suffixed with its unit ("h" / "min" / "s") so the unit + stays explicit even after truncation. + """ + if seconds < 0: + return "-" + format_duration(-seconds, show_ms=show_ms) + h = int(seconds // 3600) + m = int((seconds % 3600) // 60) + s = seconds - h * 3600 - m * 60 + if show_ms: + if h: + return f"{h:d}:{m:02d}:{s:06.3f} h" + if m: + return f"{m:d}:{s:06.3f} min" + return f"{s:.3f} s" + s_int = int(s) + if h: + return f"{h:d}:{m:02d}:{s_int:02d} h" + if m: + return f"{m:d}:{s_int:02d} min" + return f"{s_int:d} s" + + +def _runs(seq: Sequence[str]): + """Yield (start, end_exclusive, value) for each contiguous run.""" + if not seq: + return + start, cur = 0, seq[0] + for i in range(1, len(seq)): + if seq[i] != cur: + yield start, i, cur + start, cur = i, seq[i] + yield start, len(seq), cur + + +def _hex_to_rgb(h: str) -> tuple[int, int, int]: + h = h.lstrip("#") + return int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16) + + +def _chip_style(color_hex: str, *, active: bool) -> str: + r, g, b = _hex_to_rgb(color_hex) + if active: + return ( + f"background-color: rgba({r},{g},{b},255); color: white; " + f"padding: 2px 8px; border-radius: {_RADIUS_PX}px; font-weight: bold;" + ) + return ( + f"background-color: rgba({r},{g},{b},60); color: rgba({r},{g},{b},180); " + f"padding: 2px 8px; border-radius: {_RADIUS_PX}px; font-weight: bold;" + ) + +# ───────────────────────────────────────────────────────────────────────── +# EventStrip +# ───────────────────────────────────────────────────────────────────────── + +class EventStrip(QWidget): + """Horizontal strip with one cell per event, color-coded by type. + + Past + current cells are fully opaque (acts as a progress bar). Future + cells are dimmed. Same-type contiguous runs are merged into a single + fill so 1000s of events still render correctly with consistent alpha + rather than the over-stacking that per-cell alpha causes at sub-pixel + cell widths. + """ + + def __init__(self, event_types: Sequence[str], parent: QWidget | None = None): + super().__init__(parent) + self._types = list(event_types) + self._current = -1 + self.setMinimumHeight(20) + self.setMinimumWidth(120) + + def set_types(self, event_types: Sequence[str]) -> None: + self._types = list(event_types) + self._current = -1 + self.update() + + def set_current(self, index: int) -> None: + if index != self._current: + self._current = index + self.update() + + def paintEvent(self, _event) -> None: + painter = QPainter(self) + + # Empty state: grey rounded placeholder so the timeline region is + # visible before a run is loaded (replaced by the colored progress + # bar once events arrive). + if not self._types: + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(QBrush(QColor(128, 128, 128, 28))) + painter.drawRoundedRect(QRectF(self.rect()), _RADIUS_PX, _RADIUS_PX) + placeholder = self.palette().color(QPalette.ColorRole.WindowText) + placeholder.setAlpha(128) + painter.setPen(QPen(placeholder)) + painter.drawText( + self.rect(), + Qt.AlignmentFlag.AlignCenter, + "(no events loaded)", + ) + return + + painter.setRenderHint(QPainter.RenderHint.Antialiasing, False) + + n = len(self._types) + w = self.width() + h = self.height() + gap = _GAP_PX if (w / max(n, 1)) >= _MIN_GAP_AT_CELL_W else 0 + cell_w = (w - max(0, n - 1) * gap) / n + stride = cell_w + gap + + def x_for(i: int) -> float: + return i * stride + + def width_for(span: int) -> float: + return max(cell_w * span + max(0, span - 1) * gap, 1.0) + + # Future / dim layer + for start, end, t in _runs(self._types): + color = QColor(EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR)) + color.setAlpha(_FUTURE_ALPHA) + painter.fillRect(QRectF(x_for(start), 0, width_for(end - start), h), color) + + # Past + current overlay + if self._current >= 0: + past_end = min(self._current + 1, n) + for start, end, t in _runs(self._types[:past_end]): + color = QColor(EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR)) + color.setAlpha(_PAST_ALPHA) + painter.fillRect( + QRectF(x_for(start), 0, width_for(end - start), h), color + ) + + # Active border + if 0 <= self._current < n: + t = self._types[self._current] + pen = QPen(QColor(EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR)).darker(160)) + pen.setWidth(_BORDER_PX) + painter.setPen(pen) + visible_w = max(cell_w, 3.0) + x = self._current * stride + x = min(max(0.0, x - (visible_w - cell_w) / 2), w - visible_w) + painter.drawRect(QRectF(x + 0.5, 0.5, visible_w - 1, h - 1)) + + +# ───────────────────────────────────────────────────────────────────────── +# FovMap +# ───────────────────────────────────────────────────────────────────────── + +class FovMap(QWidget): + """Equal-aspect map of FOV positions with a visit-order path. + + The active dot is recolored by ``set_current(idx, color=...)`` so it + can match the current event type's color. Background is transparent so + napari's theme shows through; the drawing area is constrained to the + largest centered square so the widget never appears taller than wide. + """ + + def __init__( + self, + positions: Sequence[tuple[float, float]], + parent: QWidget | None = None, + ): + super().__init__(parent) + self._positions = list(positions) + self._current = -1 + self._active_color = EVENT_COLORS["imaging"] + self.setMinimumWidth(160) + # Expand to the available width; height is pinned square in + # resizeEvent. heightForWidth is intentionally NOT used -- + # QVBoxLayout honors it poorly and ends up distributing slack + # space above and below the widget. + from qtpy.QtWidgets import QSizePolicy + self.setSizePolicy( + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + ) + self.setFixedHeight(160) + + def resizeEvent(self, event) -> None: + super().resizeEvent(event) + # Keep the widget square: height tracks width. setFixedHeight to + # the same value is a no-op, so this settles after one relayout. + if self.height() != self.width(): + self.setFixedHeight(self.width()) + + def set_positions(self, positions: Sequence[tuple[float, float]]) -> None: + self._positions = list(positions) + self._current = -1 + self.update() + + def set_current(self, index: int, color: str | None = None) -> None: + changed = ( + index != self._current + or (color is not None and color != self._active_color) + ) + if color is not None: + self._active_color = color + self._current = index + if changed: + self.update() + + def _world_to_screen(self) -> tuple[float, float, float]: + if not self._positions: + return 1.0, 0.0, 0.0 + xs = [p[0] for p in self._positions] + ys = [p[1] for p in self._positions] + x_min, x_max = min(xs), max(xs) + y_min, y_max = min(ys), max(ys) + x_span = max(x_max - x_min, _MIN_WORLD_EXTENT) + y_span = max(y_max - y_min, _MIN_WORLD_EXTENT) + # Confine the drawing to the largest centered square -- so even if + # the widget ends up wider than tall (or vice-versa), the dots stay + # together in a square region rather than getting spread thin + # along the longer axis. + side = min(self.width(), self.height()) + usable = max(1.0, side - 2 * _MAP_PADDING_PX) + scale_x = usable / x_span if x_span > _MIN_WORLD_EXTENT * 10 else float("inf") + scale_y = usable / y_span if y_span > _MIN_WORLD_EXTENT * 10 else float("inf") + scale = min(scale_x, scale_y) + if scale == float("inf"): + scale = 1.0 + x_off = self.width() / 2 - scale * (x_min + x_max) / 2 + y_off = self.height() / 2 + scale * (y_min + y_max) / 2 # +y up + return scale, x_off, y_off + + def _to_screen(self, x: float, y: float) -> QPointF: + s, xo, yo = self._world_to_screen() + return QPointF(s * x + xo, yo - s * y) + + def paintEvent(self, _event) -> None: + painter = QPainter(self) + painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) + + # Panel background -- the map paints its own rounded-rect fill (same + # translucent neutral as the stats panel) rather than sitting in a + # QFrame, so the FOV-counter text lands exactly at the panel's + # top-left corner regardless of how the layout sizes the widget. + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(QBrush(QColor(128, 128, 128, 28))) + painter.drawRoundedRect(QRectF(self.rect()), _RADIUS_PX, _RADIUS_PX) + + # FOV counter, pinned to the panel's top-left corner. + n = len(self._positions) + if n == 0: + label = "FOV -/-" + else: + cur_txt = str(self._current + 1) if 0 <= self._current < n else "-" + label = f"FOV {cur_txt}/{n}" + text_color = self.palette().color(QPalette.ColorRole.WindowText) + painter.setPen(QPen(text_color)) + painter.drawText( + QPointF(8, 6 + painter.fontMetrics().ascent()), label + ) + + if not self._positions: + return + + # Inactive dots / path use napari's foreground text color at 50% + # alpha, so the map reads correctly on both light and dark themes. + inactive = self.palette().color(QPalette.ColorRole.WindowText) + inactive.setAlpha(128) + + pts = [self._to_screen(*p) for p in self._positions] + + if len(pts) >= 2: + pen = QPen(inactive) + pen.setWidth(_PATH_WIDTH_PX) + pen.setCapStyle(Qt.PenCapStyle.RoundCap) + pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin) + painter.setPen(pen) + for a, b in zip(pts[:-1], pts[1:]): + painter.drawLine(a, b) + + painter.setPen(Qt.PenStyle.NoPen) + painter.setBrush(QBrush(inactive)) + for pt in pts: + painter.drawEllipse(pt, _DOT_RADIUS_PX, _DOT_RADIUS_PX) + + if 0 <= self._current < len(pts): + painter.setBrush(QBrush(QColor(self._active_color))) + painter.drawEllipse(pts[self._current], _DOT_RADIUS_PX, _DOT_RADIUS_PX) + + +# ───────────────────────────────────────────────────────────────────────── +# ExperimentStatusWidget +# ───────────────────────────────────────────────────────────────────────── class ExperimentStatusWidget(QWidget): - """A read-out + Stop button for the controller's currently-bound run.""" + """Read-out + Stop button for the controller's currently-bound run.""" def __init__(self, controller: "Controller", parent: QWidget | None = None) -> None: super().__init__(parent) self._controller = controller self._handle: RunHandle | None = None + # Cached plan derived from handle.events at run start + self._event_types: list[str] = [] + self._event_fovs: list[int] = [] + self._scheduled: list[float] = [] + self._total_duration: float = 0.0 + self._build_ui() self._refresh(None) controller.runStarted.connect(self._on_run_started) - # psygnal's queued connections (used by statusChanged below) park - # callbacks in a per-thread queue that nothing drains by default. - # start_emitting_from_queue installs a QTimer on the main thread - # that calls psygnal.emit_queued() on every Qt event-loop tick, so - # worker-thread emits actually reach the widget. Idempotent: safe - # to call from multiple widgets / multiple constructions. + # Drain psygnal's queued callbacks via a main-thread QTimer; without + # this, statusChanged emissions from the worker thread would sit in + # psygnal's _GLOBAL_QUEUE forever. Idempotent across widgets. try: from psygnal.qt import start_emitting_from_queue start_emitting_from_queue() except ImportError: pass + # Tick the elapsed/remaining clocks between statusChanged emissions + # so the time fields don't visibly freeze between frames. + self._tick_timer = QTimer(self) + self._tick_timer.timeout.connect(self._tick) + self._tick_timer.start(250) + # -- UI construction ---------------------------------------------------- def _build_ui(self) -> None: self.setWindowTitle("Experiment status") + # ── State chip -- translucent neutral fill (matches the FOV map / + # stats panels), so it keeps the legend-chip shape without + # competing with the imaging/stim/ref colors. self._state_label = QLabel("idle") self._state_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self._state_label.setStyleSheet( - "font-weight: bold; padding: 4px; border-radius: 4px;" + "font-weight: bold; padding: 2px 8px; " + f"border-radius: {_RADIUS_PX}px; " + f"background-color: {_PANEL_BG};" ) - self._progress = QProgressBar() - self._progress.setRange(0, 0) # indeterminate until we have totals - self._progress.setTextVisible(True) + # ── Legend chips + self._legend_chips: dict[str, QLabel] = {} + legend_row = QHBoxLayout() + legend_row.setContentsMargins(0, 0, 0, 0) + legend_row.setSpacing(6) + for label, key in [("imaging", "imaging"), ("stim", "stim"), ("ref", "ref")]: + chip = QLabel(label) + chip.setStyleSheet(_chip_style(EVENT_COLORS[key], active=False)) + self._legend_chips[key] = chip + legend_row.addWidget(chip) + legend_row.addStretch(1) + + # ── Strip + map + self._strip = EventStrip([]) + self._map = FovMap([]) + + # ── Stats form -- inherit napari's font/palette; only the time + # values get the platform's fixed-width font (column alignment). + mono = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont) + self._event_value = QLabel("-/-") + self._elapsed_value = QLabel("-") + self._scheduled_value = QLabel("-") + self._lag_value = QLabel("-") + self._remaining_value = QLabel("-") + self._errors_value = QLabel("-") + right_align = ( + Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter + ) + for w in ( + self._event_value, self._elapsed_value, self._scheduled_value, + self._lag_value, self._remaining_value, self._errors_value, + ): + w.setAlignment(right_align) + for w in (self._elapsed_value, self._scheduled_value, + self._lag_value, self._remaining_value): + w.setFont(mono) form = QFormLayout() - self._fov_label = QLabel("—") - self._event_label = QLabel("—") - self._frames_label = QLabel("0") - self._lag_label = QLabel("—") - self._elapsed_label = QLabel("—") - self._errors_label = QLabel("0") - form.addRow("Current FOV:", self._fov_label) - form.addRow("Event index:", self._event_label) - form.addRow("Frames received:", self._frames_label) - form.addRow("Lag (ms):", self._lag_label) - form.addRow("Elapsed:", self._elapsed_label) - form.addRow("Background errors:", self._errors_label) + form.setContentsMargins(6, 6, 6, 6) + form.setSpacing(2) + form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) + # Stretch the value column so right-aligned text lands at the + # widget's right edge instead of hugging the label. + form.setFieldGrowthPolicy( + QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow + ) + form.addRow("event:", self._event_value) + form.addRow("elapsed:", self._elapsed_value) + form.addRow("scheduled:", self._scheduled_value) + form.addRow("lag:", self._lag_value) + form.addRow("remaining:", self._remaining_value) + form.addRow("errors:", self._errors_value) + + # ── Stats panel: a subtly-shaded frame echoing napari's boxed + # layer-controls sections. (The FOV map paints its own matching + # background in paintEvent, so it isn't wrapped in a frame.) + stats_panel = QFrame() + stats_panel.setObjectName("faroPanel") + stats_panel_layout = QVBoxLayout(stats_panel) + stats_panel_layout.setContentsMargins(0, 0, 0, 0) + stats_panel_layout.addLayout(form) + stats_panel.setStyleSheet( + f"QFrame#faroPanel {{ background-color: {_PANEL_BG}; " + f"border-radius: {_RADIUS_PX}px; }}" + ) + # ── Pause + Stop buttons + self._pause_btn = QPushButton("Pause") + self._pause_btn.clicked.connect(self._on_pause_clicked) + self._pause_btn.setEnabled(False) self._stop_btn = QPushButton("Stop") self._stop_btn.clicked.connect(self._on_stop_clicked) self._stop_btn.setEnabled(False) - button_row = QHBoxLayout() button_row.addStretch(1) + button_row.addWidget(self._pause_btn) button_row.addWidget(self._stop_btn) layout = QVBoxLayout(self) + layout.setContentsMargins(8, 8, 8, 8) + layout.setSpacing(6) layout.addWidget(self._state_label) - layout.addWidget(self._progress) - layout.addLayout(form) + layout.addLayout(legend_row) + layout.addWidget(self._strip) + # No stretch on the map -- resizeEvent pins it square, and the + # trailing stretch absorbs leftover vertical space below. + layout.addWidget(self._map) + layout.addWidget(stats_panel) layout.addLayout(button_row) layout.addStretch(1) # -- run binding -------------------------------------------------------- def _on_run_started(self, handle: RunHandle) -> None: - """Re-bind whenever the controller emits ``runStarted``.""" + """Re-bind to a new run; rebuild the strip + map from its events.""" if self._handle is not None: try: self._handle.statusChanged.disconnect(self._refresh) @@ -124,22 +595,37 @@ def _on_run_started(self, handle: RunHandle) -> None: pass self._handle = handle - # thread="main" routes worker-thread emits through Qt's queued - # connection to the main thread. Without it, the slot runs - # synchronously on the emitter (worker / engine) thread and - # touches QWidgets from off-main -- under napari that lands - # in vispy/OpenGL with "Cannot make QOpenGLContext current in - # a different thread" -> SIGABRT. + # thread="main" routes worker-thread emits through psygnal's main- + # thread queue (drained by start_emitting_from_queue's QTimer in + # __init__). Without it the slot would run synchronously off the + # worker thread and touch QWidgets / OpenGL from the wrong thread. handle.statusChanged.connect(self._refresh, thread="main") + + # Rebuild plan-derived widgets from the events list, if we have one + events = getattr(handle, "events", None) or [] + types, fovs, positions, scheduled = _extract_plan(events) + self._event_types = types + self._event_fovs = fovs + self._scheduled = scheduled + self._total_duration = scheduled[-1] if scheduled else 0.0 + self._strip.set_types(types) + self._map.set_positions(positions) + self._refresh(handle.status()) def _on_stop_clicked(self) -> None: - # cancel() is idempotent and a no-op when the run isn't running; - # rely on it rather than re-checking is_running here, so the button - # works even if state is "done"/"error" by the time the click lands. if self._handle is not None: self._handle.cancel() + def _on_pause_clicked(self) -> None: + """Toggle pause / resume on the bound handle.""" + if self._handle is None: + return + if self._handle.is_paused(): + self._handle.resume() + else: + self._handle.pause() + # -- refresh ------------------------------------------------------------ def _refresh(self, status: RunStatus | None) -> None: @@ -148,61 +634,159 @@ def _refresh(self, status: RunStatus | None) -> None: Also called once with ``None`` at construction time (no handle yet). """ if status is None: - self._state_label.setText("idle (no run yet)") - self._state_label.setStyleSheet( - "font-weight: bold; padding: 4px; border-radius: 4px; " - f"background-color: {_STATE_COLORS['pending']}; color: white;" - ) - self._progress.setRange(0, 0) - self._progress.setValue(0) - self._progress.setFormat("") - self._stop_btn.setEnabled(False) + self._render_idle() return - color = _STATE_COLORS.get(status.state, "#888888") + # ── State banner -- plain bold text, no background fill (a colored + # banner clashed with the imaging/stim/ref legend colors). self._state_label.setText(status.state.upper()) - self._state_label.setStyleSheet( - "font-weight: bold; padding: 4px; border-radius: 4px; " - f"background-color: {color}; color: white;" - ) - if status.n_events_total > 0: - self._progress.setRange(0, status.n_events_total) - self._progress.setValue(status.n_events_consumed) - self._progress.setFormat( - f"{status.n_events_consumed} / {status.n_events_total} events" - ) + # ── Strip + map cursor + cur_idx = self._current_index(status) + n_total = status.n_events_total or len(self._event_types) + + if 0 <= cur_idx < len(self._event_types): + t = self._event_types[cur_idx] + color = EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR) + self._strip.set_current(cur_idx) + fov_for_event = self._event_fovs[cur_idx] + self._map.set_current(fov_for_event, color=color) + self._update_legend(active_type=t) else: - self._progress.setRange(0, 0) - self._progress.setValue(0) - self._progress.setFormat("") + self._strip.set_current(-1) + self._map.set_current(-1) + self._update_legend(active_type=None) - self._fov_label.setText( - "—" if status.current_fov is None else str(status.current_fov) - ) - self._event_label.setText( - "—" if status.current_event_index is None - else ", ".join(f"{k}={v}" for k, v in status.current_event_index.items()) + # ── Stats: event index + self._event_value.setText( + f"{cur_idx + 1} / {n_total}" if n_total else "-/-" ) - self._frames_label.setText(str(status.n_frames_received)) - if status.lag_ms is None: - self._lag_label.setText("—") - else: - self._lag_label.setText(f"{status.lag_ms:+.0f}") - if status.started_at is not None: - end = status.finished_at or time.monotonic() - self._elapsed_label.setText(f"{end - status.started_at:.1f}s") - else: - self._elapsed_label.setText("—") + # ── Stats: elapsed, scheduled, lag, remaining + self._render_time_fields(status, cur_idx) + # ── Errors n_errors = len(status.background_errors) if status.fatal_error is not None: - self._errors_label.setText( - f"{n_errors} background + fatal: {type(status.fatal_error).__name__}" + self._errors_value.setText( + f"{n_errors} bg + fatal: {type(status.fatal_error).__name__}" + ) + self._errors_value.setStyleSheet( + f"color: {_LAG_BAD_COLOR}; font-weight: bold;" ) else: - self._errors_label.setText(str(n_errors)) + self._errors_value.setText(str(n_errors)) + self._errors_value.setStyleSheet("") + + # ── Buttons + self._update_buttons(status.state) + + def _update_buttons(self, state: str) -> None: + """Enable/label Pause + Stop according to the run state.""" + # Pause/Resume is meaningful only while the run is live. + live = state in ("running", "pausing", "paused") + self._pause_btn.setEnabled(live) + if state in ("paused", "pausing"): + self._pause_btn.setText("Resume") + else: + self._pause_btn.setText("Pause") + # Stop is meaningful while running OR paused (cancel breaks the + # pause-wait too). + self._stop_btn.setEnabled(live) + + def _render_idle(self) -> None: + self._state_label.setText("idle (no run yet)") + self._strip.set_current(-1) + self._map.set_current(-1) + self._update_legend(active_type=None) + self._event_value.setText("-/-") + for w in (self._elapsed_value, self._scheduled_value, + self._lag_value, self._remaining_value): + w.setText("-") + self._lag_value.setStyleSheet("") + self._errors_value.setText("-") + self._errors_value.setStyleSheet("") + self._pause_btn.setEnabled(False) + self._pause_btn.setText("Pause") + self._stop_btn.setEnabled(False) + + @staticmethod + def _current_index(status: RunStatus) -> int: + """Index of the most-recently-*snapped* event (-1 if none yet). + + Uses ``n_frames_received`` (actual imaging/ref snaps) rather than + ``n_events_consumed`` (the feed loop's queue-ahead position). The + feed loop runs 3-4 events ahead of the engine because of the + backpressure window, so keying off it would make the strip jump + and the scheduled-time field flicker between the queued event and + the snapped one. Both ``_refresh`` and the ``_tick`` QTimer call + this so they always agree on "current". + """ + return status.n_frames_received - 1 if status.n_frames_received > 0 else -1 - # Stop is only meaningful while the run is actually running. - self._stop_btn.setEnabled(status.state in ("running",)) + def _render_time_fields(self, status: RunStatus, cur_idx: int) -> None: + # Elapsed: prefer (now - started_at); fall back to last_frame_wallclock. + if status.started_at is not None: + if status.finished_at is not None: + elapsed = status.finished_at - status.started_at + else: + elapsed = time.monotonic() - status.started_at + else: + elapsed = None + + scheduled = ( + self._scheduled[cur_idx] + if 0 <= cur_idx < len(self._scheduled) + else None + ) + + # lag: prefer the controller's per-frame measurement; fall back to + # elapsed-vs-scheduled if the per-frame value isn't populated yet. + lag_s: float | None = None + if status.lag_ms is not None: + lag_s = status.lag_ms / 1000.0 + elif elapsed is not None and scheduled is not None: + lag_s = elapsed - scheduled + + remaining: float | None = None + if elapsed is not None and self._total_duration: + remaining = max(0.0, self._total_duration - elapsed) + + self._elapsed_value.setText( + format_duration(elapsed) if elapsed is not None else "-" + ) + self._scheduled_value.setText( + format_duration(scheduled) if scheduled is not None else "-" + ) + if lag_s is None: + self._lag_value.setText("-") + self._lag_value.setStyleSheet("") + else: + sign = "+" if lag_s >= 0 else "" + self._lag_value.setText(f"{sign}{format_duration(lag_s, show_ms=True)}") + if lag_s > _LAG_WARN_S: + self._lag_value.setStyleSheet( + f"color: {_LAG_BAD_COLOR}; font-weight: bold;" + ) + else: + self._lag_value.setStyleSheet("") + self._remaining_value.setText( + format_duration(remaining) if remaining is not None else "-" + ) + + def _update_legend(self, active_type: str | None) -> None: + for key, chip in self._legend_chips.items(): + chip.setStyleSheet( + _chip_style(EVENT_COLORS[key], active=(key == active_type)) + ) + + def _tick(self) -> None: + """QTimer slot: refresh time-derived fields between status emissions.""" + if self._handle is None: + return + status = self._handle.status() + # Keep the clock live while the run is active -- including while + # paused, since wall-clock elapsed (and thus lag) keeps growing. + if status.state not in ("running", "pausing", "paused"): + return + self._render_time_fields(status, self._current_index(status)) From 3c0e7984cd73c87350c821f05facbd42c028993e Mon Sep 17 00:00:00 2001 From: hinderling Date: Sat, 16 May 2026 11:27:10 +0200 Subject: [PATCH 07/41] example: multi-FOV plan in the async optogenetic demo Add a second stage position (20, 20, 0) to the baseline / stim / recovery sequences so the demo exercises a 2-FOV acquisition -- the ExperimentStatusWidget's FOV map then shows both positions and the visit-order path between them. Drop the frame interval 1.5s -> 1s. --- .../demo_sim_optogenetic_napari_async.ipynb | 602 +----------------- 1 file changed, 24 insertions(+), 578 deletions(-) diff --git a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb index 6a6b7b9..56127f4 100644 --- a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb +++ b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb @@ -32,29 +32,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
Config Groups\n",
-       "└── Channel\n",
-       "    ├── DAPI\n",
-       "    ├── membrane\n",
-       "    └── phase-contrast\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1mConfig Groups\u001b[0m\n", - "└── \u001b[1;36mChannel\u001b[0m\n", - " ├── DAPI\n", - " ├── membrane\n", - " └── phase-contrast\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from virtual_microscope.backends.optogenetic import setup_optogenetic\n", "from faro.microscope.simulation import UniMMCoreSimulation\n", @@ -78,20 +56,9 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "import napari\n", "from napari_micromanager import MainWindow\n", @@ -113,7 +80,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -226,18 +193,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Directory /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_9zg31hbr/tracks created \n", - "Storage path: /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_9zg31hbr\n" - ] - } - ], + "outputs": [], "source": [ "import os, shutil, tempfile\n", "from faro.core.pipeline import ImageProcessingPipeline\n", @@ -272,25 +230,14 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from faro.widgets import ExperimentStatusWidget\n", "\n", "status_widget = ExperimentStatusWidget(ctrl)\n", - "viewer.window.add_dock_widget(status_widget, name=\"Experiment status\", area=\"right\")" + "viewer.window.add_dock_widget(status_widget, name=\"experiment status\", area=\"right\")" ] }, { @@ -304,342 +251,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "40 events: 5 baseline + 30 stim + 5 recovery\n" - ] - }, - { - "data": { - "application/vnd.microsoft.datawrangler.viewer.v0+json": { - "columns": [ - { - "name": "index", - "rawType": "int64", - "type": "integer" - }, - { - "name": "fov", - "rawType": "int64", - "type": "integer" - }, - { - "name": "timestep", - "rawType": "int64", - "type": "integer" - }, - { - "name": "time", - "rawType": "float64", - "type": "float" - }, - { - "name": "x_pos", - "rawType": "float64", - "type": "float" - }, - { - "name": "y_pos", - "rawType": "float64", - "type": "float" - }, - { - "name": "z_pos", - "rawType": "float64", - "type": "float" - }, - { - "name": "channels", - "rawType": "object", - "type": "unknown" - }, - { - "name": "stim_channels", - "rawType": "object", - "type": "unknown" - }, - { - "name": "ref_channels", - "rawType": "object", - "type": "unknown" - }, - { - "name": "stim", - "rawType": "bool", - "type": "boolean" - }, - { - "name": "ref", - "rawType": "bool", - "type": "boolean" - }, - { - "name": "phase", - "rawType": "object", - "type": "string" - }, - { - "name": "stim_power", - "rawType": "float64", - "type": "float" - }, - { - "name": "stim_exposure", - "rawType": "float64", - "type": "float" - } - ], - "ref": "4f1c44aa-2500-4534-a1ba-06471cda1a66", - "rows": [ - [ - "0", - "0", - "0", - "0.0", - "0.0", - "0.0", - "0.0", - "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", - "()", - "()", - "False", - "False", - "baseline", - null, - null - ], - [ - "1", - "0", - "1", - "1.5", - "0.0", - "0.0", - "0.0", - "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", - "()", - "()", - "False", - "False", - "baseline", - null, - null - ], - [ - "2", - "0", - "2", - "3.0", - "0.0", - "0.0", - "0.0", - "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", - "()", - "()", - "False", - "False", - "baseline", - null, - null - ], - [ - "3", - "0", - "3", - "4.5", - "0.0", - "0.0", - "0.0", - "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", - "()", - "()", - "False", - "False", - "baseline", - null, - null - ], - [ - "4", - "0", - "4", - "6.0", - "0.0", - "0.0", - "0.0", - "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", - "()", - "()", - "False", - "False", - "baseline", - null, - null - ] - ], - "shape": { - "columns": 14, - "rows": 5 - } - }, - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
fovtimesteptimex_posy_posz_poschannelsstim_channelsref_channelsstimrefphasestim_powerstim_exposure
0000.00.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
1011.50.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
2023.00.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
3034.50.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
4046.00.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
\n", - "
" - ], - "text/plain": [ - " fov timestep time x_pos y_pos z_pos \\\n", - "0 0 0 0.0 0.0 0.0 0.0 \n", - "1 0 1 1.5 0.0 0.0 0.0 \n", - "2 0 2 3.0 0.0 0.0 0.0 \n", - "3 0 3 4.5 0.0 0.0 0.0 \n", - "4 0 4 6.0 0.0 0.0 0.0 \n", - "\n", - " channels stim_channels \\\n", - "0 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", - "1 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", - "2 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", - "3 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", - "4 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", - "\n", - " ref_channels stim ref phase stim_power stim_exposure \n", - "0 () False False baseline NaN NaN \n", - "1 () False False baseline NaN NaN \n", - "2 () False False baseline NaN NaN \n", - "3 () False False baseline NaN NaN \n", - "4 () False False baseline NaN NaN " - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from faro.core.data_structures import RTMSequence, combine\n", "from faro.core.utils import events_to_dataframe\n", @@ -647,18 +261,18 @@ "n_baseline = 5\n", "n_stim = 30\n", "n_recovery = 5\n", - "interval_s = 1.5 # seconds between frames; raise if your machine struggles\n", + "interval_s = 1 # seconds between frames; raise if your machine struggles\n", "\n", "baseline = RTMSequence(\n", " time_plan={\"interval\": interval_s, \"loops\": n_baseline},\n", - " stage_positions=[(0.0, 0.0, 0.0)],\n", + " stage_positions=[(0.0, 0.0, 0.0),(20,20,0.0)],\n", " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", " rtm_metadata={\"phase\": \"baseline\"},\n", ")\n", "\n", "stim_phase = RTMSequence(\n", " time_plan={\"interval\": interval_s, \"loops\": n_stim},\n", - " stage_positions=[(0.0, 0.0, 0.0)],\n", + " stage_positions=[(0.0, 0.0, 0.0),(20,20,0.0)],\n", " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", " stim_channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", " stim_frames=range(n_stim),\n", @@ -667,7 +281,7 @@ "\n", "recovery = RTMSequence(\n", " time_plan={\"interval\": interval_s, \"loops\": n_recovery},\n", - " stage_positions=[(0.0, 0.0, 0.0)],\n", + " stage_positions=[(0.0, 0.0, 0.0),(20,20,0.0)],\n", " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", " rtm_metadata={\"phase\": \"recovery\"},\n", ")\n", @@ -691,20 +305,11 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "run started, handle.is_running()=True\n", - "current status: running\n" - ] - } - ], + "outputs": [], "source": [ - "handle = ctrl.run_experiment(events, stim_mode=\"current\")\n", + "handle = ctrl.run_experiment(events, stim_mode=\"previous\")\n", "print(f\"run started, handle.is_running()={handle.is_running()}\")\n", "print(f\"current status: {handle.status().state}\")" ] @@ -720,24 +325,9 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "state : done\n", - "events consumed : 40 / 40\n", - "frames received : 70\n", - "current FOV : 0\n", - "current event : {'t': 39, 'p': 0}\n", - "lag (ms) : 116.06683302670717\n", - "bg errors : 0\n", - "fatal error : None\n" - ] - } - ], + "outputs": [], "source": [ "s = handle.status()\n", "print(f\"state : {s.state}\")\n", @@ -761,107 +351,9 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[notify] state=running consumed=28/40 frames=50 lag_ms=102.20704099629074\n", - "[notify] state=running consumed=28/40 frames=50 lag_ms=102.20704099629074\n", - "[notify] state=running consumed=29/40 frames=50 lag_ms=102.20704099629074\n", - "[notify] state=running consumed=29/40 frames=50 lag_ms=102.20704099629074\n", - "[notify] state=running consumed=29/40 frames=51 lag_ms=451.81912498082966\n", - "[notify] state=running consumed=29/40 frames=51 lag_ms=451.81912498082966\n", - "[notify] state=running consumed=29/40 frames=52 lag_ms=120.75233302311972\n", - "[notify] state=running consumed=29/40 frames=52 lag_ms=120.75233302311972\n", - "[notify] state=running consumed=30/40 frames=52 lag_ms=120.75233302311972\n", - "[notify] state=running consumed=30/40 frames=52 lag_ms=120.75233302311972\n", - "[notify] state=running consumed=30/40 frames=53 lag_ms=473.3314160257578\n", - "[notify] state=running consumed=30/40 frames=53 lag_ms=473.3314160257578\n", - "[notify] state=running consumed=30/40 frames=54 lag_ms=125.35141600528732\n", - "[notify] state=running consumed=30/40 frames=54 lag_ms=125.35141600528732\n", - "[notify] state=running consumed=31/40 frames=54 lag_ms=125.35141600528732\n", - "[notify] state=running consumed=31/40 frames=54 lag_ms=125.35141600528732\n", - "[notify] state=running consumed=31/40 frames=55 lag_ms=472.52970800036564\n", - "[notify] state=running consumed=31/40 frames=55 lag_ms=472.52970800036564\n", - "[notify] state=running consumed=31/40 frames=56 lag_ms=124.23370801843703\n", - "[notify] state=running consumed=31/40 frames=56 lag_ms=124.23370801843703\n", - "[notify] state=running consumed=32/40 frames=56 lag_ms=124.23370801843703\n", - "[notify] state=running consumed=32/40 frames=56 lag_ms=124.23370801843703\n", - "[notify] state=running consumed=32/40 frames=57 lag_ms=472.7424160228111\n", - "[notify] state=running consumed=32/40 frames=57 lag_ms=472.7424160228111\n", - "[notify] state=running consumed=32/40 frames=58 lag_ms=107.98183298902586\n", - "[notify] state=running consumed=32/40 frames=58 lag_ms=107.98183298902586\n", - "[notify] state=running consumed=33/40 frames=58 lag_ms=107.98183298902586\n", - "[notify] state=running consumed=33/40 frames=58 lag_ms=107.98183298902586\n", - "[notify] state=running consumed=33/40 frames=59 lag_ms=458.4957080078311\n", - "[notify] state=running consumed=33/40 frames=59 lag_ms=458.4957080078311\n", - "[notify] state=running consumed=33/40 frames=60 lag_ms=122.33808299060911\n", - "[notify] state=running consumed=33/40 frames=60 lag_ms=122.33808299060911\n", - "[notify] state=running consumed=34/40 frames=60 lag_ms=122.33808299060911\n", - "[notify] state=running consumed=34/40 frames=60 lag_ms=122.33808299060911\n", - "[notify] state=running consumed=34/40 frames=61 lag_ms=477.04116598470137\n", - "[notify] state=running consumed=34/40 frames=61 lag_ms=477.04116598470137\n", - "[notify] state=running consumed=34/40 frames=62 lag_ms=120.07075001019984\n", - "[notify] state=running consumed=34/40 frames=62 lag_ms=120.07075001019984\n", - "[notify] state=running consumed=35/40 frames=62 lag_ms=120.07075001019984\n", - "[notify] state=running consumed=35/40 frames=62 lag_ms=120.07075001019984\n", - "[notify] state=running consumed=35/40 frames=63 lag_ms=471.21175000211224\n", - "[notify] state=running consumed=35/40 frames=63 lag_ms=471.21175000211224\n", - "[notify] state=running consumed=35/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=35/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=36/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=36/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=37/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=37/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=38/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=38/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=39/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=39/40 frames=64 lag_ms=105.58366600889713\n", - "[notify] state=running consumed=39/40 frames=65 lag_ms=464.6262080059387\n", - "[notify] state=running consumed=39/40 frames=65 lag_ms=464.6262080059387\n", - "[notify] state=running consumed=40/40 frames=65 lag_ms=464.6262080059387\n", - "[notify] state=running consumed=40/40 frames=65 lag_ms=464.6262080059387\n", - "[notify] state=running consumed=40/40 frames=66 lag_ms=119.60500001441687\n", - "[notify] state=running consumed=40/40 frames=66 lag_ms=119.60500001441687\n", - "[notify] state=running consumed=40/40 frames=67 lag_ms=123.85783297941089\n", - "[notify] state=running consumed=40/40 frames=67 lag_ms=123.85783297941089\n", - "[notify] state=running consumed=40/40 frames=68 lag_ms=121.0207500262186\n", - "[notify] state=running consumed=40/40 frames=68 lag_ms=121.0207500262186\n", - "[notify] state=running consumed=40/40 frames=69 lag_ms=113.7447910150513\n", - "[notify] state=running consumed=40/40 frames=69 lag_ms=113.7447910150513\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[31;20m2026-05-15 17:48:18,561 - pymmcore-plus - ERROR - (_logger.py:154) SimCameraDevice does not support setting ROI.\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[notify] state=running consumed=40/40 frames=70 lag_ms=116.06683302670717\n", - "[notify] state=running consumed=40/40 frames=70 lag_ms=116.06683302670717\n", - "[notify] state=done consumed=40/40 frames=70 lag_ms=116.06683302670717\n", - "[notify] state=done consumed=40/40 frames=70 lag_ms=116.06683302670717\n" - ] - } - ], + "outputs": [], "source": [ "def _print_status(status):\n", " print(f\"[notify] state={status.state} consumed={status.n_events_consumed}\"\n", @@ -900,15 +392,7 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "OMP: Info #276: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.\n" - ] - } - ], + "outputs": [], "source": [ "final_status = handle.wait()\n", "print(f\"final state : {final_status.state}\")\n", @@ -939,7 +423,7 @@ " rtm_metadata={\"phase\": \"extra-recovery\"},\n", ")\n", "\n", - "handle2 = ctrl.continue_experiment(list(extra), stim_mode=\"current\")\n", + "handle2 = ctrl.continue_experiment(list(extra), stim_mode=\"previous\")\n", "print(f\"continuation started, handle2.is_running()={handle2.is_running()}\")" ] }, @@ -970,44 +454,6 @@ "ctrl.finish_experiment()\n", "print(f\"All data written to: {path}\")" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 10. Quick look at the results" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import tifffile\n", - "from glob import glob\n", - "\n", - "tracks = pd.read_parquet(os.path.join(path, \"tracks\", \"0_latest.parquet\"))\n", - "particles = tracks[\"particle\"].unique()\n", - "\n", - "fig, ax = plt.subplots(figsize=(5, 5), dpi=150)\n", - "raw_files = sorted(glob(os.path.join(path, \"raw\", \"*.tiff\")))\n", - "if raw_files:\n", - " last_img = tifffile.imread(raw_files[-1])\n", - " if last_img.ndim == 3:\n", - " last_img = last_img[0]\n", - " ax.imshow(last_img, cmap=\"gray_r\")\n", - "\n", - "cmap = plt.cm.tab20\n", - "for i, pid in enumerate(particles):\n", - " t = tracks[tracks[\"particle\"] == pid].sort_values(\"timestep\")\n", - " ax.plot(t[\"y\"], t[\"x\"], color=cmap(i % 20), lw=1.2, alpha=0.85)\n", - "ax.set_title(f\"Tracks ({len(particles)} particles)\")\n", - "ax.set_xticks([]); ax.set_yticks([])\n", - "plt.tight_layout()\n", - "plt.show()" - ] } ], "metadata": { From 4cd5399c6fe7e4044cfc39eac6c3b9488700be49 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Tue, 19 May 2026 00:02:09 +0200 Subject: [PATCH 08/41] feat: make FrameDispenser waits cancellable Add FrameDispenser.cancel() and the FrameWaitCancelled exception so a thread blocked in wait_for_frame / get_predecessor is woken immediately instead of sitting out the full timeout. This lets an experiment abort promptly: a feed loop parked in an up-to-80s stim-mask wait is released the instant the run is cancelled. --- faro/core/data_structures.py | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/faro/core/data_structures.py b/faro/core/data_structures.py index 26830ac..43819b8 100644 --- a/faro/core/data_structures.py +++ b/faro/core/data_structures.py @@ -22,6 +22,15 @@ StimMask = Union[np.ndarray, bool] +class FrameWaitCancelled(Exception): + """Raised by :class:`FrameDispenser` wait methods after :meth:`FrameDispenser.cancel`. + + Distinct from :class:`queue.Empty` (a timeout): a cancel is a + deliberate abort, not a failure, so callers should unwind quietly + rather than recording a background error. + """ + + class FrameDispenser(Generic[_T]): """Frame-ordered handoff for per-frame values between pipeline workers. @@ -58,6 +67,7 @@ def __init__(self) -> None: self._entries: dict[int, _T] = {} self._skipped: set[int] = set() self._cond = threading.Condition() + self._cancelled = False def put_for_frame(self, idx: int, value: _T) -> None: """Record *value* as this frame's output. @@ -102,6 +112,11 @@ def get_predecessor( deadline = None if timeout is None else time.monotonic() + timeout with self._cond: while True: + if self._cancelled: + raise FrameWaitCancelled( + f"FrameDispenser cancelled while waiting for the " + f"predecessor of frame {idx}" + ) status, info = self._resolve_predecessor_locked(idx) if status == "found": value = self._entries[info] @@ -143,6 +158,10 @@ def wait_for_frame(self, idx: int, timeout: Optional[float] = None) -> Optional[ deadline = None if timeout is None else time.monotonic() + timeout with self._cond: while True: + if self._cancelled: + raise FrameWaitCancelled( + f"FrameDispenser cancelled while waiting for frame {idx}" + ) if idx in self._entries: return self._entries[idx] if idx in self._skipped: @@ -180,14 +199,35 @@ def prune_below(self, idx: int) -> None: if k < idx: self._skipped.remove(k) + def cancel(self) -> None: + """Abort every blocked waiter immediately. + + Sets a latch and wakes the condition variable, so any thread + parked in :meth:`wait_for_frame` / :meth:`get_predecessor` + raises :class:`FrameWaitCancelled` on its next loop pass + instead of sitting out the full ``timeout``. This is how an + experiment tears down promptly: a feed loop that would + otherwise wait up to ``stim_mask_timeout`` seconds for a + pipeline mask is released the instant the run is cancelled. + + The latch persists until :meth:`reset` clears it — a cancelled + dispenser fails all subsequent waits. + """ + with self._cond: + self._cancelled = True + self._cond.notify_all() + def reset(self) -> None: """Clear all state. Call only when no workers are in-flight — waiters with ``timeout=None`` would otherwise block on a chain whose predecessors you just erased. + + Also lifts a :meth:`cancel` latch so the dispenser is reusable. """ with self._cond: self._entries.clear() self._skipped.clear() + self._cancelled = False self._cond.notify_all() def _resolve_predecessor_locked( From 45ba3c6b43ca1459cbd89290d3599153c10fe8fb Mon Sep 17 00:00:00 2001 From: Hinderling Date: Tue, 19 May 2026 00:02:18 +0200 Subject: [PATCH 09/41] feat: harden async experiment runs Cancellation: RunHandle gains an on_cancel hook, invoked synchronously from cancel(), that wakes a feed loop blocked in a stim-mask wait via Analyzer.cancel_pending_waits(). Previously a cancel issued during that wait took up to the stim-mask timeout (~80s) to take effect, leaving the frame handler connected in the meantime. Queue stats: Analyzer.queue_stats() / Controller.queue_stats() expose storage, pipeline and deferred queue depths for the status widget. finish_experiment runs its teardown (run wait + Analyzer drain) on a worker thread and pumps Qt, so napari stays responsive during the drain. Lag is anchored to the first frame's acquisition start rather than the worker's start time, so worker/engine startup (~1s) is no longer charged to every lag reading. --- faro/core/controller.py | 268 ++++++++++++++++++++++++++++++++++------ faro/core/run_status.py | 41 +++++- 2 files changed, 268 insertions(+), 41 deletions(-) diff --git a/faro/core/controller.py b/faro/core/controller.py index c482304..062cd7c 100644 --- a/faro/core/controller.py +++ b/faro/core/controller.py @@ -1,5 +1,5 @@ from faro.core.pipeline import store_img, ImageProcessingPipeline -from faro.core.data_structures import FovState, ImgType, StimMode +from faro.core.data_structures import FovState, FrameWaitCancelled, ImgType, StimMode from faro.core.run_status import RunHandle, RunStatus from faro.core.writers import ( Writer, @@ -42,6 +42,34 @@ class BackgroundError: traceback: str +@dataclass(frozen=True) +class QueueStats: + """Snapshot of the Analyzer's queue depths, for backpressure display. + + The three depths each flag a distinct way the analyzer can fall + behind real time: + + * ``storage_*`` -- images buffered in RAM awaiting a disk write. + Bounded; if it saturates, the camera buffer is at risk. + * ``pipeline_*`` -- tracking/segmentation tasks submitted to the + executor and not yet finished. At ``pipeline_max`` new frames + start being deferred instead of run inline. + * ``deferred_depth`` -- frames the pipeline could not keep up with, + queued (metadata only) to be reloaded from disk and processed + later. Unbounded; a steadily growing value means the pipeline is + permanently behind. + """ + + storage_depth: int # images buffered, awaiting disk write + storage_max: int # storage queue capacity + pipeline_inflight: int # pipeline tasks submitted, not yet finished + pipeline_max: int # depth at which new frames get deferred + deferred_depth: int # frames deferred for later reprocessing + stored_images: int # cumulative images written + skipped_pipeline: int # cumulative frames deferred + deferred_processed: int # cumulative deferred frames later processed + + class Analyzer: """Image analyzer with priority: Get -> Store >> Pipeline. @@ -140,6 +168,44 @@ def get_fov_state(self, fov_index: int) -> FovState: self.fov_states[fov_index] = FovState() return self.fov_states[fov_index] + def cancel_pending_waits(self) -> None: + """Wake any feed-loop thread parked in :meth:`get_stim_mask`. + + ``get_stim_mask`` blocks on ``stim_mask_queue.wait_for_frame`` + for up to ``stim_mask_timeout`` seconds. Cancelling the + dispensers makes that wait raise ``FrameWaitCancelled`` + immediately, so a cancelled run tears down without waiting out + the timeout. Called (via ``Controller._cancel_stim_waits``) + from ``RunHandle.cancel`` on the caller's thread. + + Only the stim-mask dispensers are cancelled: the tracks-queue + waiters run on pipeline workers that ``shutdown`` already + drains, and they have a file-based fallback for timeouts. + """ + # Snapshot: the feed-loop thread may insert a new FovState via + # get_fov_state() concurrently with this iteration. + for fov_state in list(self.fov_states.values()): + fov_state.stim_mask_queue.cancel() + + def queue_stats(self) -> "QueueStats": + """Return a thread-safe snapshot of the current queue depths. + + Cheap enough to poll from a UI timer. ``Queue.qsize`` is + approximate under concurrency but fine for a read-out. + """ + with self.task_lock: + inflight = self.active_pipeline_tasks + return QueueStats( + storage_depth=self._storage_queue.qsize(), + storage_max=self._storage_queue.maxsize, + pipeline_inflight=inflight, + pipeline_max=self.max_queue_size, + deferred_depth=self._deferred_queue.qsize(), + stored_images=self.stored_images, + skipped_pipeline=self.skipped_pipeline, + deferred_processed=self.deferred_processed, + ) + def _record_background_error( self, source: BackgroundErrorSource, exc: BaseException ) -> None: @@ -191,6 +257,12 @@ def get_stim_mask( mask = fov_state.stim_mask_queue.wait_for_frame( frame_idx, timeout=timeout ) + except FrameWaitCancelled: + # Run is being cancelled — the dispenser was woken by + # Analyzer.cancel_pending_waits(). Unwind quietly so the + # feed loop reaches its cancel check; not a failure, so + # no background error is recorded. + return None except QueueEmpty as e: # _build_stim_slm still log-and-continues with False, but # hardware tests check background_errors so the dropped stim @@ -626,6 +698,15 @@ def __init__(self, mic, pipeline, *, writer: Writer | None = None): # The worker thread owns it; status update sites use it via this attr. self._current_handle: RunHandle | None = None + # (p, t) index of the RTMEvent whose frames are currently arriving. + # _bump_status_for_frame uses it to detect RTMEvent boundaries so + # n_events_acquired / lag update once per RTMEvent, not per frame. + self._rtm_key: tuple | None = None + + # monotonic-clock origin for lag, anchored to the *first* frame of + # the run (see _bump_status_for_frame). None until that frame. + self._lag_origin: float | None = None + # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------ @@ -690,7 +771,11 @@ def run_experiment( events, key=lambda e: (e.min_start_time or 0, e.index.get("p", 0)) ) - handle = RunHandle(n_events_total=len(events), events=events) + handle = RunHandle( + n_events_total=len(events), + events=events, + on_cancel=self._cancel_stim_waits, + ) self._current_handle = handle handle._thread = threading.Thread( @@ -746,7 +831,9 @@ def continue_experiment( ) handle = RunHandle( - n_events_total=len(offset_events), events=offset_events + n_events_total=len(offset_events), + events=offset_events, + on_cancel=self._cancel_stim_waits, ) self._current_handle = handle @@ -769,6 +856,23 @@ def _require_no_active_run(self) -> None: "handle.cancel() first." ) + def _cancel_stim_waits(self) -> None: + """RunHandle ``on_cancel`` hook — wake a feed loop blocked on a stim mask. + + The feed loop checks ``handle.cancel_event`` at every iteration, + but while it is parked inside ``_build_stim_slm`` -> + ``Analyzer.get_stim_mask`` -> ``FrameDispenser.wait_for_frame`` + it cannot poll. Without this hook a cancel issued during that + window would not take effect until the stim-mask timeout (up to + 80 s) elapsed — and until the feed loop unwinds, its + ``finally`` block never disconnects ``_on_frame_ready``, so + stray frames (e.g. a later DMD calibration) keep reaching the + Analyzer. Cancelling the dispensers releases the wait at once. + """ + analyzer = self._analyzer + if analyzer is not None: + analyzer.cancel_pending_waits() + def _get_image_size(self) -> tuple[int, int]: """Return (height, width) of the microscope's camera frames. @@ -802,6 +906,9 @@ def _run_worker( ``handle.update`` so listeners see the progression. """ handle.update(state="running", started_at=time.monotonic()) + # Fresh RTMEvent-boundary + lag-origin trackers for this run. + self._rtm_key = None + self._lag_origin = None try: # ---- pre-loop setup ----------------------------------------- if self._experiment_start is None: @@ -897,25 +1004,80 @@ def finish_experiment(self, *, drain_timeout: float = 300.0): If a run is still in progress this blocks until it finishes — cancel it first via ``handle.cancel()`` if you want to abort. + + Teardown (waiting on the run + draining the Analyzer queues) runs + on a worker thread; this method pumps the Qt event loop while it + waits, so napari stays responsive instead of freezing for the + whole drain. """ - if self._current_handle is not None and self._current_handle.is_running(): - self._current_handle.wait() - self._current_handle = None + done = threading.Event() + box: list[BaseException] = [] - if self._analyzer is not None: - # shutdown is the gate — only snapshot background_errors and - # drop the Analyzer once it succeeds. On TimeoutError the - # Analyzer is still alive and the caller can retry. - self._analyzer.shutdown(wait=True, drain_timeout=drain_timeout) - self.background_errors.extend(self._analyzer.background_errors) - self._analyzer = None - self._t_offset = 0 - self._time_offset = 0.0 - self._experiment_start = None - self._event_queue = None - self._all_events.clear() - self._fov_positions.clear() - self._frame_buffers.clear() + def _teardown() -> None: + try: + handle = self._current_handle + if handle is not None and handle.is_running(): + try: + handle.wait() + except BaseException as run_exc: + # Remember a run-side failure but still tear down. + box.append(run_exc) + self._current_handle = None + + if self._analyzer is not None: + # shutdown is the gate — only snapshot background_errors + # and drop the Analyzer once it succeeds. On TimeoutError + # the Analyzer is still alive and the caller can retry. + self._analyzer.shutdown( + wait=True, drain_timeout=drain_timeout + ) + self.background_errors.extend( + self._analyzer.background_errors + ) + self._analyzer = None + self._t_offset = 0 + self._time_offset = 0.0 + self._experiment_start = None + self._event_queue = None + self._all_events.clear() + self._fov_positions.clear() + self._frame_buffers.clear() + except BaseException as exc: + box.append(exc) + finally: + done.set() + + threading.Thread( + target=_teardown, name="FaroFinishWorker", daemon=True + ).start() + while not done.wait(timeout=0.05): + self._pump_qt_events() + if box: + raise box[0] + + @staticmethod + def _pump_qt_events() -> None: + """Process pending Qt events if a Qt app is running; no-op otherwise. + + Used by finish_experiment (which blocks the calling/main thread by + design) to keep napari responsive while teardown runs on a worker. + """ + try: + from qtpy.QtCore import QCoreApplication + except Exception: + return + app = QCoreApplication.instance() + if app is not None: + app.processEvents() + + def queue_stats(self) -> "QueueStats | None": + """Snapshot of the Analyzer's queue depths, or None when idle. + + Returns None between experiments (no Analyzer). Status widgets + poll this to show storage / pipeline / deferred backpressure. + """ + analyzer = self._analyzer + return analyzer.queue_stats() if analyzer is not None else None def stop_run(self): """Hard-stop the run path (legacy). Prefer ``handle.cancel()``.""" @@ -1137,11 +1299,19 @@ def _run_mda_with_events(self, events, *, stim_mode, handle: RunHandle): def _bump_status_for_frame(self, event: MDAEvent) -> None: """Update RunHandle counters for the current frame; no-op if no handle. - Stim emissions are skipped: a stim frame is the SLM-illuminated - snap that fires alongside its imaging frame, so counting it would - double-update the widget per stim event (lag/elapsed appearing - to refresh twice in quick succession). Imaging + ref frames are - the meaningful "data frames" the user cares about. + An RTMEvent expands into several MDAEvents (one per imaging/ref + channel, plus stim). This handler fires per MDAEvent, so it does + two different things: + + * ``n_frames_received`` -- bumped on every imaging/ref frame. + * ``n_events_acquired`` + ``lag_ms`` -- updated only on the + *first* frame of each RTMEvent, detected by the (p, t) index + changing. So the widget's progress and the lag readout move + once per RTMEvent, not once per channel-frame. + + Stim emissions are skipped entirely: a stim frame is the + SLM-illuminated snap firing alongside its imaging frame, not a + data frame. """ handle = self._current_handle if handle is None: @@ -1151,16 +1321,44 @@ def _bump_status_for_frame(self, event: MDAEvent) -> None: return prev = handle.status() wallclock = time.time() - lag_ms: float | None = None - min_start = getattr(event, "min_start_time", None) - if min_start is not None and prev.started_at is not None: - elapsed = time.monotonic() - prev.started_at - lag_ms = (elapsed - min_start) * 1000.0 - handle.update( - n_frames_received=prev.n_frames_received + 1, - last_frame_wallclock=wallclock, - lag_ms=lag_ms, - ) + + # RTMEvent boundary: (p, t) identifies one logical timepoint+FOV. + key = (event.index.get("p"), event.index.get("t")) + is_new_rtm_event = key != self._rtm_key + + updates: dict = { + "n_frames_received": prev.n_frames_received + 1, + "last_frame_wallclock": wallclock, + } + if is_new_rtm_event: + self._rtm_key = key + updates["n_events_acquired"] = prev.n_events_acquired + 1 + # Lag = how far this RTMEvent's acquisition start drifted from + # its scheduled min_start_time. frameReady fires when the frame + # *finished*, so back out the exposure to estimate the start. + # + # The reference clock is anchored to the *first* frame of the + # run, NOT to started_at: min_start_time is relative to when + # the engine began acquiring, while started_at is stamped + # before writer init + Analyzer construction + engine/hardware + # startup (~1 s). Charging that constant startup to every lag + # reading made an on-schedule run look ~1 s behind. Anchoring + # to the first frame cancels it -- frame 0 reads ~0, later + # frames show genuine drift. Engine-agnostic: it needs only + # the frame callback firing plus useq's min_start_time / + # exposure, no engine-specific timing metadata. + min_start = getattr(event, "min_start_time", None) + if min_start is not None: + exposure_s = (getattr(event, "exposure", None) or 0.0) / 1000.0 + acq_start = time.monotonic() - exposure_s + if self._lag_origin is None: + # First frame defines t0: its acquisition start + # corresponds to this event's scheduled min_start_time. + self._lag_origin = acq_start - min_start + updates["lag_ms"] = ( + acq_start - self._lag_origin - min_start + ) * 1000.0 + handle.update(**updates) def _on_frame_ready(self, img: np.ndarray, event: MDAEvent) -> None: # Drop subsequent frames after a fatal error — the MDA is winding down. diff --git a/faro/core/run_status.py b/faro/core/run_status.py index 27f5da2..54529dd 100644 --- a/faro/core/run_status.py +++ b/faro/core/run_status.py @@ -19,8 +19,9 @@ from __future__ import annotations import threading +import traceback from dataclasses import dataclass, field, replace -from typing import TYPE_CHECKING, Any, Literal +from typing import TYPE_CHECKING, Any, Callable, Literal from psygnal import Signal @@ -41,16 +42,23 @@ class RunStatus: # Latest RTMEvent index the feed loop committed to the MDA queue. current_event_index: dict[str, int] | None = None current_fov: int | None = None - # Counts. + # Counts. Note the distinct units: + # - *_events_* counts RTMEvents (one logical timepoint+FOV; expands + # into several MDAEvents -- one per imaging/ref channel + stim). + # - n_frames_received counts MDAEvents (individual channel snaps). + # Widgets must compare like-with-like: progress is n_events_acquired + # / n_events_total, NOT n_frames_received / n_events_total. n_events_total: int = 0 # how many RTMEvents the run was started with n_events_consumed: int = 0 # RTMEvents pulled by the feed loop so far - n_frames_received: int = 0 # frames acknowledged via frameReady + n_events_acquired: int = 0 # RTMEvents whose first frame has arrived + n_frames_received: int = 0 # MDAEvent frames acknowledged via frameReady # Timing. started_at: float | None = None # time.monotonic() when the worker began finished_at: float | None = None # time.monotonic() when the worker exited last_frame_wallclock: float | None = None - # Lag: (time.monotonic() - started_at) - event.min_start_time, in ms, - # at the most recent frame_ready. Positive == we're behind schedule. + # Lag: how late the *current RTMEvent* started acquiring vs its + # scheduled min_start_time, in ms. Measured once per RTMEvent (on its + # first frame), not per channel-frame. Positive == behind schedule. lag_ms: float | None = None # Pipeline / storage backpressure visibility (best-effort). pipeline_inflight: int = 0 @@ -76,13 +84,22 @@ class RunHandle: statusChanged = Signal(RunStatus) def __init__( - self, n_events_total: int = 0, events: list | None = None + self, + n_events_total: int = 0, + events: list | None = None, + *, + on_cancel: Callable[[], None] | None = None, ) -> None: self._lock = threading.RLock() self._status = RunStatus(state="pending", n_events_total=n_events_total) self._cancel_event = threading.Event() self._pause_event = threading.Event() self._thread: threading.Thread | None = None + # Hook run synchronously on the caller's thread the first time + # cancel() is called, before it returns. The controller uses it + # to wake a feed loop parked in a stim-mask wait so cancellation + # is prompt rather than bounded by the stim-mask timeout. + self._on_cancel = on_cancel # Optional snapshot of the (sorted) RTMEvents this handle is driving. # Widgets use this to render per-event visualisations (e.g. an event # strip + FOV map) that need to know the full run plan up front. @@ -117,10 +134,22 @@ def cancel(self) -> None: next poll the loop stops feeding new events, asks the MDA engine to abort the in-flight event, and exits. Use ``wait()`` afterwards to block until the worker actually stops. + + The feed loop can also be parked deep inside a blocking stim-mask + wait, where it cannot poll the cancel event. The ``on_cancel`` + hook (set by the controller) is invoked synchronously here to + wake that wait, so cancellation takes effect immediately instead + of after the stim-mask timeout. """ + first_cancel = not self._cancel_event.is_set() self._cancel_event.set() # A cancel during pause must also release the feed loop's pause-wait. self._pause_event.clear() + if first_cancel and self._on_cancel is not None: + try: + self._on_cancel() + except Exception: + traceback.print_exc() with self._lock: if self._status.state in ("running", "pausing", "paused"): self._status = replace(self._status, state="cancelling") From b23c3b6af2742732906f9be53e9c5dbc22d71de0 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Tue, 19 May 2026 00:02:25 +0200 Subject: [PATCH 10/41] feat: refine the experiment status widget Stop now cancels the run and then runs finish_experiment(), so the next run starts clean instead of leaking the old Analyzer; the state banner shows STOPPING... while the drain runs. Stats are split into three panels (timing / queues / errors). The storage and pipeline queue depths render as grayscale fill bars that turn red past 80% of capacity; deferred shows as a plain count. The FovMap is freely resizable instead of pinned square. --- faro/widgets/experiment_status.py | 320 +++++++++++++++++++++++------- 1 file changed, 253 insertions(+), 67 deletions(-) diff --git a/faro/widgets/experiment_status.py b/faro/widgets/experiment_status.py index a95f727..5bd0ad4 100644 --- a/faro/widgets/experiment_status.py +++ b/faro/widgets/experiment_status.py @@ -17,9 +17,14 @@ - FOV map (one dot per unique FOV position, equal-aspect, with a grey path drawn in visit order; the dot for the current FOV is re-colored in the active event-type's color) - - Stats form (event N/M, elapsed, scheduled, lag, remaining, frames, - background errors) - - Stop button (calls ``handle.cancel()``) + - Stats panels (three separate shaded frames: timing -- event N/M, + elapsed, scheduled, lag, remaining; queues -- storage / pipeline / + deferred depths, the bounded two drawn as fill bars; errors) + - Pause + Stop buttons + +The Stop button cancels the run *and* calls ``finish_experiment()`` +(flush buffered frames to disk, drop the Analyzer) so the next run +starts clean; the state banner shows ``STOPPING...`` for the duration. The widget subscribes to ``ctrl.runStarted``, so it automatically re-binds to whichever run is current. Each ``RunHandle.statusChanged`` emission @@ -41,6 +46,7 @@ QFrame, QHBoxLayout, QLabel, + QProgressBar, QPushButton, QVBoxLayout, QWidget, @@ -89,6 +95,17 @@ _LAG_WARN_S = 5.0 _LAG_BAD_COLOR = "#e53935" +# Queue fill bars (storage / pipeline). The bar's chunk is a translucent +# fill drawn *behind* the "N / max" text; depth >= _QUEUE_WARN_FRAC of max +# flips fill + text to red, mirroring the lag warning. +_QUEUE_BAR_HEIGHT = 18 +_QUEUE_WARN_FRAC = 0.8 +# Neutral mid-grey: at low-ish alpha it lightens a dark theme and darkens +# a light one, so it reads on both without hardcoding a theme color (the +# same trick as _PANEL_BG). +_BAR_FILL = "rgba(128, 128, 128, 120)" +_BAR_FILL_WARN = "rgba(229, 57, 53, 130)" # _LAG_BAD_COLOR, translucent + # ───────────────────────────────────────────────────────────────────────── # Helpers (event-list introspection + small formatters) @@ -206,6 +223,58 @@ def _chip_style(color_hex: str, *, active: bool) -> str: ) +def _bar_style(warn: bool) -> str: + """Stylesheet for a queue fill bar; *warn* swaps fill + text to red.""" + fill = _BAR_FILL_WARN if warn else _BAR_FILL + text = f"color: {_LAG_BAD_COLOR}; font-weight: bold; " if warn else "" + return ( + f"QProgressBar {{ border: none; border-radius: {_RADIUS_PX}px; " + f"background-color: {_PANEL_BG}; {text}}}" + f"QProgressBar::chunk {{ background-color: {fill}; " + f"border-radius: {_RADIUS_PX}px; }}" + ) + + +def _make_queue_bar(font) -> QProgressBar: + """A compact QProgressBar used as a fill bar behind 'N / max' text. + + The chunk *is* the background fill; the format string carries the + numeric read-out. ``bar._warn`` caches the last applied warn state so + the stylesheet is only re-set when it actually changes. + """ + bar = QProgressBar() + bar.setTextVisible(True) + bar.setAlignment(Qt.AlignmentFlag.AlignCenter) + bar.setFixedHeight(_QUEUE_BAR_HEIGHT) + bar.setFont(font) + bar.setRange(0, 1) + bar.setValue(0) + bar.setFormat("-") + bar._warn = None # None -> first _set_queue_bar always applies a style + bar.setStyleSheet(_bar_style(False)) + return bar + + +def _wrap_panel(form: QFormLayout) -> QFrame: + """Wrap a stats form in a subtly-shaded, rounded panel frame. + + Echoes napari's boxed layer-controls sections. Each stats section + gets its own panel, so the layout's normal inter-widget spacing + reads as a clear gap between distinct areas (a hairline rule did + not separate them clearly enough). + """ + panel = QFrame() + panel.setObjectName("faroPanel") + lay = QVBoxLayout(panel) + lay.setContentsMargins(0, 0, 0, 0) + lay.addLayout(form) + panel.setStyleSheet( + f"QFrame#faroPanel {{ background-color: {_PANEL_BG}; " + f"border-radius: {_RADIUS_PX}px; }}" + ) + return panel + + # ───────────────────────────────────────────────────────────────────────── # EventStrip # ───────────────────────────────────────────────────────────────────────── @@ -324,22 +393,18 @@ def __init__( self._current = -1 self._active_color = EVENT_COLORS["imaging"] self.setMinimumWidth(160) - # Expand to the available width; height is pinned square in - # resizeEvent. heightForWidth is intentionally NOT used -- - # QVBoxLayout honors it poorly and ends up distributing slack - # space above and below the widget. + # The paint code (_world_to_screen) already confines the drawing + # to the largest centered square in min(width, height), so the + # *widget* itself need not be square. Make it freely resizable in + # both directions. The old approach -- setSizePolicy(.., Fixed) + # plus setFixedHeight(width) in resizeEvent -- pinned the panel's + # minimum height to its width, so once the dock was undocked and + # widened it could no longer be shrunk vertically. from qtpy.QtWidgets import QSizePolicy self.setSizePolicy( - QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed + QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding ) - self.setFixedHeight(160) - - def resizeEvent(self, event) -> None: - super().resizeEvent(event) - # Keep the widget square: height tracks width. setFixedHeight to - # the same value is a no-op, so this settles after one relayout. - if self.height() != self.width(): - self.setFixedHeight(self.width()) + self.setMinimumHeight(80) def set_positions(self, positions: Sequence[tuple[float, float]]) -> None: self._positions = list(positions) @@ -450,6 +515,9 @@ def __init__(self, controller: "Controller", parent: QWidget | None = None) -> N super().__init__(parent) self._controller = controller self._handle: RunHandle | None = None + # True while _on_stop_clicked is inside cancel()+finish_experiment(); + # _refresh keeps the state banner on "STOPPING..." for the duration. + self._finishing = False # Cached plan derived from handle.events at run start self._event_types: list[str] = [] @@ -517,47 +585,63 @@ def _build_ui(self) -> None: self._scheduled_value = QLabel("-") self._lag_value = QLabel("-") self._remaining_value = QLabel("-") + self._deferred_value = QLabel("-") self._errors_value = QLabel("-") right_align = ( Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter ) for w in ( self._event_value, self._elapsed_value, self._scheduled_value, - self._lag_value, self._remaining_value, self._errors_value, + self._lag_value, self._remaining_value, self._deferred_value, + self._errors_value, ): w.setAlignment(right_align) for w in (self._elapsed_value, self._scheduled_value, - self._lag_value, self._remaining_value): + self._lag_value, self._remaining_value, + self._deferred_value): w.setFont(mono) - form = QFormLayout() - form.setContentsMargins(6, 6, 6, 6) - form.setSpacing(2) - form.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) - # Stretch the value column so right-aligned text lands at the - # widget's right edge instead of hugging the label. - form.setFieldGrowthPolicy( - QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow - ) - form.addRow("event:", self._event_value) - form.addRow("elapsed:", self._elapsed_value) - form.addRow("scheduled:", self._scheduled_value) - form.addRow("lag:", self._lag_value) - form.addRow("remaining:", self._remaining_value) - form.addRow("errors:", self._errors_value) - - # ── Stats panel: a subtly-shaded frame echoing napari's boxed - # layer-controls sections. (The FOV map paints its own matching - # background in paintEvent, so it isn't wrapped in a frame.) - stats_panel = QFrame() - stats_panel.setObjectName("faroPanel") - stats_panel_layout = QVBoxLayout(stats_panel) - stats_panel_layout.setContentsMargins(0, 0, 0, 0) - stats_panel_layout.addLayout(form) - stats_panel.setStyleSheet( - f"QFrame#faroPanel {{ background-color: {_PANEL_BG}; " - f"border-radius: {_RADIUS_PX}px; }}" - ) + # storage / pipeline depths render as a fill bar behind "N / max" + # text -- a QProgressBar whose chunk *is* the background fill. + # deferred has no bound, so it stays a plain count label. + self._storage_bar = _make_queue_bar(mono) + self._pipeline_bar = _make_queue_bar(mono) + + def _stat_form() -> QFormLayout: + f = QFormLayout() + f.setContentsMargins(6, 6, 6, 6) + f.setSpacing(2) + f.setLabelAlignment(Qt.AlignmentFlag.AlignLeft) + # Stretch the value column so right-aligned text / the queue + # bars land at the panel's right edge, not hugging the label. + f.setFieldGrowthPolicy( + QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow + ) + return f + + timing_form = _stat_form() + timing_form.addRow("event:", self._event_value) + timing_form.addRow("elapsed:", self._elapsed_value) + timing_form.addRow("scheduled:", self._scheduled_value) + timing_form.addRow("lag:", self._lag_value) + timing_form.addRow("remaining:", self._remaining_value) + + queues_form = _stat_form() + queues_form.addRow("storage:", self._storage_bar) + queues_form.addRow("pipeline:", self._pipeline_bar) + queues_form.addRow("deferred:", self._deferred_value) + + errors_form = _stat_form() + errors_form.addRow("errors:", self._errors_value) + + # ── Stats panels: three separate shaded frames (timing / queues + # / errors). Standalone panels with the layout's normal spacing + # between them -- like the FOV map above -- read as clearly + # distinct areas. (The FOV map paints its own matching background + # in paintEvent, so it isn't wrapped here.) + timing_panel = _wrap_panel(timing_form) + queues_panel = _wrap_panel(queues_form) + errors_panel = _wrap_panel(errors_form) # ── Pause + Stop buttons self._pause_btn = QPushButton("Pause") @@ -577,12 +661,16 @@ def _build_ui(self) -> None: layout.addWidget(self._state_label) layout.addLayout(legend_row) layout.addWidget(self._strip) - # No stretch on the map -- resizeEvent pins it square, and the - # trailing stretch absorbs leftover vertical space below. - layout.addWidget(self._map) - layout.addWidget(stats_panel) + # The map is the one elastic element: give it the layout stretch + # so all leftover vertical space goes to it (the map then draws + # the largest centered square that fits). No trailing addStretch + # -- that spacer used to swallow the slack and pin the map at its + # minimum height. + layout.addWidget(self._map, 1) + layout.addWidget(timing_panel) + layout.addWidget(queues_panel) + layout.addWidget(errors_panel) layout.addLayout(button_row) - layout.addStretch(1) # -- run binding -------------------------------------------------------- @@ -614,8 +702,41 @@ def _on_run_started(self, handle: RunHandle) -> None: self._refresh(handle.status()) def _on_stop_clicked(self) -> None: - if self._handle is not None: + """Cancel the run, then finish the experiment. + + ``cancel()`` aborts the acquisition loop; ``finish_experiment()`` + then flushes buffered frames to disk and disposes of the + Analyzer, so the next run starts clean instead of leaking the + old one. The finish drain runs with the Qt loop pumped, so + napari stays responsive; the state banner reads ``STOPPING...`` + until it returns. + """ + if self._handle is None or self._finishing: + return + self._finishing = True + # Disable both buttons up front: finish_experiment() blocks this + # slot (pumping Qt), so without this a second click would re-enter. + self._stop_btn.setEnabled(False) + self._pause_btn.setEnabled(False) + self._state_label.setText("STOPPING...") + failed = False + try: self._handle.cancel() + self._controller.finish_experiment() + except BaseException as exc: # noqa: BLE001 - surface, don't crash the slot + import traceback + traceback.print_exc() + self._state_label.setText(f"STOP FAILED: {type(exc).__name__}") + failed = True + finally: + self._finishing = False + if failed: + # finish_experiment may leave the Analyzer alive (e.g. a drain + # timeout) -- re-enable Stop so the user can retry. + self._stop_btn.setEnabled(True) + else: + # Teardown done -- reflect the run's final state. + self._refresh(self._handle.status() if self._handle else None) def _on_pause_clicked(self) -> None: """Toggle pause / resume on the bound handle.""" @@ -638,8 +759,14 @@ def _refresh(self, status: RunStatus | None) -> None: return # ── State banner -- plain bold text, no background fill (a colored - # banner clashed with the imaging/stim/ref legend colors). - self._state_label.setText(status.state.upper()) + # banner clashed with the imaging/stim/ref legend colors). While + # _on_stop_clicked runs, keep it on STOPPING... -- statusChanged + # emissions from the winding-down worker would otherwise flip it + # to DONE before the finish drain completes. + if self._finishing: + self._state_label.setText("STOPPING...") + else: + self._state_label.setText(status.state.upper()) # ── Strip + map cursor cur_idx = self._current_index(status) @@ -678,8 +805,13 @@ def _refresh(self, status: RunStatus | None) -> None: self._errors_value.setText(str(n_errors)) self._errors_value.setStyleSheet("") - # ── Buttons - self._update_buttons(status.state) + # ── Queue depths + self._render_queue_fields() + + # ── Buttons -- left alone while finishing (deliberately disabled + # by _on_stop_clicked; a stale "running" emission must not re-enable). + if not self._finishing: + self._update_buttons(status.state) def _update_buttons(self, state: str) -> None: """Enable/label Pause + Stop according to the run state.""" @@ -706,23 +838,26 @@ def _render_idle(self) -> None: self._lag_value.setStyleSheet("") self._errors_value.setText("-") self._errors_value.setStyleSheet("") + self._render_queue_fields() self._pause_btn.setEnabled(False) self._pause_btn.setText("Pause") self._stop_btn.setEnabled(False) @staticmethod def _current_index(status: RunStatus) -> int: - """Index of the most-recently-*snapped* event (-1 if none yet). - - Uses ``n_frames_received`` (actual imaging/ref snaps) rather than - ``n_events_consumed`` (the feed loop's queue-ahead position). The - feed loop runs 3-4 events ahead of the engine because of the - backpressure window, so keying off it would make the strip jump - and the scheduled-time field flicker between the queued event and - the snapped one. Both ``_refresh`` and the ``_tick`` QTimer call - this so they always agree on "current". + """Index of the RTMEvent currently being acquired (-1 if none yet). + + Uses ``n_events_acquired`` -- RTMEvents whose first frame has + arrived -- which is the *same unit* as ``n_events_total`` and as + the event strip / FOV map, so progress can never exceed the + total. Deliberately NOT ``n_frames_received`` (that counts + per-channel MDAEvents, ~2-3 per RTMEvent, so it overshoots the + total) and NOT ``n_events_consumed`` (the feed loop runs several + events ahead of the engine because of the backpressure window, + which would make the strip jump). Both ``_refresh`` and the + ``_tick`` QTimer call this so they always agree on "current". """ - return status.n_frames_received - 1 if status.n_frames_received > 0 else -1 + return status.n_events_acquired - 1 if status.n_events_acquired > 0 else -1 def _render_time_fields(self, status: RunStatus, cur_idx: int) -> None: # Elapsed: prefer (now - started_at); fall back to last_frame_wallclock. @@ -774,6 +909,53 @@ def _render_time_fields(self, status: RunStatus, cur_idx: int) -> None: format_duration(remaining) if remaining is not None else "-" ) + def _render_queue_fields(self) -> None: + """Refresh the storage / pipeline / deferred read-outs. + + Polled from the QTimer so the depths track live between frames -- + in particular the storage bar is seen draining to 0 while + finish_experiment() runs. Shows empty "-" bars when no experiment + is active. + """ + stats = self._controller.queue_stats() + if stats is None: + self._set_queue_bar(self._storage_bar, 0, 0) + self._set_queue_bar(self._pipeline_bar, 0, 0) + self._deferred_value.setText("-") + return + self._set_queue_bar( + self._storage_bar, stats.storage_depth, stats.storage_max + ) + self._set_queue_bar( + self._pipeline_bar, stats.pipeline_inflight, stats.pipeline_max + ) + self._deferred_value.setText(str(stats.deferred_depth)) + + @staticmethod + def _set_queue_bar(bar: QProgressBar, depth: int, maximum: int) -> None: + """Drive one queue bar: fill fraction, 'N / max' text, warn color. + + Fill + text turn red once depth reaches _QUEUE_WARN_FRAC of max -- + a near-full storage queue means disk writes can't keep pace and + the camera buffer is at risk. ``maximum <= 0`` renders an empty + "-" bar (no active experiment). + """ + if maximum <= 0: + bar.setRange(0, 1) + bar.setValue(0) + bar.setFormat("-") + warn = False + else: + bar.setRange(0, maximum) + bar.setValue(min(depth, maximum)) + bar.setFormat(f"{depth} / {maximum}") + warn = depth >= _QUEUE_WARN_FRAC * maximum + # Re-style only on a warn-state change -- setStyleSheet every tick + # would force a needless restyle/repaint. + if warn != bar._warn: + bar._warn = warn + bar.setStyleSheet(_bar_style(warn)) + def _update_legend(self, active_type: str | None) -> None: for key, chip in self._legend_chips.items(): chip.setStyleSheet( @@ -781,9 +963,13 @@ def _update_legend(self, active_type: str | None) -> None: ) def _tick(self) -> None: - """QTimer slot: refresh time-derived fields between status emissions.""" + """QTimer slot: refresh time-derived + queue fields between emissions.""" if self._handle is None: return + # Queue depths move continuously and independently of frames -- poll + # them every tick, including during the finish drain so the storage + # queue is seen counting down to 0. + self._render_queue_fields() status = self._handle.status() # Keep the clock live while the run is active -- including while # paused, since wall-clock elapsed (and thus lag) keeps growing. From b45d683521e3ac7c04796fb5610b196e6e60e357 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 12:58:28 +0200 Subject: [PATCH 11/41] Drain engine queue on interactive pause Empty the backpressure window (~3 queued MDAEvents) into a held buffer when the user pauses, refilling on resume. On sparse experiments the queued events would otherwise keep snapping for minutes after Pause. min_start_times are not shifted -- late events catch up on resume. --- faro/core/controller.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/faro/core/controller.py b/faro/core/controller.py index 062cd7c..edb6535 100644 --- a/faro/core/controller.py +++ b/faro/core/controller.py @@ -1186,16 +1186,23 @@ def _run_mda_with_events(self, events, *, stim_mode, handle: RunHandle): if handle.cancel_event.is_set(): break - # Pause: stop feeding new events before pulling the next - # one. The MDA engine drains whatever is already queued - # (in-flight event + backpressure window), then idles. - # No new events are fed until resume() clears the event. + # Drain the backpressure window so queued events don't keep + # snapping while the user adjusts hardware. min_start_times + # are not shifted; late events catch up on resume. if handle.pause_event.is_set(): + held: list[MDAEvent] = [] + try: + while True: + held.append(self._queue.get_nowait()) + except QueueEmpty: + pass handle.update(state="paused") while handle.pause_event.is_set(): if handle.cancel_event.is_set(): break time.sleep(0.05) + for ev in held: + self._queue.put(ev) if handle.cancel_event.is_set(): break handle.update(state="running") From 3ea0172b6dace19ef791a1924f23a9c34dba7268 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 12:58:36 +0200 Subject: [PATCH 12/41] Add fixed-duration WaitEvents between experiment phases WaitEvent is an RTMEvent that emits no MDAEvents; wait(duration_s) builds one and combine() treats it as a pure time marker -- it extends wall-clock for subsequent phases but claims no t/p index. The feed loop waits for the engine to catch up, then counts down to max(scheduled_start, now) + duration_s so a pause-drain can't eat the wait window. Adds a "waiting" RunState + wait_remaining_s, and anchors started_at to the first acquired frame so a leading wait doesn't tick elapsed/lag. Demo notebook brackets the stim phase with wait(5). --- .../demo_sim_optogenetic_napari_async.ipynb | 587 +++++++++++++++++- faro/core/controller.py | 61 +- faro/core/data_structures.py | 48 +- faro/core/run_status.py | 9 +- 4 files changed, 668 insertions(+), 37 deletions(-) diff --git a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb index 56127f4..fa59bae 100644 --- a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb +++ b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb @@ -30,9 +30,38 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "OMP: Info #276: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.\n" + ] + }, + { + "data": { + "text/html": [ + "
Config Groups\n",
+       "└── Channel\n",
+       "    ├── DAPI\n",
+       "    ├── membrane\n",
+       "    └── phase-contrast\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1mConfig Groups\u001b[0m\n", + "└── \u001b[1;36mChannel\u001b[0m\n", + " ├── DAPI\n", + " ├── membrane\n", + " └── phase-contrast\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "from virtual_microscope.backends.optogenetic import setup_optogenetic\n", "from faro.microscope.simulation import UniMMCoreSimulation\n", @@ -56,9 +85,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import napari\n", "from napari_micromanager import MainWindow\n", @@ -80,7 +120,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -193,9 +233,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Directory /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_38bt4vb6/tracks created \n", + "Storage path: /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_38bt4vb6\n" + ] + } + ], "source": [ "import os, shutil, tempfile\n", "from faro.core.pipeline import ImageProcessingPipeline\n", @@ -230,9 +279,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "from faro.widgets import ExperimentStatusWidget\n", "\n", @@ -251,17 +311,351 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "32 events: 10s wait + 5 baseline + 5 stim + 10s wait + 5 recovery\n" + ] + }, + { + "data": { + "application/vnd.microsoft.datawrangler.viewer.v0+json": { + "columns": [ + { + "name": "index", + "rawType": "int64", + "type": "integer" + }, + { + "name": "fov", + "rawType": "int64", + "type": "integer" + }, + { + "name": "timestep", + "rawType": "int64", + "type": "integer" + }, + { + "name": "time", + "rawType": "float64", + "type": "float" + }, + { + "name": "x_pos", + "rawType": "float64", + "type": "float" + }, + { + "name": "y_pos", + "rawType": "float64", + "type": "float" + }, + { + "name": "z_pos", + "rawType": "float64", + "type": "float" + }, + { + "name": "channels", + "rawType": "object", + "type": "unknown" + }, + { + "name": "stim_channels", + "rawType": "object", + "type": "unknown" + }, + { + "name": "ref_channels", + "rawType": "object", + "type": "unknown" + }, + { + "name": "stim", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "ref", + "rawType": "bool", + "type": "boolean" + }, + { + "name": "phase", + "rawType": "object", + "type": "unknown" + }, + { + "name": "stim_power", + "rawType": "float64", + "type": "float" + }, + { + "name": "stim_exposure", + "rawType": "float64", + "type": "float" + } + ], + "ref": "0d6583ba-b29c-4152-8bd6-a2e49efd99f9", + "rows": [ + [ + "0", + "0", + "0", + "0.0", + null, + null, + null, + "()", + "()", + "()", + "False", + "False", + null, + null, + null + ], + [ + "1", + "0", + "0", + "11.0", + "0.0", + "0.0", + "0.0", + "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", + "()", + "()", + "False", + "False", + "baseline", + null, + null + ], + [ + "2", + "0", + "0", + "21.0", + null, + null, + null, + "()", + "()", + "()", + "False", + "False", + null, + null, + null + ], + [ + "3", + "1", + "0", + "11.0", + "20.0", + "20.0", + "0.0", + "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", + "()", + "()", + "False", + "False", + "baseline", + null, + null + ], + [ + "4", + "0", + "1", + "12.0", + "0.0", + "0.0", + "0.0", + "({'config': 'phase-contrast', 'exposure': 50.0, 'group': None},)", + "()", + "()", + "False", + "False", + "baseline", + null, + null + ] + ], + "shape": { + "columns": 14, + "rows": 5 + } + }, + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
fovtimesteptimex_posy_posz_poschannelsstim_channelsref_channelsstimrefphasestim_powerstim_exposure
0000.0NaNNaNNaN()()()FalseFalseNaNNaNNaN
10011.00.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
20021.0NaNNaNNaN()()()FalseFalseNaNNaNNaN
31011.020.020.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
40112.00.00.00.0({'config': 'phase-contrast', 'exposure': 50.0...()()FalseFalsebaselineNaNNaN
\n", + "
" + ], + "text/plain": [ + " fov timestep time x_pos y_pos z_pos \\\n", + "0 0 0 0.0 NaN NaN NaN \n", + "1 0 0 11.0 0.0 0.0 0.0 \n", + "2 0 0 21.0 NaN NaN NaN \n", + "3 1 0 11.0 20.0 20.0 0.0 \n", + "4 0 1 12.0 0.0 0.0 0.0 \n", + "\n", + " channels stim_channels \\\n", + "0 () () \n", + "1 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", + "2 () () \n", + "3 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", + "4 ({'config': 'phase-contrast', 'exposure': 50.0... () \n", + "\n", + " ref_channels stim ref phase stim_power stim_exposure \n", + "0 () False False NaN NaN NaN \n", + "1 () False False baseline NaN NaN \n", + "2 () False False NaN NaN NaN \n", + "3 () False False baseline NaN NaN \n", + "4 () False False baseline NaN NaN " + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from faro.core.data_structures import RTMSequence, combine\n", + "from faro.core.data_structures import RTMSequence, combine, wait\n", "from faro.core.utils import events_to_dataframe\n", "\n", "n_baseline = 5\n", - "n_stim = 30\n", + "n_stim = 5\n", "n_recovery = 5\n", "interval_s = 1 # seconds between frames; raise if your machine struggles\n", + "wait_s = 10 # pre-baseline settle + post-stim recovery pauses\n", "\n", "baseline = RTMSequence(\n", " time_plan={\"interval\": interval_s, \"loops\": n_baseline},\n", @@ -286,9 +680,12 @@ " rtm_metadata={\"phase\": \"recovery\"},\n", ")\n", "\n", - "events = combine(baseline, stim_phase, recovery, axis=\"t\")\n", + "events = combine(wait(wait_s), baseline, stim_phase, wait(wait_s), recovery, axis=\"t\")\n", "df_events = events_to_dataframe(events)\n", - "print(f\"{len(events)} events: {n_baseline} baseline + {n_stim} stim + {n_recovery} recovery\")\n", + "print(\n", + " f\"{len(events)} events: {wait_s}s wait + {n_baseline} baseline + {n_stim} stim \"\n", + " f\"+ {wait_s}s wait + {n_recovery} recovery\"\n", + ")\n", "df_events.head()" ] }, @@ -305,9 +702,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "run started, handle.is_running()=True\n", + "current status: running\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/homebrew/Cellar/python@3.12/3.12.12/Frameworks/Python.framework/Versions/3.12/lib/python3.12/threading.py:1012: UserWarning: FOV 0 position changed: (None, None, None) -> (0.0, 0.0, 0.0). Tracking continuity may be broken.\n", + " self._target(*self._args, **self._kwargs)\n", + "/opt/homebrew/Cellar/python@3.12/3.12.12/Frameworks/Python.framework/Versions/3.12/lib/python3.12/threading.py:1012: UserWarning: FOV 0 position changed: (0.0, 0.0, 0.0) -> (None, None, None). Tracking continuity may be broken.\n", + " self._target(*self._args, **self._kwargs)\n" + ] + } + ], "source": [ "handle = ctrl.run_experiment(events, stim_mode=\"previous\")\n", "print(f\"run started, handle.is_running()={handle.is_running()}\")\n", @@ -325,9 +741,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "state : waiting\n", + "events consumed : 1 / 32\n", + "frames received : 0\n", + "current FOV : None\n", + "current event : {}\n", + "lag (ms) : None\n", + "bg errors : 0\n", + "fatal error : None\n" + ] + } + ], "source": [ "s = handle.status()\n", "print(f\"state : {s.state}\")\n", @@ -351,9 +782,108 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", + "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", + "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", + "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", + "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", + "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", + "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", + "[notify] state=running consumed=1/32 frames=0 lag_ms=None\n", + "[notify] state=running consumed=2/32 frames=0 lag_ms=None\n", + "[notify] state=running consumed=3/32 frames=0 lag_ms=None\n", + "[notify] state=running consumed=4/32 frames=0 lag_ms=None\n", + "[notify] state=running consumed=5/32 frames=0 lag_ms=None\n", + "[notify] state=running consumed=6/32 frames=0 lag_ms=None\n", + "[notify] state=running consumed=6/32 frames=1 lag_ms=0.0\n", + "[notify] state=running consumed=7/32 frames=1 lag_ms=0.0\n", + "[notify] state=running consumed=7/32 frames=2 lag_ms=260.28279203455895\n", + "[notify] state=running consumed=8/32 frames=2 lag_ms=260.28279203455895\n", + "[notify] state=running consumed=8/32 frames=3 lag_ms=3.8464170647785068\n", + "[notify] state=running consumed=9/32 frames=3 lag_ms=3.8464170647785068\n", + "[notify] state=running consumed=9/32 frames=4 lag_ms=203.0736249871552\n", + "[notify] state=running consumed=10/32 frames=4 lag_ms=203.0736249871552\n", + "[notify] state=running consumed=10/32 frames=5 lag_ms=-1.7102500423789024\n", + "[notify] state=running consumed=11/32 frames=5 lag_ms=-1.7102500423789024\n", + "[notify] state=running consumed=11/32 frames=6 lag_ms=233.86037501040846\n", + "[notify] state=running consumed=12/32 frames=6 lag_ms=233.86037501040846\n", + "[notify] state=running consumed=12/32 frames=7 lag_ms=13.227791991084814\n", + "[notify] state=running consumed=12/32 frames=8 lag_ms=205.30395896639675\n", + "[notify] state=running consumed=12/32 frames=9 lag_ms=-16.48891600780189\n", + "[notify] state=running consumed=12/32 frames=10 lag_ms=201.7523340182379\n", + "[notify] state=running consumed=13/32 frames=10 lag_ms=201.7523340182379\n", + "[notify] state=running consumed=14/32 frames=10 lag_ms=201.7523340182379\n", + "[notify] state=running consumed=14/32 frames=11 lag_ms=141.35595900006592\n", + "[notify] state=running consumed=14/32 frames=12 lag_ms=544.8306669713929\n", + "[notify] state=running consumed=15/32 frames=12 lag_ms=544.8306669713929\n", + "[notify] state=running consumed=16/32 frames=12 lag_ms=544.8306669713929\n", + "[notify] state=running consumed=16/32 frames=13 lag_ms=215.40708397515118\n", + "[notify] state=running consumed=16/32 frames=14 lag_ms=554.8036249820143\n", + "[notify] state=running consumed=17/32 frames=14 lag_ms=554.8036249820143\n", + "[notify] state=running consumed=18/32 frames=14 lag_ms=554.8036249820143\n", + "[notify] state=running consumed=18/32 frames=15 lag_ms=132.24141695536673\n", + "[notify] state=running consumed=18/32 frames=16 lag_ms=511.2279170425609\n", + "[notify] state=running consumed=19/32 frames=16 lag_ms=511.2279170425609\n", + "[notify] state=running consumed=20/32 frames=16 lag_ms=511.2279170425609\n", + "[notify] state=running consumed=20/32 frames=17 lag_ms=201.05287502519786\n", + "[notify] state=running consumed=20/32 frames=18 lag_ms=596.0728749632835\n", + "[notify] state=running consumed=21/32 frames=18 lag_ms=596.0728749632835\n", + "[notify] state=running consumed=22/32 frames=18 lag_ms=596.0728749632835\n", + "[notify] state=running consumed=22/32 frames=19 lag_ms=205.42204205412418\n", + "[notify] state=running consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=running consumed=22/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=running consumed=23/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=running consumed=24/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=running consumed=25/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=running consumed=26/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=running consumed=27/32 frames=20 lag_ms=553.5061249975115\n", + "[notify] state=running consumed=27/32 frames=21 lag_ms=-18.20641604717821\n", + "[notify] state=running consumed=28/32 frames=21 lag_ms=-18.20641604717821\n", + "[notify] state=running consumed=28/32 frames=22 lag_ms=211.55899995937943\n", + "[notify] state=running consumed=29/32 frames=22 lag_ms=211.55899995937943\n", + "[notify] state=running consumed=29/32 frames=23 lag_ms=-2.2149999858811498\n", + "[notify] state=running consumed=30/32 frames=23 lag_ms=-2.2149999858811498\n", + "[notify] state=running consumed=30/32 frames=24 lag_ms=212.07604196388274\n", + "[notify] state=running consumed=31/32 frames=24 lag_ms=212.07604196388274\n", + "[notify] state=running consumed=31/32 frames=25 lag_ms=-1.4927500160411\n", + "[notify] state=running consumed=32/32 frames=25 lag_ms=-1.4927500160411\n", + "[notify] state=running consumed=32/32 frames=26 lag_ms=202.00087502598763\n", + "[notify] state=running consumed=32/32 frames=27 lag_ms=-1.0451660491526127\n", + "[notify] state=running consumed=32/32 frames=28 lag_ms=202.48041697777808\n", + "[notify] state=running consumed=32/32 frames=29 lag_ms=0.7713340455666184\n", + "[notify] state=running consumed=32/32 frames=30 lag_ms=210.9835840528831\n", + "[notify] state=done consumed=32/32 frames=30 lag_ms=210.9835840528831\n" + ] + } + ], "source": [ "def _print_status(status):\n", " print(f\"[notify] state={status.state} consumed={status.n_events_consumed}\"\n", @@ -372,7 +902,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -390,9 +920,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "final state : done\n", + "frames received : 80\n" + ] + } + ], "source": [ "final_status = handle.wait()\n", "print(f\"final state : {final_status.state}\")\n", diff --git a/faro/core/controller.py b/faro/core/controller.py index edb6535..dc23b5a 100644 --- a/faro/core/controller.py +++ b/faro/core/controller.py @@ -1,5 +1,5 @@ from faro.core.pipeline import store_img, ImageProcessingPipeline -from faro.core.data_structures import FovState, FrameWaitCancelled, ImgType, StimMode +from faro.core.data_structures import FovState, FrameWaitCancelled, ImgType, StimMode, WaitEvent from faro.core.run_status import RunHandle, RunStatus from faro.core.writers import ( Writer, @@ -905,7 +905,9 @@ def _run_worker( the final wall-clock offset update. All status updates flow through ``handle.update`` so listeners see the progression. """ - handle.update(state="running", started_at=time.monotonic()) + # started_at is set on the first acquired frame (see _on_frame_ready) + # so a pre-acquisition WaitEvent doesn't tick elapsed/lag. + handle.update(state="running") # Fresh RTMEvent-boundary + lag-origin trackers for this run. self._rtm_key = None self._lag_origin = None @@ -1223,6 +1225,57 @@ def _run_mda_with_events(self, events, *, stim_mode, handle: RunHandle): continue break + if isinstance(rtm_event, WaitEvent): + prev = handle.status() + handle.update( + current_event_index=dict(rtm_event.index), + n_events_consumed=prev.n_events_consumed + 1, + ) + # Feed loop runs ~3 events ahead of the engine; let + # those in-flight imaging events finish before the + # cursor jumps onto the wait cell. + while not handle.cancel_event.is_set(): + s = handle.status() + if s.n_events_acquired >= s.n_events_consumed - 1: + break + if handle.cancel_event.wait(0.05): + break + if handle.cancel_event.is_set(): + break + + base = self._experiment_start or time.monotonic() + # Anchor to whichever is later: scheduled start or now. + # A pause-drain can push wallclock past the scheduled + # window; without max() the wait would flash through. + scheduled_start = base + (rtm_event.min_start_time or 0) + deadline = max(scheduled_start, time.monotonic()) + rtm_event.duration_s + handle.update(state="waiting", wait_remaining_s=rtm_event.duration_s) + last_displayed = int(rtm_event.duration_s) + while True: + remaining = deadline - time.monotonic() + if remaining <= 0: + break + # Throttle emissions to once per displayed second: + # the widget renders int(remaining), so sub-second + # updates only churn the Qt event queue. + cur = int(remaining) + if cur != last_displayed: + handle.update(wait_remaining_s=remaining) + last_displayed = cur + if handle.cancel_event.wait(min(0.1, remaining)): + break + if handle.cancel_event.is_set(): + break + # Bump n_events_acquired so the cursor stays monotonic + # when state leaves "waiting". + new_state = "pausing" if handle.pause_event.is_set() else "running" + handle.update( + state=new_state, + wait_remaining_s=None, + n_events_acquired=handle.status().n_events_acquired + 1, + ) + continue + # Status update: the feed loop committed to this RTMEvent. prev = handle.status() fov = rtm_event.index.get("p") @@ -1340,6 +1393,10 @@ def _bump_status_for_frame(self, event: MDAEvent) -> None: if is_new_rtm_event: self._rtm_key = key updates["n_events_acquired"] = prev.n_events_acquired + 1 + if prev.started_at is None: + # Anchor elapsed clock to first acquisition (matches _lag_origin). + exposure_s_for_start = (getattr(event, "exposure", None) or 0.0) / 1000.0 + updates["started_at"] = time.monotonic() - exposure_s_for_start # Lag = how far this RTMEvent's acquisition start drifted from # its scheduled min_start_time. frameReady fires when the frame # *finished*, so back out the exposure to estimate the start. diff --git a/faro/core/data_structures.py b/faro/core/data_structures.py index 43819b8..3cbf730 100644 --- a/faro/core/data_structures.py +++ b/faro/core/data_structures.py @@ -579,6 +579,23 @@ def _resolve_ch(ch): return events +class WaitEvent(RTMEvent): + """A timed pause of ``duration_s`` seconds; emits no MDAEvents.""" + + duration_s: float + + def plan_events(self, **_kwargs) -> list[MDAEvent]: + return [] + + +def wait(duration_s: float, *, min_start_time: float | None = None) -> WaitEvent: + """Construct a :class:`WaitEvent`; composes with :func:`combine`:: + + events = combine(baseline, wait(60), stim, axis="t") + """ + return WaitEvent(duration_s=duration_s, min_start_time=min_start_time) + + # --------------------------------------------------------------------------- # Frame-set helpers # --------------------------------------------------------------------------- @@ -816,18 +833,30 @@ def _combine_pair( if not events_b: return events_a - max_key = max(e.index.get(axis, 0) for e in events_a) + 1 + # WaitEvents are pure time markers: they don't claim an axis index + # (so subsequent t/p numbering is unaffected) but their duration_s + # extends the wall-clock end of the preceding sequence. + max_key = max( + (e.index.get(axis, 0) for e in events_a if not isinstance(e, WaitEvent)), + default=-1, + ) + 1 time_offset = 0.0 if offset_time: - max_time_a = max(e.min_start_time or 0 for e in events_a) + max_time_a = max( + (e.min_start_time or 0) + (e.duration_s if isinstance(e, WaitEvent) else 0) + for e in events_a + ) time_offset = max_time_a + _infer_interval(b, events_b) offset_b: list[RTMEvent] = [] for ev in events_b: - updates: dict = { - "index": {**dict(ev.index), axis: ev.index.get(axis, 0) + max_key}, - } + if isinstance(ev, WaitEvent): + updates = {} + else: + updates = { + "index": {**dict(ev.index), axis: ev.index.get(axis, 0) + max_key}, + } if offset_time: updates["min_start_time"] = (ev.min_start_time or 0) + time_offset offset_b.append(ev.model_copy(update=updates)) @@ -843,7 +872,7 @@ def _combine_pair( def combine( - *sources: RTMSequence | Iterable[RTMEvent], + *sources: RTMSequence | RTMEvent | Iterable[RTMEvent], axis: str = Axis.TIME, offset_time: bool | None = None, ) -> list[RTMEvent]: @@ -893,11 +922,14 @@ def combine( f"channels per position is a v2 feature." ) - result: list[RTMEvent] = list(sources[0]) + def _as_list(src): + return [src] if isinstance(src, RTMEvent) else list(src) + + result: list[RTMEvent] = _as_list(sources[0]) for src in sources[1:]: result = _combine_pair( result, - src, + _as_list(src), axis=axis, offset_time=offset_time, ) diff --git a/faro/core/run_status.py b/faro/core/run_status.py index 54529dd..7dd7e28 100644 --- a/faro/core/run_status.py +++ b/faro/core/run_status.py @@ -30,7 +30,7 @@ RunState = Literal[ - "pending", "running", "pausing", "paused", "cancelling", "done", "error" + "pending", "running", "pausing", "paused", "waiting", "cancelling", "done", "error" ] @@ -50,16 +50,19 @@ class RunStatus: # / n_events_total, NOT n_frames_received / n_events_total. n_events_total: int = 0 # how many RTMEvents the run was started with n_events_consumed: int = 0 # RTMEvents pulled by the feed loop so far - n_events_acquired: int = 0 # RTMEvents whose first frame has arrived + n_events_acquired: int = 0 # RTMEvents whose first frame has arrived (WaitEvents bump on completion) n_frames_received: int = 0 # MDAEvent frames acknowledged via frameReady # Timing. - started_at: float | None = None # time.monotonic() when the worker began + started_at: float | None = None # time.monotonic() when the first frame began acquiring finished_at: float | None = None # time.monotonic() when the worker exited last_frame_wallclock: float | None = None # Lag: how late the *current RTMEvent* started acquiring vs its # scheduled min_start_time, in ms. Measured once per RTMEvent (on its # first frame), not per channel-frame. Positive == behind schedule. lag_ms: float | None = None + # Seconds left on an active WaitEvent countdown. Non-None only while + # state == "waiting"; cleared back to None when the wait ends. + wait_remaining_s: float | None = None # Pipeline / storage backpressure visibility (best-effort). pipeline_inflight: int = 0 storage_queue_depth: int = 0 From b7e97ec017a08cb32a454739abb5dbae04247c3e Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 12:58:42 +0200 Subject: [PATCH 13/41] Render wait events + polish the experiment status strip Show a "WAITING hh:mm:ss" countdown banner, keep the strip cursor on the wait cell during the countdown, and draw wait cells as solid gray (hatch overlaid only when wide enough to read). Pause->Resume flips immediately during a wait by reading pause_event rather than the run state. Also remove the 1px inter-phase gap (runs span to the next run's start; active border widened to match) and dim inactive legend chips. --- faro/widgets/experiment_status.py | 83 ++++++++++++++++++++----------- 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/faro/widgets/experiment_status.py b/faro/widgets/experiment_status.py index 5bd0ad4..fc5c639 100644 --- a/faro/widgets/experiment_status.py +++ b/faro/widgets/experiment_status.py @@ -52,7 +52,7 @@ QWidget, ) -from faro.core.data_structures import ImgType +from faro.core.data_structures import ImgType, WaitEvent from faro.core.run_status import RunHandle, RunStatus if TYPE_CHECKING: @@ -67,6 +67,7 @@ "imaging": "#2e7d32", "stim": "#1565c0", "ref": "#ef6c00", + "wait": "#9e9e9e", } DEFAULT_EVENT_COLOR = "#888888" @@ -84,6 +85,7 @@ _BORDER_PX = 2 _GAP_PX = 1 _MIN_GAP_AT_CELL_W = 3.0 +_WAIT_HATCH_MIN_W = 8.0 # min cell width (px) to overlay the wait hatch # FOV map _DOT_RADIUS_PX = 5 @@ -112,15 +114,17 @@ # ───────────────────────────────────────────────────────────────────────── def _event_type_token(ev) -> str: - """Map an RTMEvent to one of {"ref", "stim", "imaging"} for visualisation. + """Map an RTMEvent to one of {"wait", "ref", "stim", "imaging"} for visualisation. Order of precedence matches what the user sees as the *dominant* effect - for that timepoint: ref > stim > imaging. + for that timepoint: wait > ref > stim > imaging. RTMEvent doesn't expose ``stim`` / ``ref`` booleans directly -- ``events_to_dataframe`` derives them from the channel tuple lengths, so we do the same here. """ + if isinstance(ev, WaitEvent): + return "wait" if getattr(ev, "ref_channels", ()): return "ref" if getattr(ev, "stim_channels", ()): @@ -218,7 +222,7 @@ def _chip_style(color_hex: str, *, active: bool) -> str: f"padding: 2px 8px; border-radius: {_RADIUS_PX}px; font-weight: bold;" ) return ( - f"background-color: rgba({r},{g},{b},60); color: rgba({r},{g},{b},180); " + f"background-color: rgba({r},{g},{b},60); color: rgba({r},{g},{b},105); " f"padding: 2px 8px; border-radius: {_RADIUS_PX}px; font-weight: bold;" ) @@ -339,24 +343,32 @@ def paintEvent(self, _event) -> None: def x_for(i: int) -> float: return i * stride - def width_for(span: int) -> float: - return max(cell_w * span + max(0, span - 1) * gap, 1.0) + def _fill_run(start: int, end: int, t: str, alpha: int) -> None: + color = QColor(EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR)) + color.setAlpha(alpha) + # Span to the next run's start so phase boundaries don't leave + # the trailing inter-cell gap visible (within a run the merged + # fill already covers those gaps). + x0 = x_for(start) + x1 = min(x_for(end), float(w)) + rect = QRectF(x0, 0, x1 - x0, h) + # Solid base keeps thin cells visible; a bare hatch over a + # 1-2px wide wait cell renders as near-transparent and reads + # as a gap. Overlay the hatch only when wide enough to show. + painter.fillRect(rect, color) + if t == "wait" and rect.width() >= _WAIT_HATCH_MIN_W: + hatch = QColor(0, 0, 0, alpha // 3) + painter.fillRect(rect, QBrush(hatch, Qt.BrushStyle.BDiagPattern)) # Future / dim layer for start, end, t in _runs(self._types): - color = QColor(EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR)) - color.setAlpha(_FUTURE_ALPHA) - painter.fillRect(QRectF(x_for(start), 0, width_for(end - start), h), color) + _fill_run(start, end, t, _FUTURE_ALPHA) # Past + current overlay if self._current >= 0: past_end = min(self._current + 1, n) for start, end, t in _runs(self._types[:past_end]): - color = QColor(EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR)) - color.setAlpha(_PAST_ALPHA) - painter.fillRect( - QRectF(x_for(start), 0, width_for(end - start), h), color - ) + _fill_run(start, end, t, _PAST_ALPHA) # Active border if 0 <= self._current < n: @@ -364,9 +376,10 @@ def width_for(span: int) -> float: pen = QPen(QColor(EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR)).darker(160)) pen.setWidth(_BORDER_PX) painter.setPen(pen) - visible_w = max(cell_w, 3.0) - x = self._current * stride - x = min(max(0.0, x - (visible_w - cell_w) / 2), w - visible_w) + x0 = x_for(self._current) + cell = min(x_for(self._current + 1), float(w)) - x0 + visible_w = max(cell, 3.0) + x = min(max(0.0, x0 - (visible_w - cell) / 2), w - visible_w) painter.drawRect(QRectF(x + 0.5, 0.5, visible_w - 1, h - 1)) @@ -566,7 +579,7 @@ def _build_ui(self) -> None: legend_row = QHBoxLayout() legend_row.setContentsMargins(0, 0, 0, 0) legend_row.setSpacing(6) - for label, key in [("imaging", "imaging"), ("stim", "stim"), ("ref", "ref")]: + for label, key in [("imaging", "imaging"), ("stim", "stim"), ("ref", "ref"), ("wait", "wait")]: chip = QLabel(label) chip.setStyleSheet(_chip_style(EVENT_COLORS[key], active=False)) self._legend_chips[key] = chip @@ -746,6 +759,10 @@ def _on_pause_clicked(self) -> None: self._handle.resume() else: self._handle.pause() + # statusChanged may not fire if state didn't transition (pause + # during "waiting" defers the state flip until the wait ends), + # so refresh the label locally to keep it in sync. + self._update_buttons(self._handle.status().state) # -- refresh ------------------------------------------------------------ @@ -765,6 +782,8 @@ def _refresh(self, status: RunStatus | None) -> None: # to DONE before the finish drain completes. if self._finishing: self._state_label.setText("STOPPING...") + elif status.state == "waiting" and status.wait_remaining_s is not None: + self._state_label.setText(f"WAITING {format_duration(status.wait_remaining_s)}") else: self._state_label.setText(status.state.upper()) @@ -776,8 +795,11 @@ def _refresh(self, status: RunStatus | None) -> None: t = self._event_types[cur_idx] color = EVENT_COLORS.get(t, DEFAULT_EVENT_COLOR) self._strip.set_current(cur_idx) - fov_for_event = self._event_fovs[cur_idx] - self._map.set_current(fov_for_event, color=color) + if t == "wait": # no FOV + self._map.set_current(-1) + else: + fov_for_event = self._event_fovs[cur_idx] + self._map.set_current(fov_for_event, color=color) self._update_legend(active_type=t) else: self._strip.set_current(-1) @@ -815,15 +837,13 @@ def _refresh(self, status: RunStatus | None) -> None: def _update_buttons(self, state: str) -> None: """Enable/label Pause + Stop according to the run state.""" - # Pause/Resume is meaningful only while the run is live. - live = state in ("running", "pausing", "paused") + live = state in ("running", "pausing", "paused", "waiting") self._pause_btn.setEnabled(live) - if state in ("paused", "pausing"): - self._pause_btn.setText("Resume") - else: - self._pause_btn.setText("Pause") - # Stop is meaningful while running OR paused (cancel breaks the - # pause-wait too). + # Drive the label off pause_event so a click during "waiting" + # flips to Resume immediately, even though state stays "waiting" + # until the wait completes. + paused = self._handle is not None and self._handle.is_paused() + self._pause_btn.setText("Resume" if paused else "Pause") self._stop_btn.setEnabled(live) def _render_idle(self) -> None: @@ -856,7 +876,12 @@ def _current_index(status: RunStatus) -> int: events ahead of the engine because of the backpressure window, which would make the strip jump). Both ``_refresh`` and the ``_tick`` QTimer call this so they always agree on "current". + While ``waiting``, ``n_events_acquired`` doesn't bump (no frame + arrives for a WaitEvent), so use ``n_events_consumed`` to keep + the cursor on the wait cell during the countdown. """ + if status.state == "waiting" and status.n_events_consumed > 0: + return status.n_events_consumed - 1 return status.n_events_acquired - 1 if status.n_events_acquired > 0 else -1 def _render_time_fields(self, status: RunStatus, cur_idx: int) -> None: @@ -973,6 +998,6 @@ def _tick(self) -> None: status = self._handle.status() # Keep the clock live while the run is active -- including while # paused, since wall-clock elapsed (and thus lag) keeps growing. - if status.state not in ("running", "pausing", "paused"): + if status.state not in ("running", "pausing", "paused", "waiting"): return self._render_time_fields(status, self._current_index(status)) From eea40865f07f3155ef06c6ba7a22802bd4b69dc7 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 13:21:35 +0200 Subject: [PATCH 14/41] Test interactive pause + WaitEvent behaviour Pin the invariants: pause/resume changes when frames are acquired, not what -- a paused run yields byte-identical OME-Zarr to an unpaused one, same frame count, clean cancel-during-pause. WaitEvents claim no t/p index, emit no MDAEvents (add time, not frames), and shift subsequent min_start_times by at least their duration. Pause is driven by polling status() with min_start_time-spaced events so the tests are deterministic. --- tests/test_pause_and_wait.py | 229 +++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 tests/test_pause_and_wait.py diff --git a/tests/test_pause_and_wait.py b/tests/test_pause_and_wait.py new file mode 100644 index 0000000..21f6a4b --- /dev/null +++ b/tests/test_pause_and_wait.py @@ -0,0 +1,229 @@ +"""Tests for interactive pause (queue-drain) and fixed-duration WaitEvents. + +Two features pinned here: + +* **Interactive pause** drains the engine's backpressure window into a + held buffer and refills on resume. The invariant: pausing/resuming a + run changes *when* frames are acquired, never *what* — same frame + count, same indices, byte-identical OME-Zarr output. + +* **WaitEvents** insert a timed gap between phases. They claim no t/p + index (so downstream indices are unchanged vs. no wait), emit no + MDAEvents (so they add no frames), and shift subsequent events' + ``min_start_time`` later by at least their duration. + +Pause is driven from the main thread by polling ``handle.status()`` so +the tests are deterministic; events are spaced via ``min_start_time`` so +the real MDA engine is slow enough to observe the paused state. +""" + +from __future__ import annotations + +import os +import time + +import numpy as np +import pytest +import zarr + +from faro.core.controller import Controller +from faro.core.data_structures import RTMEvent, WaitEvent, combine, wait +from faro.core.writers import OmeZarrWriter +from faro.tracking.trackpy import TrackerTrackpy + +from tests.fake_microscope import FakeMicroscope +from tests.fixtures import CircleScene, make_events, make_pipeline + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _spaced(events: list[RTMEvent], dt: float) -> list[RTMEvent]: + """Stamp ``min_start_time = i*dt`` so the MDA engine paces itself.""" + return [ + e.model_copy(update={"min_start_time": i * dt}) + for i, e in enumerate(events) + ] + + +def _wait_until(predicate, *, timeout: float = 5.0, poll: float = 0.005) -> bool: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if predicate(): + return True + time.sleep(poll) + return False + + +def _trackpy(): + return TrackerTrackpy(search_range=50, memory=3) + + +def _raw_tiff_names(path: str) -> list[str]: + raw_dir = os.path.join(path, "raw") + if not os.path.isdir(raw_dir): + return [] + return sorted(f for f in os.listdir(raw_dir) if f.endswith(".tiff")) + + +def _run_capture_zarr(path: str, events, *, pause: bool) -> np.ndarray: + """Run ``events`` into an OME-Zarr store, optionally pausing once + mid-run, then return the raw array (t, c, y, x).""" + os.makedirs(path, exist_ok=True) + writer = OmeZarrWriter(path, store_stim_images=False) + pipeline = make_pipeline(path, tracker=_trackpy(), with_stim=False) + ctrl = Controller(FakeMicroscope(CircleScene()), pipeline, writer=writer) + + handle = ctrl.run_experiment(events, validate=False) + if pause: + assert _wait_until(lambda: handle.status().n_events_consumed >= 2), ( + "run finished before it could be paused — widen spacing" + ) + handle.pause() + assert _wait_until(lambda: handle.status().state == "paused"), ( + "feed loop never reported the paused state" + ) + handle.resume() + handle.wait() + ctrl._analyzer.wait_idle() + ctrl._analyzer.shutdown(wait=True) # closes the writer + + root = zarr.open_group(os.path.join(path, "acquisition.ome.zarr"), mode="r") + return np.asarray(root["0"]) + + +# --------------------------------------------------------------------------- +# WaitEvent semantics (combine-level, no acquisition) +# --------------------------------------------------------------------------- + + +class TestWaitEventSemantics: + def test_wait_constructs_waitevent(self): + w = wait(5.0) + assert isinstance(w, WaitEvent) + assert w.duration_s == 5.0 + + def test_wait_emits_no_mda_events(self): + assert wait(5.0).plan_events() == [] + + def test_wait_claims_no_index_and_drops_no_frames(self): + """combine with/without a wait yields identical frame indices — + the wait must not consume a t-slot.""" + a, b = make_events(3), make_events(3) + no_wait = combine(a, b, axis="t") + with_wait = combine(make_events(3), wait(10.0), make_events(3), axis="t") + + frames_no = [e.index for e in no_wait if not isinstance(e, WaitEvent)] + frames_with = [e.index for e in with_wait if not isinstance(e, WaitEvent)] + assert frames_no == frames_with + assert sum(isinstance(e, WaitEvent) for e in with_wait) == 1 + + def test_wait_shifts_subsequent_min_start_time(self): + duration = 10.0 + no_wait = combine(make_events(3), make_events(3), axis="t") + with_wait = combine( + make_events(3), wait(duration), make_events(3), axis="t" + ) + + # The second phase begins at the first event with t == 3. + def first_second_phase_time(events): + return next( + e.min_start_time + for e in events + if not isinstance(e, WaitEvent) and e.index.get("t") == 3 + ) + + shift = first_second_phase_time(with_wait) - first_second_phase_time(no_wait) + assert shift >= duration + + +# --------------------------------------------------------------------------- +# WaitEvent in a real run — adds time, not frames +# --------------------------------------------------------------------------- + + +class TestWaitEventRun: + def test_wait_adds_no_frames(self, tmp_dir): + """A wait between two 2-frame phases yields exactly 4 raw frames.""" + events = combine( + make_events(2), wait(0.05), make_events(2), axis="t" + ) + pipeline = make_pipeline(tmp_dir, tracker=_trackpy(), with_stim=False) + ctrl = Controller(FakeMicroscope(CircleScene()), pipeline) + handle = ctrl.run_experiment(events, validate=False) + handle.wait() + ctrl._analyzer.wait_idle() + ctrl._analyzer.shutdown(wait=True) + assert len(_raw_tiff_names(tmp_dir)) == 4 + + def test_wait_passes_through_waiting_state(self, tmp_dir): + events = combine(make_events(2), wait(0.3), make_events(2), axis="t") + pipeline = make_pipeline(tmp_dir, tracker=_trackpy(), with_stim=False) + ctrl = Controller(FakeMicroscope(CircleScene()), pipeline) + + seen_waiting = [] + handle = ctrl.run_experiment(events, validate=False) + handle.statusChanged.connect( + lambda s: seen_waiting.append(s.state == "waiting") if s.state == "waiting" else None + ) + handle.wait() + ctrl.finish_experiment() + assert any(seen_waiting), "run never entered the 'waiting' state" + + +# --------------------------------------------------------------------------- +# Interactive pause — output equivalence + state machine +# --------------------------------------------------------------------------- + + +class TestInteractivePause: + def test_pause_resume_preserves_frame_count(self, tmp_dir): + events = _spaced(make_events(8), dt=0.1) + pipeline = make_pipeline(tmp_dir, tracker=_trackpy(), with_stim=False) + ctrl = Controller(FakeMicroscope(CircleScene()), pipeline) + + handle = ctrl.run_experiment(events, validate=False) + assert _wait_until(lambda: handle.status().n_events_consumed >= 2) + handle.pause() + assert _wait_until(lambda: handle.status().state == "paused") + handle.resume() + final = handle.wait() + ctrl._analyzer.wait_idle() + ctrl._analyzer.shutdown(wait=True) + + assert final.state == "done" + assert len(_raw_tiff_names(tmp_dir)) == 8 + assert final.n_events_acquired == 8 + + def test_pause_produces_identical_zarr(self, tmp_dir): + """Paused and unpaused runs of the same events must yield + byte-identical OME-Zarr raw data.""" + events = _spaced(make_events(8), dt=0.1) + raw_no_pause = _run_capture_zarr( + os.path.join(tmp_dir, "nopause"), events, pause=False + ) + raw_paused = _run_capture_zarr( + os.path.join(tmp_dir, "paused"), events, pause=True + ) + np.testing.assert_array_equal(raw_no_pause, raw_paused) + + def test_cancel_during_pause_exits_cleanly(self, tmp_dir): + events = _spaced(make_events(8), dt=0.1) + pipeline = make_pipeline(tmp_dir, tracker=_trackpy(), with_stim=False) + ctrl = Controller(FakeMicroscope(CircleScene()), pipeline) + + handle = ctrl.run_experiment(events, validate=False) + assert _wait_until(lambda: handle.status().n_events_consumed >= 2) + handle.pause() + assert _wait_until(lambda: handle.status().state == "paused") + handle.cancel() + assert _wait_until(lambda: not handle.is_running(), timeout=10) + ctrl.finish_experiment() + # The worker drains and exits cleanly to a terminal "done" state; + # cancellation is proven by stopping early (not all 8 acquired) + # with no fatal error rather than by the transient "cancelling". + final = handle.status() + assert final.fatal_error is None + assert final.n_events_acquired < 8 From bc6c0b2f0ea28da4dafb64f325909e8797d684e6 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 13:22:23 +0200 Subject: [PATCH 15/41] Add motile to the test extra tests/fixtures.py imports TrackerMotile at module load, so test collection fails without motile installed. --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 56dcf12..80ddd88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,9 @@ dependencies = [ test = [ "pytest", "pytest-xdist", + # tests/fixtures.py imports TrackerMotile at module load, so the whole + # suite fails to collect without motile. + "motile", ] stardist = [ From c367a2c4835c59e43bfdc2c3c40fb6f00b07c916 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 13:24:18 +0200 Subject: [PATCH 16/41] Move motile into its own extra, referenced by test motile is the non-default tracking backend (a runtime dep), not a test tool, so list it as a feature extra alongside the other backends and have the test extra pull it in via faro[motile]. --- pyproject.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 80ddd88..ac562b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,8 +29,12 @@ dependencies = [ test = [ "pytest", "pytest-xdist", - # tests/fixtures.py imports TrackerMotile at module load, so the whole - # suite fails to collect without motile. + # The suite exercises both tracking backends; motile is the non-default + # one (trackpy is a core dep). + "faro[motile]", +] + +motile = [ "motile", ] From 94fe0313a7bd078ad824529e6af222b415fecf4e Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 13:39:24 +0200 Subject: [PATCH 17/41] Handle WaitEvents in event introspection and validation A WaitEvent carries no channels and no metadata, so events_to_dataframe emitted a bogus zero-channel imaging row for it and validate_pipeline falsely flagged it as missing required metadata. Skip WaitEvents in both (they are timed gaps, not acquired frames). Add regression tests, including that validate_hardware already tolerates them. --- faro/core/pipeline.py | 4 +++- faro/core/utils.py | 3 +++ tests/test_events_to_dataframe.py | 25 ++++++++++++++++++++++++- tests/test_validate_events.py | 10 ++++++++++ tests/test_validate_hardware.py | 8 +++++++- 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/faro/core/pipeline.py b/faro/core/pipeline.py index 15a0f1e..9be5faf 100644 --- a/faro/core/pipeline.py +++ b/faro/core/pipeline.py @@ -14,7 +14,7 @@ from faro.stimulation.base import StimWithImage, StimWithPipeline import faro.tracking.base as abstract_tracker import faro.feature_extraction.base as abstract_fe -from faro.core.data_structures import FovState, ImgType, SegmentationMethod +from faro.core.data_structures import FovState, ImgType, SegmentationMethod, WaitEvent from faro.core.utils import labels_to_particles, create_folders from datetime import datetime import queue @@ -311,6 +311,8 @@ def validate_pipeline(self, events) -> bool: stim_required = getattr(self.stimulator, "required_metadata", set()) for event in events: + if isinstance(event, WaitEvent): + continue # timed gap, carries no acquisition metadata meta_keys = set(event.metadata.keys()) # General requirements (all events) missing = general_required - meta_keys diff --git a/faro/core/utils.py b/faro/core/utils.py index 8b2c46c..2611eac 100644 --- a/faro/core/utils.py +++ b/faro/core/utils.py @@ -10,6 +10,7 @@ FovState, RTMEvent, RTMSequence, + WaitEvent, ) import math import random @@ -741,6 +742,8 @@ def events_to_dataframe(events: list) -> pd.DataFrame: """ rows = [] for e in events: + if isinstance(e, WaitEvent): + continue # timed gap, not an acquired frame channels = getattr(e, "channels", ()) stim_channels = getattr(e, "stim_channels", ()) ref_channels = getattr(e, "ref_channels", ()) diff --git a/tests/test_events_to_dataframe.py b/tests/test_events_to_dataframe.py index e7e31cf..234e3e8 100644 --- a/tests/test_events_to_dataframe.py +++ b/tests/test_events_to_dataframe.py @@ -4,7 +4,7 @@ from useq import MDASequence -from faro.core.data_structures import Channel, ImgType, RTMSequence, combine +from faro.core.data_structures import Channel, ImgType, RTMSequence, combine, wait from faro.core.utils import events_to_dataframe @@ -51,6 +51,29 @@ def test_events_to_dataframe_works_for_both(self): assert (df_rtm["timestep"] == df_mda["timestep"]).all() +class TestWaitEventInDataframe: + """WaitEvents are timed gaps, not acquired frames — they must not + appear as rows in events_to_dataframe.""" + + def test_wait_event_dropped(self): + phase1 = RTMSequence( + time_plan={"interval": 1.0, "loops": 3}, + stage_positions=[(0.0, 0.0, 0.0)], + channels=[{"config": "phase-contrast", "exposure": 50}], + ) + phase2 = RTMSequence( + time_plan={"interval": 1.0, "loops": 2}, + stage_positions=[(0.0, 0.0, 0.0)], + channels=[{"config": "phase-contrast", "exposure": 50}], + ) + events = combine(phase1, wait(10.0), phase2, axis="t") + df = events_to_dataframe(events) + + # 3 + 2 frames; the wait contributes no row. + assert len(df) == 5 + assert sorted(df["timestep"].tolist()) == [0, 1, 2, 3, 4] + + class TestRefPhaseInDataframe: """Ref as a separate phase shows up correctly in events_to_dataframe.""" diff --git a/tests/test_validate_events.py b/tests/test_validate_events.py index c3de837..9ce8248 100644 --- a/tests/test_validate_events.py +++ b/tests/test_validate_events.py @@ -23,6 +23,7 @@ RTMEvent, RTMSequence, SegmentationMethod, + wait, ) from faro.core.pipeline import ImageProcessingPipeline from faro.segmentation.base import Segmentator @@ -346,6 +347,15 @@ def test_fe_metadata_present_passes(self, tmp_path_cleanup): events = _make_events(metadata={"fe_threshold": 0.5}) assert pipeline.validate_pipeline(events) is True + def test_wait_event_skips_metadata_check(self, tmp_path_cleanup): + """A WaitEvent carries no metadata and must not trip the + required-metadata check that applies to acquired events.""" + pipeline = _make_pipeline( + tmp_path_cleanup, tracker=TrackerNeedsCondition(), + ) + events = _make_events(metadata={"condition": "control"}) + [wait(5.0)] + assert pipeline.validate_pipeline(events) is True + # --- Multiple components with metadata requirements --- def test_multiple_components_all_missing(self, tmp_path_cleanup): diff --git a/tests/test_validate_hardware.py b/tests/test_validate_hardware.py index a7410a6..a4ed69f 100644 --- a/tests/test_validate_hardware.py +++ b/tests/test_validate_hardware.py @@ -10,7 +10,7 @@ import pytest -from faro.core.data_structures import Channel, PowerChannel, RTMEvent +from faro.core.data_structures import Channel, PowerChannel, RTMEvent, wait from faro.core.utils import validate_hardware from tests.fake_mmc import build_validation_core as _core @@ -67,6 +67,12 @@ def test_stim_channel_unknown_fails(self): assert result is False assert any("nonexistent-laser" in str(warning.message) for warning in w) + def test_wait_event_ignored(self): + """A WaitEvent has no channels and must not trip config checks.""" + mmc = _core() + events = _make_events(channels=[Channel("phase-contrast", 50)]) + [wait(5.0)] + assert validate_hardware(events, mmc) is True + def test_multiple_groups_searched(self): """Config can be in any group — not just 'Channel'.""" mmc = _core(config_groups={ From 51759093eb7cf802002bd8da7f2a70d83305f422 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 13:39:29 +0200 Subject: [PATCH 18/41] Add Niesen.shutdown() for thread + COM cleanup Niesen runs a WakeUpLaser keepalive thread and holds a DMD/SLM handle but had no shutdown() override, so pymmcore native threads could keep the process alive as a zombie that blocks the next session. Mirror Moench.shutdown: stop the keepalive thread and unloadAllDevices. --- faro/microscope/pertzlab/niesen.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/faro/microscope/pertzlab/niesen.py b/faro/microscope/pertzlab/niesen.py index 57313e6..1954411 100644 --- a/faro/microscope/pertzlab/niesen.py +++ b/faro/microscope/pertzlab/niesen.py @@ -107,3 +107,23 @@ def calibrate_dmd( def post_experiment(self): """Post-process the experiment.""" self.wl.stop() + + def shutdown(self): + """Tear down hardware state so the microscope can be discarded. + + Stops the wake-up-laser keepalive thread and unloads all + Micro-Manager devices so COM ports and the SLM handle are + released. Without this, pymmcore's native threads keep the + Python process alive after the main thread exits, leaving a + zombie that blocks the next session. + """ + wl = getattr(self, "wl", None) + if wl is not None: + try: + wl.stop() + except Exception: + pass + try: + self.mmc.unloadAllDevices() + except Exception: + pass From 30b6b8834e462c01fb7fe4dbf01701f81422ea3f Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 14:06:30 +0200 Subject: [PATCH 19/41] Refresh async optogenetic demo notebook outputs Re-run outputs for the wait-bracketed experiment (no source changes). --- .../demo_sim_optogenetic_napari_async.ipynb | 114 ++---------------- 1 file changed, 13 insertions(+), 101 deletions(-) diff --git a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb index fa59bae..3a4bd20 100644 --- a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb +++ b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb @@ -91,7 +91,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 2, @@ -240,8 +240,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Directory /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_38bt4vb6/tracks created \n", - "Storage path: /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_38bt4vb6\n" + "Directory /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_yg4xg969/tracks created \n", + "Storage path: /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/napari_async_optogenetic_yg4xg969\n" ] } ], @@ -285,7 +285,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 5, @@ -311,7 +311,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -401,7 +401,7 @@ "type": "float" } ], - "ref": "0d6583ba-b29c-4152-8bd6-a2e49efd99f9", + "ref": "98917d0a-3a0b-4f59-9a19-cf6299e997ee", "rows": [ [ "0", @@ -642,7 +642,7 @@ "4 () False False baseline NaN NaN " ] }, - "execution_count": 8, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -702,7 +702,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -717,9 +717,9 @@ "name": "stderr", "output_type": "stream", "text": [ - "/opt/homebrew/Cellar/python@3.12/3.12.12/Frameworks/Python.framework/Versions/3.12/lib/python3.12/threading.py:1012: UserWarning: FOV 0 position changed: (None, None, None) -> (0.0, 0.0, 0.0). Tracking continuity may be broken.\n", - " self._target(*self._args, **self._kwargs)\n", "/opt/homebrew/Cellar/python@3.12/3.12.12/Frameworks/Python.framework/Versions/3.12/lib/python3.12/threading.py:1012: UserWarning: FOV 0 position changed: (0.0, 0.0, 0.0) -> (None, None, None). Tracking continuity may be broken.\n", + " self._target(*self._args, **self._kwargs)\n", + "/opt/homebrew/Cellar/python@3.12/3.12.12/Frameworks/Python.framework/Versions/3.12/lib/python3.12/threading.py:1012: UserWarning: FOV 0 position changed: (None, None, None) -> (0.0, 0.0, 0.0). Tracking continuity may be broken.\n", " self._target(*self._args, **self._kwargs)\n" ] } @@ -741,7 +741,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -782,7 +782,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -791,97 +791,9 @@ "" ] }, - "execution_count": 11, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", - "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", - "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", - "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", - "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", - "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", - "[notify] state=waiting consumed=1/32 frames=0 lag_ms=None\n", - "[notify] state=running consumed=1/32 frames=0 lag_ms=None\n", - "[notify] state=running consumed=2/32 frames=0 lag_ms=None\n", - "[notify] state=running consumed=3/32 frames=0 lag_ms=None\n", - "[notify] state=running consumed=4/32 frames=0 lag_ms=None\n", - "[notify] state=running consumed=5/32 frames=0 lag_ms=None\n", - "[notify] state=running consumed=6/32 frames=0 lag_ms=None\n", - "[notify] state=running consumed=6/32 frames=1 lag_ms=0.0\n", - "[notify] state=running consumed=7/32 frames=1 lag_ms=0.0\n", - "[notify] state=running consumed=7/32 frames=2 lag_ms=260.28279203455895\n", - "[notify] state=running consumed=8/32 frames=2 lag_ms=260.28279203455895\n", - "[notify] state=running consumed=8/32 frames=3 lag_ms=3.8464170647785068\n", - "[notify] state=running consumed=9/32 frames=3 lag_ms=3.8464170647785068\n", - "[notify] state=running consumed=9/32 frames=4 lag_ms=203.0736249871552\n", - "[notify] state=running consumed=10/32 frames=4 lag_ms=203.0736249871552\n", - "[notify] state=running consumed=10/32 frames=5 lag_ms=-1.7102500423789024\n", - "[notify] state=running consumed=11/32 frames=5 lag_ms=-1.7102500423789024\n", - "[notify] state=running consumed=11/32 frames=6 lag_ms=233.86037501040846\n", - "[notify] state=running consumed=12/32 frames=6 lag_ms=233.86037501040846\n", - "[notify] state=running consumed=12/32 frames=7 lag_ms=13.227791991084814\n", - "[notify] state=running consumed=12/32 frames=8 lag_ms=205.30395896639675\n", - "[notify] state=running consumed=12/32 frames=9 lag_ms=-16.48891600780189\n", - "[notify] state=running consumed=12/32 frames=10 lag_ms=201.7523340182379\n", - "[notify] state=running consumed=13/32 frames=10 lag_ms=201.7523340182379\n", - "[notify] state=running consumed=14/32 frames=10 lag_ms=201.7523340182379\n", - "[notify] state=running consumed=14/32 frames=11 lag_ms=141.35595900006592\n", - "[notify] state=running consumed=14/32 frames=12 lag_ms=544.8306669713929\n", - "[notify] state=running consumed=15/32 frames=12 lag_ms=544.8306669713929\n", - "[notify] state=running consumed=16/32 frames=12 lag_ms=544.8306669713929\n", - "[notify] state=running consumed=16/32 frames=13 lag_ms=215.40708397515118\n", - "[notify] state=running consumed=16/32 frames=14 lag_ms=554.8036249820143\n", - "[notify] state=running consumed=17/32 frames=14 lag_ms=554.8036249820143\n", - "[notify] state=running consumed=18/32 frames=14 lag_ms=554.8036249820143\n", - "[notify] state=running consumed=18/32 frames=15 lag_ms=132.24141695536673\n", - "[notify] state=running consumed=18/32 frames=16 lag_ms=511.2279170425609\n", - "[notify] state=running consumed=19/32 frames=16 lag_ms=511.2279170425609\n", - "[notify] state=running consumed=20/32 frames=16 lag_ms=511.2279170425609\n", - "[notify] state=running consumed=20/32 frames=17 lag_ms=201.05287502519786\n", - "[notify] state=running consumed=20/32 frames=18 lag_ms=596.0728749632835\n", - "[notify] state=running consumed=21/32 frames=18 lag_ms=596.0728749632835\n", - "[notify] state=running consumed=22/32 frames=18 lag_ms=596.0728749632835\n", - "[notify] state=running consumed=22/32 frames=19 lag_ms=205.42204205412418\n", - "[notify] state=running consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=waiting consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=running consumed=22/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=running consumed=23/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=running consumed=24/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=running consumed=25/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=running consumed=26/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=running consumed=27/32 frames=20 lag_ms=553.5061249975115\n", - "[notify] state=running consumed=27/32 frames=21 lag_ms=-18.20641604717821\n", - "[notify] state=running consumed=28/32 frames=21 lag_ms=-18.20641604717821\n", - "[notify] state=running consumed=28/32 frames=22 lag_ms=211.55899995937943\n", - "[notify] state=running consumed=29/32 frames=22 lag_ms=211.55899995937943\n", - "[notify] state=running consumed=29/32 frames=23 lag_ms=-2.2149999858811498\n", - "[notify] state=running consumed=30/32 frames=23 lag_ms=-2.2149999858811498\n", - "[notify] state=running consumed=30/32 frames=24 lag_ms=212.07604196388274\n", - "[notify] state=running consumed=31/32 frames=24 lag_ms=212.07604196388274\n", - "[notify] state=running consumed=31/32 frames=25 lag_ms=-1.4927500160411\n", - "[notify] state=running consumed=32/32 frames=25 lag_ms=-1.4927500160411\n", - "[notify] state=running consumed=32/32 frames=26 lag_ms=202.00087502598763\n", - "[notify] state=running consumed=32/32 frames=27 lag_ms=-1.0451660491526127\n", - "[notify] state=running consumed=32/32 frames=28 lag_ms=202.48041697777808\n", - "[notify] state=running consumed=32/32 frames=29 lag_ms=0.7713340455666184\n", - "[notify] state=running consumed=32/32 frames=30 lag_ms=210.9835840528831\n", - "[notify] state=done consumed=32/32 frames=30 lag_ms=210.9835840528831\n" - ] } ], "source": [ From 062225d37c111601826cebdd7d81fb49421a092d Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 14:13:45 +0200 Subject: [PATCH 20/41] Document async runs, pause, and timed waits in the README run_experiment/continue_experiment are non-blocking and return a RunHandle; update every example to call .wait() and add a usage-level note covering pause/resume/cancel and the ExperimentStatusWidget, plus a "Timed waits" note for wait() between phases via combine. --- README.md | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8dbe422..a8db1b6 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,8 @@ events = RTMSequence( # 4. Run! ctrl = Controller(mic, pipeline) -ctrl.run_experiment(list(events), stim_mode="current") +handle = ctrl.run_experiment(list(events), stim_mode="current") +handle.wait() # run_experiment is non-blocking; wait() blocks until done ``` ## Pipeline @@ -132,6 +133,14 @@ events = combine(setup_a, setup_b, axis="p") `combine()` is variadic (`combine(a, b, c, d, ..., axis=...)`), handles the N=0 and N=1 degenerate cases, and is the only composition primitive — there is deliberately no shorthand operator, so every multi-step experiment reads the composition axis explicitly. +**Timed waits between phases.** `wait(seconds)` inserts a fixed-duration pause — e.g. to let cells recover before stimulating. It acquires no frames and just delays everything after it: + +```python +from faro.core.data_structures import wait + +events = combine(baseline, wait(60), stim_phase, axis="t") +``` + ### Stimulation Stimulation channels are acquired on specific frames, controlled via DMD/SLM. Define them with `stim_channels` and `stim_frames`: @@ -235,11 +244,25 @@ events = apply_fov_batching(events, time_per_fov=2.0) from faro.core.controller import Controller ctrl = Controller(mic, pipeline) -ctrl.run_experiment(events, stim_mode="current") +handle = ctrl.run_experiment(events, stim_mode="current") +handle.wait() # block until the run finishes ``` `validate_events()` runs automatically before the experiment starts (disable with `validate=False`). It checks both pipeline compatibility and hardware limits. +`run_experiment()` and `continue_experiment()` are **non-blocking** — they return a `RunHandle` so the kernel stays free (e.g. to use the napari viewer). Call `handle.wait()` to block until the run finishes. + +```python +handle = ctrl.run_experiment(events, stim_mode="current") +handle.pause(); handle.resume() # stop/resume acquiring; schedule is preserved +handle.cancel() # graceful stop +handle.wait() # block until done + +# Live status badge, progress strip, and Pause/Stop buttons in napari: +from faro.widgets import ExperimentStatusWidget +viewer.window.add_dock_widget(ExperimentStatusWidget(ctrl), area="right") +``` + ### Experiment Continuation Call `run_experiment()` once, then `continue_experiment()` to append more phases. The Analyzer (and all per-FOV tracking state) is reused, so timesteps, filenames, and particle IDs continue seamlessly. @@ -249,7 +272,7 @@ ctrl = Controller(mic, pipeline) # Phase 1: baseline — find cells, measure growth rate phase1 = RTMSequence(time_plan={"interval": 10, "loops": 60}, ...) -ctrl.run_experiment(phase1, validate=False) +ctrl.run_experiment(phase1, validate=False).wait() # wait() before reading results # Analyse phase-1 results to decide what to do next df = pd.read_parquet("tracks/000_latest.parquet") @@ -257,7 +280,7 @@ fast_growers = df.groupby("particle")["area"].apply(lambda x: x.diff().mean()) # Phase 2: stimulate based on analysis phase2 = RTMSequence(time_plan={"interval": 10, "loops": 120}, ...) -ctrl.continue_experiment(phase2) +ctrl.continue_experiment(phase2).wait() # Always call finish_experiment() when done ctrl.finish_experiment() @@ -270,12 +293,12 @@ ctrl.run_experiment(baseline_events, validate=False) # runs in background threa ctrl.extend_experiment(extra_events) # non-blocking, appends to running acquisition ``` -| Method | When to use | -|--------|-------------| -| `run_experiment()` | First acquisition — creates a fresh Analyzer | -| `continue_experiment()` | Subsequent phases — reuses Analyzer, offsets timesteps | -| `extend_experiment()` | Mid-run additions — pushes events into the running loop | -| `finish_experiment()` | Cleanup — shuts down Analyzer, resets state | +| Method | When to use | Returns | +|--------|-------------|---------| +| `run_experiment()` | First acquisition — creates a fresh Analyzer | `RunHandle` (non-blocking) | +| `continue_experiment()` | Subsequent phases — reuses Analyzer, offsets timesteps | `RunHandle` (non-blocking) | +| `extend_experiment()` | Mid-run additions — pushes events into the running loop | — (non-blocking) | +| `finish_experiment()` | Cleanup — shuts down Analyzer, resets state | — (blocks until drained) | ## Simulated Controller @@ -287,7 +310,7 @@ It supports both **TIFF** (`raw/`, `ref/` folders) and **OME-Zarr** (`acquisitio from faro.core.controller import ControllerSimulated ctrl = ControllerSimulated(mic, pipeline, old_data_project_path="/path/to/old_experiment") -ctrl.run_experiment(events, stim_mode="current") +ctrl.run_experiment(events, stim_mode="current").wait() ``` Use cases: From bccdb031511c5eb3d7522cc9efcc22c8485f7de3 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 15:34:33 +0200 Subject: [PATCH 21/41] Block on run_experiment().wait() in non-interactive notebooks run_experiment is now non-blocking; these notebooks read results or call post_experiment right after, so add .wait(). The demo_sim_optogenetic runs had post_experiment() between run and finish, which raced. Edit-only; outputs not refreshed. --- .../01_demo_microsocpe/demo_microscope.ipynb | 234 +- .../demo_sim_optogenetic.ipynb | 209 +- .../ome_zarr_writer.ipynb | 2093 +---------------- .../ome_zarr_writer_plate.ipynb | 668 +----- .../tiff_writer.ipynb | 450 +--- .../stim_rtmsequence_demo_mic.ipynb | 46 +- 6 files changed, 27 insertions(+), 3673 deletions(-) diff --git a/experiments/01_demo_microsocpe/demo_microscope.ipynb b/experiments/01_demo_microsocpe/demo_microscope.ipynb index b8ac232..103b7d6 100644 --- a/experiments/01_demo_microsocpe/demo_microscope.ipynb +++ b/experiments/01_demo_microsocpe/demo_microscope.ipynb @@ -250,236 +250,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
[04/02/26 16:26:05] INFO     MDA Started: GeneratorMDASequence()                                     _runner.py:378\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:05]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m MDA Started: \u001b[1;35mGeneratorMDASequence\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=494889;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=524151;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#378\u001b\\\u001b[2m378\u001b[0m\u001b]8;;\u001b\\\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 0, 'fname': '000_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=396421;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=557215;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:06] INFO     index={'t': 1, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 1, 'fname': '000_00001', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:06]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=812928;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=648595;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:07] INFO     index={'t': 2, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=2.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 2, 'fname': '000_00002', 'time': 2.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:07]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=268993;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=147524;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m2.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:08] INFO     index={'t': 3, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=3.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 3, 'fname': '000_00003', 'time': 3.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:08]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=47802;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=69148;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m3\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m3.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:09] INFO     index={'t': 4, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=4.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 4, 'fname': '000_00004', 'time': 4.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:09]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=437065;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=950385;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m4\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00004'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m4.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:10] INFO     index={'t': 5, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=5.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 5, 'fname': '000_00005', 'time': 5.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:10]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=717530;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=619451;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m5\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00005'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m5.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:11] INFO     index={'t': 6, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=6.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 6, 'fname': '000_00006', 'time': 6.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:11]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=376837;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=919649;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m6\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00006'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m6.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:12] INFO     index={'t': 7, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=7.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 7, 'fname': '000_00007', 'time': 7.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:12]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=417937;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=493013;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m7\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00007'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m7.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:13] INFO     index={'t': 8, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=8.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 8, 'fname': '000_00008', 'time': 8.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:13]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=168168;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=385801;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m8\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00008'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m8.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 16:26:14] INFO     index={'t': 9, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=9.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 9, 'fname': '000_00009', 'time': 9.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI'], 'img_type': <ImgType.IMG_RAW: 1>}                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 16:26:14]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m9\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=158593;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=582889;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m9\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m9\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00009'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m9.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     MDA Finished: GeneratorMDASequence()                                    _runner.py:465\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m MDA Finished: \u001b[1;35mGeneratorMDASequence\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=569362;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=370691;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#465\u001b\\\u001b[2m465\u001b[0m\u001b]8;;\u001b\\\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from faro.core.controller import Controller\n", - "from faro.core.writers import OmeZarrWriter\n", - "\n", - "writer = OmeZarrWriter(storage_path=path)\n", - "\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "ctrl.run_experiment(events)\n", - "ctrl.finish_experiment()" - ] + "outputs": [], + "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\n\nwriter = OmeZarrWriter(storage_path=path)\n\nctrl = Controller(mic, pipeline, writer=writer)\nctrl.run_experiment(events).wait()\nctrl.finish_experiment()" }, { "cell_type": "markdown", @@ -887,4 +661,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic.ipynb b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic.ipynb index 858cb84..260c15a 100644 --- a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic.ipynb +++ b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic.ipynb @@ -2646,123 +2646,10 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "9efecc101b4a46989bfa391f21577d85", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Experiment: 0%| | 0/40 [00:000.5s): 1/40\n", - " WARNING: 1 frames could not keep up with requested timing. Consider increasing time_interval or reducing FOV count.\n" - ] - } - ], - "source": [ - "import sys, io, logging, time as _time\n", - "from tqdm.auto import tqdm\n", - "from faro.core.data_structures import ImgType\n", - "from faro.core.controller import Controller\n", - "from faro.core.writers import TiffWriter\n", - "\n", - "writer = TiffWriter(storage_path=path)\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "\n", - "n_total = len(events)\n", - "pbar = tqdm(total=n_total, desc=\"Experiment\", unit=\"frames\")\n", - "_frame_count = [0]\n", - "_t0 = [None] # wall-clock start (set on first frame)\n", - "_max_delay = [0.0] # track worst-case delay\n", - "_late_frames = [0] # count of frames that arrived late\n", - "\n", - "# Frames arriving more than this many seconds late trigger a warning\n", - "_LATE_THRESHOLD = 0.5\n", - "\n", - "\n", - "def _on_frame(img, event):\n", - " md = event.metadata or {}\n", - " if md.get(\"img_type\") != ImgType.IMG_RAW:\n", - " return\n", - "\n", - " _frame_count[0] += 1\n", - " pbar.update(1)\n", - "\n", - " # --- Timing check ---\n", - " now = _time.time()\n", - " if _t0[0] is None:\n", - " _t0[0] = now # first imaging frame = reference\n", - " expected = md.get(\"time\", 0)\n", - " elapsed = now - _t0[0]\n", - " delay = elapsed - expected\n", - " _max_delay[0] = max(_max_delay[0], delay)\n", - " late = \"\"\n", - " if delay > _LATE_THRESHOLD:\n", - " _late_frames[0] += 1\n", - " late = f\" LATE +{delay:.1f}s\"\n", - "\n", - " fov = md.get(\"fov\", 0)\n", - " n_cells = ctrl._analyzer.get_fov_state(fov).n_cells_latest\n", - " ts = md.get(\"timestep\", 0)\n", - " pbar.set_postfix_str(\n", - " f\"frame {ts+1}/{n_total}, cells={n_cells}, delay={delay:+.2f}s{late}\"\n", - " )\n", - "\n", - "\n", - "core.mda.events.frameReady.connect(_on_frame)\n", - "\n", - "# Suppress verbose per-frame logs from pipeline and pymmcore-plus\n", - "_stdout = sys.stdout\n", - "sys.stdout = io.StringIO()\n", - "_mda_logger = logging.getLogger(\"pymmcore-plus\")\n", - "_prev_level = _mda_logger.level\n", - "_mda_logger.setLevel(logging.WARNING)\n", - "try:\n", - " ctrl.run_experiment(events, stim_mode=\"current\")\n", - " mic.post_experiment()\n", - " ctrl.finish_experiment()\n", - "finally:\n", - " sys.stdout = _stdout\n", - " _mda_logger.setLevel(_prev_level)\n", - " try:\n", - " core.mda.events.frameReady.disconnect(_on_frame)\n", - " except Exception:\n", - " pass\n", - " pbar.close()\n", - "\n", - "# Print timing summary\n", - "print(f\"\\nTiming summary:\")\n", - "print(f\" Max delay: {_max_delay[0]:.2f}s\")\n", - "print(f\" Late frames (>{_LATE_THRESHOLD}s): {_late_frames[0]}/{n_total}\")\n", - "if _late_frames[0] > 0:\n", - " print(\n", - " f\" WARNING: {_late_frames[0]} frames could not keep up with requested timing.\"\n", - " f\" Consider increasing time_interval or reducing FOV count.\"\n", - " )" - ] + "outputs": [], + "source": "import sys, io, logging, time as _time\nfrom tqdm.auto import tqdm\nfrom faro.core.data_structures import ImgType\nfrom faro.core.controller import Controller\nfrom faro.core.writers import TiffWriter\n\nwriter = TiffWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\n\nn_total = len(events)\npbar = tqdm(total=n_total, desc=\"Experiment\", unit=\"frames\")\n_frame_count = [0]\n_t0 = [None] # wall-clock start (set on first frame)\n_max_delay = [0.0] # track worst-case delay\n_late_frames = [0] # count of frames that arrived late\n\n# Frames arriving more than this many seconds late trigger a warning\n_LATE_THRESHOLD = 0.5\n\n\ndef _on_frame(img, event):\n md = event.metadata or {}\n if md.get(\"img_type\") != ImgType.IMG_RAW:\n return\n\n _frame_count[0] += 1\n pbar.update(1)\n\n # --- Timing check ---\n now = _time.time()\n if _t0[0] is None:\n _t0[0] = now # first imaging frame = reference\n expected = md.get(\"time\", 0)\n elapsed = now - _t0[0]\n delay = elapsed - expected\n _max_delay[0] = max(_max_delay[0], delay)\n late = \"\"\n if delay > _LATE_THRESHOLD:\n _late_frames[0] += 1\n late = f\" LATE +{delay:.1f}s\"\n\n fov = md.get(\"fov\", 0)\n n_cells = ctrl._analyzer.get_fov_state(fov).n_cells_latest\n ts = md.get(\"timestep\", 0)\n pbar.set_postfix_str(\n f\"frame {ts+1}/{n_total}, cells={n_cells}, delay={delay:+.2f}s{late}\"\n )\n\n\ncore.mda.events.frameReady.connect(_on_frame)\n\n# Suppress verbose per-frame logs from pipeline and pymmcore-plus\n_stdout = sys.stdout\nsys.stdout = io.StringIO()\n_mda_logger = logging.getLogger(\"pymmcore-plus\")\n_prev_level = _mda_logger.level\n_mda_logger.setLevel(logging.WARNING)\ntry:\n ctrl.run_experiment(events, stim_mode=\"current\").wait()\n mic.post_experiment()\n ctrl.finish_experiment()\nfinally:\n sys.stdout = _stdout\n _mda_logger.setLevel(_prev_level)\n try:\n core.mda.events.frameReady.disconnect(_on_frame)\n except Exception:\n pass\n pbar.close()\n\n# Print timing summary\nprint(f\"\\nTiming summary:\")\nprint(f\" Max delay: {_max_delay[0]:.2f}s\")\nprint(f\" Late frames (>{_LATE_THRESHOLD}s): {_late_frames[0]}/{n_total}\")\nif _late_frames[0] > 0:\n print(\n f\" WARNING: {_late_frames[0]} frames could not keep up with requested timing.\"\n f\" Consider increasing time_interval or reducing FOV count.\"\n )" }, { "cell_type": "markdown", @@ -3302,93 +3189,11 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "id": "cell-25", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Directory /var/folders/zy/d2yp5vws25l6vkr2g5l4t39c0000gn/T/test_optogenetic_sim_multiphase5a297anq/tracks created \n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "fddc7e0c2ade4dbdacbe3621d988fee3", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Multi-phase: 0%| | 0/50 [00:00[04/02/26 09:02:45] INFO MDA Started: GeneratorMDASequence() _runner.py:378\n", - "\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:45]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m MDA Started: \u001b[1;35mGeneratorMDASequence\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=728559;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=20873;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#378\u001b\\\u001b[2m378\u001b[0m\u001b]8;;\u001b\\\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 0, 'fname': '000_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=52286;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=716636;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 0, 'fname': '000_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=409514;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=399863;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 1, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=1.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 1, 'timestep': 0, 'fname': '001_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=908341;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=458268;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 1, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 1,                       \n",
-       "                             'timestep': 0, 'fname': '001_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=967219;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=827831;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 2, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 2, 'timestep': 0, 'fname': '002_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=18054;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=409817;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:46] INFO     index={'t': 0, 'p': 2, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 2,                       \n",
-       "                             'timestep': 0, 'fname': '002_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:46]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=911226;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=22889;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 3, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 3, 'timestep': 0, 'fname': '003_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=747507;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=908344;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 3, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 3,                       \n",
-       "                             'timestep': 0, 'fname': '003_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=468565;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=152443;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 4, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 4, 'timestep': 0, 'fname': '004_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=934566;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=587064;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 4, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 4,                       \n",
-       "                             'timestep': 0, 'fname': '004_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=163621;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=877245;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 5, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 5, 'timestep': 0, 'fname': '005_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=953337;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=389876;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 5, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 5,                       \n",
-       "                             'timestep': 0, 'fname': '005_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=598783;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=78765;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 6, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 6, 'timestep': 0, 'fname': '006_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=500017;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=496287;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 6, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 6,                       \n",
-       "                             'timestep': 0, 'fname': '006_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=935740;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=709262;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 7, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 7, 'timestep': 0, 'fname': '007_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=709559;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=43334;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:47] INFO     index={'t': 0, 'p': 7, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 7,                       \n",
-       "                             'timestep': 0, 'fname': '007_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:47]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=486576;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=171283;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 8, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 8, 'timestep': 0, 'fname': '008_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=431587;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=183792;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 8, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 8,                       \n",
-       "                             'timestep': 0, 'fname': '008_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=883053;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=154335;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 1, 'fname': '000_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=505724;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=540251;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 1, 'fname': '000_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=511030;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=882527;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 0} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 0, 'timestep': 1, 'fname':                        \n",
-       "                             '000_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=986107;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=252197;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 1, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=1.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 1, 'timestep': 1, 'fname': '001_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=574227;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=239882;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 1, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 1,                       \n",
-       "                             'timestep': 1, 'fname': '001_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=784364;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=419060;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 1} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 1, 'timestep': 1, 'fname':                        \n",
-       "                             '001_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=759394;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=181064;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'001_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 2, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 2, 'timestep': 1, 'fname': '002_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=345013;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=944588;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:48] INFO     index={'t': 1, 'p': 2, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 2,                       \n",
-       "                             'timestep': 1, 'fname': '002_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:48]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=402213;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=126090;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 2} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 2, 'timestep': 1, 'fname':                        \n",
-       "                             '002_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=287780;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=592630;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'002_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 3, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 3, 'timestep': 1, 'fname': '003_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=886551;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=332224;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 3, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 3,                       \n",
-       "                             'timestep': 1, 'fname': '003_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=92242;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=655638;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 3} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 3, 'timestep': 1, 'fname':                        \n",
-       "                             '003_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=780346;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=284267;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'003_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 4, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 4, 'timestep': 1, 'fname': '004_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=656323;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=606776;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 4, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 4,                       \n",
-       "                             'timestep': 1, 'fname': '004_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=538648;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=754654;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 4} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 4, 'timestep': 1, 'fname':                        \n",
-       "                             '004_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=397637;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=962921;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'004_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 5, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 5, 'timestep': 1, 'fname': '005_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=412431;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=21726;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 5, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 5,                       \n",
-       "                             'timestep': 1, 'fname': '005_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=55606;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=859300;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:49] INFO     index={'t': 1, 'p': 5} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 5, 'timestep': 1, 'fname':                        \n",
-       "                             '005_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:49]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=963248;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=442724;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'005_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 6, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 6, 'timestep': 1, 'fname': '006_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=266828;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=327422;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 6, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 6,                       \n",
-       "                             'timestep': 1, 'fname': '006_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=721574;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=538662;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 6} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 6, 'timestep': 1, 'fname':                        \n",
-       "                             '006_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=414363;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=626019;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'006_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 7, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 7, 'timestep': 1, 'fname': '007_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=289679;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=226154;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 7, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 7,                       \n",
-       "                             'timestep': 1, 'fname': '007_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=627318;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=999096;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 7} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 7, 'timestep': 1, 'fname':                        \n",
-       "                             '007_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=127200;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=467582;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'007_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 8, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 8, 'timestep': 1, 'fname': '008_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=797145;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=495574;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 8, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 8,                       \n",
-       "                             'timestep': 1, 'fname': '008_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=608671;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=925475;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 8} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 8, 'timestep': 1, 'fname':                        \n",
-       "                             '008_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=503453;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=922009;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'008_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:50] INFO     index={'t': 2, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 2, 'fname': '000_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:50]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=250335;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=788235;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 2, 'fname': '000_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=661724;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=412828;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 1, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=1.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 1, 'timestep': 2, 'fname': '001_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=430998;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=939574;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 1, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 1,                       \n",
-       "                             'timestep': 2, 'fname': '001_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=10920;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=86364;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 2, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 2, 'timestep': 2, 'fname': '002_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=660681;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=446334;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 2, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 2,                       \n",
-       "                             'timestep': 2, 'fname': '002_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=961130;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=501559;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 3, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 3, 'timestep': 2, 'fname': '003_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=37282;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=378759;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 3, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 3,                       \n",
-       "                             'timestep': 2, 'fname': '003_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=16995;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=536411;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 4, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 4, 'timestep': 2, 'fname': '004_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=168383;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=82211;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 4, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 4,                       \n",
-       "                             'timestep': 2, 'fname': '004_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=435733;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=419453;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:51] INFO     index={'t': 2, 'p': 5, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 5, 'timestep': 2, 'fname': '005_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:51]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=112455;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=582007;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 5, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 5,                       \n",
-       "                             'timestep': 2, 'fname': '005_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=889098;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=651178;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 6, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 6, 'timestep': 2, 'fname': '006_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=704532;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=268824;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 6, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 6,                       \n",
-       "                             'timestep': 2, 'fname': '006_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=289748;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=73254;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 7, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 7, 'timestep': 2, 'fname': '007_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=295683;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=316675;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 7, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 7,                       \n",
-       "                             'timestep': 2, 'fname': '007_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=229056;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=900128;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 8, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 8, 'timestep': 2, 'fname': '008_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=681978;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=58650;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 8, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 8,                       \n",
-       "                             'timestep': 2, 'fname': '008_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=807195;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=220100;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 3, 'fname': '000_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=993214;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=926475;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 3, 'fname': '000_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=369910;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=72399;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:52] INFO     index={'t': 3, 'p': 0, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 0, 'timestep': 3,                   \n",
-       "                             'fname': '000_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:52]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=158705;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=96673;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 0} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 0, 'timestep': 3, 'fname':                        \n",
-       "                             '000_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=239390;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=433437;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 1, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=1.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 1, 'timestep': 3, 'fname': '001_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=180755;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=244581;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 1, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 1,                       \n",
-       "                             'timestep': 3, 'fname': '001_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=622425;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=637726;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 1, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 1, 'timestep': 3,                   \n",
-       "                             'fname': '001_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=646215;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=734527;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 1} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 1, 'timestep': 3, 'fname':                        \n",
-       "                             '001_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=667203;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=919844;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'001_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 2, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 2, 'timestep': 3, 'fname': '002_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=451946;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=276285;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 2, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 2,                       \n",
-       "                             'timestep': 3, 'fname': '002_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=477831;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=131596;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 2, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 2, 'timestep': 3,                   \n",
-       "                             'fname': '002_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=375096;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=72296;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 2} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 2, 'timestep': 3, 'fname':                        \n",
-       "                             '002_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=442909;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=609364;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'002_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 3, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 3, 'timestep': 3, 'fname': '003_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=911490;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=160218;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:53] INFO     index={'t': 3, 'p': 3, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 3,                       \n",
-       "                             'timestep': 3, 'fname': '003_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:53]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=106576;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=401044;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 3, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 3, 'timestep': 3,                   \n",
-       "                             'fname': '003_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=354676;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=20929;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'003_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 3} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 3, 'timestep': 3, 'fname':                        \n",
-       "                             '003_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m3\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=572396;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=777995;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'003_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 4, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 4, 'timestep': 3, 'fname': '004_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=401240;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=608943;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 4, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 4,                       \n",
-       "                             'timestep': 3, 'fname': '004_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=593104;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=684619;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 4, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 4, 'timestep': 3,                   \n",
-       "                             'fname': '004_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=348550;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=736315;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'004_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 4} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 4, 'timestep': 3, 'fname':                        \n",
-       "                             '004_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m4\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=558517;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=47004;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'004_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 5, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 5, 'timestep': 3, 'fname': '005_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=12509;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=516400;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 5, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 5,                       \n",
-       "                             'timestep': 3, 'fname': '005_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=110144;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=296595;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 5, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 5, 'timestep': 3,                   \n",
-       "                             'fname': '005_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=615707;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=197475;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'005_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 5} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 5, 'timestep': 3, 'fname':                        \n",
-       "                             '005_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m5\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=548126;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=432194;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'005_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:54] INFO     index={'t': 3, 'p': 6, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 6, 'timestep': 3, 'fname': '006_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:54]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=514630;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=211701;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 6, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 6,                       \n",
-       "                             'timestep': 3, 'fname': '006_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=736692;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=353908;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 6, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 6, 'timestep': 3,                   \n",
-       "                             'fname': '006_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=824878;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=415270;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'006_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 6} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 6, 'timestep': 3, 'fname':                        \n",
-       "                             '006_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m6\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=299195;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=436641;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'006_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 7, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 7, 'timestep': 3, 'fname': '007_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=314234;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=710227;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 7, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 7,                       \n",
-       "                             'timestep': 3, 'fname': '007_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=257699;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=967330;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 7, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 7, 'timestep': 3,                   \n",
-       "                             'fname': '007_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=736299;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=671081;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'007_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 7} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 7, 'timestep': 3, 'fname':                        \n",
-       "                             '007_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m7\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=877064;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=573612;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'007_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 8, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 8, 'timestep': 3, 'fname': '008_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=822433;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=192703;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 8, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 8,                       \n",
-       "                             'timestep': 3, 'fname': '008_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=549412;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=582442;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/02/26 09:02:55] INFO     index={'t': 3, 'p': 8, 'c': 2} channel=Channel(config='Rhodamine')      _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 metadata={'fov': 8, 'timestep': 3,                   \n",
-       "                             'fname': '008_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI',                 \n",
-       "                             'FITC'], 'stim_power': None, 'stim_exposure': 100.0, 'img_type':                      \n",
-       "                             <ImgType.IMG_REF: 3>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/02/26 09:02:55]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Rhodamine'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=307810;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=625240;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'fname'\u001b[0m: \u001b[32m'008_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_REF:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m3\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 8} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 8, 'timestep': 3, 'fname':                        \n",
-       "                             '008_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m8\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=234026;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=360137;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'008_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     MDA Finished: GeneratorMDASequence()                                    _runner.py:465\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m MDA Finished: \u001b[1;35mGeneratorMDASequence\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=24146;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=882084;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#465\u001b\\\u001b[2m465\u001b[0m\u001b]8;;\u001b\\\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Experiment complete.\n" - ] - } - ], - "source": [ - "from faro.core.controller import Controller\n", - "\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "ctrl.run_experiment(events)\n", - "ctrl.finish_experiment()\n", - "\n", - "print(\"Experiment complete.\")" - ] + "outputs": [], + "source": "from faro.core.controller import Controller\n\nctrl = Controller(mic, pipeline, writer=writer)\nctrl.run_experiment(events).wait()\nctrl.finish_experiment()\n\nprint(\"Experiment complete.\")" }, { "cell_type": "markdown", @@ -2566,4 +481,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/experiments/03_demo_writer_implementation/ome_zarr_writer_plate.ipynb b/experiments/03_demo_writer_implementation/ome_zarr_writer_plate.ipynb index 27f297d..e4a45b1 100644 --- a/experiments/03_demo_writer_implementation/ome_zarr_writer_plate.ipynb +++ b/experiments/03_demo_writer_implementation/ome_zarr_writer_plate.ipynb @@ -247,670 +247,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
[04/01/26 18:07:48] INFO     MDA Started: GeneratorMDASequence()                                     _runner.py:378\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 18:07:48]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m MDA Started: \u001b[1;35mGeneratorMDASequence\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=674946;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=772363;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#378\u001b\\\u001b[2m378\u001b[0m\u001b]8;;\u001b\\\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 0, 'fname': '000_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=789389;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=398802;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 0, 'fname': '000_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=669338;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=541317;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 1, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=1.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 1, 'timestep': 0, 'fname': '001_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=713599;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=585434;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 1, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 1,                       \n",
-       "                             'timestep': 0, 'fname': '001_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=724329;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=676121;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 18:07:49] INFO     index={'t': 0, 'p': 2, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 2, 'timestep': 0, 'fname': '002_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 18:07:49]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=90513;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=496824;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 2, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 2,                       \n",
-       "                             'timestep': 0, 'fname': '002_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=690288;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=833851;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 1, 'fname': '000_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=138309;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=475300;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 1, 'fname': '000_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=247224;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=792674;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 0} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 0, 'timestep': 1, 'fname':                        \n",
-       "                             '000_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=190152;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=972588;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 1, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=1.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 1, 'timestep': 1, 'fname': '001_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=178487;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=956673;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 1, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 1,                       \n",
-       "                             'timestep': 1, 'fname': '001_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=884593;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=652035;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 1} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 1, 'timestep': 1, 'fname':                        \n",
-       "                             '001_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=327167;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=890495;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'001_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 2, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 2, 'timestep': 1, 'fname': '002_00001', 'time': 0.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=622314;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=882301;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 2, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.5 z_pos=0.0 metadata={'fov': 2,                       \n",
-       "                             'timestep': 1, 'fname': '002_00001', 'time': 0.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=202618;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=635682;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 18:07:50] INFO     index={'t': 1, 'p': 2} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=0.5 metadata={'fov': 2, 'timestep': 1, 'fname':                        \n",
-       "                             '002_00001', 'time': 0.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 18:07:50]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=508262;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=717046;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'002_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 2, 'fname': '000_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=124292;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=439929;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 2, 'fname': '000_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=587043;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=377522;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 1, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=1.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 1, 'timestep': 2, 'fname': '001_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=363037;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=293924;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 1, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 1,                       \n",
-       "                             'timestep': 2, 'fname': '001_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=12535;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=582083;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 2, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 2, 'timestep': 2, 'fname': '002_00002', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=243331;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=419720;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 2, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 2,                       \n",
-       "                             'timestep': 2, 'fname': '002_00002', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=870990;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=113330;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 3, 'fname': '000_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=367154;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=526370;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 3, 'fname': '000_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=719463;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=422785;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 0} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 0, 'timestep': 3, 'fname':                        \n",
-       "                             '000_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=97359;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=533;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 18:07:51] INFO     index={'t': 3, 'p': 1, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=1.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 1, 'timestep': 3, 'fname': '001_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 18:07:51]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=405618;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=804735;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 1, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 1,                       \n",
-       "                             'timestep': 3, 'fname': '001_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=730488;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=246479;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'001_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 1} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 1, 'timestep': 3, 'fname':                        \n",
-       "                             '001_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=163326;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=338826;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'001_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 2, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.5 x_pos=2.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 2, 'timestep': 3, 'fname': '002_00003', 'time': 1.5,                 \n",
-       "                             'stim': True, 'channels': ['DAPI', 'FITC'], 'stim_power': None,                       \n",
-       "                             'stim_exposure': 100.0, 'img_type': <ImgType.IMG_RAW: 1>}                             \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=766689;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=843976;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 2, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.5 z_pos=0.0 metadata={'fov': 2,                       \n",
-       "                             'timestep': 3, 'fname': '002_00003', 'time': 1.5, 'stim': True,                       \n",
-       "                             'channels': ['DAPI', 'FITC'], 'stim_power': None, 'stim_exposure':                    \n",
-       "                             100.0, 'img_type': <ImgType.IMG_RAW: 1>}                                              \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=187147;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=484161;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'002_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 2} channel=Channel(config='Cy5') exposure=100.0     _runner.py:337\n",
-       "                             min_start_time=1.5 metadata={'fov': 2, 'timestep': 3, 'fname':                        \n",
-       "                             '002_00003', 'time': 1.5, 'stim': True, 'channels': ['DAPI', 'FITC'],                 \n",
-       "                             'stim_power': None, 'stim_exposure': 100.0, 'img_type':                               \n",
-       "                             <ImgType.IMG_STIM: 2>}                                                                \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m2\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'Cy5'\u001b[0m\u001b[1m)\u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b]8;id=482789;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=332543;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.5\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'002_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.5\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;92mTrue\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim_power'\u001b[0m: \u001b[3;35mNone\u001b[0m, \u001b[32m'stim_exposure'\u001b[0m: \u001b[1;36m100.0\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_STIM:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m2\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     MDA Finished: GeneratorMDASequence()                                    _runner.py:465\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m MDA Finished: \u001b[1;35mGeneratorMDASequence\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=607404;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=57147;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#465\u001b\\\u001b[2m465\u001b[0m\u001b]8;;\u001b\\\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Experiment complete.\n" - ] - } - ], - "source": [ - "from faro.core.controller import Controller\n", - "\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "ctrl.run_experiment(events)\n", - "ctrl.finish_experiment()\n", - "\n", - "print(\"Experiment complete.\")" - ] + "outputs": [], + "source": "from faro.core.controller import Controller\n\nctrl = Controller(mic, pipeline, writer=writer)\nctrl.run_experiment(events).wait()\nctrl.finish_experiment()\n\nprint(\"Experiment complete.\")" }, { "cell_type": "markdown", @@ -1164,4 +504,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/experiments/03_demo_writer_implementation/tiff_writer.ipynb b/experiments/03_demo_writer_implementation/tiff_writer.ipynb index 795c57c..17726bd 100644 --- a/experiments/03_demo_writer_implementation/tiff_writer.ipynb +++ b/experiments/03_demo_writer_implementation/tiff_writer.ipynb @@ -230,452 +230,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
[04/01/26 12:48:39] INFO     MDA Started: GeneratorMDASequence()                                     _runner.py:378\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:39]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m MDA Started: \u001b[1;35mGeneratorMDASequence\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=809786;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=570283;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#378\u001b\\\u001b[2m378\u001b[0m\u001b]8;;\u001b\\\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=0.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 0, 'fname': '000_00000', 'time': 0,                   \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=444791;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=997601;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 0, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=0.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 0, 'fname': '000_00000', 'time': 0, 'stim': False,                        \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=681959;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=575149;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00000'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:40] INFO     index={'t': 1, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=1.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 1, 'fname': '000_00001', 'time': 1.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:40]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=122352;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=427006;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 1, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=1.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 1, 'fname': '000_00001', 'time': 1.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=12943;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=156631;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m1\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m1\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00001'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m1.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:41] INFO     index={'t': 2, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=2.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 2, 'fname': '000_00002', 'time': 2.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:41]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=975111;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=94027;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m2.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 2, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=2.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 2, 'fname': '000_00002', 'time': 2.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=627817;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=863970;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m2\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m2\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00002'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m2.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:42] INFO     index={'t': 3, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=3.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 3, 'fname': '000_00003', 'time': 3.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:42]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=539976;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=277514;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m3\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m3.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 3, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=3.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 3, 'fname': '000_00003', 'time': 3.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=148283;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=398423;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m3\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m3\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00003'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m3.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:43] INFO     index={'t': 4, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=4.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 4, 'fname': '000_00004', 'time': 4.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:43]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=92492;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=615835;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m4\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00004'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m4.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 4, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=4.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 4, 'fname': '000_00004', 'time': 4.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=894555;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=912017;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m4\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m4\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00004'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m4.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:44] INFO     index={'t': 5, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=5.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 5, 'fname': '000_00005', 'time': 5.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:44]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=175752;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=776062;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m5\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00005'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m5.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 5, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=5.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 5, 'fname': '000_00005', 'time': 5.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=926112;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=729931;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m5\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m5\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00005'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m5.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:45] INFO     index={'t': 6, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=6.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 6, 'fname': '000_00006', 'time': 6.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:45]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=813712;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=584856;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m6\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00006'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m6.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 6, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=6.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 6, 'fname': '000_00006', 'time': 6.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=190896;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=695213;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m6\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m6\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00006'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m6.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:46] INFO     index={'t': 7, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=7.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 7, 'fname': '000_00007', 'time': 7.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:46]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=197260;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=495841;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m7\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00007'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m7.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 7, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=7.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 7, 'fname': '000_00007', 'time': 7.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=228591;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=439837;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m7\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m7\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00007'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m7.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:47] INFO     index={'t': 8, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=8.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 8, 'fname': '000_00008', 'time': 8.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:47]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=525250;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=716632;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m8\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00008'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m8.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 8, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=8.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 8, 'fname': '000_00008', 'time': 8.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=168114;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=64073;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m8\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m8\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00008'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m8.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[04/01/26 12:48:48] INFO     index={'t': 9, 'p': 0, 'c': 0} channel=Channel(config='DAPI')           _runner.py:337\n",
-       "                             exposure=50.0 min_start_time=9.0 x_pos=0.0 y_pos=0.0 z_pos=0.0                        \n",
-       "                             metadata={'fov': 0, 'timestep': 9, 'fname': '000_00009', 'time': 9.0,                 \n",
-       "                             'stim': False, 'channels': ['DAPI', 'FITC'], 'img_type':                              \n",
-       "                             <ImgType.IMG_RAW: 1>}                                                                 \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m[04/01/26 12:48:48]\u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m9\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m0\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'DAPI'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=531232;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=270000;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m50\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m9\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mx_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33my_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'timestep'\u001b[0m: \u001b[1;36m9\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00009'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m9.0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     index={'t': 9, 'p': 0, 'c': 1} channel=Channel(config='FITC')           _runner.py:337\n",
-       "                             exposure=100.0 min_start_time=9.0 z_pos=0.0 metadata={'fov': 0,                       \n",
-       "                             'timestep': 9, 'fname': '000_00009', 'time': 9.0, 'stim': False,                      \n",
-       "                             'channels': ['DAPI', 'FITC'], 'img_type': <ImgType.IMG_RAW: 1>}                       \n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m \u001b[33mindex\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m't'\u001b[0m: \u001b[1;36m9\u001b[0m, \u001b[32m'p'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[32m'c'\u001b[0m: \u001b[1;36m1\u001b[0m\u001b[1m}\u001b[0m \u001b[33mchannel\u001b[0m=\u001b[1;35mChannel\u001b[0m\u001b[1m(\u001b[0m\u001b[33mconfig\u001b[0m=\u001b[32m'FITC'\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=906189;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=611317;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#337\u001b\\\u001b[2m337\u001b[0m\u001b]8;;\u001b\\\n", - "\u001b[2;36m \u001b[0m \u001b[33mexposure\u001b[0m=\u001b[1;36m100\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmin_start_time\u001b[0m=\u001b[1;36m9\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mz_pos\u001b[0m=\u001b[1;36m0\u001b[0m\u001b[1;36m.0\u001b[0m \u001b[33mmetadata\u001b[0m=\u001b[1m{\u001b[0m\u001b[32m'fov'\u001b[0m: \u001b[1;36m0\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'timestep'\u001b[0m: \u001b[1;36m9\u001b[0m, \u001b[32m'fname'\u001b[0m: \u001b[32m'000_00009'\u001b[0m, \u001b[32m'time'\u001b[0m: \u001b[1;36m9.0\u001b[0m, \u001b[32m'stim'\u001b[0m: \u001b[3;91mFalse\u001b[0m, \u001b[2m \u001b[0m\n", - "\u001b[2;36m \u001b[0m \u001b[32m'channels'\u001b[0m: \u001b[1m[\u001b[0m\u001b[32m'DAPI'\u001b[0m, \u001b[32m'FITC'\u001b[0m\u001b[1m]\u001b[0m, \u001b[32m'img_type'\u001b[0m: \u001b[1m<\u001b[0m\u001b[1;95mImgType.IMG_RAW:\u001b[0m\u001b[39m \u001b[0m\u001b[1;36m1\u001b[0m\u001b[1m>\u001b[0m\u001b[1m}\u001b[0m \u001b[2m \u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
                    INFO     MDA Finished: GeneratorMDASequence()                                    _runner.py:465\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[2;36m \u001b[0m\u001b[2;36m \u001b[0m\u001b[34mINFO \u001b[0m MDA Finished: \u001b[1;35mGeneratorMDASequence\u001b[0m\u001b[1m(\u001b[0m\u001b[1m)\u001b[0m \u001b]8;id=378565;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py\u001b\\\u001b[2m_runner.py\u001b[0m\u001b]8;;\u001b\\\u001b[2m:\u001b[0m\u001b]8;id=966271;file://c:\\Users\\Alex\\Programmierung\\01_git\\PhD\\faro_main\\.venv\\Lib\\site-packages\\pymmcore_plus\\mda\\_runner.py#465\u001b\\\u001b[2m465\u001b[0m\u001b]8;;\u001b\\\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Experiment complete.\n" - ] - } - ], - "source": [ - "from faro.core.controller import Controller\n", - "\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "ctrl.run_experiment(events)\n", - "ctrl.finish_experiment()\n", - "\n", - "print(\"Experiment complete.\")" - ] + "outputs": [], + "source": "from faro.core.controller import Controller\n\nctrl = Controller(mic, pipeline, writer=writer)\nctrl.run_experiment(events).wait()\nctrl.finish_experiment()\n\nprint(\"Experiment complete.\")" }, { "cell_type": "code", @@ -706,4 +264,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence_demo_mic.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence_demo_mic.ipynb index 44041ba..2ff8be6 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence_demo_mic.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence_demo_mic.ipynb @@ -588,48 +588,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ControllerSimulated] frameReady: img_type=ImgType.IMG_RAW fname=000_00000\n", - "[ControllerSimulated] frameReady: img_type=ImgType.IMG_RAW fname=001_00000\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "c:\\Users\\Jungfrau\\Documents\\alandolt\\code\\restructure_repo\\faro\\.venv\\Lib\\site-packages\\cellpose\\dynamics.py:524: UserWarning: Sparse invariant checks are implicitly disabled. Memory errors (e.g. SEGFAULT) will occur when operating on a sparse tensor which violates the invariants, but checks incur performance overhead. To silence this warning, explicitly opt in or out. See `torch.sparse.check_sparse_tensor_invariants.__doc__` for guidance. (Triggered internally at C:\\actions-runner\\_work\\pytorch\\pytorch\\pytorch\\aten\\src\\ATen\\Context.cpp:767.)\n", - " coo = torch.sparse_coo_tensor(pt, torch.ones(pt.shape[1], device=pt.device, dtype=torch.int),\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ControllerSimulated] frameReady: img_type=ImgType.IMG_RAW fname=000_00001\n", - "[ControllerSimulated] frameReady: img_type=ImgType.IMG_RAW fname=001_00001\n", - "[ControllerSimulated] frameReady: img_type=ImgType.IMG_RAW fname=000_00002\n", - "[ControllerSimulated] frameReady: img_type=ImgType.IMG_RAW fname=001_00002\n" - ] - } - ], - "source": [ - "from faro.core.controller import ControllerSimulated\n", - "from faro.core.writers import OmeZarrWriter\n", - "\n", - "writer = OmeZarrWriter(storage_path=path)\n", - "\n", - "ctrl = ControllerSimulated(\n", - " mic, pipeline, old_data_project_path=demo_data_path, writer=writer\n", - ")\n", - "ctrl.run_experiment(events)\n", - "ctrl.finish_experiment()" - ] + "outputs": [], + "source": "from faro.core.controller import ControllerSimulated\nfrom faro.core.writers import OmeZarrWriter\n\nwriter = OmeZarrWriter(storage_path=path)\n\nctrl = ControllerSimulated(\n mic, pipeline, old_data_project_path=demo_data_path, writer=writer\n)\nctrl.run_experiment(events).wait()\nctrl.finish_experiment()" }, { "cell_type": "markdown", @@ -1543,4 +1505,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From bce2d9ff2270c195526a51e319f83a2813514c2b Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 15:53:26 +0200 Subject: [PATCH 22/41] Migrate cell-migration + line-stim notebooks to async run Split each run cell into an async launch (dock ExperimentStatusWidget, return handle) and a finalize cell (handle.wait() before post_experiment and result reads). Drop the post-run time.sleep(10/90) plumbing waits, now covered by handle.wait() + finish/post drain. Edit-only. --- .../21_cell_migration/cell_migration.ipynb | 38 ++++---------- .../cell_migration_test.ipynb | 46 ++++------------- .../line_stimulation.ipynb | 49 +++---------------- 3 files changed, 26 insertions(+), 107 deletions(-) diff --git a/experiments/21_cell_migration/cell_migration.ipynb b/experiments/21_cell_migration/cell_migration.ipynb index a96d608..6e0bbf0 100644 --- a/experiments/21_cell_migration/cell_migration.ipynb +++ b/experiments/21_cell_migration/cell_migration.ipynb @@ -1818,7 +1818,7 @@ " \n", " \n", "\n", - "

480 rows \u00d7 20 columns

\n", + "

480 rows × 20 columns

\n", "" ], "text/plain": [ @@ -1970,34 +1970,14 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "from faro.core.controller import Controller\n", - "from faro.core.writers import OmeZarrWriter\n", - "from faro.core.conversion import df_to_events\n", - "\n", - "for _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n", - " time.sleep(1)\n", - "\n", - "try:\n", - " mm_wdg._core_link.cleanup()\n", - "\n", - "except:\n", - " pass\n", - "\n", - "events = df_to_events(df_acquire)\n", - "writer = OmeZarrWriter(storage_path=path)\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "ctrl.run_experiment(events, stim_mode=\"current\")\n", - "mic.post_experiment()\n", - "time.sleep(10)\n", - "\n", - "utils.generate_exp_data_from_tracks(path)\n", - "\n", - "from napari_micromanager._core_link import CoreViewerLink\n", - "\n", - "if \"viewer\" in locals():\n", - " mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" - ] + "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\ntry:\n mm_wdg._core_link.cleanup()\n\nexcept:\n pass\n\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" + }, + { + "cell_type": "code", + "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nmic.post_experiment()\n\nutils.generate_exp_data_from_tracks(path)\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)", + "metadata": {}, + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", diff --git a/experiments/21_cell_migration/cell_migration_test.ipynb b/experiments/21_cell_migration/cell_migration_test.ipynb index b25f0c3..a471217 100644 --- a/experiments/21_cell_migration/cell_migration_test.ipynb +++ b/experiments/21_cell_migration/cell_migration_test.ipynb @@ -936,42 +936,14 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "from faro.core.controller import ControllerSimulated\n", - "from faro.core.writers import OmeZarrWriter\n", - "from faro.core.conversion import df_to_events\n", - "\n", - "for _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n", - " time.sleep(1)\n", - "\n", - "try:\n", - " mm_wdg._core_link.cleanup()\n", - "except:\n", - " pass\n", - "\n", - "print(\n", - " f\"Running in simulated mode with old data from {path_with_old_data_for_simulation}\"\n", - ")\n", - "events = df_to_events(df_acquire)\n", - "writer = OmeZarrWriter(storage_path=path)\n", - "ctrl = ControllerSimulated(\n", - " mic, pipeline, path_with_old_data_for_simulation, writer=writer\n", - ")\n", - "ctrl.run_experiment(events, stim_mode=\"current\")\n", - "print(\"Experiment finished\")\n", - "mic.post_experiment()\n", - "\n", - "time.sleep(90)\n", - "\n", - "fovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\n", - "fovs_i_list.sort()\n", - "dfs = []\n", - "for fov_i in fovs_i_list:\n", - " track_file = os.path.join(path, \"tracks\", fov_i)\n", - " df = pd.read_parquet(track_file)\n", - " dfs.append(df)\n", - "pd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))" - ] + "source": "from faro.core.controller import ControllerSimulated\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nprint(\n f\"Running in simulated mode with old data from {path_with_old_data_for_simulation}\"\n)\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = ControllerSimulated(\n mic, pipeline, path_with_old_data_for_simulation, writer=writer\n)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" + }, + { + "cell_type": "code", + "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nprint(\"Experiment finished\")\nmic.post_experiment()\n\nfovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\nfovs_i_list.sort()\ndfs = []\nfor fov_i in fovs_i_list:\n track_file = os.path.join(path, \"tracks\", fov_i)\n df = pd.read_parquet(track_file)\n dfs.append(df)\npd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))", + "metadata": {}, + "execution_count": null, + "outputs": [] } ], "metadata": { @@ -996,4 +968,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/experiments/22_line_stimulation/line_stimulation.ipynb b/experiments/22_line_stimulation/line_stimulation.ipynb index 6797ae0..71c599b 100644 --- a/experiments/22_line_stimulation/line_stimulation.ipynb +++ b/experiments/22_line_stimulation/line_stimulation.ipynb @@ -2439,47 +2439,14 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "from faro.core.controller import Controller\n", - "from faro.core.writers import OmeZarrWriter\n", - "from faro.core.conversion import df_to_events\n", - "\n", - "for _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n", - " time.sleep(1)\n", - "\n", - "try:\n", - "\n", - " mm_wdg._core_link.cleanup()\n", - "\n", - "except:\n", - " pass\n", - "\n", - "events = df_to_events(df_acquire)\n", - "writer = OmeZarrWriter(storage_path=path)\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "ctrl.run_experiment(events, stim_mode=\"current\")\n", - "print(\"Experiment finished\")\n", - "mic.post_experiment()\n", - "\n", - "time.sleep(10)\n", - "\n", - "fovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\n", - "fovs_i_list.sort()\n", - "dfs = []\n", - "\n", - "for fov_i in fovs_i_list:\n", - "\n", - " track_file = os.path.join(path, \"tracks\", fov_i)\n", - " df = pd.read_parquet(track_file)\n", - " dfs.append(df)\n", - "\n", - "pd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))\n", - "\n", - "from napari_micromanager._core_link import CoreViewerLink\n", - "\n", - "if \"viewer\" in locals():\n", - " mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" - ] + "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" + }, + { + "cell_type": "code", + "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nprint(\"Experiment finished\")\nmic.post_experiment()\n\nfovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\nfovs_i_list.sort()\ndfs = []\n\nfor fov_i in fovs_i_list:\n\n track_file = os.path.join(path, \"tracks\", fov_i)\n df = pd.read_parquet(track_file)\n dfs.append(df)\n\npd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)", + "metadata": {}, + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", From ec233d788eb23ff6aeda1ac5486b521b7387eefa Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 15:55:23 +0200 Subject: [PATCH 23/41] Drain the pipeline before reading results in 21/22 notebooks handle.wait() only joins the run worker, not the analyzer that writes tracks. The deleted time.sleep() was the crude analyzer-drain wait; replace it with ctrl.finish_experiment() (waits for the run + drains the pipeline) before generate_exp_data_from_tracks / parquet reads. --- experiments/21_cell_migration/cell_migration.ipynb | 2 +- experiments/21_cell_migration/cell_migration_test.ipynb | 2 +- experiments/22_line_stimulation/line_stimulation.ipynb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/experiments/21_cell_migration/cell_migration.ipynb b/experiments/21_cell_migration/cell_migration.ipynb index 6e0bbf0..383f0b2 100644 --- a/experiments/21_cell_migration/cell_migration.ipynb +++ b/experiments/21_cell_migration/cell_migration.ipynb @@ -1974,7 +1974,7 @@ }, { "cell_type": "code", - "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nmic.post_experiment()\n\nutils.generate_exp_data_from_tracks(path)\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)", + "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nmic.post_experiment()\nctrl.finish_experiment() # drain the pipeline so all tracks are written\n\nutils.generate_exp_data_from_tracks(path)\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)", "metadata": {}, "execution_count": null, "outputs": [] diff --git a/experiments/21_cell_migration/cell_migration_test.ipynb b/experiments/21_cell_migration/cell_migration_test.ipynb index a471217..5ddccbe 100644 --- a/experiments/21_cell_migration/cell_migration_test.ipynb +++ b/experiments/21_cell_migration/cell_migration_test.ipynb @@ -940,7 +940,7 @@ }, { "cell_type": "code", - "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nprint(\"Experiment finished\")\nmic.post_experiment()\n\nfovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\nfovs_i_list.sort()\ndfs = []\nfor fov_i in fovs_i_list:\n track_file = os.path.join(path, \"tracks\", fov_i)\n df = pd.read_parquet(track_file)\n dfs.append(df)\npd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))", + "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nprint(\"Experiment finished\")\nmic.post_experiment()\nctrl.finish_experiment() # drain the pipeline so all tracks are written\n\nfovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\nfovs_i_list.sort()\ndfs = []\nfor fov_i in fovs_i_list:\n track_file = os.path.join(path, \"tracks\", fov_i)\n df = pd.read_parquet(track_file)\n dfs.append(df)\npd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))", "metadata": {}, "execution_count": null, "outputs": [] diff --git a/experiments/22_line_stimulation/line_stimulation.ipynb b/experiments/22_line_stimulation/line_stimulation.ipynb index 71c599b..b01b44d 100644 --- a/experiments/22_line_stimulation/line_stimulation.ipynb +++ b/experiments/22_line_stimulation/line_stimulation.ipynb @@ -2443,7 +2443,7 @@ }, { "cell_type": "code", - "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nprint(\"Experiment finished\")\nmic.post_experiment()\n\nfovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\nfovs_i_list.sort()\ndfs = []\n\nfor fov_i in fovs_i_list:\n\n track_file = os.path.join(path, \"tracks\", fov_i)\n df = pd.read_parquet(track_file)\n dfs.append(df)\n\npd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)", + "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nprint(\"Experiment finished\")\nmic.post_experiment()\nctrl.finish_experiment() # drain the pipeline so all tracks are written\n\nfovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\nfovs_i_list.sort()\ndfs = []\n\nfor fov_i in fovs_i_list:\n\n track_file = os.path.join(path, \"tracks\", fov_i)\n df = pd.read_parquet(track_file)\n dfs.append(df)\n\npd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)", "metadata": {}, "execution_count": null, "outputs": [] From e95f94ba6c1f6a04084e106bdf1b350508a1a954 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 16:08:51 +0200 Subject: [PATCH 24/41] Migrate 11 single/two-phase stim notebooks to async run stim_rtmsequence: dock ExperimentStatusWidget; its existing finish_experiment() already waits for the async run + drains. stim_dfacquire: serialize the two-controller phases with handle.wait() so they don't run concurrently on one microscope, and dock the widget. (Pre-existing bug left untouched: cell-12/cell-16 are mis-tagged as markdown, so df_acquire_2 + post-processing don't execute.) --- .../stim_dfacquire.ipynb | 34 ++++--------------- .../stim_rtmsequence.ipynb | 5 +-- 2 files changed, 7 insertions(+), 32 deletions(-) diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb index d53cf8f..2b41467 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb @@ -3,7 +3,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "# ERK-KTR Full FOV Stimulation Pipeline (df_acquire API)\n\nThis notebook uses the **legacy `df_acquire` API** to build acquisition events via pandas DataFrames.\nIt is functionally equivalent to `Full_FOV_stim_ERKKTR_RTMSequence.ipynb`, which uses the newer **`RTMSequence` API** \u2014 a more concise, declarative way to define multi-phase experiments.\n\n**When to use which API:**\n- **RTMSequence** (recommended): phases are defined as `RTMSequence` objects and concatenated with `+`. Stim frames, ref frames, and channels are declared per-phase. See `Full_FOV_stim_ERKKTR_RTMSequence.ipynb`.\n- **df_acquire** (this notebook): gives full control over the acquisition DataFrame. Useful for complex per-FOV condition assignments, wellplate layouts, or custom stim schedules that don't map cleanly to RTMSequence phases.\n\n**Workflow overview:**\n1. Initialize microscope and define channels / stimulation treatments\n2. Configure the image processing pipeline (segmentation, tracking, feature extraction)\n3. Select FOV positions in napari and build a `df_acquire` DataFrame per phase\n4. Convert DataFrames to events and run the experiment\n5. Post-process tracks into a single `exp_data.parquet`" + "source": "# ERK-KTR Full FOV Stimulation Pipeline (df_acquire API)\n\nThis notebook uses the **legacy `df_acquire` API** to build acquisition events via pandas DataFrames.\nIt is functionally equivalent to `Full_FOV_stim_ERKKTR_RTMSequence.ipynb`, which uses the newer **`RTMSequence` API** — a more concise, declarative way to define multi-phase experiments.\n\n**When to use which API:**\n- **RTMSequence** (recommended): phases are defined as `RTMSequence` objects and concatenated with `+`. Stim frames, ref frames, and channels are declared per-phase. See `Full_FOV_stim_ERKKTR_RTMSequence.ipynb`.\n- **df_acquire** (this notebook): gives full control over the acquisition DataFrame. Useful for complex per-FOV condition assignments, wellplate layouts, or custom stim schedules that don't map cleanly to RTMSequence phases.\n\n**Workflow overview:**\n1. Initialize microscope and define channels / stimulation treatments\n2. Configure the image processing pipeline (segmentation, tracking, feature extraction)\n3. Select FOV positions in napari and build a `df_acquire` DataFrame per phase\n4. Convert DataFrames to events and run the experiment\n5. Post-process tracks into a single `exp_data.parquet`" }, { "cell_type": "markdown", @@ -31,7 +31,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "### Experimental Settings\n\n**Microscope:** Jungfrau (no DMD). Change the import to use a different microscope.\n\n> **RTMSequence equivalent:** Channel definitions are the same, but stimulation is defined via `PowerChannel` + `stim_channels`/`stim_frames` on `RTMSequence` instead of `StimTreatment` objects. The RTMSequence API also handles per-phase timing automatically \u2014 no need for manual `N_FRAMES_PHASE_*` or `TIME_PER_FOV` bookkeeping." + "source": "### Experimental Settings\n\n**Microscope:** Jungfrau (no DMD). Change the import to use a different microscope.\n\n> **RTMSequence equivalent:** Channel definitions are the same, but stimulation is defined via `PowerChannel` + `stim_channels`/`stim_frames` on `RTMSequence` instead of `StimTreatment` objects. The RTMSequence API also handles per-phase timing automatically — no need for manual `N_FRAMES_PHASE_*` or `TIME_PER_FOV` bookkeeping." }, { "cell_type": "code", @@ -1915,7 +1915,7 @@ " \n", " \n", "\n", - "

300 rows \u00d7 22 columns

\n", + "

300 rows × 22 columns

\n", "" ], "text/plain": [ @@ -2081,36 +2081,14 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "from faro.core.controller import Controller\n", - "from faro.core.writers import OmeZarrWriter\n", - "from faro.core.conversion import (\n", - " df_to_events,\n", - ") # df_acquire-specific: converts DataFrame -> list[RTMEvent]\n", - "\n", - "# Optional: wait before starting\n", - "for _ in range(0, int(SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600)):\n", - " time.sleep(1)\n", - "\n", - "# Disconnect napari live view so it does not interfere with the acquisition engine\n", - "try:\n", - " mm_wdg._core_link.cleanup()\n", - "except:\n", - " pass\n", - "\n", - "# Phase 1: convert df_acquire -> events and run\n", - "events = df_to_events(df_acquire)\n", - "writer = OmeZarrWriter(storage_path=path)\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "ctrl.run_experiment(events, stim_mode=\"current\")" - ] + "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import (\n df_to_events,\n) # df_acquire-specific: converts DataFrame -> list[RTMEvent]\nfrom faro.widgets import ExperimentStatusWidget\n\n# Optional: wait before starting\nfor _ in range(0, int(SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600)):\n time.sleep(1)\n\n# Disconnect napari live view so it does not interfere with the acquisition engine\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\n# Phase 1: convert df_acquire -> events and run (non-blocking; returns a handle)\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], - "source": "# Phase 2: convert second df_acquire -> events and run with a new Controller\n# Note: df_acquire requires separate Controller instances per phase\n# RTMSequence equivalent: phases are concatenated and run with a single ctrl.run_experiment()\nevents_2 = df_to_events(df_acquire_2)\nctrl_2 = Controller(mic, pipeline, writer=writer)\nctrl_2.run_experiment(events_2, stim_mode=\"current\")" + "source": "# Phase 2: convert second df_acquire -> events and run with a new Controller\n# Note: df_acquire requires separate Controller instances per phase\n# RTMSequence equivalent: phases are concatenated and run with a single ctrl.run_experiment()\nhandle.wait() # phase 1 must finish before phase 2 starts on the same microscope\nevents_2 = df_to_events(df_acquire_2)\nctrl_2 = Controller(mic, pipeline, writer=writer)\nhandle2 = ctrl_2.run_experiment(events_2, stim_mode=\"current\")" }, { "cell_type": "markdown", @@ -2169,4 +2147,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb index 6289da3..c621b7f 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb @@ -241,10 +241,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "ctrl.validate_events(events) # checks channels, positions, pipeline compatibility" - ], + "source": "ctrl = Controller(mic, pipeline, writer=writer)\nctrl.validate_events(events) # checks channels, positions, pipeline compatibility\n\n# Live status + pause/stop buttons; re-binds when run_experiment starts.\nfrom faro.widgets import ExperimentStatusWidget\n\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)", "id": "5718ece3" }, { From 5edf76d1d72c775eba7dbb7d2dea99ae0d93d0c7 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 21 May 2026 16:22:19 +0200 Subject: [PATCH 25/41] Migrate stim_ramp multi-phase notebook to async run Six sequential phases driven by run_experiment + chained continue_experiment on one controller, with manual drug pipetting at each cell boundary. Add .wait() after every run/continue so a phase finishes before the next continue_experiment (which would otherwise raise "already running") and before the napari reconnect/pipetting pause. Preserves the original blocking semantics; dock the status widget once (re-binds on each phase via runStarted). --- .../stim_ramp_dfacquire.ipynb | 101 +----------------- 1 file changed, 5 insertions(+), 96 deletions(-) diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb index ca30bbb..824432f 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb @@ -690,43 +690,7 @@ "id": "414db88b", "metadata": {}, "outputs": [], - "source": [ - "from datetime import datetime\n", - "import time\n", - "\n", - "## Optional: schedule experiment start to align with drug pipetting\n", - "start_at = \"2026-04-02 08:45:00\" # target time for 1st drug addition\n", - "\n", - "# Wait so that df_acquire_1_1 finishes right at start_at (drug pipetting time)\n", - "wait_seconds = (\n", - " datetime.strptime(start_at, \"%Y-%m-%d %H:%M:%S\") - datetime.now()\n", - ").total_seconds() - df_acquire_1_1.time.max()\n", - "if wait_seconds > 0:\n", - " print(\n", - " f\"Waiting {wait_seconds/60:.1f} minutes, 1st pipetting scheduled at {start_at}\"\n", - " )\n", - " for _ in range(0, int(wait_seconds)):\n", - " time.sleep(1)\n", - "\n", - "print(\"Starting experiment\")\n", - "\n", - "# Disconnect napari live view so it does not interfere with the MDA engine\n", - "try:\n", - " mm_wdg._core_link.cleanup()\n", - "except:\n", - " pass\n", - "\n", - "## Phase 1: Group 1 pre-drug -- uses run_experiment (creates a new Analyzer)\n", - "ctrl = Controller(mic, pipeline, writer=writer)\n", - "events = df_to_events(df_acquire_1_1)\n", - "ctrl.run_experiment(events, stim_mode=\"current\")\n", - "\n", - "# Reconnect napari live view (pause point: pipette drug for group 1)\n", - "from napari_micromanager._core_link import CoreViewerLink\n", - "\n", - "if \"viewer\" in locals():\n", - " mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" - ] + "source": "from datetime import datetime\nimport time\nfrom faro.widgets import ExperimentStatusWidget\n\n## Optional: schedule experiment start to align with drug pipetting\nstart_at = \"2026-04-02 08:45:00\" # target time for 1st drug addition\n\n# Wait so that df_acquire_1_1 finishes right at start_at (drug pipetting time)\nwait_seconds = (\n datetime.strptime(start_at, \"%Y-%m-%d %H:%M:%S\") - datetime.now()\n).total_seconds() - df_acquire_1_1.time.max()\nif wait_seconds > 0:\n print(\n f\"Waiting {wait_seconds/60:.1f} minutes, 1st pipetting scheduled at {start_at}\"\n )\n for _ in range(0, int(wait_seconds)):\n time.sleep(1)\n\nprint(\"Starting experiment\")\n\n# Disconnect napari live view so it does not interfere with the MDA engine\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\n## Phase 1: Group 1 pre-drug -- uses run_experiment (creates a new Analyzer)\nctrl = Controller(mic, pipeline, writer=writer)\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nevents = df_to_events(df_acquire_1_1)\n# Non-blocking; wait() blocks until the phase finishes so the next phase's\n# continue_experiment doesn't hit \"a run is already in progress\".\nctrl.run_experiment(events, stim_mode=\"current\").wait()\n\n# Reconnect napari live view (pause point: pipette drug for group 1)\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" }, { "cell_type": "code", @@ -734,26 +698,7 @@ "id": "1e25eca3", "metadata": {}, "outputs": [], - "source": [ - "## Phase 2: Group 1 post-drug + Group 2 pre-drug\n", - "## Uses continue_experiment to preserve tracking state from phase 1\n", - "try:\n", - " mm_wdg._core_link.cleanup()\n", - "except:\n", - " pass\n", - "\n", - "events = df_to_events(df_acquire_1_2)\n", - "ctrl.continue_experiment(events, stim_mode=\"current\") # preserves pipeline state\n", - "\n", - "events = df_to_events(df_acquire_2_1)\n", - "ctrl.continue_experiment(events, stim_mode=\"current\")\n", - "\n", - "# Reconnect napari (pause point: pipette drug for group 2)\n", - "from napari_micromanager._core_link import CoreViewerLink\n", - "\n", - "if \"viewer\" in locals():\n", - " mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" - ] + "source": "## Phase 2: Group 1 post-drug + Group 2 pre-drug\n## Uses continue_experiment to preserve tracking state from phase 1\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nevents = df_to_events(df_acquire_1_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait() # preserves pipeline state\n\nevents = df_to_events(df_acquire_2_1)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n# Reconnect napari (pause point: pipette drug for group 2)\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" }, { "cell_type": "code", @@ -761,25 +706,7 @@ "id": "eb3178f6", "metadata": {}, "outputs": [], - "source": [ - "## Phase 3: Group 2 post-drug + Group 3 pre-drug\n", - "try:\n", - " mm_wdg._core_link.cleanup()\n", - "except:\n", - " pass\n", - "\n", - "events = df_to_events(df_acquire_2_2)\n", - "ctrl.continue_experiment(events, stim_mode=\"current\")\n", - "\n", - "events = df_to_events(df_acquire_3_1)\n", - "ctrl.continue_experiment(events, stim_mode=\"current\")\n", - "\n", - "# Reconnect napari (pause point: pipette drug for group 3)\n", - "from napari_micromanager._core_link import CoreViewerLink\n", - "\n", - "if \"viewer\" in locals():\n", - " mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" - ] + "source": "## Phase 3: Group 2 post-drug + Group 3 pre-drug\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nevents = df_to_events(df_acquire_2_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\nevents = df_to_events(df_acquire_3_1)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n# Reconnect napari (pause point: pipette drug for group 3)\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" }, { "cell_type": "code", @@ -799,25 +726,7 @@ "id": "0d0c67f2", "metadata": {}, "outputs": [], - "source": [ - "## Phase 4: Group 3 post-drug (final phase)\n", - "try:\n", - " mm_wdg._core_link.cleanup()\n", - "except:\n", - " pass\n", - "\n", - "events = df_to_events(df_acquire_3_2)\n", - "ctrl.continue_experiment(events, stim_mode=\"current\")\n", - "\n", - "## Finish: flush pipeline queue, close zarr store, merge tracks\n", - "ctrl.finish_experiment()\n", - "utils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet\n", - "\n", - "from napari_micromanager._core_link import CoreViewerLink\n", - "\n", - "if \"viewer\" in locals():\n", - " mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" - ] + "source": "## Phase 4: Group 3 post-drug (final phase)\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nevents = df_to_events(df_acquire_3_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n## Finish: flush pipeline queue, close zarr store, merge tracks\nctrl.finish_experiment()\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" }, { "cell_type": "code", @@ -911,4 +820,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From c2551a57bbedaeb7ccfea8325cea9bebe4bc64aa Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 28 May 2026 10:26:11 +0200 Subject: [PATCH 26/41] Fix mis-tagged markdown cells in stim_dfacquire cell-12 (builds df_acquire_2) and cell-16 (post-processing) were tagged as markdown, so they never executed -- cell-15 referenced an undefined df_acquire_2 and results were never written. Convert both to code. The post-processing cell also gets the async treatment: wait on phase 2, drain via finish_experiment (replacing the dropped sleep), then read. --- .../11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb index 2b41467..437a98a 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb @@ -2065,7 +2065,7 @@ }, { "attachments": {}, - "cell_type": "markdown", + "cell_type": "code", "metadata": {}, "source": "# Phase 2: separate df_acquire with optocheck channel on specific timepoints\n# RTMSequence equivalent: RTMSequence(..., ref_channels=(optocheck_channel,), ref_frames=[-1])\ndf_acquire_2 = utils.generate_df_acquire(\n fovs,\n n_frames=N_FRAMES_PHASE_2,\n time_between_timesteps=TIME_BETWEEN_TIMESTEPS,\n time_per_fov=TIME_PER_FOV,\n channels=channels,\n channel_optocheck=channel_optocheck, # df_acquire passes optocheck as a parameter here\n optocheck_timepoints=optocheck_timepoints, # absolute timestep indices (not per-phase like RTMSequence)\n phase_name=\"PostDrug\",\n phase_id=1,\n condition=condition,\n)\ndf_acquire_2 = utils.apply_stim_treatments_to_df_acquire(\n df_acquire_2,\n stim_phase_2,\n condition,\n n_fovs_per_well=n_fovs_per_well,\n add_stim_exposure_group=ADD_STIM_EXPOSURE_GROUP,\n regular_spacing_between_stimulations=REGULAR_SPACING_BETWEEN_STIMULATIONS,\n)\ndf_acquire_2" }, @@ -2091,9 +2091,9 @@ "source": "# Phase 2: convert second df_acquire -> events and run with a new Controller\n# Note: df_acquire requires separate Controller instances per phase\n# RTMSequence equivalent: phases are concatenated and run with a single ctrl.run_experiment()\nhandle.wait() # phase 1 must finish before phase 2 starts on the same microscope\nevents_2 = df_to_events(df_acquire_2)\nctrl_2 = Controller(mic, pipeline, writer=writer)\nhandle2 = ctrl_2.run_experiment(events_2, stim_mode=\"current\")" }, { - "cell_type": "markdown", + "cell_type": "code", "metadata": {}, - "source": "# Post-processing (same for both APIs)\nmic.post_experiment() # currently a no-op on Jungfrau, but good practice to call\ntime.sleep(10)\n\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet\n\n# Reconnect napari live view\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" + "source": "# Post-processing (same for both APIs)\nhandle2.wait() # block until phase 2 finishes\nmic.post_experiment() # currently a no-op on Jungfrau, but good practice to call\nctrl_2.finish_experiment() # drain the pipeline so all tracks are written\n\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet\n\n# Reconnect napari live view\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" }, { "cell_type": "code", From 6dfbc6b20c802f474bba0f925e25765457266481 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 28 May 2026 11:08:05 +0200 Subject: [PATCH 27/41] Drop the napari core_link teardown dance from run cells napari-micromanager keeps routing frames into its preview layer during a run (the controller only stops continuous acquisition once at MDA start), so the manual mm_wdg._core_link.cleanup() before each run + CoreViewerLink reconnect after is no longer needed. Remove it from run/phase cells so the live link stays connected throughout. DMD-calibration cells (which drive the camera directly, outside an MDA run) and the manual reconnect/break utility cells are left as-is. --- .../stim_dfacquire.ipynb | 6 ++-- .../stim_ramp_dfacquire.ipynb | 8 ++--- .../stim_rtmsequence.ipynb | 36 ++----------------- .../21_cell_migration/cell_migration.ipynb | 4 +-- .../cell_migration_test.ipynb | 2 +- .../line_stimulation.ipynb | 4 +-- 6 files changed, 16 insertions(+), 44 deletions(-) diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb index 437a98a..d89933c 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb @@ -2081,7 +2081,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import (\n df_to_events,\n) # df_acquire-specific: converts DataFrame -> list[RTMEvent]\nfrom faro.widgets import ExperimentStatusWidget\n\n# Optional: wait before starting\nfor _ in range(0, int(SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600)):\n time.sleep(1)\n\n# Disconnect napari live view so it does not interfere with the acquisition engine\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\n# Phase 1: convert df_acquire -> events and run (non-blocking; returns a handle)\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" + "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import (\n df_to_events,\n) # df_acquire-specific: converts DataFrame -> list[RTMEvent]\nfrom faro.widgets import ExperimentStatusWidget\n\n# Optional: wait before starting\nfor _ in range(0, int(SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600)):\n time.sleep(1)\n\n# Phase 1: convert df_acquire -> events and run (non-blocking; returns a handle)\n# napari-micromanager keeps routing frames during the run -- no need to tear\n# down the live link first.\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" }, { "cell_type": "code", @@ -2093,7 +2093,9 @@ { "cell_type": "code", "metadata": {}, - "source": "# Post-processing (same for both APIs)\nhandle2.wait() # block until phase 2 finishes\nmic.post_experiment() # currently a no-op on Jungfrau, but good practice to call\nctrl_2.finish_experiment() # drain the pipeline so all tracks are written\n\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet\n\n# Reconnect napari live view\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" + "source": "# Post-processing (same for both APIs)\nhandle2.wait() # block until phase 2 finishes\nmic.post_experiment() # currently a no-op on Jungfrau, but good practice to call\nctrl_2.finish_experiment() # drain the pipeline so all tracks are written\n\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet", + "execution_count": null, + "outputs": [] }, { "cell_type": "code", diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb index 824432f..7be6dca 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb @@ -690,7 +690,7 @@ "id": "414db88b", "metadata": {}, "outputs": [], - "source": "from datetime import datetime\nimport time\nfrom faro.widgets import ExperimentStatusWidget\n\n## Optional: schedule experiment start to align with drug pipetting\nstart_at = \"2026-04-02 08:45:00\" # target time for 1st drug addition\n\n# Wait so that df_acquire_1_1 finishes right at start_at (drug pipetting time)\nwait_seconds = (\n datetime.strptime(start_at, \"%Y-%m-%d %H:%M:%S\") - datetime.now()\n).total_seconds() - df_acquire_1_1.time.max()\nif wait_seconds > 0:\n print(\n f\"Waiting {wait_seconds/60:.1f} minutes, 1st pipetting scheduled at {start_at}\"\n )\n for _ in range(0, int(wait_seconds)):\n time.sleep(1)\n\nprint(\"Starting experiment\")\n\n# Disconnect napari live view so it does not interfere with the MDA engine\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\n## Phase 1: Group 1 pre-drug -- uses run_experiment (creates a new Analyzer)\nctrl = Controller(mic, pipeline, writer=writer)\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nevents = df_to_events(df_acquire_1_1)\n# Non-blocking; wait() blocks until the phase finishes so the next phase's\n# continue_experiment doesn't hit \"a run is already in progress\".\nctrl.run_experiment(events, stim_mode=\"current\").wait()\n\n# Reconnect napari live view (pause point: pipette drug for group 1)\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" + "source": "from datetime import datetime\nimport time\nfrom faro.widgets import ExperimentStatusWidget\n\n## Optional: schedule experiment start to align with drug pipetting\nstart_at = \"2026-04-02 08:45:00\" # target time for 1st drug addition\n\n# Wait so that df_acquire_1_1 finishes right at start_at (drug pipetting time)\nwait_seconds = (\n datetime.strptime(start_at, \"%Y-%m-%d %H:%M:%S\") - datetime.now()\n).total_seconds() - df_acquire_1_1.time.max()\nif wait_seconds > 0:\n print(\n f\"Waiting {wait_seconds/60:.1f} minutes, 1st pipetting scheduled at {start_at}\"\n )\n for _ in range(0, int(wait_seconds)):\n time.sleep(1)\n\nprint(\"Starting experiment\")\n\n## Phase 1: Group 1 pre-drug -- uses run_experiment (creates a new Analyzer)\n## napari-micromanager keeps routing frames during the run -- the live link\n## stays connected throughout (no cleanup/reconnect needed).\nctrl = Controller(mic, pipeline, writer=writer)\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nevents = df_to_events(df_acquire_1_1)\n# Non-blocking; wait() blocks until the phase finishes so the next phase's\n# continue_experiment doesn't hit \"a run is already in progress\".\nctrl.run_experiment(events, stim_mode=\"current\").wait()\n\n# Pause point: pipette drug for group 1, then run the next cell." }, { "cell_type": "code", @@ -698,7 +698,7 @@ "id": "1e25eca3", "metadata": {}, "outputs": [], - "source": "## Phase 2: Group 1 post-drug + Group 2 pre-drug\n## Uses continue_experiment to preserve tracking state from phase 1\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nevents = df_to_events(df_acquire_1_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait() # preserves pipeline state\n\nevents = df_to_events(df_acquire_2_1)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n# Reconnect napari (pause point: pipette drug for group 2)\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" + "source": "## Phase 2: Group 1 post-drug + Group 2 pre-drug\n## Uses continue_experiment to preserve tracking state from phase 1\nevents = df_to_events(df_acquire_1_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait() # preserves pipeline state\n\nevents = df_to_events(df_acquire_2_1)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n# Pause point: pipette drug for group 2, then run the next cell." }, { "cell_type": "code", @@ -706,7 +706,7 @@ "id": "eb3178f6", "metadata": {}, "outputs": [], - "source": "## Phase 3: Group 2 post-drug + Group 3 pre-drug\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nevents = df_to_events(df_acquire_2_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\nevents = df_to_events(df_acquire_3_1)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n# Reconnect napari (pause point: pipette drug for group 3)\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" + "source": "## Phase 3: Group 2 post-drug + Group 3 pre-drug\nevents = df_to_events(df_acquire_2_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\nevents = df_to_events(df_acquire_3_1)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n# Pause point: pipette drug for group 3, then run the next cell." }, { "cell_type": "code", @@ -726,7 +726,7 @@ "id": "0d0c67f2", "metadata": {}, "outputs": [], - "source": "## Phase 4: Group 3 post-drug (final phase)\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nevents = df_to_events(df_acquire_3_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n## Finish: flush pipeline queue, close zarr store, merge tracks\nctrl.finish_experiment()\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" + "source": "## Phase 4: Group 3 post-drug (final phase)\nevents = df_to_events(df_acquire_3_2)\nctrl.continue_experiment(events, stim_mode=\"current\").wait()\n\n## Finish: flush pipeline queue, close zarr store, merge tracks\nctrl.finish_experiment()\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet" }, { "cell_type": "code", diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb index c621b7f..067412f 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb @@ -247,15 +247,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": [ - "### Run experiment\n", - "\n", - "The optional sleep loop delays acquisition start (set `SLEEP_BEFORE_EXPERIMENT_START_in_H` above).\n", - "\n", - "`mm_wdg._core_link.cleanup()` disconnects the napari live view so it does not interfere with the MDA engine during acquisition.\n", - "\n", - "`ctrl.run_experiment(events, stim_mode=\"current\")` starts the acquisition. Images are stored and pipeline runs asynchronously." - ], + "source": "### Run experiment\n\nThe optional sleep loop delays acquisition start (set `SLEEP_BEFORE_EXPERIMENT_START_in_H` above).\n\n`ctrl.run_experiment(events, stim_mode=\"current\")` starts the acquisition (non-blocking). Images are stored and the pipeline runs asynchronously; napari-micromanager keeps showing frames in its preview layer during the run, so the live link does not need to be torn down first.", "id": "8fc9c376" }, { @@ -263,19 +255,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "# Optional: wait before starting (set SLEEP_BEFORE_EXPERIMENT_START_in_H above)\n", - "for _ in range(0, int(SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600)):\n", - " time.sleep(1)\n", - "\n", - "# Disconnect napari live view so it does not interfere with the acquisition engine\n", - "try:\n", - " mm_wdg._core_link.cleanup()\n", - "except:\n", - " pass\n", - "\n", - "ctrl.run_experiment(events, stim_mode=\"current\")" - ], + "source": "# Optional: wait before starting (set SLEEP_BEFORE_EXPERIMENT_START_in_H above)\nfor _ in range(0, int(SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600)):\n time.sleep(1)\n\n# napari-micromanager keeps routing frames into the preview layer during the\n# run; no need to tear down the live link first.\nctrl.run_experiment(events, stim_mode=\"current\")", "id": "e9146e29" }, { @@ -296,17 +276,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "ctrl.finish_experiment() # flush pipeline queue and close zarr store\n", - "\n", - "utils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet\n", - "\n", - "# Reconnect napari live view after acquisition is done\n", - "from napari_micromanager._core_link import CoreViewerLink\n", - "\n", - "if \"viewer\" in locals():\n", - " mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)" - ], + "source": "ctrl.finish_experiment() # flush pipeline queue and close zarr store\n\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet", "id": "6d784fe9" } ], diff --git a/experiments/21_cell_migration/cell_migration.ipynb b/experiments/21_cell_migration/cell_migration.ipynb index 383f0b2..4f2954b 100644 --- a/experiments/21_cell_migration/cell_migration.ipynb +++ b/experiments/21_cell_migration/cell_migration.ipynb @@ -1970,11 +1970,11 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\ntry:\n mm_wdg._core_link.cleanup()\n\nexcept:\n pass\n\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" + "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\n# napari-micromanager keeps routing frames during the run -- no need to tear\n# down the live link first.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" }, { "cell_type": "code", - "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nmic.post_experiment()\nctrl.finish_experiment() # drain the pipeline so all tracks are written\n\nutils.generate_exp_data_from_tracks(path)\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)", + "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nmic.post_experiment()\nctrl.finish_experiment() # drain the pipeline so all tracks are written\n\nutils.generate_exp_data_from_tracks(path)", "metadata": {}, "execution_count": null, "outputs": [] diff --git a/experiments/21_cell_migration/cell_migration_test.ipynb b/experiments/21_cell_migration/cell_migration_test.ipynb index 5ddccbe..fc35011 100644 --- a/experiments/21_cell_migration/cell_migration_test.ipynb +++ b/experiments/21_cell_migration/cell_migration_test.ipynb @@ -936,7 +936,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "from faro.core.controller import ControllerSimulated\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nprint(\n f\"Running in simulated mode with old data from {path_with_old_data_for_simulation}\"\n)\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = ControllerSimulated(\n mic, pipeline, path_with_old_data_for_simulation, writer=writer\n)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" + "source": "from faro.core.controller import ControllerSimulated\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\nprint(\n f\"Running in simulated mode with old data from {path_with_old_data_for_simulation}\"\n)\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = ControllerSimulated(\n mic, pipeline, path_with_old_data_for_simulation, writer=writer\n)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\n# napari-micromanager keeps routing frames during the run -- no need to tear\n# down the live link first.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" }, { "cell_type": "code", diff --git a/experiments/22_line_stimulation/line_stimulation.ipynb b/experiments/22_line_stimulation/line_stimulation.ipynb index b01b44d..b163332 100644 --- a/experiments/22_line_stimulation/line_stimulation.ipynb +++ b/experiments/22_line_stimulation/line_stimulation.ipynb @@ -2439,11 +2439,11 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\ntry:\n mm_wdg._core_link.cleanup()\nexcept:\n pass\n\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" + "source": "from faro.core.controller import Controller\nfrom faro.core.writers import OmeZarrWriter\nfrom faro.core.conversion import df_to_events\nfrom faro.widgets import ExperimentStatusWidget\n\nfor _ in range(0, SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600):\n time.sleep(1)\n\nevents = df_to_events(df_acquire)\nwriter = OmeZarrWriter(storage_path=path)\nctrl = Controller(mic, pipeline, writer=writer)\n\n# Live status + pause/stop buttons for the (non-blocking) run.\n# napari-micromanager keeps routing frames during the run -- no need to tear\n# down the live link first.\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)\nhandle = ctrl.run_experiment(events, stim_mode=\"current\")" }, { "cell_type": "code", - "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nprint(\"Experiment finished\")\nmic.post_experiment()\nctrl.finish_experiment() # drain the pipeline so all tracks are written\n\nfovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\nfovs_i_list.sort()\ndfs = []\n\nfor fov_i in fovs_i_list:\n\n track_file = os.path.join(path, \"tracks\", fov_i)\n df = pd.read_parquet(track_file)\n dfs.append(df)\n\npd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))\n\nfrom napari_micromanager._core_link import CoreViewerLink\n\nif \"viewer\" in locals():\n mm_wdg._core_link = CoreViewerLink(viewer, mic.mmc)", + "source": "# Block until the run finishes, then tear down and collect results.\nhandle.wait()\nprint(\"Experiment finished\")\nmic.post_experiment()\nctrl.finish_experiment() # drain the pipeline so all tracks are written\n\nfovs_i_list = os.listdir(os.path.join(path, \"tracks\"))\nfovs_i_list.sort()\ndfs = []\n\nfor fov_i in fovs_i_list:\n\n track_file = os.path.join(path, \"tracks\", fov_i)\n df = pd.read_parquet(track_file)\n dfs.append(df)\n\npd.concat(dfs).to_parquet(os.path.join(path, \"exp_data.parquet\"))", "metadata": {}, "execution_count": null, "outputs": [] From 6dae4e67c8b05ea3bc3ff757b3c71ca4b30bdda1 Mon Sep 17 00:00:00 2001 From: hinderling Date: Thu, 28 May 2026 11:10:59 +0200 Subject: [PATCH 28/41] Bump virtual-microscope lock pin; relock motile extra uv lock --upgrade-package virtual-microscope: e2aca8da -> bd4ac3e3 to pick up the JIT pre-warm + SimCameraDevice digital-ROI/MDA-teardown fixes on its default branch. Also syncs the motile extra into the lock (added to pyproject earlier but not yet relocked). --- uv.lock | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/uv.lock b/uv.lock index 62bd20e..c7db5b0 100644 --- a/uv.lock +++ b/uv.lock @@ -925,6 +925,9 @@ convpaint = [ { name = "napari-convpaint" }, { name = "scipy" }, ] +motile = [ + { name = "motile" }, +] remote-segmentation = [ { name = "imaging-server-kit" }, ] @@ -934,6 +937,7 @@ stardist = [ { name = "tensorflow" }, ] test = [ + { name = "motile" }, { name = "pytest" }, { name = "pytest-xdist" }, ] @@ -947,11 +951,13 @@ virtual-microscope = [ requires-dist = [ { name = "cellpose", marker = "extra == 'cellpose'", specifier = ">=4.1.1" }, { name = "csbdeep", marker = "extra == 'stardist'" }, + { name = "faro", extras = ["motile"], marker = "extra == 'test'" }, { name = "imageio", extras = ["ffmpeg"], marker = "extra == 'virtual-microscope'" }, { name = "imaging-server-kit", marker = "extra == 'remote-segmentation'" }, { name = "ipykernel" }, { name = "ipywidgets" }, { name = "matplotlib" }, + { name = "motile", marker = "extra == 'motile'" }, { name = "napari", extras = ["pyqt6"] }, { name = "napari-convpaint", marker = "extra == 'convpaint'" }, { name = "napari-micromanager" }, @@ -978,7 +984,7 @@ requires-dist = [ { name = "virtual-microscope", marker = "extra == 'virtual-microscope'", git = "https://github.com/hinderling/virtual-microscope.git" }, { name = "zarr", specifier = ">=3" }, ] -provides-extras = ["test", "stardist", "cellpose", "convpaint", "remote-segmentation", "virtual-microscope", "api74", "api75"] +provides-extras = ["test", "motile", "stardist", "cellpose", "convpaint", "remote-segmentation", "virtual-microscope", "api74", "api75"] [[package]] name = "fastapi" @@ -1548,6 +1554,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "ilpy" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyscipopt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/cd/03e6701419beb1c50e3e425d2378dd3f00dd8423e49dcdf6c893bc16b77e/ilpy-0.5.3.tar.gz", hash = "sha256:ec762291186ea92b2b6c5650fe9ad3f61492d832d67d591162a68575e23cbc80", size = 28778, upload-time = "2026-04-17T15:27:25.092Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/1d/45feda02c92a2d1fa6bd456fa01dcf954b2f495158bdc19dc3e9ce971fbb/ilpy-0.5.3-py3-none-any.whl", hash = "sha256:c96afcbf0d850ad7f00176f91463b827bdfaf93490a8e9658a1d834a334dd5b2", size = 26026, upload-time = "2026-04-17T15:27:23.816Z" }, +] + [[package]] name = "imagecodecs" version = "2025.11.11" @@ -2297,6 +2315,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" }, ] +[[package]] +name = "motile" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ilpy" }, + { name = "networkx" }, + { name = "numpy" }, + { name = "structsvm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/17/4817d148419f77df3e0fdcb41916d67cc77dc6d5b05ed0cb4865a3f69918/motile-0.4.0.tar.gz", hash = "sha256:10dd7dd3bcf586ef389148ca91aadeb163f05f50020b8acafcd4050b574f385d", size = 44576, upload-time = "2026-03-27T18:49:38.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/04/6b9c88ea5f874564838688485f79a8540458d81d0125d6cf5c5d8139ca24/motile-0.4.0-py3-none-any.whl", hash = "sha256:d4f7d6570f67bac2e7963904e78f938d9dad3222f6e2363a33621e77cdecaa86", size = 32941, upload-time = "2026-03-27T18:49:37.496Z" }, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -3909,6 +3942,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/42/efb7ced69f7d1d31eb8f19b2d778aeb182be7e070569d02b9057ac478e3e/pyqt6_sip-13.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:42b62530a9b6a9c6e29c2941b8ab78258652da0aeae4eb1fc9a0631d19a7a7b2", size = 49597, upload-time = "2026-03-09T13:01:34.49Z" }, ] +[[package]] +name = "pyscipopt" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/b9/ef40e260c3e722d9d4617dd70548c1e3c51b065decd341cf9b9031957bf1/pyscipopt-6.2.1.tar.gz", hash = "sha256:3da1634ff341c8665fcf100486f7968ddbbf160dc56d83e99338717d2245ef6a", size = 1683168, upload-time = "2026-05-16T15:06:54.486Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/dc/347f5b1a30972fb86759d6975acab3cb0fcbd1df6ea0f26873a8a46ddc32/pyscipopt-6.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbfbc9fce11f8ee0383e64fcfef4bb1e7b5ae6a1063dd964008cf20f906fdd4d", size = 8411318, upload-time = "2026-05-16T15:05:01.205Z" }, + { url = "https://files.pythonhosted.org/packages/79/70/319ef747c8b639165e78a5013c65eae9fa265f364abd6d85cb932719aa30/pyscipopt-6.2.1-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:df9581445061a58c8681e884b4f00801f4ee1dc150d9e5d30faa3cd05f6f5c7c", size = 12259796, upload-time = "2026-05-16T15:05:03.887Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d9/cdecac650bbab6c8d5fcf9577b77bda53d8bbe972b6285e12530b4782d77/pyscipopt-6.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7839074b1dff5cd6e243ea86c0f43cd9f7e9e19257b1fdc5549cf9c9cff0fa6d", size = 16214522, upload-time = "2026-05-16T15:05:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5b/3ce96f3ed012d3f17556748b27d49ba42b3662283ecb02ae45c70ed19de1/pyscipopt-6.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faf86357d83772508c5b1925570ee7e08385fa1a736e0ee33bf0e5c421dd845f", size = 17566755, upload-time = "2026-05-16T15:05:11.138Z" }, + { url = "https://files.pythonhosted.org/packages/8a/50/9fceeb125674c0e955b2175cbba24f2b32ba5f8b635c3484648eb802d090/pyscipopt-6.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:784d8fbee8134c7c1cf590a60972008159033938a5044d9ed28cde9abf4f86be", size = 48216285, upload-time = "2026-05-16T15:05:17.131Z" }, + { url = "https://files.pythonhosted.org/packages/76/ac/251c595d0eddd4e5480717298118bd252c3d8e5c169d2d95efc862e259ab/pyscipopt-6.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:353725a8f65c86299502aadca12c3a1c7622a64746ea12251906ae0165c843ae", size = 8405270, upload-time = "2026-05-16T15:05:20.386Z" }, + { url = "https://files.pythonhosted.org/packages/95/34/28ae4af84b6d372404da9d5ff636f1341826f80b793d98f86cac03bd0aa4/pyscipopt-6.2.1-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:fdba06632975280df18e684aa4b9867b1167cb3a6aa9a201e7ee3b108985af51", size = 12257430, upload-time = "2026-05-16T15:05:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/fa/6f/5173067773b5e650566f90c3356bd3a71c02349e6644222a8971c0196687/pyscipopt-6.2.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8860afb9d43c93b653c7295ef674ad1ba156c731591fb7e33b5991c1136b0dc6", size = 16207762, upload-time = "2026-05-16T15:05:26.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/76/59288c92b7ee914c351c6003b63249e0702d990d1fdd9ff97e65ffc1c57b/pyscipopt-6.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:725503ea90fd0962f1111a5b46ee3e271b7375e1b0dfda708ce223dbebaeff5e", size = 17560629, upload-time = "2026-05-16T15:05:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/0d/42/2b1d48d11311c2fda215c8da83f8f8cabec3b5a82936a7229919ad9141e5/pyscipopt-6.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a5621ddcb59f7816e87c3deeb1bf52f8693e016a1a29148201b712adb84956e3", size = 48215647, upload-time = "2026-05-16T15:05:36.5Z" }, + { url = "https://files.pythonhosted.org/packages/fc/af/4ddfd3802ff69ad58f44e6d96878265c4ea55cac2313aae1994611b0d6aa/pyscipopt-6.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:299dbff65d60853d545788457d02720e3693df19ade4aaba1bbe8b575239ff4b", size = 8418209, upload-time = "2026-05-16T15:05:40.166Z" }, + { url = "https://files.pythonhosted.org/packages/9a/53/61ab5575a6e29547b6d13c37d7005b3f348897b48dd6cbcd4261196f2e25/pyscipopt-6.2.1-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:8bd6fb0f2a6c8bf4ef49e524d4c022b7b539098193ce312e94d29c6254658ff6", size = 12267358, upload-time = "2026-05-16T15:05:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fd/851dedcabdb6d6168eae0e2c31008d02f20cf018b05d091a5bda6f5da6a1/pyscipopt-6.2.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bb7858ceececa09658c3bfa58476862255353fea52d4f512ff97160f138f92cd", size = 16171696, upload-time = "2026-05-16T15:05:46.851Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/ac663246709e6ab94b225c40e9dac863a91b1b86e12c2ea65c951f944bea/pyscipopt-6.2.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cdf0e460b834f06ea68a13e59f581f7ecbec5a466a76bbf1267ac5b4573bb52", size = 17430536, upload-time = "2026-05-16T15:05:50.267Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ca/05d80040767d1d2443ea6cd6cc42985cdd532feeaecfcc19f103c8b45a1b/pyscipopt-6.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:6aed03b621decb09b38f399773bf8cf2c707e965990b778a24d28c8cc90a0756", size = 49375214, upload-time = "2026-05-16T15:05:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/05/d4/caa18165a2faf876d8d482b0b92bd75abf284af2b51b64ebee62c1b47065/pyscipopt-6.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:18c6283b8fc47929787a043204e7bb26f41e414d1e8597d394057bcc60fc3ee0", size = 8528074, upload-time = "2026-05-16T15:05:59.776Z" }, + { url = "https://files.pythonhosted.org/packages/0f/3c/f6593bf4459e788f2302878369c5eb65ec5b04fd6e18524296fe05365c6b/pyscipopt-6.2.1-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:622e9640cdfc22632079ed4f73d88653d6170ee89ca6bdb6459d0db334fa7a87", size = 12341833, upload-time = "2026-05-16T15:06:02.402Z" }, + { url = "https://files.pythonhosted.org/packages/49/db/bc80dfa03ef13701b818aca26428fe05c389a71a8ec5654e1e4ea60ef3a4/pyscipopt-6.2.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a85fb7f73fd06fad9a0358b2eb47d94dbe94ee52baf85e045beb5e216022b6c7", size = 16735540, upload-time = "2026-05-16T15:06:05.522Z" }, + { url = "https://files.pythonhosted.org/packages/58/ee/97cd080513efdedea2fcd1f9b71d02e775f5e445ab39f8f012c14e4b4c5e/pyscipopt-6.2.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d323fa83b855308587794cd0ab04ae6dd341c227202d0ddaec2761917034e972", size = 17628571, upload-time = "2026-05-16T15:06:09.608Z" }, + { url = "https://files.pythonhosted.org/packages/7b/75/e280c162ae55a5db7f9031c4b92774964a8eebef94d2d463a389c9f6294b/pyscipopt-6.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:138e312cd692b7c853e7eab572986bc6d2dd28acd6e16347d73c96c225613056", size = 49551043, upload-time = "2026-05-16T15:06:15.368Z" }, +] + [[package]] name = "pytest" version = "9.0.3" @@ -4684,6 +4748,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] +[[package]] +name = "structsvm" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ilpy" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/6f/7116252e13ce88a3fe4d04a3197afa12eeb60ca54daaaa6ccf442f53e361/structsvm-0.2.0.tar.gz", hash = "sha256:f457dc12718f886b604c461e3970bd186056e14f2eb403b1a27dfac165b6f581", size = 7380, upload-time = "2025-04-16T12:59:56.337Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/2c/6a37fa71d82ea628c3c1ce4a034df78e9b81a2b724de3af1f36a8b93ab18/structsvm-0.2.0-py3-none-any.whl", hash = "sha256:fa42375c863e08f5d431cd16f1515b2436d89ad56a85101a75b95825b51ed262", size = 6945, upload-time = "2025-04-16T12:59:54.693Z" }, +] + [[package]] name = "superqt" version = "0.7.6" @@ -5157,7 +5234,7 @@ wheels = [ [[package]] name = "virtual-microscope" version = "0.1.0" -source = { git = "https://github.com/hinderling/virtual-microscope.git#e2aca8da2a71c9a0298a485ba69f115366860392" } +source = { git = "https://github.com/hinderling/virtual-microscope.git#bd4ac3e3c24c78605a1921093fe647daaf181dc6" } dependencies = [ { name = "numba" }, { name = "numpy" }, From 97c30e4a58e14f373c6fb9a74ffa21ab478f6648 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Thu, 28 May 2026 11:28:19 +0200 Subject: [PATCH 29/41] feat: run DMD calibration in the background calibrate_dmd gains background=True (default): it runs dmd.calibrate on a DMDCalibration worker thread while pumping Qt in the caller, so napari keeps updating the live preview during calibration instead of freezing. Adds a module-level _pump_qt_events helper; background=False runs the calibration synchronously as before. Also fix dmd.py's frameReady.disconnect() calls: the calibration's frame-collection blocks now disconnect their own named handlers (frameReady.disconnect(handler)) instead of the no-arg disconnect, which also tore down napari-micromanager's preview listener and left the preview dead after calibration. --- faro/core/dmd.py | 18 ++--- faro/microscope/pertzlab/moench.py | 107 +++++++++++++++++++++-------- 2 files changed, 88 insertions(+), 37 deletions(-) diff --git a/faro/core/dmd.py b/faro/core/dmd.py index 6a11010..568d08f 100644 --- a/faro/core/dmd.py +++ b/faro/core/dmd.py @@ -229,15 +229,20 @@ def calibrate( ) events.append(event_p) - self.mmc.mda.events.frameReady.disconnect() - + # Connect a calibration-only frame collector. Do NOT call the + # no-arg frameReady.disconnect() -- that removes EVERY listener, + # including napari-micromanager's preview updater and the faro + # controller's pipeline callback, and they never come back. + # Connect our own handler alongside the others and disconnect + # only it when the collection loop is done. @self.mmc.mda.events.frameReady.connect - def new_frame(img: np.ndarray, event: MDAEvent): + def _collect_calibration_frame(img: np.ndarray, event: MDAEvent): calibration_images.append(img) for event in events: self.mmc.mda.run([event]) time.sleep(0.1) + self.mmc.mda.events.frameReady.disconnect(_collect_calibration_frame) calibration_images = np.array(calibration_images) for img in calibration_images: @@ -281,7 +286,6 @@ def new_frame(img: np.ndarray, event: MDAEvent): ) if np.sum(inliers) < 4: - self.mmc.mda.events.frameReady.disconnect() self.mmc.mda.run( [ MDAEvent( @@ -350,15 +354,14 @@ def new_frame(img: np.ndarray, event: MDAEvent): ) events.append(event_p) - self.mmc.mda.events.frameReady.disconnect() - @self.mmc.mda.events.frameReady.connect - def new_frame(img: np.ndarray, event: MDAEvent): + def _collect_test_frame(img: np.ndarray, event: MDAEvent): test_image.append(img) for event in events: self.mmc.mda.run([event]) time.sleep(0.5) + self.mmc.mda.events.frameReady.disconnect(_collect_test_frame) calibration_images = np.array(calibration_images) for img in test_image: img = skimage.filters.gaussian(img, sigma=1) @@ -399,7 +402,6 @@ def new_frame(img: np.ndarray, event: MDAEvent): axs[3].set_ylim(camera_height, 0) plt.show() - self.mmc.mda.events.frameReady.disconnect() self.mmc.mda.run( [ MDAEvent( diff --git a/faro/microscope/pertzlab/moench.py b/faro/microscope/pertzlab/moench.py index 88e261e..2c48cba 100644 --- a/faro/microscope/pertzlab/moench.py +++ b/faro/microscope/pertzlab/moench.py @@ -35,6 +35,21 @@ def _set_c_numeric_locale(): continue +def _pump_qt_events() -> None: + """Process pending Qt events if a Qt app is running; no-op otherwise. + + Used by ``calibrate_dmd(background=True)`` to keep napari responsive + while the calibration MDAs run on a worker thread. + """ + try: + from qtpy.QtCore import QCoreApplication + except Exception: + return + app = QCoreApplication.instance() + if app is not None: + app.processEvents() + + class KeepDMDAlive: def __init__(self, mmc): self.mmc = mmc @@ -97,14 +112,12 @@ class Moench(PyMMCoreMicroscope): "power": 10, } BINNING = "2x2" - # ROI: full width (no x crop), symmetric top-bottom crop to ROI_HEIGHT. - # ROI_X / ROI_Y / ROI_WIDTH are recomputed in set_roi() against the - # live camera dimensions so the values stay correct under any binning. - # Defaults below match Prime BSI (2048 unbinned -> 1024 with 2x2). + # ROI applied as-is after binning — no centering recomputation. + # Defaults below are for Prime BSI under 2x2 binning (1024 binned px wide). ROI_X = 0 - ROI_Y = 112 + ROI_Y = 30 ROI_WIDTH = 1024 - ROI_HEIGHT = 800 + ROI_HEIGHT = 792 SET_ROI_REQUIRED = True # Devices whose Busy() flag is unreliable — waitForDevice on these @@ -161,36 +174,72 @@ def calibrate_dmd( exposure=25, marker_style="x", calibration_points_DMD=None, + background=True, ): + """Calibrate the DMD against the camera (if not already calibrated). + + Args: + background: When True (default), the calibration MDAs run on a + worker thread while this call pumps the Qt event loop, so + napari stays responsive and previews the calibration spots + live. The call still blocks until calibration finishes -- + it just doesn't freeze the GUI. Set False to run + synchronously on the calling thread. + + Note: + With ``background=True`` and ``verbose=True`` the matplotlib + diagnostic plots are created from the worker thread. With the + Jupyter inline backend this routes to the running cell fine; + if plots misbehave, use ``background=False`` for verbose runs. + """ self.disable_log_output() - "Calibrate the DMD if it is not already calibrated." "" - if self.dmd is not None and self.dmd.affine is None: + if self.dmd is None or self.dmd.affine is not None: + return + + def _do_calibration() -> None: self.wakeup_dmd.stop() - self.dmd.calibrate( - verbose=verbose, - n_points=n_points, - radius=radius, - exposure=exposure, - marker_style=marker_style, - calibration_points_DMD=calibration_points_DMD, - ) - self.wakeup_dmd.run() + try: + self.dmd.calibrate( + verbose=verbose, + n_points=n_points, + radius=radius, + exposure=exposure, + marker_style=marker_style, + calibration_points_DMD=calibration_points_DMD, + ) + finally: + self.wakeup_dmd.run() - def set_roi(self): - """Apply full-width, symmetric top-bottom ROI of height ROI_HEIGHT. + if not background: + _do_calibration() + return - Recomputes ROI_X / ROI_Y / ROI_WIDTH from the camera's live - full-frame dimensions under the active binning, then writes - them back to the instance so downstream callers reading - ``mic.ROI_*`` see the actual values in effect. - """ + # Run on a worker thread; pump Qt here so napari keeps repainting + # and previewing the calibration frames. The call still blocks + # until calibration is done -- it just doesn't starve the GUI. + done = threading.Event() + box: list[BaseException] = [] + + def _worker() -> None: + try: + _do_calibration() + except BaseException as exc: # noqa: BLE001 + box.append(exc) + finally: + done.set() + + threading.Thread( + target=_worker, name="DMDCalibration", daemon=True + ).start() + while not done.wait(timeout=0.05): + _pump_qt_events() + if box: + raise box[0] + + def set_roi(self): + """Apply the class's ROI_* settings as-is (after binning is set).""" self.mmc.clearROI() - full_w = self.mmc.getImageWidth() - full_h = self.mmc.getImageHeight() - self.ROI_X = 0 - self.ROI_Y = max(0, (full_h - self.ROI_HEIGHT) // 2) - self.ROI_WIDTH = full_w self.mmc.setROI(self.ROI_X, self.ROI_Y, self.ROI_WIDTH, self.ROI_HEIGHT) def post_experiment(self): From fc07d3a3bc2d25cf1913190f325b54cc68298933 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Thu, 28 May 2026 11:50:32 +0200 Subject: [PATCH 30/41] test: remove the stale top-level hardware test duplicates tests/hardware/test_cell_migration.py and test_line_stimulation.py were left behind when the hardware suite moved into tests/hardware/pertzlab/. They're superseded by the pertzlab copies (line_stimulation is identical bar the import; cell_migration's pertzlab version adds the shared cellpose fixture + a timestep-ordering check). Running both trees in one pytest session spun up a second session-scoped Moench, which then failed to open the DMD ("Mosaic3: No Mosaic3 devices found") because the first microscope already held it. Drop the duplicates and their now-orphaned tests/hardware/conftest.py. --- tests/hardware/conftest.py | 303 ------------------------ tests/hardware/test_cell_migration.py | 134 ----------- tests/hardware/test_line_stimulation.py | 123 ---------- 3 files changed, 560 deletions(-) delete mode 100644 tests/hardware/conftest.py delete mode 100644 tests/hardware/test_cell_migration.py delete mode 100644 tests/hardware/test_line_stimulation.py diff --git a/tests/hardware/conftest.py b/tests/hardware/conftest.py deleted file mode 100644 index 9b4e3fd..0000000 --- a/tests/hardware/conftest.py +++ /dev/null @@ -1,303 +0,0 @@ -"""Fixtures for the hardware-in-the-loop test suite. - -Each fixture is session-scoped so the microscope is initialized only -once per pytest run, regardless of how many hardware tests fire. The -``--scope`` CLI option (defined in ``tests/conftest.py``) selects which -Pertzlab microscope class to instantiate. - -Safety: -- ``safe_positions`` reads the stage XY at session start and returns - positions as RELATIVE offsets so we never move into uncalibrated - territory or hit stage limits. -- The Z stage is read for record-keeping only — never written. -- The stage is restored to its original XY at session end. -""" - -from __future__ import annotations - -import os -from pathlib import Path - -import numpy as np -import pytest -import zarr - -from tests.conftest import resolve_scope - - -# --------------------------------------------------------------------------- -# Per-scope channel mapping -# --------------------------------------------------------------------------- -# Channel groups and channel names per scope are inferred from each -# microscope's Micro-Manager cfg in ``pertzlab_mic_configs/``. Update -# these tables when adding a new scope or when a cfg's groups change. -# -# Each scope provides: -# channel_group — Micro-Manager ConfigGroup that hosts the channels -# imaging_channel — primary imaging channel (passed to segmentation) -# optocheck_channel — second channel acquired on the last frame as a ref -# stim_channel — stimulation channel (driven by the DMD path) -# Exposures are intentionally short so the smoke test stays under a minute. -SCOPE_CHANNELS = { - "moench": { - "channel_group": "TTL_ERK", - "imaging_channel": "mScarlet3", - "imaging_exposure": 100.0, - "optocheck_channel": "mCitrine", - "optocheck_exposure": 100.0, - "stim_channel": "CyanStim", - "stim_exposure": 100.0, - "stim_power": 5, - }, - "niesen": { - "channel_group": "WF_DMD", - "imaging_channel": "Red", - "imaging_exposure": 100.0, - "optocheck_channel": "Green", - "optocheck_exposure": 100.0, - "stim_channel": "CyanStim", - "stim_exposure": 100.0, - "stim_power": 5, - }, - "jungfrau": { - "channel_group": "TTL_ERK", - "imaging_channel": "mScarlet3", - "imaging_exposure": 100.0, - "optocheck_channel": "mCitrine", - "optocheck_exposure": 100.0, - "stim_channel": "CyanStim", - "stim_exposure": 100.0, - "stim_power": 5, - }, -} - - -# Per-scope DMD calibration profile used to instantiate ``DMD`` where -# the microscope class doesn't already do so in its constructor (only -# Jungfrau today — its DMD isn't yet wired into the cfg, so this will -# raise loudly when the SLM device is missing, surfacing the gap). -SCOPE_DMD_PROFILES: dict[str, dict] = { - "jungfrau": { - "channel_group": "TTL_ERK", - "channel_config": "CyanStim", - "device_name": "LED", - "property_name": "Cyan_Level", - "power": 5, - }, -} - - -# --------------------------------------------------------------------------- -# Scope selection -# --------------------------------------------------------------------------- - - -@pytest.fixture(scope="session") -def scope_name(request: pytest.FixtureRequest) -> str: - """Return the active scope name, skipping if none is selected.""" - name = resolve_scope(request.config) - if name is None: - pytest.skip("hardware test — pass --scope or set FARO_SCOPE") - return name - - -@pytest.fixture(scope="session") -def scope_config(scope_name: str) -> dict: - """Return the per-scope channel mapping.""" - return SCOPE_CHANNELS[scope_name] - - -# --------------------------------------------------------------------------- -# Synthetic DMD affine -# --------------------------------------------------------------------------- - - -@pytest.fixture(scope="session") -def synthetic_affine() -> np.ndarray: - """Identity 3x3 affine in skimage convention. - - Hardware tests don't validate optical alignment — they only need - the DMD code path to run end-to-end without raising. The identity - matrix lets ``skimage.transform.warp`` resample the camera image - into DMD space by direct coordinate copy (with cropping/padding - when DMD and camera differ in size). No interactive calibration - or precomputed ``.npy`` is needed on disk. - """ - return np.eye(3) - - -# --------------------------------------------------------------------------- -# Microscope factory -# --------------------------------------------------------------------------- - - -@pytest.fixture(scope="session") -def microscope(scope_name: str, synthetic_affine: np.ndarray): - """Instantiate the selected Pertzlab microscope once per session. - - Loads the Micro-Manager cfg, applies the per-scope ROI when the - class requests it, and injects a synthetic DMD affine matrix so - stim code paths run without an interactive calibration step. - - For Moench and Niesen the constructor accepts an - ``affine_calibration_matrix`` and creates the DMD inside - ``init_scope()``. Jungfrau today has no DMD setup in its - constructor — we instantiate one manually after init. If the - Jungfrau cfg lacks an SLM device this will raise with a clear - error pointing at the gap, which is the desired signal. - """ - if scope_name == "moench": - from faro.microscope.pertzlab.moench import Moench - - mic = Moench(affine_calibration_matrix=synthetic_affine) - elif scope_name == "niesen": - from faro.microscope.pertzlab.niesen import Niesen - - mic = Niesen( - affine_calibration_matrix=synthetic_affine, fast_init=True - ) - elif scope_name == "jungfrau": - from faro.core.dmd import DMD - from faro.microscope.pertzlab.jungfrau import Jungfrau - - mic = Jungfrau() - mic.dmd = DMD( - mic.mmc, - SCOPE_DMD_PROFILES["jungfrau"], - affine_matrix=synthetic_affine, - ) - mic.dmd_needs_to_be_waken = False - else: - raise ValueError(f"unknown scope: {scope_name!r}") - - # Force a known camera binning BEFORE applying the ROI — most MM - # configs reset the ROI on a binning change, so the order matters. - # 2x2 keeps the test frame small and fast. - try: - mic.mmc.setConfig("Binning", "2x2") - except Exception as e: - print(f"[hardware fixture] could not set Binning=2x2 on {scope_name}: {e}") - - # Apply per-scope ROI when production code does so. - if getattr(mic, "SET_ROI_REQUIRED", False) and hasattr(mic, "set_roi"): - mic.set_roi() - - yield mic - - # Release all hardware handles so the Python process can exit. - # Cleanup lives on the microscope class itself, not in the fixture, - # so notebooks / scripts get the same behavior. - try: - mic.shutdown() - except Exception: - pass - - -# --------------------------------------------------------------------------- -# Safe (relative) stage positions -# --------------------------------------------------------------------------- - - -def _parse_offset_env(raw: str) -> list[tuple[float, float]]: - """Parse ``"dx0,dy0;dx1,dy1;..."`` into a list of (dx, dy) tuples.""" - offsets: list[tuple[float, float]] = [] - for pair in raw.split(";"): - pair = pair.strip() - if not pair: - continue - dx_str, dy_str = pair.split(",") - offsets.append((float(dx_str), float(dy_str))) - return offsets - - -@pytest.fixture(scope="session") -def safe_positions(microscope) -> list[dict]: - """Generate FOV positions as RELATIVE offsets from the stage's - current XY so the test never moves into uncalibrated territory or - crashes the objective. - - Workflow: - 1. The user manually parks the stage on a sample area before - starting the test run. - 2. This fixture snapshots ``(start_x, start_y)`` from the - current XY stage position and ``start_z`` from the focus - drive (read-only — Z is never moved). - 3. Three nearby FOVs are returned as ``(start + offset)`` - tuples. Default offsets are ``(0,0)``, ``(40,0)``, ``(0,40)`` - microns — well inside a single 40x camera FOV (~130 µm) - so cells overlap between FOVs but no large stage motion - ever happens. - 4. At session end the stage is restored to ``(start_x, start_y)`` - so an interrupted manual session can resume seamlessly. - - Override the offsets via ``FARO_HW_TEST_OFFSETS_UM`` formatted as - ``"dx0,dy0;dx1,dy1;..."`` (in microns). Set to a single ``"0,0"`` - pair to restrict the test to a single position. - """ - mmc = microscope.mmc - start_x, start_y = mmc.getXYPosition() - try: - start_z = mmc.getPosition() - except Exception: - start_z = 0.0 - - raw_offsets = os.environ.get("FARO_HW_TEST_OFFSETS_UM") - if raw_offsets: - offsets = _parse_offset_env(raw_offsets) - else: - offsets = [(0.0, 0.0), (40.0, 0.0), (0.0, 40.0)] - - positions = [ - { - "x": start_x + dx, - "y": start_y + dy, - "z": start_z, - "name": f"fov_{i}", - } - for i, (dx, dy) in enumerate(offsets) - ] - - yield positions - - # Best-effort: return the stage to where the user left it. Swallow - # exceptions so a teardown failure can't mask a test failure above. - try: - mmc.setXYPosition(start_x, start_y) - except Exception: - pass - - -# --------------------------------------------------------------------------- -# Shared post-run assertions -# --------------------------------------------------------------------------- - - -def assert_clean_run(controller, tmp_path: Path, *, expect_tracks: bool) -> None: - """Post-run smoke checks shared by every hardware test. - - Fails loudly on any background-thread error (experiments log-and- - continue, but a hardware test is meaningless if it swallows them) - and confirms the run produced a napari-loadable OME-Zarr store. - """ - assert not controller.background_errors, ( - "Background errors during acquisition:\n" - + "\n".join( - f" [{e.source}] {e.exc_type}: {e.message}" - for e in controller.background_errors - ) - ) - - zarr_path = tmp_path / "acquisition.ome.zarr" - assert zarr_path.is_dir(), f"OME-Zarr store not created at {zarr_path}" - - grp = zarr.open_group(str(zarr_path), mode="r") - assert "ome" in grp.attrs, ( - "OME metadata missing on root group — store will not load in napari" - ) - - assert (tmp_path / "events.json").is_file(), "events.json not written" - - if expect_tracks: - tracks_dir = tmp_path / "tracks" - assert tracks_dir.is_dir(), "tracks/ folder not created" - assert list(tracks_dir.glob("*.parquet")), "no track parquet files written" diff --git a/tests/hardware/test_cell_migration.py b/tests/hardware/test_cell_migration.py deleted file mode 100644 index f859562..0000000 --- a/tests/hardware/test_cell_migration.py +++ /dev/null @@ -1,134 +0,0 @@ -"""Hardware smoke test: end-to-end cell migration acquisition. - -Converted from ``experiments/21_cell_migration/cell_migration.ipynb``. -Runs a short multi-FOV / multi-timestep acquisition with cellpose -segmentation, trackpy tracking, ``StimPercentageOfCell`` DMD -stimulation, and an optocheck reference channel on the last frame -(exercises the ref-channel write path). - -Why this test exists: - The notebook is the canonical end-to-end demo for the segmentation - + tracking + per-cell stimulation pipeline. Converting it to a - pytest target lets us validate the whole stack against real - hardware in CI-style runs without needing napari or interactive - FOV selection. - -Differences from the notebook (intentional, for test ergonomics): - * No napari / napari-micromanager. FOV positions come from the - ``safe_positions`` fixture as relative offsets from the stage's - current XY (max 40 µm), so the stage never makes a large move. - * Output goes to pytest's ``tmp_path`` (auto-cleaned) instead of - a hardcoded drive letter. - * Segmentation is local (cellpose) instead of remote - (imaging-server-kit) so the test doesn't depend on a separate - server being reachable. - * The DMD affine matrix is the synthetic identity from - ``synthetic_affine`` — no interactive calibration step. - * Frame count and interval are scaled down (4 frames × 5 s) so - the whole test takes about a minute. - -The test is gated by ``--scope`` / ``FARO_SCOPE`` and skipped by -default. See ``tests/conftest.py`` for the gating logic. -""" - -from __future__ import annotations - -import pytest -from useq import Position, TIntervalLoops - -from faro.core.controller import Controller -from faro.core.data_structures import ( - Channel as RTMChannel, - PowerChannel, - RTMSequence, - SegmentationMethod, -) -from faro.core.pipeline import ImageProcessingPipeline -from faro.core.writers import OmeZarrWriter -from faro.feature_extraction.optocheck import OptoCheckFE -from faro.feature_extraction.simple import SimpleFE -from faro.stimulation.percentage_of_cell import StimPercentageOfCell -from faro.tracking.trackpy import TrackerTrackpy -from tests.hardware.conftest import assert_clean_run - - -N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 -STIM_CELL_PERCENTAGE = 0.3 - - -@pytest.mark.hardware -def test_cell_migration_smoke( - microscope, scope_config, safe_positions, tmp_path -) -> None: - """End-to-end smoke test: segmentation + tracking + stim + ref.""" - - cfg = scope_config - - imaging = RTMChannel( - config=cfg["imaging_channel"], - exposure=cfg["imaging_exposure"], - group=cfg["channel_group"], - ) - optocheck = RTMChannel( - config=cfg["optocheck_channel"], - exposure=cfg["optocheck_exposure"], - group=cfg["channel_group"], - ) - stim = PowerChannel( - config=cfg["stim_channel"], - exposure=cfg["stim_exposure"], - group=cfg["channel_group"], - power=cfg["stim_power"], - ) - - sequence = RTMSequence( - stage_positions=[ - Position(x=p["x"], y=p["y"], z=p["z"], name=p["name"]) - for p in safe_positions - ], - time_plan=TIntervalLoops( - interval=TIME_BETWEEN_TIMESTEPS_S, - loops=N_FRAMES, - ), - channels=[imaging], - stim_channels=(stim,), - # Stim every frame except the first (no prior segmentation yet). - stim_frames=frozenset(range(1, N_FRAMES)), - ref_channels=(optocheck,), - # ``-1`` resolves to the last timepoint via _resolve_frame_set. - ref_frames=frozenset({-1}), - # StimPercentageOfCell reads this from per-event metadata. - rtm_metadata={"stim_cell_percentage": STIM_CELL_PERCENTAGE}, - ) - - from faro.segmentation.cellpose_v4 import CellposeV4 - - pipeline = ImageProcessingPipeline( - storage_path=str(tmp_path), - segmentators=[ - SegmentationMethod( - name="labels", - segmentation_class=CellposeV4(min_size=50, gpu=True), - use_channel=0, - save_tracked=True, - ), - ], - feature_extractor=SimpleFE("labels"), - feature_extractor_ref=OptoCheckFE(used_mask="labels"), - stimulator=StimPercentageOfCell(), - tracker=TrackerTrackpy(), - ) - - writer = OmeZarrWriter( - storage_path=str(tmp_path), - store_stim_images=True, - ) - - controller = Controller(microscope, pipeline, writer=writer) - try: - controller.run_experiment(list(sequence), stim_mode="current") - finally: - controller.finish_experiment() - - assert_clean_run(controller, tmp_path, expect_tracks=True) diff --git a/tests/hardware/test_line_stimulation.py b/tests/hardware/test_line_stimulation.py deleted file mode 100644 index 876fabf..0000000 --- a/tests/hardware/test_line_stimulation.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Hardware smoke test: end-to-end line-stimulation acquisition. - -Converted from ``experiments/22_line_stimulation/line_stimulation.ipynb``. -Runs a short multi-FOV / multi-timestep acquisition with a moving-line -DMD pattern. No segmentation, tracking, or feature extraction — this -exercises the geometric ``StimLine`` path on its own. The optocheck -reference channel is acquired on the last frame to validate ref-channel -handling without segmentation. - -Why this test exists: - The notebook is the canonical demo for the "stim independent of - cell labels" pipeline shape. It's the simplest hardware path — - image, stim with a precomputed mask, image, repeat — and a useful - early signal that the camera + DMD + filter wheel + LED + writer - chain is wired correctly. - -Differences from the notebook (intentional, for test ergonomics): - * No napari / napari-micromanager. FOV positions come from the - ``safe_positions`` fixture as relative offsets from the stage's - current XY (max 40 µm), so the stage never makes a large move. - * No ``fovs.json`` file dependency. - * Output goes to pytest's ``tmp_path`` (auto-cleaned) instead of - a hardcoded drive letter. - * The DMD affine matrix is the synthetic identity from - ``synthetic_affine`` — no interactive calibration step. - * Frame count and interval are scaled down (4 frames × 5 s) so - the whole test takes about half a minute. - -The test is gated by ``--scope`` / ``FARO_SCOPE`` and skipped by -default. See ``tests/conftest.py`` for the gating logic. -""" - -from __future__ import annotations - -import pytest -from useq import Position, TIntervalLoops - -from faro.core.controller import Controller -from faro.core.data_structures import ( - Channel as RTMChannel, - PowerChannel, - RTMSequence, -) -from faro.core.pipeline import ImageProcessingPipeline -from faro.core.writers import OmeZarrWriter -from faro.stimulation.moving_line_20x import StimLine -from tests.hardware.conftest import assert_clean_run - - -N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 - - -@pytest.mark.hardware -def test_line_stimulation_smoke( - microscope, scope_config, safe_positions, tmp_path -) -> None: - """End-to-end smoke test: geometric line stim, no segmentation.""" - - cfg = scope_config - - imaging = RTMChannel( - config=cfg["imaging_channel"], - exposure=cfg["imaging_exposure"], - group=cfg["channel_group"], - ) - optocheck = RTMChannel( - config=cfg["optocheck_channel"], - exposure=cfg["optocheck_exposure"], - group=cfg["channel_group"], - ) - stim = PowerChannel( - config=cfg["stim_channel"], - exposure=cfg["stim_exposure"], - group=cfg["channel_group"], - power=cfg["stim_power"], - ) - - image_height = microscope.mmc.getImageHeight() - image_width = microscope.mmc.getImageWidth() - - sequence = RTMSequence( - stage_positions=[ - Position(x=p["x"], y=p["y"], z=p["z"], name=p["name"]) - for p in safe_positions - ], - time_plan=TIntervalLoops( - interval=TIME_BETWEEN_TIMESTEPS_S, - loops=N_FRAMES, - ), - channels=[imaging], - stim_channels=(stim,), - stim_frames=frozenset(range(1, N_FRAMES)), - ref_channels=(optocheck,), - ref_frames=frozenset({-1}), - ) - - pipeline = ImageProcessingPipeline( - storage_path=str(tmp_path), - # No segmentator/tracker/FE — pure geometric stim from a - # closed-form mask, dispatched by Analyzer's metadata-only path. - stimulator=StimLine( - first_stim_frame=1, - frames_for_1_loop=N_FRAMES, - stripe_width=image_width // 4, - n_frames_total=N_FRAMES, - mask_height=image_height, - mask_width=image_width, - ), - ) - - writer = OmeZarrWriter( - storage_path=str(tmp_path), - store_stim_images=True, - ) - - controller = Controller(microscope, pipeline, writer=writer) - try: - controller.run_experiment(list(sequence), stim_mode="current") - finally: - controller.finish_experiment() - - assert_clean_run(controller, tmp_path, expect_tracks=False) From 396ca7ee9fca6611bd83f6ac4043e9637dab7e48 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Thu, 28 May 2026 12:00:45 +0200 Subject: [PATCH 31/41] test: speed up the pipeline-lag hardware test (~2 min -> ~30 s) Scale the lag stress test down: N_FRAMES 12->6, interval 5->2 s, pipeline delay 7->3 s. The lag invariant is the delay/interval ratio (interval < delay < 2*interval), preserved here, so coverage is identical -- the pipeline still lags ~1.5 frames and every stim mask must still land without a dispenser skip or deadlock. Verified green on the Moench. --- tests/hardware/pertzlab/test_pipeline_lag.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/hardware/pertzlab/test_pipeline_lag.py b/tests/hardware/pertzlab/test_pipeline_lag.py index 1a46bfc..7cd4541 100644 --- a/tests/hardware/pertzlab/test_pipeline_lag.py +++ b/tests/hardware/pertzlab/test_pipeline_lag.py @@ -34,12 +34,16 @@ ) -# Long enough to cover lag recovery; short enough to stay under ~2 min. -N_FRAMES = 12 -TIME_BETWEEN_TIMESTEPS_S = 5.0 -# Pipeline artificial latency — picked to sit between one and two frame -# intervals so the pipeline consistently lags acquisition by ~2 frames. -SLOW_PIPELINE_DELAY_S = 7.0 +# Enough frames to build up *and* drain sustained lag, but scaled to +# finish in well under a minute on the rig (was 12 @ 5 s + 7 s delay). +N_FRAMES = 6 +TIME_BETWEEN_TIMESTEPS_S = 2.0 +# Pipeline artificial latency — kept between one and two frame intervals +# (interval < delay < 2*interval) so the pipeline still consistently lags +# acquisition by ~1.5 frames, exercising backpressure and the dispenser's +# walk-past-skipped path. The ratio, not the absolute value, is what makes +# this a lag test, so shrinking both keeps the coverage identical. +SLOW_PIPELINE_DELAY_S = 3.0 @pytest.mark.hardware From 281e8ed7378619f8f459d907b23b99bac2d9af64 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Thu, 28 May 2026 12:08:12 +0200 Subject: [PATCH 32/41] test: halve the hardware-test interval (5 s -> 2 s) TIME_BETWEEN_TIMESTEPS_S dominated wall-clock on every acquisition test (4 frames x 5 s = 20 s scheduled per test). Drop it to 2 s -- the 3-FOV acquisition fits comfortably and the tests assert correctness (which frames stim, masks present, no background errors), not the interval, so coverage is unchanged. Also drop stim_mask_timeout's pipeline delay 10 s -> 3 s (still above its 1 s timeout) to speed the post-run drain. Verified on the Moench: pertzlab suite 14 passed / 1 skipped, 4:15 -> 2:50. The cellpose + empty-fov tests got the same mechanical interval change but were not run here (cellpose extra not installed; empty-fov skips without .preflight.json). --- tests/hardware/pertzlab/test_cell_migration.py | 6 +++--- tests/hardware/pertzlab/test_empty_fov_no_crash.py | 2 +- tests/hardware/pertzlab/test_line_stimulation.py | 6 +++--- .../pertzlab/test_ref_channel_no_duplicate_pipeline_runs.py | 2 +- tests/hardware/pertzlab/test_stim_mask_timeout.py | 4 ++-- tests/hardware/pertzlab/test_stim_timing.py | 2 +- tests/hardware/pertzlab/test_stim_with_cellpose.py | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/hardware/pertzlab/test_cell_migration.py b/tests/hardware/pertzlab/test_cell_migration.py index 0ea6328..af75c40 100644 --- a/tests/hardware/pertzlab/test_cell_migration.py +++ b/tests/hardware/pertzlab/test_cell_migration.py @@ -24,8 +24,8 @@ server being reachable. * The DMD affine matrix is the synthetic identity from ``synthetic_affine`` — no interactive calibration step. - * Frame count and interval are scaled down (4 frames × 5 s) so - the whole test takes about a minute. + * Frame count and interval are scaled down (4 frames × 2 s) so + the whole test stays short. The test is gated by ``--scope`` / ``FARO_SCOPE`` and skipped by default. See ``tests/conftest.py`` for the gating logic. @@ -56,7 +56,7 @@ N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 +TIME_BETWEEN_TIMESTEPS_S = 2.0 STIM_CELL_PERCENTAGE = 0.3 diff --git a/tests/hardware/pertzlab/test_empty_fov_no_crash.py b/tests/hardware/pertzlab/test_empty_fov_no_crash.py index 5240fcc..be82452 100644 --- a/tests/hardware/pertzlab/test_empty_fov_no_crash.py +++ b/tests/hardware/pertzlab/test_empty_fov_no_crash.py @@ -37,7 +37,7 @@ N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 +TIME_BETWEEN_TIMESTEPS_S = 2.0 STIM_FRACTION = 0.3 diff --git a/tests/hardware/pertzlab/test_line_stimulation.py b/tests/hardware/pertzlab/test_line_stimulation.py index 0b3db5d..62bcbdf 100644 --- a/tests/hardware/pertzlab/test_line_stimulation.py +++ b/tests/hardware/pertzlab/test_line_stimulation.py @@ -23,8 +23,8 @@ a hardcoded drive letter. * The DMD affine matrix is the synthetic identity from ``synthetic_affine`` — no interactive calibration step. - * Frame count and interval are scaled down (4 frames × 5 s) so - the whole test takes about half a minute. + * Frame count and interval are scaled down (4 frames × 2 s) so + the whole test stays short. The test is gated by ``--scope`` / ``FARO_SCOPE`` and skipped by default. See ``tests/conftest.py`` for the gating logic. @@ -48,7 +48,7 @@ N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 +TIME_BETWEEN_TIMESTEPS_S = 2.0 @pytest.mark.hardware diff --git a/tests/hardware/pertzlab/test_ref_channel_no_duplicate_pipeline_runs.py b/tests/hardware/pertzlab/test_ref_channel_no_duplicate_pipeline_runs.py index a9aecc8..581e544 100644 --- a/tests/hardware/pertzlab/test_ref_channel_no_duplicate_pipeline_runs.py +++ b/tests/hardware/pertzlab/test_ref_channel_no_duplicate_pipeline_runs.py @@ -36,7 +36,7 @@ N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 +TIME_BETWEEN_TIMESTEPS_S = 2.0 @pytest.mark.hardware diff --git a/tests/hardware/pertzlab/test_stim_mask_timeout.py b/tests/hardware/pertzlab/test_stim_mask_timeout.py index 9d966ae..7dd4631 100644 --- a/tests/hardware/pertzlab/test_stim_mask_timeout.py +++ b/tests/hardware/pertzlab/test_stim_mask_timeout.py @@ -35,12 +35,12 @@ N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 +TIME_BETWEEN_TIMESTEPS_S = 2.0 # Pipeline sleeps much longer than the analyzer's timeout so the # controller is guaranteed to time out waiting for the mask. A # queue-based stimulator (StimWithImage / StimWithPipeline) is # required — base Stim is called synchronously and has no timeout. -SLOW_PIPELINE_DELAY_S = 10.0 +SLOW_PIPELINE_DELAY_S = 3.0 SHORT_TIMEOUT_S = 1.0 diff --git a/tests/hardware/pertzlab/test_stim_timing.py b/tests/hardware/pertzlab/test_stim_timing.py index ab15386..32887c8 100644 --- a/tests/hardware/pertzlab/test_stim_timing.py +++ b/tests/hardware/pertzlab/test_stim_timing.py @@ -44,7 +44,7 @@ N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 +TIME_BETWEEN_TIMESTEPS_S = 2.0 class _WholeFovStim(Stim): diff --git a/tests/hardware/pertzlab/test_stim_with_cellpose.py b/tests/hardware/pertzlab/test_stim_with_cellpose.py index 09fbad3..61c3f88 100644 --- a/tests/hardware/pertzlab/test_stim_with_cellpose.py +++ b/tests/hardware/pertzlab/test_stim_with_cellpose.py @@ -34,7 +34,7 @@ N_FRAMES = 4 -TIME_BETWEEN_TIMESTEPS_S = 5.0 +TIME_BETWEEN_TIMESTEPS_S = 2.0 STIM_FRACTION = 0.3 From 4283f2963fcd5f3d30704f0ff575acdf0b91ddb3 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Tue, 19 May 2026 09:59:47 +0200 Subject: [PATCH 33/41] feat: stagger per-FOV min_start_time in apply_fov_batching apply_fov_batching gains offset_min_start_time (default True). FOVs in a batch are imaged sequentially, not simultaneously, so the k-th FOV of a batch only starts ~k * time_per_fov after the batch's first FOV. Encoding that offset into each event's min_start_time keeps the scheduled per-FOV frame interval consistent and makes lag measurement meaningful (lag is acquisition-start minus min_start_time). The first FOV of every batch gets a 0 within-batch offset; batches after the first still get their batch wall-clock offset on top. --- faro/core/utils.py | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/faro/core/utils.py b/faro/core/utils.py index 2611eac..d91be48 100644 --- a/faro/core/utils.py +++ b/faro/core/utils.py @@ -918,6 +918,7 @@ def apply_fov_batching( events: list[RTMEvent], time_per_fov: float, n_parallel: int | None = None, + offset_min_start_time: bool = True, ) -> list[RTMEvent]: """Adjust timing so that overflow FOVs run in subsequent batches. @@ -931,6 +932,15 @@ def apply_fov_batching( time_per_fov: Time (in seconds) to image one FOV. n_parallel: Max FOVs per batch. If *None*, computed from ``time_per_fov`` and the inferred timepoint interval. + offset_min_start_time: When True (default), stagger each FOV's + ``min_start_time`` by its position within its batch times + ``time_per_fov``. FOVs in a batch are imaged sequentially, + not simultaneously, so the k-th FOV of a batch only starts + ~``k * time_per_fov`` after the first. Encoding that in + ``min_start_time`` keeps the scheduled per-FOV frame interval + consistent and makes lag measurement meaningful. The first + FOV of every batch gets 0 offset (its batch wall-clock + offset still applies for batches > 0). Returns: New list of RTMEvent with adjusted ``min_start_time`` and ``t`` @@ -940,30 +950,40 @@ def apply_fov_batching( fov_ids = sorted({e.index.get("p", 0) for e in events}) n_fovs = len(fov_ids) - if n_fovs <= n_parallel: + # A single batch with no per-FOV stagger requested is a no-op. + if n_fovs <= n_parallel and not offset_min_start_time: return list(events) - # Map each FOV to its batch index - fov_to_batch = {fov: i // n_parallel for i, fov in enumerate(fov_ids)} + # Sorted position of each FOV: batch = pos // n_parallel, + # within-batch slot = pos % n_parallel. + fov_pos = {fov: i for i, fov in enumerate(fov_ids)} - # Per-batch wall-clock offset only — the ``t`` index stays per-FOV + # Per-batch wall-clock offset — the ``t`` index stays per-FOV # relative (every FOV uses 0..N-1) so the writer's time axis is # aligned across batches instead of concatenated. Without this each # batch was mapped to a disjoint slab of t and the zarr store had # n_batches * N empty rows per FOV. - batch0_events = [e for e in events if fov_to_batch[e.index.get("p", 0)] == 0] - max_time_batch0 = max(e.min_start_time or 0 for e in batch0_events) + batch0_events = [ + e for e in events if fov_pos[e.index.get("p", 0)] // n_parallel == 0 + ] + max_time_batch0 = max((e.min_start_time or 0 for e in batch0_events), default=0) batch_duration = max_time_batch0 + n_parallel * time_per_fov result: list[RTMEvent] = [] for ev in events: fov = ev.index.get("p", 0) - batch = fov_to_batch[fov] - if batch == 0: + pos = fov_pos[fov] + batch = pos // n_parallel + slot = pos % n_parallel + + offset = batch * batch_duration + if offset_min_start_time: + offset += slot * time_per_fov + + if offset == 0: result.append(ev) else: - time_offset = batch * batch_duration - new_time = (ev.min_start_time or 0) + time_offset + new_time = (ev.min_start_time or 0) + offset result.append(ev.model_copy(update={"min_start_time": new_time})) result.sort(key=lambda e: (e.min_start_time or 0, e.index.get("p", 0))) From 1908c21215d6ca2e037e9c1cf42ea4eecc7dc028 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Thu, 28 May 2026 13:03:16 +0200 Subject: [PATCH 34/41] fix: poll status in the widget tick so it updates without queued signals statusChanged is delivered cross-thread (worker -> Qt main) via psygnal's queued emission, which proved unreliable in some embeddings (notably Jupyter notebooks): the event strip / FOV cursor / counters stayed frozen even though handle.status() was current and Stop still worked. The _tick QTimer already polled the time + queue fields between emissions; make it do a full _refresh from the latest status snapshot so the whole widget stays live regardless of signal delivery. The statusChanged connection stays as a push optimisation; _refresh's repaints are change-guarded, so a full refresh at the 250 ms tick is cheap. --- faro/widgets/experiment_status.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/faro/widgets/experiment_status.py b/faro/widgets/experiment_status.py index fc5c639..6c17223 100644 --- a/faro/widgets/experiment_status.py +++ b/faro/widgets/experiment_status.py @@ -988,16 +988,18 @@ def _update_legend(self, active_type: str | None) -> None: ) def _tick(self) -> None: - """QTimer slot: refresh time-derived + queue fields between emissions.""" + """QTimer slot: poll the handle and refresh the whole widget. + + ``statusChanged`` is the push path, but its cross-thread queued + delivery (psygnal worker thread -> Qt main thread) proved + unreliable in some embeddings -- notably Jupyter notebooks -- + leaving the strip / FOV cursor / counters frozen even though + ``handle.status()`` is current. Polling the latest snapshot here + every tick guarantees the whole widget stays live regardless of + signal delivery; the push connection remains as an optimisation. + ``_refresh``'s repaints are change-guarded, so a full refresh at + this cadence is cheap. + """ if self._handle is None: return - # Queue depths move continuously and independently of frames -- poll - # them every tick, including during the finish drain so the storage - # queue is seen counting down to 0. - self._render_queue_fields() - status = self._handle.status() - # Keep the clock live while the run is active -- including while - # paused, since wall-clock elapsed (and thus lag) keeps growing. - if status.state not in ("running", "pausing", "paused", "waiting"): - return - self._render_time_fields(status, self._current_index(status)) + self._refresh(self._handle.status()) From a74281bf8ad813884b731aacc31a1c1681ca250d Mon Sep 17 00:00:00 2001 From: Hinderling Date: Thu, 28 May 2026 13:32:37 +0200 Subject: [PATCH 35/41] fix: validate phase_id up front for phased experiments pipeline.run() names the per-phase tracks file {fov}_phase_{phase_id}_latest.parquet whenever phase metadata is present and reads metadata['phase_id'] directly. The guard keyed off "phase_id in metadata OR phase_name in metadata", so supplying phase_name without phase_id passed the guard and then KeyError'd mid-run, after acquisition had already started. - validate_pipeline now flags any event carrying phase_name but no phase_id, so validate_events fails up front with a clear message. - run() keys the per-phase filename off phase_id alone, so the combination can no longer crash even when validation is skipped. --- faro/core/pipeline.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/faro/core/pipeline.py b/faro/core/pipeline.py index 9be5faf..8a14723 100644 --- a/faro/core/pipeline.py +++ b/faro/core/pipeline.py @@ -329,6 +329,16 @@ def validate_pipeline(self, events) -> bool: f"Stim event t={event.index.get('t')} p={event.index.get('p')} " f"missing stim metadata: {missing_stim}" ) + # Phased-storage requirement: run() names the per-phase tracks file + # {fov}_phase_{phase_id}_latest.parquet and reads metadata['phase_id'] + # directly, so phase_name without phase_id would crash storage mid-run. + if "phase_name" in meta_keys and "phase_id" not in meta_keys: + warnings_list.append( + f"Event t={event.index.get('t')} p={event.index.get('p')} " + f"has 'phase_name' but no 'phase_id'; add phase_id to the " + f"RTMSequence rtm_metadata (the pipeline needs it to name the " + f"per-phase tracks file)." + ) if warnings_list: import warnings as w @@ -381,7 +391,10 @@ def run( frame_idx = event.index.get("t", 0) filename_for_parquet = f"{metadata['fov']}_latest.parquet" - if "phase_id" in metadata or "phase_name" in metadata: + # A per-phase tracks file needs phase_id; key off it (not phase_name) + # so phase_name-without-phase_id can't KeyError here. validate_pipeline + # flags that combination up front. + if "phase_id" in metadata: metadata["fov_timestep"] = frame_idx filename_for_parquet = ( f"{metadata['fov']}_phase_{metadata['phase_id']}_latest.parquet" From 012fb75b57af6660444c04744a5c12f300387f78 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Thu, 28 May 2026 14:41:11 +0200 Subject: [PATCH 36/41] fix: guard phase_id access in the deferred pipeline too pipeline_post.run() (the deferred/reprocess-from-disk path) had the same "phase_id in metadata or phase_name in metadata" guard followed by a direct metadata['phase_id'] access as pipeline.run(). Key it off phase_id alone so a phase_name-without-phase_id event can't KeyError on the deferred path either. --- faro/core/pipeline_post.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/faro/core/pipeline_post.py b/faro/core/pipeline_post.py index 7fab940..ffd2bb6 100644 --- a/faro/core/pipeline_post.py +++ b/faro/core/pipeline_post.py @@ -517,7 +517,9 @@ def run_on_fov(self, fov_id) -> dict: df_tracked = convert_track_dtypes(df_tracked) filename_for_parquet = f"{metadata['fov']}_latest.parquet" - if "phase_id" in metadata or "phase_name" in metadata: + # Key off phase_id alone (not phase_name) so phase_name-without-phase_id + # can't KeyError here either; mirrors pipeline.run(). + if "phase_id" in metadata: metadata["fov_timestep"] = fov_obj.fov_timestep_counter filename_for_parquet = ( f"{metadata['fov']}_phase_{metadata['phase_id']}_latest.parquet" From 14b2d3f4b8b2f36ab030a33e4374c147e9445185 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Thu, 28 May 2026 15:59:49 +0200 Subject: [PATCH 37/41] fix: don't add an inferred interval across a WaitEvent boundary A WaitEvent carries an explicit gap (duration_s); _combine_pair was adding the inferred inter-source interval on top of it, double-counting the wait. The feed loop sleeps for duration_s AND the next source's min_start_time included the wait *plus* an interval -- so combine(wait(10), phase) at a 10 s interval started the first acquisition at t=20 (10 s wait countdown + 10 s engine pacing) instead of t=10. Skip _infer_interval when either side of the merge boundary is a WaitEvent. combine(wait(10), phase) now starts the first frame at t=10, and combine(a, wait(10), b) starts b exactly duration_s after a's last frame (verified: [0,10,20] + wait(10) -> [30,40,50]). --- faro/core/data_structures.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/faro/core/data_structures.py b/faro/core/data_structures.py index 3cbf730..f1b58ad 100644 --- a/faro/core/data_structures.py +++ b/faro/core/data_structures.py @@ -847,7 +847,17 @@ def _combine_pair( (e.min_start_time or 0) + (e.duration_s if isinstance(e, WaitEvent) else 0) for e in events_a ) - time_offset = max_time_a + _infer_interval(b, events_b) + # A WaitEvent is an *explicit* gap (its duration_s), so the inferred + # inter-source interval must NOT be added across a wait boundary -- + # otherwise the wait is double-counted: the feed loop sleeps for + # duration_s AND the next source's min_start_time would include the + # wait *plus* an interval. E.g. combine(wait(10), phase) with a 10 s + # interval started the first acquisition at t=20 instead of t=10. + boundary_has_wait = isinstance(events_a[-1], WaitEvent) or isinstance( + events_b[0], WaitEvent + ) + gap = 0.0 if boundary_has_wait else _infer_interval(b, events_b) + time_offset = max_time_a + gap offset_b: list[RTMEvent] = [] for ev in events_b: From 0dea2ac5dda009a8bd4ee42c70c5b1b27cb31057 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Mon, 1 Jun 2026 10:52:33 +0200 Subject: [PATCH 38/41] =?UTF-8?q?chore:=20env=20fixes=20=E2=80=94=20opencv?= =?UTF-8?q?=20in=20cellpose=20extra=20+=20napari-mm=20git=20pin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two general env gaps that bit when bootstrapping a fresh worktree against this branch. - The cellpose extra didn't declare opencv-python, but cellpose imports cv2 at module load. It was only being pulled in transitively via the virtual-microscope extra; `uv sync --extra cellpose` alone failed with ModuleNotFoundError: No module named 'cv2'. Declare opencv-python in the cellpose extra so it's self-sufficient. - PyPI napari-micromanager is 0.2.0, whose MainWindow lacks the `mmcore=` kwarg and predates the async-relevant fixes (_image_snapped race, device-leak teardown, preview-during-run). The async demo and the random-stim notebooks all use MainWindow(viewer, mmcore=mic.mmc). Track upstream main like the other faro envs. uv.lock follows. --- pyproject.toml | 8 ++++++++ uv.lock | 12 +++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ac562b4..9c3a5bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,10 @@ cellpose = [ # so [tool.uv.sources] picks up the cu128 index pin (uv only routes # source overrides for packages declared as direct deps). "torchvision", + # cellpose imports cv2 but doesn't pin opencv itself; declare it here + # so the cellpose extra is self-sufficient (was only pulled in + # transitively via the virtual-microscope extra before). + "opencv-python", ] convpaint = [ @@ -99,6 +103,10 @@ torch = { index = "pytorch-cu130" } torchvision = { index = "pytorch-cu130" } virtual-microscope = { git = "https://github.com/hinderling/virtual-microscope.git" } pymmcore-widgets = {git = "https://github.com/pymmcore-plus/pymmcore-widgets.git" } +# PyPI napari-micromanager is 0.2.0 (no MainWindow(mmcore=...) and predates +# the async-relevant fixes: _image_snapped race, device-leak teardown, +# preview-during-run). Track upstream main like the other faro envs. +napari-micromanager = { git = "https://github.com/pymmcore-plus/napari-micromanager.git", branch = "main" } [tool.pytest.ini_options] markers = [ diff --git a/uv.lock b/uv.lock index c7db5b0..1525661 100644 --- a/uv.lock +++ b/uv.lock @@ -918,6 +918,7 @@ api75 = [ ] cellpose = [ { name = "cellpose" }, + { name = "opencv-python" }, { name = "torch" }, { name = "torchvision" }, ] @@ -960,10 +961,11 @@ requires-dist = [ { name = "motile", marker = "extra == 'motile'" }, { name = "napari", extras = ["pyqt6"] }, { name = "napari-convpaint", marker = "extra == 'convpaint'" }, - { name = "napari-micromanager" }, + { name = "napari-micromanager", git = "https://github.com/pymmcore-plus/napari-micromanager.git?branch=main" }, { name = "napari-ome-zarr" }, { name = "numpy" }, { name = "ome-writers" }, + { name = "opencv-python", marker = "extra == 'cellpose'" }, { name = "opencv-python", marker = "extra == 'virtual-microscope'" }, { name = "pandas" }, { name = "pyarrow" }, @@ -2572,8 +2574,8 @@ wheels = [ [[package]] name = "napari-micromanager" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } +version = "0.3.1.post2.dev1+g6a927178d" +source = { git = "https://github.com/pymmcore-plus/napari-micromanager.git?branch=main#6a927178d927a12220a62dcc9ac4c4b741b352b4" } dependencies = [ { name = "fonticon-materialdesignicons6" }, { name = "napari" }, @@ -2584,10 +2586,6 @@ dependencies = [ { name = "useq-schema" }, { name = "zarr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/d1/09ff9bddcbd40c3e46f1c83ca532b825604dcc761ffa787a61c138356cb2/napari_micromanager-0.2.0.tar.gz", hash = "sha256:25d7e9961a24e7de3d6fc4b7b3bae4ab085d8e5811ddea73fc2d4e98f1c8d1f0", size = 23851, upload-time = "2026-02-15T19:12:02.649Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/fc/a02dc9aec741ead0510be8d06feea672ceef24ec3b5ea41021d4c311d9bf/napari_micromanager-0.2.0-py3-none-any.whl", hash = "sha256:6a94c3b421c1c915f4ce539c8e9bc72861ab21ba797b4cac5d491dfe25d717ac", size = 23732, upload-time = "2026-02-15T19:12:01.045Z" }, -] [[package]] name = "napari-ome-zarr" From 57c75da27db76fc860a2ffa2a1d3fe2ca02afb09 Mon Sep 17 00:00:00 2001 From: Hinderling Date: Mon, 1 Jun 2026 12:43:57 +0200 Subject: [PATCH 39/41] fix: force psygnal signal backend reliably (no setdefault race) faro/microscope/base.py used os.environ.setdefault to force pymmcore-plus onto its synchronous psygnal backend, but pymmcore_widgets (transitively imported by napari-micromanager) does the same setdefault to 'qt' at its own import. Whichever runs first wins -- and pymmcore_widgets usually does, because notebooks `import napari_micromanager` before any faro import. Visible symptom: MDARunner is constructed as a QMDASignaler, frameReady is queued to the main thread, and the controller's pipeline silently never sees a frame (no segmentation, no stim mask -> 80 s stim timeouts, widget stuck at 0). - faro/__init__.py: hard-set PYMM_SIGNALS_BACKEND='psygnal' as the very first statement, before any submodule import. Overrides pymmcore_widgets's setdefault regardless of import order. pymmcore-plus reads the env lazily (at each signaler construction), so this still wins as long as faro is imported before the first CMMCorePlus.mda is accessed -- which `import faro` / `from faro...` guarantees, since it precedes Moench(). - faro/microscope/base.py: drop the now-redundant setdefault; replace with a one-line pointer to faro/__init__.py. - faro/microscope/pymmcore.py: add a runtime guard in connect_frame() that raises a clear, actionable RuntimeError if the MDA runner is already Qt-backed -- catches the edge case where a CMMCorePlus was constructed before any faro import. Verified across the three relevant scenarios: - napari-mm imported before faro: override wins, MDARunner is psygnal. - Headless (no Qt around): psygnal anyway, hard-set is a no-op. - MDARunner already Qt-backed before faro ran: guard fires with remediation. --- faro/__init__.py | 24 +++++++++++++++++++ faro/microscope/base.py | 18 +++----------- faro/microscope/pymmcore.py | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/faro/__init__.py b/faro/__init__.py index e69de29..1e02ba4 100644 --- a/faro/__init__.py +++ b/faro/__init__.py @@ -0,0 +1,24 @@ +"""FARO -- Feedback Adaptive Real-time Optogenetics.""" + +# Force pymmcore-plus to use the synchronous psygnal signal backend BEFORE +# any submodule import can pull in pymmcore_widgets, which sets +# PYMM_SIGNALS_BACKEND='qt' via os.environ.setdefault at its own import. +# napari-micromanager transitively imports pymmcore_widgets, so a notebook +# that did `import napari_micromanager` before any faro import would +# otherwise lock the backend to 'qt' -- routing core.mda.events.frameReady +# through Qt's queued delivery and silently starving the controller's +# pipeline whenever the main thread is busy. faro's async controller +# (RunHandle / run_experiment) needs the data path direct + synchronous on +# the engine thread, so we set this *explicitly* and *hard* (not setdefault): +# pymmcore_widgets's own setdefault wins whenever it imports first, so faro +# can only get its way by overriding outright. pymmcore-plus reads this env +# lazily (in pymmcore_plus._util.signals_backend(), at each signaler +# construction in CMMCorePlus / MDARunner), so this line still takes effect +# as long as it runs before any CMMCorePlus is instantiated or any +# mmc.mda is first accessed -- which `import faro` (or `from faro...`) +# guarantees, since it precedes the faro-side Moench() call that does so. +# +# Headless paths are unaffected: with no Qt around, pymmcore-plus would +# pick psygnal anyway. Setting this is a no-op there. +import os +os.environ["PYMM_SIGNALS_BACKEND"] = "psygnal" diff --git a/faro/microscope/base.py b/faro/microscope/base.py index f4d29ce..a82e20a 100644 --- a/faro/microscope/base.py +++ b/faro/microscope/base.py @@ -12,21 +12,9 @@ category=FutureWarning, ) -# Force pymmcore-plus to use the psygnal signal backend regardless of whether -# a QApplication is loaded. With "auto" (the default), pymmcore-plus picks -# the qt backend whenever Qt is around, which routes core.mda.events.frameReady -# through Qt.AutoConnection -- queued delivery to the main thread for any -# non-QObject listener. The Controller's frame handler runs on the MDA engine -# thread (no Qt objects touched); since Controller.run_experiment now spawns -# a worker thread and Controller.RunHandle.wait() joins on that worker, -# the main thread is typically blocked while the engine emits frames. -# A queued Qt connection on top of that means frame callbacks pile up -# undelivered -- the engine completes happily, but no frames reach the -# pipeline. Forcing 'psygnal' keeps the data path direct and synchronous, -# decoupled from any GUI loop. Widgets that subscribe to RunHandle's own -# psygnal signals still get Qt-routed updates because *those* receivers -# are QObjects. -os.environ.setdefault("PYMM_SIGNALS_BACKEND", "psygnal") +# PYMM_SIGNALS_BACKEND is hard-set to 'psygnal' in faro/__init__.py before any +# submodule (including this one) is imported. A setdefault here would be a +# no-op anyway: pymmcore_widgets pre-empts to 'qt' whenever it imports first. import numpy as np from useq import MDAEvent diff --git a/faro/microscope/pymmcore.py b/faro/microscope/pymmcore.py index b14a457..c85dff1 100644 --- a/faro/microscope/pymmcore.py +++ b/faro/microscope/pymmcore.py @@ -32,6 +32,7 @@ def run_mda(self, event_iter): return self.mmc.run_mda(event_iter) def connect_frame(self, callback): + self._check_signal_backend() self.mmc.mda.events.frameReady.connect(callback) def disconnect_frame(self, callback): @@ -91,6 +92,52 @@ def get_power_properties(self) -> dict[str, tuple[str, str]]: # Manual POWER_PROPERTIES override auto-detected ones return {**detected, **self.POWER_PROPERTIES} + # ------------------------------------------------------------------ + # Internal: signal-backend safety net + # ------------------------------------------------------------------ + + def _check_signal_backend(self) -> None: + """Fail loud if pymmcore-plus is using the Qt MDA signal backend. + + faro's async pipeline runs frameReady on the engine thread; the Qt + backend routes it through queued delivery to the main thread instead, + and the controller's pipeline silently never sees the frames. faro + sets ``PYMM_SIGNALS_BACKEND='psygnal'`` from ``faro/__init__.py`` so + any MDARunner constructed after that point is psygnal-backed -- but + if a CMMCorePlus.mda was already accessed before ``import faro`` + (typically because ``import napari_micromanager`` ran first and its + transitive ``import pymmcore_widgets`` set the env to ``'qt'``), the + runner is locked Qt-backed and faro's override is too late. Detect + that here so the user gets a clear remediation rather than silent + frame loss. + + Accessing ``self.mmc.mda`` lazily constructs the runner if it hasn't + been already, so this also doubles as a forcing function: a runner + built right here picks up the now-correct env. + """ + if self.mmc is None: + return + sig_type = type(self.mmc.mda.events).__name__ + if sig_type == "QMDASignaler": + raise RuntimeError( + "pymmcore-plus is using the Qt MDA signal backend " + f"({sig_type}), but faro's async controller pipeline requires " + "the synchronous psygnal backend so frameReady runs on the " + "engine thread regardless of the main thread's state. With " + "Qt-backed signals frame callbacks are queued to the main " + "thread and the pipeline never sees a frame.\n\n" + "Cause: a pymmcore-plus MDA runner was constructed before " + "PYMM_SIGNALS_BACKEND was set to 'psygnal'. faro/__init__.py " + "sets this as early as it can, but napari-micromanager " + "(via pymmcore_widgets) sets it to 'qt' first whenever it " + "imports before any faro import.\n\n" + "Fix: ensure `import faro` (or any `from faro...`) runs " + "before `import napari_micromanager` (or `import " + "pymmcore_widgets`). Or, as the very first line of your " + "notebook/script (above every import):\n\n" + " import os; os.environ['PYMM_SIGNALS_BACKEND'] = 'psygnal'" + ) + def validate_hardware(self, events) -> bool: # Materialize once so both the base and util checks can iterate. events = list(events) From ea8e1e7728989e3b8e5b1b7b5ed3d273b9179f32 Mon Sep 17 00:00:00 2001 From: Alex Landolt Date: Tue, 2 Jun 2026 13:37:13 +0200 Subject: [PATCH 40/41] fix(notebooks): bind napari-micromanager to the experiment core via mmcore= Pass the microscope core explicitly to napari-micromanager (MainWindow(viewer, mmcore=mic.mmc)) instead of patching mm_wdg._mmc after construction. Why: `mm_wdg._mmc = mic.mmc` only repoints the top-level MainWindow wrapper. Its sub-widgets (live, MDA, properties) bind to CMMCorePlus.instance() at construction time, so on a fresh install the viewer drove a different core than the experiment and never controlled the hardware. The mmcore= argument binds every sub-widget to mic.mmc. This shares the experiment core deliberately and scoped to the viewer. We intentionally do NOT make mic.mmc the global singleton: that unifies napari-mm with the faro experiment core process-wide and leaves napari-mm unable to operate the core after run_experiment (custom MDA engine + frame listeners drive it from the worker thread). Applies to all example notebooks; the 02_demo sim notebook uses mmcore=core. --- .../demo_sim_optogenetic_napari_async.ipynb | 20 +- .../stim_dfacquire.ipynb | 33 +++- .../stim_ramp_dfacquire.ipynb | 7 +- .../stim_rtmsequence.ipynb | 174 ++++++++++++++---- .../21_cell_migration/cell_migration.ipynb | 9 +- .../cell_migration_test.ipynb | 7 +- .../line_stimulation.ipynb | 9 +- 7 files changed, 195 insertions(+), 64 deletions(-) diff --git a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb index 3a4bd20..f4d0dd6 100644 --- a/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb +++ b/experiments/02_demo_sim_optogenetic/demo_sim_optogenetic_napari_async.ipynb @@ -104,8 +104,7 @@ "from napari_micromanager import MainWindow\n", "\n", "viewer = napari.Viewer()\n", - "mm_wdg = MainWindow(viewer)\n", - "mm_wdg._mmc = core\n", + "mm_wdg = MainWindow(viewer, mmcore=core) # widget to control Micro-Manager from napari\n", "viewer.window.add_dock_widget(mm_wdg, name=\"napari-micromanager\")" ] }, @@ -655,18 +654,18 @@ "n_stim = 5\n", "n_recovery = 5\n", "interval_s = 1 # seconds between frames; raise if your machine struggles\n", - "wait_s = 10 # pre-baseline settle + post-stim recovery pauses\n", + "wait_s = 10 # pre-baseline settle + post-stim recovery pauses\n", "\n", "baseline = RTMSequence(\n", " time_plan={\"interval\": interval_s, \"loops\": n_baseline},\n", - " stage_positions=[(0.0, 0.0, 0.0),(20,20,0.0)],\n", + " stage_positions=[(0.0, 0.0, 0.0), (20, 20, 0.0)],\n", " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", " rtm_metadata={\"phase\": \"baseline\"},\n", ")\n", "\n", "stim_phase = RTMSequence(\n", " time_plan={\"interval\": interval_s, \"loops\": n_stim},\n", - " stage_positions=[(0.0, 0.0, 0.0),(20,20,0.0)],\n", + " stage_positions=[(0.0, 0.0, 0.0), (20, 20, 0.0)],\n", " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", " stim_channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", " stim_frames=range(n_stim),\n", @@ -675,7 +674,7 @@ "\n", "recovery = RTMSequence(\n", " time_plan={\"interval\": interval_s, \"loops\": n_recovery},\n", - " stage_positions=[(0.0, 0.0, 0.0),(20,20,0.0)],\n", + " stage_positions=[(0.0, 0.0, 0.0), (20, 20, 0.0)],\n", " channels=[{\"config\": \"phase-contrast\", \"exposure\": 50}],\n", " rtm_metadata={\"phase\": \"recovery\"},\n", ")\n", @@ -798,9 +797,12 @@ ], "source": [ "def _print_status(status):\n", - " print(f\"[notify] state={status.state} consumed={status.n_events_consumed}\"\n", - " f\"/{status.n_events_total} frames={status.n_frames_received}\"\n", - " f\" lag_ms={status.lag_ms}\")\n", + " print(\n", + " f\"[notify] state={status.state} consumed={status.n_events_consumed}\"\n", + " f\"/{status.n_events_total} frames={status.n_frames_received}\"\n", + " f\" lag_ms={status.lag_ms}\"\n", + " )\n", + "\n", "\n", "handle.statusChanged.connect(_print_status)" ] diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb index d89933c..9bf74b1 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_dfacquire.ipynb @@ -195,8 +195,9 @@ "import napari\n", "\n", "viewer = napari.Viewer()\n", - "mm_wdg = MainWindow(viewer)\n", - "mm_wdg._mmc = mic.mmc\n", + "mm_wdg = MainWindow(\n", + " viewer, mmcore=mic.mmc\n", + ") # widget to control Micro-Manager from napari\n", "viewer.window.add_dock_widget(mm_wdg)\n", "data_mda_fovs = None\n", "load_from_file = False" @@ -2067,7 +2068,31 @@ "attachments": {}, "cell_type": "code", "metadata": {}, - "source": "# Phase 2: separate df_acquire with optocheck channel on specific timepoints\n# RTMSequence equivalent: RTMSequence(..., ref_channels=(optocheck_channel,), ref_frames=[-1])\ndf_acquire_2 = utils.generate_df_acquire(\n fovs,\n n_frames=N_FRAMES_PHASE_2,\n time_between_timesteps=TIME_BETWEEN_TIMESTEPS,\n time_per_fov=TIME_PER_FOV,\n channels=channels,\n channel_optocheck=channel_optocheck, # df_acquire passes optocheck as a parameter here\n optocheck_timepoints=optocheck_timepoints, # absolute timestep indices (not per-phase like RTMSequence)\n phase_name=\"PostDrug\",\n phase_id=1,\n condition=condition,\n)\ndf_acquire_2 = utils.apply_stim_treatments_to_df_acquire(\n df_acquire_2,\n stim_phase_2,\n condition,\n n_fovs_per_well=n_fovs_per_well,\n add_stim_exposure_group=ADD_STIM_EXPOSURE_GROUP,\n regular_spacing_between_stimulations=REGULAR_SPACING_BETWEEN_STIMULATIONS,\n)\ndf_acquire_2" + "source": [ + "# Phase 2: separate df_acquire with optocheck channel on specific timepoints\n", + "# RTMSequence equivalent: RTMSequence(..., ref_channels=(optocheck_channel,), ref_frames=[-1])\n", + "df_acquire_2 = utils.generate_df_acquire(\n", + " fovs,\n", + " n_frames=N_FRAMES_PHASE_2,\n", + " time_between_timesteps=TIME_BETWEEN_TIMESTEPS,\n", + " time_per_fov=TIME_PER_FOV,\n", + " channels=channels,\n", + " channel_optocheck=channel_optocheck, # df_acquire passes optocheck as a parameter here\n", + " optocheck_timepoints=optocheck_timepoints, # absolute timestep indices (not per-phase like RTMSequence)\n", + " phase_name=\"PostDrug\",\n", + " phase_id=1,\n", + " condition=condition,\n", + ")\n", + "df_acquire_2 = utils.apply_stim_treatments_to_df_acquire(\n", + " df_acquire_2,\n", + " stim_phase_2,\n", + " condition,\n", + " n_fovs_per_well=n_fovs_per_well,\n", + " add_stim_exposure_group=ADD_STIM_EXPOSURE_GROUP,\n", + " regular_spacing_between_stimulations=REGULAR_SPACING_BETWEEN_STIMULATIONS,\n", + ")\n", + "df_acquire_2" + ] }, { "cell_type": "markdown", @@ -2149,4 +2174,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb index 7be6dca..8432b2d 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_ramp_dfacquire.ipynb @@ -406,8 +406,9 @@ "import napari\n", "\n", "viewer = napari.Viewer()\n", - "mm_wdg = MainWindow(viewer)\n", - "mm_wdg._mmc = mic.mmc # point the widget at our CMMCore instance\n", + "mm_wdg = MainWindow(\n", + " viewer, mmcore=mic.mmc\n", + ") # widget to control Micro-Manager from napari\n", "viewer.window.add_dock_widget(mm_wdg) # dock Micro-Manager controls in napari" ] }, @@ -820,4 +821,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb b/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb index 067412f..ec8e009 100644 --- a/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb +++ b/experiments/11_erk_experiments_full_fov_stim/stim_rtmsequence.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "4324b85e", "metadata": {}, "source": [ "# ERK-KTR Full FOV Stimulation Pipeline (RTMSequence)\n", @@ -14,19 +15,30 @@ "3. Select FOV positions in napari and build acquisition events as RTMSequence phases\n", "4. Validate and run the experiment\n", "5. Post-process tracks into a single `exp_data.parquet`" - ], - "id": "4324b85e" + ] }, { "cell_type": "code", "execution_count": null, + "id": "13251d65", "metadata": {}, "outputs": [], - "source": "import os\nimport time\nfrom faro.core.data_structures import (\n Channel, # basic imaging channel (config, exposure, group)\n PowerChannel, # imaging channel with light-source power control (adds power 0-100)\n RTMSequence, # defines one phase of the acquisition (time plan, channels, stim, etc.)\n SegmentationMethod,\n combine, # compose multi-phase experiments along an axis (t or p)\n)\nimport faro.core.utils as utils", - "id": "13251d65" + "source": [ + "import os\n", + "import time\n", + "from faro.core.data_structures import (\n", + " Channel, # basic imaging channel (config, exposure, group)\n", + " PowerChannel, # imaging channel with light-source power control (adds power 0-100)\n", + " RTMSequence, # defines one phase of the acquisition (time plan, channels, stim, etc.)\n", + " SegmentationMethod,\n", + " combine, # compose multi-phase experiments along an axis (t or p)\n", + ")\n", + "import faro.core.utils as utils" + ] }, { "cell_type": "markdown", + "id": "be74a40c", "metadata": {}, "source": [ "### Experimental Settings\n", @@ -43,12 +55,12 @@ "- `stim_channel` -- the stimulation light channel. Adjust `power` (intensity, 0-100) and `exposure` (ms).\n", "- `imaging_channels` -- channels acquired every timepoint. Add/remove channels as needed; the order matters (channel 0 is used for segmentation by default).\n", "- `optocheck_channel` -- reference channel acquired only on selected frames to verify optogenetic tool expression. Typically has a longer exposure." - ], - "id": "be74a40c" + ] }, { "cell_type": "code", "execution_count": null, + "id": "32c48cc9", "metadata": {}, "outputs": [], "source": [ @@ -58,12 +70,12 @@ "mic.mmc.setChannelGroup(\n", " \"TTL_ERK\"\n", ") # select the channel group configured in Micro-Manager" - ], - "id": "32c48cc9" + ] }, { "cell_type": "code", "execution_count": null, + "id": "170b1635", "metadata": {}, "outputs": [], "source": [ @@ -99,11 +111,11 @@ "optocheck_channel = PowerChannel(\n", " config=\"mCitrine\", exposure=600, group=\"TTL_ERK\", power=99\n", ")" - ], - "id": "170b1635" + ] }, { "cell_type": "markdown", + "id": "c37dfd04", "metadata": {}, "source": [ "### Pipeline Setup\n", @@ -130,12 +142,12 @@ "- **Optocheck** (`feature_extractor_ref`): Feature extraction on reference channel frames. `OptoCheckFE(used_mask=\"labels\")` measures optogenetic reporter intensity.\n", "\n", "**Writer:** `OmeZarrWriter` saves imaging + stim readout into a single OME-Zarr store." - ], - "id": "c37dfd04" + ] }, { "cell_type": "code", "execution_count": null, + "id": "dbe6e9fe", "metadata": {}, "outputs": [], "source": [ @@ -174,11 +186,11 @@ "from faro.core.writers import OmeZarrWriter\n", "\n", "writer = OmeZarrWriter(storage_path=path) # saves images + stim readout into OME-Zarr" - ], - "id": "dbe6e9fe" + ] }, { "cell_type": "markdown", + "id": "e1f31197", "metadata": {}, "source": [ "### GUI\n", @@ -186,12 +198,12 @@ "Opens a napari viewer with the Micro-Manager widget. Use this to:\n", "- Live-view the camera feed\n", "- Select FOV positions (used by `generate_fov_positions` in the next step) using the MDA widget" - ], - "id": "e1f31197" + ] }, { "cell_type": "code", "execution_count": null, + "id": "ddcb59ab", "metadata": {}, "outputs": [], "source": [ @@ -199,28 +211,89 @@ "import napari\n", "\n", "viewer = napari.Viewer()\n", - "mm_wdg = MainWindow(viewer)\n", - "mm_wdg._mmc = mic.mmc # point the widget at our CMMCore instance\n", + "mm_wdg = MainWindow(\n", + " viewer, mmcore=mic.mmc\n", + ") # widget to control Micro-Manager from napari\n", "viewer.window.add_dock_widget(mm_wdg) # dock Micro-Manager controls in napari" - ], - "id": "ddcb59ab" + ] }, { "cell_type": "markdown", + "id": "339f54f4", "metadata": {}, - "source": "### Build acquisition events\n\n**FOV positions:** `generate_fov_positions(mic, viewer=viewer)` reads positions from the napari MDA widget. Alternatives: `filename=\"path/to/config.json\"` to load from file, or `fake_fovs=N` for testing.\n\n**RTMSequence parameters:**\n- `time_plan` -- `{\"interval\": seconds, \"loops\": N}` defines the time-lapse schedule\n- `stage_positions` -- list of FOV positions (from `generate_fov_positions`)\n- `channels` -- imaging channels acquired at every timepoint\n- `stim_channels` -- stimulation channel(s). Only used on frames listed in `stim_frames`\n- `stim_frames` -- which frames get stimulated, **relative to this phase** (0-indexed). Accepts `range()`, `set`, or `frozenset`. Negative indices count from end (e.g. `-1` = last frame)\n- `stim_exposure` -- override stim exposure per-frame. `None` uses the channel's default. Can be a single value or a list matching `stim_frames` length for ramped protocols\n- `ref_channels` -- reference/optocheck channel(s). Only acquired on `ref_frames`\n- `ref_frames` -- which frames get reference imaging (e.g. `[-1]` = last frame only)\n- `rtm_metadata` -- experiment metadata: `phase_name`, `phase_id`, `treatment_name`\n\n**Multi-phase experiments:** Use `combine(phase_1, phase_2, axis=\"t\")` to chain phases sequentially in time. Timepoints and timing are automatically offset. Note: `stim_frames` and `ref_frames` are resolved per-phase before combining (e.g. `range(1, 2)` means frame 1 within that phase, not globally). Use `axis=\"p\"` to run sub-experiments in parallel at additional FOV indices.\n\n**FOV batching:** `apply_fov_batching(events, time_per_fov=2.0)` adjusts timing when the number of FOVs exceeds what can be imaged within one interval. FOVs are split into sequential batches automatically.", - "id": "339f54f4" + "source": [ + "### Build acquisition events\n", + "\n", + "**FOV positions:** `generate_fov_positions(mic, viewer=viewer)` reads positions from the napari MDA widget. Alternatives: `filename=\"path/to/config.json\"` to load from file, or `fake_fovs=N` for testing.\n", + "\n", + "**RTMSequence parameters:**\n", + "- `time_plan` -- `{\"interval\": seconds, \"loops\": N}` defines the time-lapse schedule\n", + "- `stage_positions` -- list of FOV positions (from `generate_fov_positions`)\n", + "- `channels` -- imaging channels acquired at every timepoint\n", + "- `stim_channels` -- stimulation channel(s). Only used on frames listed in `stim_frames`\n", + "- `stim_frames` -- which frames get stimulated, **relative to this phase** (0-indexed). Accepts `range()`, `set`, or `frozenset`. Negative indices count from end (e.g. `-1` = last frame)\n", + "- `stim_exposure` -- override stim exposure per-frame. `None` uses the channel's default. Can be a single value or a list matching `stim_frames` length for ramped protocols\n", + "- `ref_channels` -- reference/optocheck channel(s). Only acquired on `ref_frames`\n", + "- `ref_frames` -- which frames get reference imaging (e.g. `[-1]` = last frame only)\n", + "- `rtm_metadata` -- experiment metadata: `phase_name`, `phase_id`, `treatment_name`\n", + "\n", + "**Multi-phase experiments:** Use `combine(phase_1, phase_2, axis=\"t\")` to chain phases sequentially in time. Timepoints and timing are automatically offset. Note: `stim_frames` and `ref_frames` are resolved per-phase before combining (e.g. `range(1, 2)` means frame 1 within that phase, not globally). Use `axis=\"p\"` to run sub-experiments in parallel at additional FOV indices.\n", + "\n", + "**FOV batching:** `apply_fov_batching(events, time_per_fov=2.0)` adjusts timing when the number of FOVs exceeds what can be imaged within one interval. FOVs are split into sequential batches automatically." + ] }, { "cell_type": "code", "execution_count": null, + "id": "6dfbf548", "metadata": {}, "outputs": [], - "source": "fov_positions = utils.generate_fov_positions(mic, viewer=viewer)\n\n# Phase 1: baseline imaging with one stim pulse\nphase_1 = RTMSequence(\n time_plan={\"interval\": 10.0, \"loops\": 4}, # 4 timepoints, 10 s apart\n stage_positions=fov_positions,\n channels=imaging_channels,\n stim_channels=(stim_channel,),\n stim_frames=range(1, 2), # stimulate on frame 1 only (0-indexed, per-phase)\n rtm_metadata={\n \"phase_name\": \"PreDrug\",\n \"phase_id\": 0,\n \"treatment_name\": \"Priming Phase 1 pre Drug\",\n },\n)\n\n# Phase 2: post-drug with sustained stim + optocheck on last frame\nphase_2 = RTMSequence(\n time_plan={\"interval\": 10.0, \"loops\": 4}, # 4 timepoints, 10 s apart\n stage_positions=fov_positions,\n channels=imaging_channels,\n stim_channels=(stim_channel,),\n stim_frames=range(1, 3), # stimulate on frames 1 and 2\n ref_channels=(optocheck_channel,),\n ref_frames=[-1], # optocheck on last frame only\n rtm_metadata={\n \"phase_name\": \"PostDrug\",\n \"phase_id\": 1,\n \"treatment_name\": \"Sustained Phase 2 post Drug\",\n },\n)\n\n# Chain phases along the time axis -- timepoints and timing are automatically offset\nevents = combine(phase_1, phase_2, axis=\"t\")\n\n# Split FOVs into sequential batches if too many to image within one interval\nevents = utils.apply_fov_batching(events, time_per_fov=2.0)\n\nprint(f\"Total events: {len(events)}\")\nutils.events_to_dataframe(events).sort_values(\"timestep\")", - "id": "6dfbf548" + "source": [ + "fov_positions = utils.generate_fov_positions(mic, viewer=viewer)\n", + "\n", + "# Phase 1: baseline imaging with one stim pulse\n", + "phase_1 = RTMSequence(\n", + " time_plan={\"interval\": 10.0, \"loops\": 4}, # 4 timepoints, 10 s apart\n", + " stage_positions=fov_positions,\n", + " channels=imaging_channels,\n", + " stim_channels=(stim_channel,),\n", + " stim_frames=range(1, 2), # stimulate on frame 1 only (0-indexed, per-phase)\n", + " rtm_metadata={\n", + " \"phase_name\": \"PreDrug\",\n", + " \"phase_id\": 0,\n", + " \"treatment_name\": \"Priming Phase 1 pre Drug\",\n", + " },\n", + ")\n", + "\n", + "# Phase 2: post-drug with sustained stim + optocheck on last frame\n", + "phase_2 = RTMSequence(\n", + " time_plan={\"interval\": 10.0, \"loops\": 4}, # 4 timepoints, 10 s apart\n", + " stage_positions=fov_positions,\n", + " channels=imaging_channels,\n", + " stim_channels=(stim_channel,),\n", + " stim_frames=range(1, 3), # stimulate on frames 1 and 2\n", + " ref_channels=(optocheck_channel,),\n", + " ref_frames=[-1], # optocheck on last frame only\n", + " rtm_metadata={\n", + " \"phase_name\": \"PostDrug\",\n", + " \"phase_id\": 1,\n", + " \"treatment_name\": \"Sustained Phase 2 post Drug\",\n", + " },\n", + ")\n", + "\n", + "# Chain phases along the time axis -- timepoints and timing are automatically offset\n", + "events = combine(phase_1, phase_2, axis=\"t\")\n", + "\n", + "# Split FOVs into sequential batches if too many to image within one interval\n", + "events = utils.apply_fov_batching(events, time_per_fov=2.0)\n", + "\n", + "print(f\"Total events: {len(events)}\")\n", + "utils.events_to_dataframe(events).sort_values(\"timestep\")" + ] }, { "cell_type": "markdown", + "id": "83bc9339", "metadata": {}, "source": [ "### Validate Experiment\n", @@ -233,33 +306,57 @@ "The controller also accepts a `stim_mode` parameter (set in `run_experiment`):\n", "- `\"current\"` (default) -- acquire imaging, run pipeline, then stimulate in the same timepoint\n", "- `\"previous\"` -- stimulate using the mask from the previous timepoint, then acquire (lower latency)" - ], - "id": "83bc9339" + ] }, { "cell_type": "code", "execution_count": null, + "id": "5718ece3", "metadata": {}, "outputs": [], - "source": "ctrl = Controller(mic, pipeline, writer=writer)\nctrl.validate_events(events) # checks channels, positions, pipeline compatibility\n\n# Live status + pause/stop buttons; re-binds when run_experiment starts.\nfrom faro.widgets import ExperimentStatusWidget\n\nviewer.window.add_dock_widget(\n ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n)", - "id": "5718ece3" + "source": [ + "ctrl = Controller(mic, pipeline, writer=writer)\n", + "ctrl.validate_events(events) # checks channels, positions, pipeline compatibility\n", + "\n", + "# Live status + pause/stop buttons; re-binds when run_experiment starts.\n", + "from faro.widgets import ExperimentStatusWidget\n", + "\n", + "viewer.window.add_dock_widget(\n", + " ExperimentStatusWidget(ctrl), name=\"experiment status\", area=\"right\"\n", + ")" + ] }, { "cell_type": "markdown", + "id": "8fc9c376", "metadata": {}, - "source": "### Run experiment\n\nThe optional sleep loop delays acquisition start (set `SLEEP_BEFORE_EXPERIMENT_START_in_H` above).\n\n`ctrl.run_experiment(events, stim_mode=\"current\")` starts the acquisition (non-blocking). Images are stored and the pipeline runs asynchronously; napari-micromanager keeps showing frames in its preview layer during the run, so the live link does not need to be torn down first.", - "id": "8fc9c376" + "source": [ + "### Run experiment\n", + "\n", + "The optional sleep loop delays acquisition start (set `SLEEP_BEFORE_EXPERIMENT_START_in_H` above).\n", + "\n", + "`ctrl.run_experiment(events, stim_mode=\"current\")` starts the acquisition (non-blocking). Images are stored and the pipeline runs asynchronously; napari-micromanager keeps showing frames in its preview layer during the run, so the live link does not need to be torn down first." + ] }, { "cell_type": "code", "execution_count": null, + "id": "e9146e29", "metadata": {}, "outputs": [], - "source": "# Optional: wait before starting (set SLEEP_BEFORE_EXPERIMENT_START_in_H above)\nfor _ in range(0, int(SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600)):\n time.sleep(1)\n\n# napari-micromanager keeps routing frames into the preview layer during the\n# run; no need to tear down the live link first.\nctrl.run_experiment(events, stim_mode=\"current\")", - "id": "e9146e29" + "source": [ + "# Optional: wait before starting (set SLEEP_BEFORE_EXPERIMENT_START_in_H above)\n", + "for _ in range(0, int(SLEEP_BEFORE_EXPERIMENT_START_in_H * 3600)):\n", + " time.sleep(1)\n", + "\n", + "# napari-micromanager keeps routing frames into the preview layer during the\n", + "# run; no need to tear down the live link first.\n", + "ctrl.run_experiment(events, stim_mode=\"current\")" + ] }, { "cell_type": "markdown", + "id": "8c50baa4", "metadata": {}, "source": [ "### Post-processing\n", @@ -268,16 +365,19 @@ "1. `finish_experiment()` flushes remaining pipeline tasks and closes the zarr writer\n", "2. `generate_exp_data_from_tracks()` merges per-FOV track parquet files into a single `exp_data.parquet`\n", "3. The napari live view is reconnected for manual inspection" - ], - "id": "8c50baa4" + ] }, { "cell_type": "code", "execution_count": null, + "id": "6d784fe9", "metadata": {}, "outputs": [], - "source": "ctrl.finish_experiment() # flush pipeline queue and close zarr store\n\nutils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet", - "id": "6d784fe9" + "source": [ + "ctrl.finish_experiment() # flush pipeline queue and close zarr store\n", + "\n", + "utils.generate_exp_data_from_tracks(path) # merge per-FOV tracks into exp_data.parquet" + ] } ], "metadata": { @@ -301,4 +401,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} diff --git a/experiments/21_cell_migration/cell_migration.ipynb b/experiments/21_cell_migration/cell_migration.ipynb index 4f2954b..0547bc7 100644 --- a/experiments/21_cell_migration/cell_migration.ipynb +++ b/experiments/21_cell_migration/cell_migration.ipynb @@ -148,8 +148,9 @@ "import napari\n", "\n", "viewer = napari.Viewer()\n", - "mm_wdg = MainWindow(viewer)\n", - "mm_wdg._mmc = mic.mmc\n", + "mm_wdg = MainWindow(\n", + " viewer, mmcore=mic.mmc\n", + ") # widget to control Micro-Manager from napari\n", "viewer.window.add_dock_widget(mm_wdg)\n", "data_mda_fovs = None\n", "load_from_file = False" @@ -170,7 +171,7 @@ "source": [ "if mic.SET_ROI_REQUIRED:\n", " mic.mmc.clearROI()\n", - " mic.mmc.setROI(mic.ROI_X, mic.ROI_Y, mic.ROI_WIDTH, mic.ROI_HEIGHT)\n" + " mic.mmc.setROI(mic.ROI_X, mic.ROI_Y, mic.ROI_WIDTH, mic.ROI_HEIGHT)" ] }, { @@ -2040,4 +2041,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/experiments/21_cell_migration/cell_migration_test.ipynb b/experiments/21_cell_migration/cell_migration_test.ipynb index fc35011..1797ac3 100644 --- a/experiments/21_cell_migration/cell_migration_test.ipynb +++ b/experiments/21_cell_migration/cell_migration_test.ipynb @@ -154,8 +154,9 @@ "import napari\n", "\n", "viewer = napari.Viewer()\n", - "mm_wdg = MainWindow(viewer)\n", - "mm_wdg._mmc = mic.mmc\n", + "mm_wdg = MainWindow(\n", + " viewer, mmcore=mic.mmc\n", + ") # widget to control Micro-Manager from napari\n", "viewer.window.add_dock_widget(mm_wdg)\n", "data_mda_fovs = None\n", "load_from_file = False" @@ -968,4 +969,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/experiments/22_line_stimulation/line_stimulation.ipynb b/experiments/22_line_stimulation/line_stimulation.ipynb index b163332..58359f6 100644 --- a/experiments/22_line_stimulation/line_stimulation.ipynb +++ b/experiments/22_line_stimulation/line_stimulation.ipynb @@ -213,8 +213,9 @@ "import napari\n", "\n", "viewer = napari.Viewer()\n", - "mm_wdg = MainWindow(viewer)\n", - "mm_wdg._mmc = mic.mmc\n", + "mm_wdg = MainWindow(\n", + " viewer, mmcore=mic.mmc\n", + ") # widget to control Micro-Manager from napari\n", "viewer.window.add_dock_widget(mm_wdg)\n", "data_mda_fovs = None\n", "load_from_file = False" @@ -235,7 +236,7 @@ "source": [ "if mic.SET_ROI_REQUIRED:\n", " mic.mmc.clearROI()\n", - " mic.mmc.setROI(mic.ROI_X, mic.ROI_Y, mic.ROI_WIDTH, mic.ROI_HEIGHT)\n" + " mic.mmc.setROI(mic.ROI_X, mic.ROI_Y, mic.ROI_WIDTH, mic.ROI_HEIGHT)" ] }, { @@ -2509,4 +2510,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From e4a615ac5a9335fd0734206a775c13fb93f1e554 Mon Sep 17 00:00:00 2001 From: Alex Landolt Date: Tue, 2 Jun 2026 13:37:23 +0200 Subject: [PATCH 41/41] Add nbformat to test environement in pyproject.toml (usefull for claude to modify notebooks) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 9c3a5bc..193f3b6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ test = [ # The suite exercises both tracking backends; motile is the non-default # one (trackpy is a core dep). "faro[motile]", + "nbformat", ] motile = [