Skip to content

Replay scrubber, concurrent wash monitoring, run-sequence editing, softer imager check - #19

Merged
Heerpa merged 14 commits into
developfrom
feature/replay-monitoring-seqedit
Oct 21, 2026
Merged

Heerpa merged 14 commits into
developfrom
feature/replay-monitoring-seqedit

Conversation

@Heerpa

@Heerpa Heerpa commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Four operator-facing features for the PycroFlow GUI, each as its own tested commit.

1 · Post-run replay (master scrubber). A run's live streams are recorded to a *_replay.jsonl timeline (seam thumbnail/metrics/state/log — thumbnails already carry image bytes + detection boxes, so no extra capture — plus 0.5 s polls of run-sequence progress and fluidics state). The Run Sequence tab's Progress group gains a ▶ Replay run toggle revealing a timeline slider + play/speed controls; scrubbing re-emits recorded LiveUpdates through the real live panels (image + boxes), drives the fluid schematic (live poll suspended), highlights the active run-sequence step, and replays the fluidics webcam — per-round .avi clips aligned to the timeline via a wall-clock anchor, decoded with the pure-Python AVI reader (no OpenCV) and shown in the Webcams tab.

2 · Concurrent wash/flush monitoring (opt-in). Exchange designs gain monitor_fluid (+ monitor_t_exp / monitor_margin_s). When on, the builder acquires a short movie concurrently with each per-round imager injection and inter-round wash (monitor-start → illumination to acq power → movie sized from the estimated flush duration + margin; the following acquisition's fluid_wait resyncs). Monitoring acquisitions are flagged, excluded from the exchange-round count, and tagged on the registry FOV.

3 · Run-sequence editing. Add / duplicate / delete / move steps from the Run Sequence tab. Any structural edit flags the run as modified (registry records sequence_modified, experiment_type suffixed -modified, badge shown) and runs a consistency check (schemas.check_runnable) that detects orphan waits and circular deadlocks — inline hint after each edit, hard Start gate for edited sequences.

4 · Softer imager verification. Reusing an imager (notably initial_imager re-listed in the rounds) no longer hard-errors — redundant imaging rounds are legitimate (e.g. for testing). Now a non-blocking advisory (design_warnings) on Save/Translate; ordinary repeats in the imagers list aren't flagged.

Branch note

Forked from feature/ibidi-liveint-bundle, which is already merged into develop, so this PR is a clean 8-commit diff (just this work). It targets develop directly because its integration branch has already been promoted — not the usual mid-flight integration-branch flow.

New modules

live_analysis/replay_recorder.py, live_analysis/replay_player.py, monitoring/webcam_replay.py, schemas/protocol_schema.check_runnable, protocols/builder.create_stepset_monitor.

Testing

  • 822 tests pass (new: test_replay.py, test_protocol_schema.py, plus additions to protocols/design/services/GUI suites). The one failure, test_placeholder_when_monet_absent, is pre-existing and environmental (monet installed in this dev container — fails on the base branch too).
  • Lint clean (black 79 + flake8).
  • A high-effort multi-angle code review ran over the diff; one low-severity finding (stale replay after a structural edit) was fixed in the last commit.
  • Instrument-dependent behavior (real concurrent monitor timing, on-rig webcam alignment) is for the acquisition PC and out of scope for CI.

🤖 Generated with Claude Code

Heerpa and others added 14 commits October 15, 2026 07:06
Reusing an imager (notably initial_imager re-listed in the exchange rounds)
previously raised a hard ValidationError, blocking the legitimate case of
deliberately redundant imaging rounds (e.g. for testing). Downgrade it to a
soft advisory: add ExchangeExperiment.design_warnings() + a module-level
design_warnings() collector, surfaced non-blocking in the Experiment Design
tab on Save/Translate. Plain repeats within the imagers list (ordinary
Exchange-PAINT) are deliberately NOT warned about.

Also lands check_runnable() in protocol_schema (orphan-wait + deadlock-cycle
detection) ahead of the sequence-editing work that gates on it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion

Operators can now add/duplicate/delete/move Run Sequence steps from the Run
Sequence tab (edit toolbar operating on the selected list/row). Any structural
edit flags the run as modified: the run metadata records sequence_modified and
suffixes experiment_type with -modified (build_experiment_payload, threaded
through live_run), and a '⚠ modified sequence' badge shows in the tab.

Editing can introduce a deadlock, so ExperimentService.check_runnable() (over
schemas.check_runnable) detects orphan waits and circular cross-subsystem
waits, mirroring the orchestrator's (target,value) matching incl. the legacy
'<target> <value>' prefix. It runs inline after each edit (non-blocking hint)
and as a hard Start gate — scoped to hand-edited sequences so un-edited/legacy
protocols keep their existing behavior.

Tests: test_protocol_schema (new) for check_runnable; service modified-flag +
start-gate scoping; headless GUI add/delete/move + inline-deadlock; payload
modified flag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an opt-in (Exchange design: monitor_fluid, monitor_t_exp, monitor_margin_s)
that, for each per-round imager injection and inter-round wash, acquires a
short movie CONCURRENTLY with the fluid step so the operator can watch washing/
flushing efficiency live. create_stepset_monitor emits a fluid 'monitor-start'
signal before the injection, raises the laser to acquisition power via illu
('monitor-power'), and sizes the img movie from the estimated flush duration
(protocols.timing) + margin — the following acquisition's fluid_wait resyncs.

Monitoring acquires are tagged (monitoring: True, '(wash monitor)' name): the
Run Sequence tab excludes them from the exchange-round count/goto (_is_monitoring,
mirroring _is_dark) and live_run flags the FOV so monitoring movies aren't
mistaken for scientific datasets.

Tests: builder monitor insertion + paired signals + frame scaling +
check_runnable clean + off-by-default; GUI round-count exclusion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record a run's live streams to a JSONL timeline and scrub them afterwards.
ReplayRecorder taps the live seam (thumbnails carry image bytes + boxes, so no
extra capture) and polls progress + fluid-state at 0.5 s; ReplayController
loads the timeline and, on seek, reconstructs each stream as of time T and
pushes it to sinks. The Run Sequence tab's Progress group gains a '▶ Replay
run' toggle that reveals a timeline slider + play/speed controls; scrubbing
re-emits recorded LiveUpdates through the real live panels (image + boxes),
drives the fluid schematic (live poll suspended), and highlights the active
run-sequence step. main_window owns the recorder lifecycle (start on run start,
finalize + offer replay on finish) and wires the controller sinks across tabs.

Webcam replay (per-round .avi clips already on disk) is a documented follow-up.

Tests: test_replay (codec round-trip, recorder, controller seek semantics);
headless GUI scrubber drives the step highlight + re-emits live updates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ager-check

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Defensively finalize any still-open recording at the top of
_start_replay_recording so a prior run or a failed recorder start cannot leave
a stale _replay_hub_client that _stop_replay_recording would later remove from
the wrong hub. (code-review follow-up)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete the replay feature's webcam leg. The recorder now stamps a wall-clock
anchor (t0_utc) in the timeline's meta header; WebcamReplaySource
(monitoring/webcam_replay.py) discovers the run's per-round .avi clips, maps
each clip's UTC-stamped filename to a run-relative ms position, and — on seek —
decodes the composite frame on screen at that moment (pure-Python AVI reader,
no OpenCV) via a one-clip cache. ReplayController gains a set_webcam sink
(driven with the scrub time); the Webcams tab gains show_replay_frame +
set_replay_mode (live preview suspended). main_window aligns the clips using
the monitoring controller's new output_dir/fps accessors and wires the sink.

Also fixes a latent F841 (unused var) in the sequence-edit GUI test.

Tests: WebcamReplaySource discovery/alignment/seek over real AVI clips written
by RawAviWriter; controller webcam-sink dispatch; webcams-tab replay display.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Editing a finished run's sequence (add/delete/move) desyncs the step lists
from the recorded timeline, so a subsequent scrub would highlight the wrong
steps. Clear the replay controller on any structural edit (exits replay mode,
disables the button). (code-review follow-up)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
black 25.9.0 flagged these under 'black --check .'; my local pre-commit checks
used 'black --check -q … | tail' which hid the 'would reformat' output and
masked the non-zero exit through the pipe. Reformatted; 'black --check .' and
'flake8 .' now pass repo-wide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The replay slider was already a free seek (drag to any moment; the speed combo
only sets autoplay rate). Add protocol-position jumping: in replay mode,
clicking a step or picking a round seeks the timeline to when that step/round
became active, via ReplayController.first_time_reaching(system, index) over the
recorded progress snapshots. Editing stays available in replay (and still
clears the stale replay).

Tests: controller step->time mapping (incl. never-reached -> None); GUI
click-a-step jumps the slider to the recorded time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replay was limited to the just-finished run (everything in memory). Make a run
self-contained and reopenable:

- The recorder embeds the run's compiled protocol in the timeline's meta header
  (alongside t0_utc + run_id), so a *_replay.jsonl file alone reconstructs both
  the timeline and the step lists. ReplayController exposes run_id + protocol.
- 'Open past run…' in the Run Sequence tab picks a saved *_replay.jsonl (its run
  id disambiguates multiple runs in one save_dir), loads the embedded protocol
  into the view, and enters replay. main_window.open_past_run wires it.
- WebcamReplaySource.from_run finds the run's clips under the file's folder and
  its fluidics_cam[_N] subfolders, filtered by (sanitized) run id so clips from
  other runs in the same folder are not mixed in; from_dir gained a run_id
  filter.

Tests: recorder embeds protocol; controller exposes run_id/protocol; from_run
subfolder search + run-id filter; headless open_past_run loads protocol +
enters replay + shows a webcam frame.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
During replay the overall + steps-in-round bars and the Round-k/N status line
showed stale live state; only the step lists were shaded. Factor the bar
rendering out of _poll_progress into shared helpers (_set_overall_bar,
_set_step_status, _current_round_counts) and have replay_set_progress drive the
same bars from the scrubbed progress — without the live-only side effects
(round stopwatch, substep bars, finish detection). So scrubbing now moves the
progress bars to the run's position at time T, matching the live display.

Tests: replay scrubber asserts the overall bar/count reflect the scrubbed
progress.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The replay transport lived inside the Run Sequence tab, so scrubbing was only
possible while on that tab. Extract it into a ReplayBar widget
(gui/widgets/replay_bar.py) placed above the QTabWidget, so a finished (or
opened-from-disk) run can be scrubbed while viewing ANY tab's data — the
controller's sinks already drive the live view / webcam / fluid schematic /
step-highlight regardless of which tab is in front.

The Run Sequence tab keeps only its replay responsibilities: replay_set_progress
(the progress sink) and routing step/round clicks back to the bar as timeline
jumps (on_replay_seek_step) plus dropping a stale replay on edit
(on_sequence_edited). main_window owns the bar, wires its sinks across the tabs,
and set_replay_active tells the tab when clicks should jump the timeline.

Tests updated to drive w.replay_bar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Heerpa
Heerpa merged commit 4116a80 into develop Oct 21, 2026
2 checks passed
@Heerpa
Heerpa deleted the feature/replay-monitoring-seqedit branch October 21, 2026 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant