diff --git a/.agents/skills/protocol-yaml/SKILL.md b/.agents/skills/protocol-yaml/SKILL.md index 8324f96b..005b3ea9 100644 --- a/.agents/skills/protocol-yaml/SKILL.md +++ b/.agents/skills/protocol-yaml/SKILL.md @@ -55,7 +55,8 @@ it does NOT advance the protocol clock. Only `wait` commands do. Therefore: Editing only ONE of the pair is **masked** by the other — "the timeline won't update" is almost always a half-edited pair, not a render bug. - **Bind both fields to one anchor** (`duration: *show_dur` on both) so they can never - drift — the `open_dur`/`cl_dur` pattern from `protocols/fictrac_direction_test`. + drift — the `open_dur`/`cl_dur` pattern from `fictrac_direction_test` (private + `reiserlab/arena-experiments`, `protocols/shared/`). - Waits between two `trialParams` in one condition define how long the FIRST one actually shows (the second overrides it). Waits longer than the duration = intentional hold/blank; fine, but mean it. @@ -163,7 +164,7 @@ rejection). Authored as an added rotational **velocity**: A negative frequency is a harmless no-op (warned): negate `bias_amplitude` instead. - `stopClosedLoop` clears the bias automatically. Reference: `docs/development/closed-loop-bias.md`; worked example: - `protocols/fictrac_bias_test.yaml`. + `protocols/shared/fictrac_bias_test.yaml` in the private `reiserlab/arena-experiments`. **Per-trial start position = the trialParams `frame_index` (Studio v0.91 / bridge 3.4).** A closed-loop trial opens on the Mode-3 `trialParams` `frame_index` (0-based, wraps mod the pattern's diff --git a/course/cshl-2026/docs/fictrac.md b/course/cshl-2026/docs/fictrac.md index 3ae857ee..0a7a7221 100644 --- a/course/cshl-2026/docs/fictrac.md +++ b/course/cshl-2026/docs/fictrac.md @@ -119,7 +119,7 @@ Closed-loop and the live scope need the **bridge** running on the rig computer: bridge"* to a live trace once data flows. Closed-loop trials show a green **CLOSED LOOP** tag. -**Quick check:** load [`fictrac_direction_test`](protocols/README.md) — it runs +**Quick check:** load [`fictrac_direction_test`](protocols/README.md) (since 2026-10-29 in the lab's private `reiserlab/arena-experiments`, `protocols/shared/`) — it runs open-loop motion first, then a closed-loop block that the fly steers. If the closed-loop block responds to nudging the ball, the bridge is working. diff --git a/course/cshl-2026/docs/protocols/README.md b/course/cshl-2026/docs/protocols/README.md index 6daded02..963fbed8 100644 --- a/course/cshl-2026/docs/protocols/README.md +++ b/course/cshl-2026/docs/protocols/README.md @@ -53,7 +53,8 @@ Animated pattern previews are embedded on all four protocol pages. FicTrac): full-field brightness, panel map, a moving bar, and LED levels 2/5/10/20/40%. Use it to confirm a rig works after setup. - **`fictrac_direction_test`** — open-loop motion then a closed-loop block, to - confirm the [FicTrac](../fictrac.md) bridge is working. + confirm the [FicTrac](../fictrac.md) bridge is working. Since 2026-10-29 it lives in + the lab's private `reiserlab/arena-experiments` (`protocols/shared/`), not in the site library. - **`optomotor_led_test`** — a slow grating with the LED switched on for the last few seconds; the reference example of "fire a command partway through a trial." diff --git a/docs/development/rig-software-repos-and-runners.md b/docs/development/rig-software-repos-and-runners.md index 37ec574d..8df3ffc8 100644 --- a/docs/development/rig-software-repos-and-runners.md +++ b/docs/development/rig-software-repos-and-runners.md @@ -111,7 +111,7 @@ stay as history. | **pyDisplayTools** | `tools/patfile.py` (pattern-file reader; the repo already has `pyPatternTools`), `nodes/arena_display.py` (Python port of `js/panel-color.js`, okey-cokey branch), the pure functions of `fictrac-bridge/bridge.py` (`Pipeline` mapping, `frame_schema_line`, `compact_arena_command`), `fictrac-bridge/fictrac_sim.py` moves to rig-nodes as a device simulator | | **stays** (gateway, export, composition) | `nodes/bridge_node.py`, `nodes/mcap_convert.py`, `nodes/_rows.py` (behavior_v2 `kf` rows), `nodes/export_log.py`, `nodes/rig.py` (arena composition; its generic preflight/supervision helpers are extracted to rig-nodes first), `pixi.toml`, `pixi.lock`, `.gitignore`, `tests/studio_emulator.py`, `tests/check_runlog_readers.js`, the gateway half of `tests/test_ros2_poc.py` (node-level tests move with their nodes) | | **stays** (arena visualization) | `nodes/viz_node.py`, `tests/test_viz_node.py`, the arena derivation in `nodes/foxglove_json.py`, `foxglove/arena-fictrac.json`, `foxglove/arena-bias.json`, `docs/foxglove-arena-layout.json`, `tools/tiff_run_to_mcap.py`; from okey-cokey: `nodes/stimulus_node.py`, `nodes/stimulus_core.py`, `nodes/dashboard_replay.py`, `tests/test_stimulus.py`, `tests/test_stimulus_live.py`, `foxglove/arena-okey-cokey.json`, `tests/fixtures/protocols-2026-10-14/*` | -| **stays** (rig-specific config and records) | `README.md`, `WINDOWS-TEST.md`, `SCANIMAGE-DAY.md`, `scanimage-day-2026-10-16.md`, `windows-results-ww18-2026-10-15.md`, `docs/plan-b-explainer*.html`, `docs/build-standalone-explainer.py`, `docs/img/*`; from okey-cokey: `config/bias/okey-cokey-bfs-u3-51s5m.json`, `tools/okey-cokey-start.ps1`, `tools/bias-ufmf.ps1`, the `okey-cokey-*.md` run sheets, `HANDOVER-okey-cokey-2026-10-21.md`, `docs/okey-cokey-foxglove-dashboard-plan.md` | +| **stays** (tool code and generic records) | `README.md`, `WINDOWS-TEST.md`, `docs/plan-b-explainer*.html`, `docs/build-standalone-explainer.py`, `docs/img/*`, `foxglove/arena-okey-cokey.json` (a layout `rig.py --foxglove` loads), `tools/ufmf.py`, `tools/patfile.py`. **Moved 2026-10-29 (LAB-227) to the private `reiserlab/arena-experiments` `rigs//`:** `windows-results-ww18-2026-10-15.md` → `rigs/ww18/`; from okey-cokey → `rigs/ww14/`: `config/bias/okey-cokey-bfs-u3-51s5m.json`, `tools/okey-cokey-start.ps1`, `tools/bias-ufmf.ps1`, the `okey-cokey-*.md` run sheets, `HANDOVER-okey-cokey-2026-10-21.md`, `docs/okey-cokey-foxglove-dashboard-plan.md`. `SCANIMAGE-DAY.md` and `scanimage-day-2026-10-16.md` went to the private 2P repo with #284. | #### Amendment 2026-10-28: the imaging work is private diff --git a/protocols/MOVED.md b/protocols/MOVED.md new file mode 100644 index 00000000..b3c06e27 --- /dev/null +++ b/protocols/MOVED.md @@ -0,0 +1,29 @@ +# Protocols and rig records that left this repo (2026-10-29, LAB-227) + +Experiment material now lives in private repos so that a rig's protocol tweaks never need a +webDisplayTools release. The rule: a protocol stays here only if a tool, test, QC profile or doc +fixture depends on it. Copies were made from commit `59a377b` (Release 2026-10-28); each destination +has a `PROVENANCE.md`. Open a moved protocol in the Studio, signed in, with +`arena_studio.html?repo=&p=`. + +| Was here | Now (repo → path) | +|---|---| +| `protocols/bias_camera_closed_loop_poc.yaml` | `reiserlab/arena-experiments` → `protocols/ww18/bias_camera_closed_loop_poc.yaml` | +| `protocols/bias_camera_closed_loop_poc_ww18.yaml` | `reiserlab/arena-experiments` → `protocols/ww18/bias_camera_closed_loop_poc_ww18.yaml` | +| `protocols/bias_camera_closed_loop_poc_ww18_soak10.yaml` | `reiserlab/arena-experiments` → `protocols/ww18/bias_camera_closed_loop_poc_ww18_soak10.yaml` | +| `protocols/fictrac_bias_test.yaml` | `reiserlab/arena-experiments` → `protocols/shared/fictrac_bias_test.yaml` | +| `protocols/fictrac_direction_test.yaml` | `reiserlab/arena-experiments` → `protocols/shared/fictrac_direction_test.yaml` | +| `protocols/fictrac_coupling_bias_fly_test.yaml` | `reiserlab/arena-experiments` → `protocols/shared/fictrac_coupling_bias_fly_test.yaml` | +| `protocols/led_activation_quadrant_test.yaml` + `_patterns/` | `reiserlab/arena-experiments` → `protocols/shared/led_activation_quadrant_test.yaml` + `_patterns/` | +| `protocols/mode3_drill_1trial.yaml` | `reiserlab/arena-experiments` → `protocols/shared/mode3_drill_1trial.yaml` | +| `protocols/g6_2p_color_led_sweep.yaml` + `_patterns/` | `reiserlab/scanimage-2026-bergamo` → `protocols/g6_2p_color_led_sweep.yaml` + `_patterns/` | +| `protocols/g6_2x10_2p_diagnostics.yaml` + `_patterns/` | `reiserlab/scanimage-2026-bergamo` → `protocols/g6_2x10_2p_diagnostics.yaml` + `_patterns/` | +| `protocols/g6_2x10_2p_duty_sweep.yaml` | `reiserlab/scanimage-2026-bergamo` → `protocols/g6_2x10_2p_duty_sweep.yaml` | +| `rig/windows-results-ww18-2026-10-15.md` | `reiserlab/arena-experiments` → `rigs/ww18/` | +| `rig/okey-cokey-pc-*.md`, `rig/HANDOVER-okey-cokey-2026-10-21.md`, `rig/docs/okey-cokey-foxglove-dashboard-plan.md`, `rig/config/bias/okey-cokey-bfs-u3-51s5m.json`, `rig/tools/okey-cokey-start.ps1`, `rig/tools/bias-ufmf.ps1` | `reiserlab/arena-experiments` → `rigs/ww14/` (same names; `config/` and `tools/` subfolders) | + +A copy of `g6_2p_color_led_sweep.yaml` stays as the test fixture `tests/fixtures/v3_g6_2p_color_led_sweep.yaml`. +The pattern generators `scripts/make-color-optomotor-patterns.js --gs16` and +`scripts/make-2p-diagnostic-patterns.js` now take `--out ` pointing into the 2P repo. + +Pages under `docs/development/` that cite the old paths are historical records and were not edited. diff --git a/protocols/bias_camera_closed_loop_poc.yaml b/protocols/bias_camera_closed_loop_poc.yaml deleted file mode 100644 index 19069352..00000000 --- a/protocols/bias_camera_closed_loop_poc.yaml +++ /dev/null @@ -1,136 +0,0 @@ -version: 3 - -# BIAS camera + FicTrac closed loop through the ROS 2 bridge node (LAB-303 connectors). -# The `camera` plugin (BiasPlugin) is driven by the WEB runner: each command goes to the -# bridge as connector_command, the bridge's BIAS node runs it over BIAS's HTTP port, and -# the reply (with the video artifact for startRecording) lands in the run log. -# -# cam_start : connect → startRecording (one video for the whole run) → getTimestamp -# 2 closed-loop trials (none, then a constant +90°/s bias), 15 s each -# cam_sync : getTimestamp (a mid-run sync point against the bridge clock) -# cam_stop : getTimestamp → stopRecording -# -# Video path: relative to the bias node's --video-dir (like MATLAB's experiment folder). - -experiment_info: - name: "BIAS camera + closed loop — ROS 2 PoC Mac bench (LAB-303)" - author: "webDisplayTools" - -rig: "./configs/rigs/cshl_g6_2x10_ball.yaml" - -plugins: - - name: "fictrac" - type: "class" - matlab: - class: "FicTracPlugin" - config: - bridge_url: "ws://localhost:8765" - fictrac_port: 60000 - proto: "udp" - offset: 0 - - name: "camera" - type: "class" - matlab: - class: "BiasPlugin" - -variables: - cl_dur: &cl_dur 15 - -experiment: - - name: "camera_run" - trials: - - "cam_start" - - "cl_bias_none" - - "cam_sync" - - "cl_bias_constant" - - "cam_stop" - repetitions: 1 - - "shutdown" - -conditions: - - name: "cam_start" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "connect" - - type: "plugin" - plugin_name: "camera" - command_name: "startRecording" - params: - filename: "poc_studio_run.avi" - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "wait" - duration: 1 - - - name: "cl_bias_none" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "G6_2x10_grat_rot_20px_50pct_4c-B-Y" - pattern_ID: 1 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "none" - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cam_sync" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "wait" - duration: 1 - - - name: "cl_bias_constant" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "G6_2x10_grat_rot_20px_50pct_4c-B-Y" - pattern_ID: 1 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "constant" - bias_amplitude: 90 - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cam_stop" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "plugin" - plugin_name: "camera" - command_name: "stopRecording" - - type: "wait" - duration: 1 - - - name: "shutdown" - commands: - - type: "controller" - command_name: "stopDisplay" - - type: "controller" - command_name: "allOff" diff --git a/protocols/bias_camera_closed_loop_poc_ww18.yaml b/protocols/bias_camera_closed_loop_poc_ww18.yaml deleted file mode 100644 index 7a3d89c9..00000000 --- a/protocols/bias_camera_closed_loop_poc_ww18.yaml +++ /dev/null @@ -1,139 +0,0 @@ -version: 3 -# ww18 variant (2026-10-15): the site library's G6 2x10 rotating square grating, SD slot 2 -# (patterns/g6_2x10/patterns/002_grating_sq.pat, 20 frames), in place of the Mac bench card's -# G6_2x10_grat_rot_20px_50pct_4c-B-Y. Otherwise identical to bias_camera_closed_loop_poc.yaml. - -# BIAS camera + FicTrac closed loop through the ROS 2 bridge node (LAB-303 connectors). -# The `camera` plugin (BiasPlugin) is driven by the WEB runner: each command goes to the -# bridge as connector_command, the bridge's BIAS node runs it over BIAS's HTTP port, and -# the reply (with the video artifact for startRecording) lands in the run log. -# -# cam_start : connect → startRecording (one video for the whole run) → getTimestamp -# 2 closed-loop trials (none, then a constant +90°/s bias), 15 s each -# cam_sync : getTimestamp (a mid-run sync point against the bridge clock) -# cam_stop : getTimestamp → stopRecording -# -# Video path: relative to the bias node's --video-dir (like MATLAB's experiment folder). - -experiment_info: - name: "BIAS camera + closed loop — ROS 2 PoC, Windows rig ww18 (LAB-303)" - author: "webDisplayTools" - -rig: "./configs/rigs/cshl_g6_2x10_ball.yaml" - -plugins: - - name: "fictrac" - type: "class" - matlab: - class: "FicTracPlugin" - config: - bridge_url: "ws://localhost:8765" - fictrac_port: 60000 - proto: "udp" - offset: 0 - - name: "camera" - type: "class" - matlab: - class: "BiasPlugin" - -variables: - cl_dur: &cl_dur 15 - -experiment: - - name: "camera_run" - trials: - - "cam_start" - - "cl_bias_none" - - "cam_sync" - - "cl_bias_constant" - - "cam_stop" - repetitions: 1 - - "shutdown" - -conditions: - - name: "cam_start" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "connect" - - type: "plugin" - plugin_name: "camera" - command_name: "startRecording" - params: - filename: "poc_studio_run.avi" - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "wait" - duration: 1 - - - name: "cl_bias_none" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "none" - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cam_sync" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "wait" - duration: 1 - - - name: "cl_bias_constant" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "constant" - bias_amplitude: 90 - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cam_stop" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "plugin" - plugin_name: "camera" - command_name: "stopRecording" - - type: "wait" - duration: 1 - - - name: "shutdown" - commands: - - type: "controller" - command_name: "stopDisplay" - - type: "controller" - command_name: "allOff" diff --git a/protocols/bias_camera_closed_loop_poc_ww18_soak10.yaml b/protocols/bias_camera_closed_loop_poc_ww18_soak10.yaml deleted file mode 100644 index 1cdcd5c8..00000000 --- a/protocols/bias_camera_closed_loop_poc_ww18_soak10.yaml +++ /dev/null @@ -1,140 +0,0 @@ -version: 3 -# ww18 SOAK variant (2026-10-15): both closed-loop epochs 300 s (~10 min run). -# ww18 variant (2026-10-15): the site library's G6 2x10 rotating square grating, SD slot 2 -# (patterns/g6_2x10/patterns/002_grating_sq.pat, 20 frames), in place of the Mac bench card's -# G6_2x10_grat_rot_20px_50pct_4c-B-Y. Otherwise identical to bias_camera_closed_loop_poc.yaml. - -# BIAS camera + FicTrac closed loop through the ROS 2 bridge node (LAB-303 connectors). -# The `camera` plugin (BiasPlugin) is driven by the WEB runner: each command goes to the -# bridge as connector_command, the bridge's BIAS node runs it over BIAS's HTTP port, and -# the reply (with the video artifact for startRecording) lands in the run log. -# -# cam_start : connect → startRecording (one video for the whole run) → getTimestamp -# 2 closed-loop trials (none, then a constant +90°/s bias), 15 s each -# cam_sync : getTimestamp (a mid-run sync point against the bridge clock) -# cam_stop : getTimestamp → stopRecording -# -# Video path: relative to the bias node's --video-dir (like MATLAB's experiment folder). - -experiment_info: - name: "BIAS camera + closed loop — ROS 2 PoC, Windows rig ww18, 10-min soak (2 x 300 s closed loop) (LAB-303)" - author: "webDisplayTools" - -rig: "./configs/rigs/cshl_g6_2x10_ball.yaml" - -plugins: - - name: "fictrac" - type: "class" - matlab: - class: "FicTracPlugin" - config: - bridge_url: "ws://localhost:8765" - fictrac_port: 60000 - proto: "udp" - offset: 0 - - name: "camera" - type: "class" - matlab: - class: "BiasPlugin" - -variables: - cl_dur: &cl_dur 300 - -experiment: - - name: "camera_run" - trials: - - "cam_start" - - "cl_bias_none" - - "cam_sync" - - "cl_bias_constant" - - "cam_stop" - repetitions: 1 - - "shutdown" - -conditions: - - name: "cam_start" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "connect" - - type: "plugin" - plugin_name: "camera" - command_name: "startRecording" - params: - filename: "poc_studio_run.avi" - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "wait" - duration: 1 - - - name: "cl_bias_none" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "none" - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cam_sync" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "wait" - duration: 1 - - - name: "cl_bias_constant" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "constant" - bias_amplitude: 90 - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cam_stop" - commands: - - type: "plugin" - plugin_name: "camera" - command_name: "getTimestamp" - - type: "plugin" - plugin_name: "camera" - command_name: "stopRecording" - - type: "wait" - duration: 1 - - - name: "shutdown" - commands: - - type: "controller" - command_name: "stopDisplay" - - type: "controller" - command_name: "allOff" diff --git a/protocols/fictrac_bias_test.yaml b/protocols/fictrac_bias_test.yaml deleted file mode 100644 index 2e936592..00000000 --- a/protocols/fictrac_bias_test.yaml +++ /dev/null @@ -1,306 +0,0 @@ -version: 3 - -# ============================================================================ -# FicTrac closed-loop BIAS (disturbance) test — web runner only. -# -# A "bias" is a smooth disturbance added to the closed loop, so the display -# keeps rotating even when the fly holds still. Sweeping it lets you measure how -# well the fly REJECTS the disturbance (does it counter-turn to null the motion?). -# -# Uses ONLY built-in g6_2x10 patterns already on the SD card: -# 4 frame2_h_ccw_200f (a 200-frame full-azimuth rotation) -# -# The bias is authored as an added rotational VELOCITY in deg/s (`bias_amplitude` -# = the PEAK velocity), which the bridge integrates into a bias ANGLE and folds -# into its heading→frame-index mapping. At the rig's 1.8°/frame pitch (360/200 px), -# a bias of 90 deg/s advances the display 90°/s = 50 frames/s — independent of the -# closed-loop `coupling` (default 1 = the display follows the ball 1:1). -# -# bias_type what the display does (with a still fly) -# none nothing — the plain closed-loop baseline -# constant rotates steadily, one direction, forever -# sine swings smoothly back and forth over ±A/(2πf) -# square constant-speed rotation that reverses, over ±A/(4f) -# -# DIRECTION (confirmed on the arena, 2026-08-04, at the normal positive gain 1.8): -# a POSITIVE bias_amplitude turns the display CLOCKWISE — equivalently, the pattern -# sweeps rightward across the fly's visual field. Negative is counter-clockwise. -# This is the sign your analysis must assume; get it backwards and a fly correctly -# counter-turning against the disturbance will read as following it. -# -# The periodic waveforms are ZERO-MEAN in position: the display is pushed equally -# left and right rather than drifting to one side, and always starts at the frame -# it was already on (no jump at onset). Note the excursion SHRINKS as frequency -# rises for a fixed amplitude — at 90 deg/s the sine covers ±28.6° at 0.5 Hz but -# only ±14.3° at 1 Hz. Full semantics: docs/development/closed-loop-bias.md. -# -# DURATION knobs (`variables:` below): cl_dur drives every trial's trialParams -# duration AND its bracketing wait, so they always move together. -# -# TIMING — the `wait` is load-bearing in every condition here: it holds the -# closed loop ACTIVE for cl_dur before stopClosedLoop fires. Drop it and the -# stop fires immediately, so no disturbance would ever be seen. -# -# Total ≈ 4 min at defaults (8 trials × 30 s). Needs the FicTrac bridge -# (`pixi run bridge`); with no fly, `pixi run sim` supplies synthetic heading. -# NOTE (web-first): MATLAB has no FicTracPlugin (its closed loop is analog -# Mode 4, computed on the controller, where the host cannot inject a bias), so -# this protocol runs on the WEB runner only. -# ============================================================================ - -experiment_info: - name: "FicTrac closed-loop bias (disturbance) test" - author: "webDisplayTools" - -rig: "./configs/rigs/cshl_g6_2x10_ball.yaml" - -plugins: - - name: "fictrac" - type: "class" - matlab: - class: "FicTracPlugin" - config: - bridge_url: "ws://localhost:8765" - fictrac_port: 60000 - proto: "udp" - offset: 0 - -variables: - # ── Duration knob ────────────────────────────────────────────────────────── - cl_dur: &cl_dur 30 # seconds per closed-loop trial (drives duration AND wait) - # ── Disturbance knobs ────────────────────────────────────────────────────── - # amp is the PEAK velocity in deg/s; freq only applies to sine/square. Change - # these once here (or in the Edit view's Variables panel) to rescale the sweep. - bias_amp: &bias_amp 90 # 90 deg/s = 50 frames/s at gain 1.8 - bias_amp_slow: &bias_amp_slow 30 # a gentler disturbance for comparison - bias_freq: &bias_freq 0.5 # Hz — one back-and-forth cycle every 2 s - bias_freq_fast: &bias_freq_fast 1.0 # Hz — halves the position excursion - -experiment: - - name: "bias_sweep" - trials: - - "cl_bias_none" - - "cl_bias_constant" - - "cl_bias_constant_rev" - - "cl_bias_sine" - - "cl_bias_sine_fast" - - "cl_bias_sine_slow" - - "cl_bias_square" - - "cl_bias_square_fast" - repetitions: 1 - - "shutdown" - -conditions: - # NOTE: no explicit fictrac connect/disconnect. The runner auto-connects the - # bridge at run start, and must NOT disconnect it at the end — the recorded-run - # log is exported from the bridge AFTER the sequence finishes, so a - # protocol-level disconnect would close the bridge before the log commits. - - # ---- baseline: closed loop with NO disturbance ------------------------------ - # The control condition. Omitting bias_type entirely is equivalent to - # bias_type "none"; it is spelled out here so the contrast is obvious. - - name: "cl_bias_none" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "none" - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- constant: a steady drift the fly must walk against -------------------- - # bias_frequency is ignored by `constant` (there is no period), so it is omitted. - # NEGATE bias_amplitude to reverse the direction — negating the FREQUENCY does - # nothing, because the waveform is even in frequency. - - name: "cl_bias_constant" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "constant" - bias_amplitude: *bias_amp - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_bias_constant_rev" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "constant" - bias_amplitude: -90 - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- sine: the classic frequency-response disturbance ---------------------- - # Position excursion is ±A/(2πf): ±28.6° at 90 deg/s + 0.5 Hz, ±14.3° at 1 Hz. - - name: "cl_bias_sine" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "sine" - bias_amplitude: *bias_amp - bias_frequency: *bias_freq - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_bias_sine_fast" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "sine" - bias_amplitude: *bias_amp - bias_frequency: *bias_freq_fast - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_bias_sine_slow" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "sine" - bias_amplitude: *bias_amp_slow - bias_frequency: *bias_freq - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- square: constant-speed rotation that reverses ------------------------- - # Position is a symmetric triangle spanning ±A/(4f): ±45° at 90 deg/s + 0.5 Hz. - - name: "cl_bias_square" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "square" - bias_amplitude: *bias_amp - bias_frequency: *bias_freq - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_bias_square_fast" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - bias_type: "square" - bias_amplitude: *bias_amp - bias_frequency: *bias_freq_fast - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- shutdown -------------------------------------------------------------- - # Blank the arena only. Do NOT disconnect the bridge here — the run-log export - # runs after the sequence, and needs the bridge still connected. - - name: "shutdown" - commands: - - type: "controller" - command_name: "stopDisplay" - - type: "controller" - command_name: "allOff" diff --git a/protocols/fictrac_coupling_bias_fly_test.yaml b/protocols/fictrac_coupling_bias_fly_test.yaml deleted file mode 100644 index 21ce8efb..00000000 --- a/protocols/fictrac_coupling_bias_fly_test.yaml +++ /dev/null @@ -1,493 +0,0 @@ -version: 3 - -# ============================================================================ -# Closed-loop COUPLING × BIAS — a first test on a real fly (web runner, Mode 3) -# -# Needs Arena Studio v0.85+ and bridge 3.3+ (`pixi run bridge` prints the version). -# Pattern: the built-in 200-frame full-azimuth rotation on the G6 2×10 card: -# 4 frame2_h_ccw_200f (1 frame = 1 azimuth pixel = 1.8°, the pitch the rig supplies) -# -# COUPLING SIGN. On the fly-on-ball rigs the NATURAL, stabilizing closed loop is -# coupling **−1** (it is exactly what the old `gain: -1.8` did: same frame index for -# the same turn). `k_nat` below is that value; every other coupling is expressed -# relative to it, so flipping ONE number adapts the whole protocol to a +1 rig: -# k_nat -1 natural closed loop (the display counter-rotates the fly's turn 1:1) -# k_under -0.75 the world turns LESS than the ball -# k_over -1.25 the world turns MORE than the ball -# k_rev +1 reversed — the sanity check; the fly should NOT be able to stabilize -# -# BIAS SIGN (bridge 3.3): the disturbance is defined in DISPLAY degrees and is NOT -# scaled by the coupling — a positive bias_amplitude turns the display CLOCKWISE from -# above (pattern sweeps rightward across the fly's view) on every rig, including the -# −1 rigs. (Under the old `gain: -1.8` it was counter-clockwise — do not carry that over.) -# -# BLOCKS (20 s closed-loop trials, 5 s dark ITI, 2 repetitions each, randomized): -# A. coupling only: k_nat · k_under · k_over · k_rev -# B. bias at k_nat: constant ±30 °/s (one revolution per 12 s) -# sine 30 °/s @ 0.25 Hz (position ±19°) · sine 60 °/s @ 0.5 Hz (±19°) -# square 30 °/s @ 0.25 Hz (position ±30°) -# C. bias × coupling: k_under + constant 30 · k_over + constant 30 -# k_under + sine 30 @ 0.25 · k_over + sine 30 @ 0.25 -# coupling 0 + constant 30 — OPEN-LOOP REPLAY: the display -# ignores the fly and just drifts; the control for block B -# plus a k_nat baseline at the start and the end. ≈ 13 min total. -# -# WHAT TO LOOK FOR. Bridge 3.3 keeps the heading unwrapped, so the fractional -# couplings must show NO once-per-revolution jump (the old `gain` bug). The report -# (`pixi run python scripts/closed-loop-report.py --svg cl.svg`) gives, -# per trial, the fly's turning, the feature's frontal fraction, the reconstructed bias -# and a REJECTION index (1 = fully counter-turned the bias, 0 = ignored it). Expect a -# high frontal fraction at k_nat and none at k_rev; rejection > 0 on block B; ≈ 0 -# rejection and a steady 30 °/s drift on the coupling-0 control; block C shows how a -# weaker / stronger loop changes the rejection. -# -# Dry run without a fly: `pixi run sim -- --model fly --kp 2 --frame-dir -1` for a −1 -# rig (docs/development/model-fly-bench.md). Web-only: MATLAB has no FicTracPlugin. -# ============================================================================ - -experiment_info: - name: "Closed-loop coupling × bias — fly test" - author: "webDisplayTools" - -rig: "./configs/rigs/cshl_g6_2x10_ball.yaml" - -plugins: - - name: "fictrac" - type: "class" - matlab: - class: "FicTracPlugin" - config: - bridge_url: "ws://localhost:8765" - fictrac_port: 60000 - proto: "udp" - offset: 0 - -variables: - # ── durations (drive trialParams duration AND the closed-loop wait together) ── - cl_dur: &cl_dur 20 # seconds per closed-loop trial - iti_dur: &iti_dur 5 # dark inter-trial interval, s - # ── couplings — flip the signs of all four for a +1 rig ───────────────────── - k_nat: &k_nat -1 # natural closed loop on the fly-on-ball rigs (= the old gain -1.8) - k_under: &k_under -0.75 # the world turns 0.75× the ball - k_over: &k_over -1.25 # the world turns 1.25× the ball - k_rev: &k_rev 1 # reversed — the fly cannot stabilize - # ── disturbances (peak velocity deg/s, frequency Hz) — gentle on purpose ───── - amp_slow: &_slow 30 # 30 deg/s: one revolution per 12 s - amp_fast: &_fast 60 # 60 deg/s at twice the frequency = the same ±19° excursion - f_slow: &f_slow 0.25 # Hz — one back-and-forth every 4 s - f_fast: &f_fast 0.5 # Hz - -experiment: - - "cl_baseline" - - name: "A_coupling" - trials: - - "cl_nat" - - "cl_under" - - "cl_over" - - "cl_rev" - repetitions: 2 - randomize: true - intertrial: "dark_iti" - - name: "B_bias_at_natural" - trials: - - "cl_bias_const_pos" - - "cl_bias_const_neg" - - "cl_bias_sine_slow" - - "cl_bias_sine_fast" - - "cl_bias_square" - repetitions: 2 - randomize: true - intertrial: "dark_iti" - - name: "C_bias_x_coupling" - trials: - - "cl_under_bias_const" - - "cl_over_bias_const" - - "cl_under_bias_sine" - - "cl_over_bias_sine" - - "cl_k0_bias_const" - repetitions: 2 - randomize: true - intertrial: "dark_iti" - - "cl_baseline" - - "shutdown" - -conditions: - # NOTE: no explicit fictrac connect/disconnect — the runner auto-connects the bridge - # at run start and the run log is exported from the bridge AFTER the sequence, so a - # protocol-level disconnect would lose the log. - - # ---- baseline: natural closed loop, no disturbance ------------------------- - - name: "cl_baseline" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_nat - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- A. coupling sweep ------------------------------------------------------- - - name: "cl_nat" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_nat - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_under" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_under - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_over" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_over - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_rev" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_rev - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- B. bias at the natural coupling ----------------------------------------- - - name: "cl_bias_const_pos" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_nat - bias_type: "constant" - bias_amplitude: *amp_slow # clockwise from above - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_bias_const_neg" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_nat - bias_type: "constant" - bias_amplitude: -30 # counter-clockwise (negate the amplitude, never the frequency) - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_bias_sine_slow" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_nat - bias_type: "sine" - bias_amplitude: *amp_slow - bias_frequency: *f_slow - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_bias_sine_fast" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_nat - bias_type: "sine" - bias_amplitude: *amp_fast - bias_frequency: *f_fast - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_bias_square" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_nat - bias_type: "square" - bias_amplitude: *amp_slow - bias_frequency: *f_slow - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- C. bias × coupling ------------------------------------------------------ - - name: "cl_under_bias_const" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_under - bias_type: "constant" - bias_amplitude: *amp_slow - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_over_bias_const" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_over - bias_type: "constant" - bias_amplitude: *amp_slow - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_under_bias_sine" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_under - bias_type: "sine" - bias_amplitude: *amp_slow - bias_frequency: *f_slow - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "cl_over_bias_sine" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: *k_over - bias_type: "sine" - bias_amplitude: *amp_slow - bias_frequency: *f_slow - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # The control: coupling 0 = the display ignores the fly and simply drifts at 30 °/s. - - name: "cl_k0_bias_const" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: 0 - bias_type: "constant" - bias_amplitude: *amp_slow - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- dark ITI ------------------------------------------------------------------ - - name: "dark_iti" - commands: - - type: "controller" - command_name: "allOff" - - type: "wait" - duration: *iti_dur - - # ---- shutdown ------------------------------------------------------------------ - - name: "shutdown" - commands: - - type: "controller" - command_name: "stopDisplay" - - type: "controller" - command_name: "allOff" diff --git a/protocols/fictrac_direction_test.yaml b/protocols/fictrac_direction_test.yaml deleted file mode 100644 index e4e3f194..00000000 --- a/protocols/fictrac_direction_test.yaml +++ /dev/null @@ -1,271 +0,0 @@ -version: 3 - -# ============================================================================ -# FicTrac closed-loop + open-loop direction/speed test (web runner). -# -# Uses ONLY the built-in g6_2x10 patterns already on the SD card — no new -# patterns needed: -# 2 grating_sq · 3 grating_sine · 4 frame2_h_ccw_200f -# -# BLOCK 1 — OPEN LOOP (Mode 2), ~4 min: -# both gratings (sq, sine) × 3 speeds (5/15/30 Hz) × 2 directions -# (forward = +frame_rate, reverse = −frame_rate) = 12 trials, repeated 2×. -# Each trial displays for `open_dur` seconds. Reverse uses NEGATIVE frame_rate -# (int16, fw #4). -# -# BLOCK 2 — CLOSED LOOP (Mode 3 + FicTrac), ~1.5 min: -# frame2_h_ccw_200f (a 200-frame full-azimuth rotation) host-stepped by FicTrac -# heading through the bridge; gain 1.8 = 360/200 (one azimuth pixel per index), -# `cl_dur` seconds per trial, repeated 3×. -# -# DURATION knobs (`variables:` below): change ONE number to rescale a block. -# open_dur → every open-loop trial · cl_dur → the closed-loop trial. -# -# TIMING — no redundant waits. The web runner holds each trial for its -# trialParams `duration` on the HOST (the firmware auto-stop, issue #4, isn't -# landed), so a plain display trial needs NO `wait` — the open-loop trials are -# just a trialParams, and `open_dur` is their single duration knob. The -# closed-loop trial DOES need a wait: it brackets the closed-loop hold -# (startClosedLoop → wait → stopClosedLoop), so `cl_dur` drives both its -# trialParams duration and that wait. -# -# Total ≈ 5.5 min at defaults. Needs the FicTrac bridge (`pixi run bridge`); -# with no fly, `pixi run sim` supplies synthetic heading for the closed-loop -# block. NOTE (web-first): MATLAB has no FicTracPlugin (its closed-loop is -# analog Mode 4), so the closed-loop block runs on the WEB runner only. -# ============================================================================ - -experiment_info: - name: "FicTrac closed-loop + open-loop direction/speed test" - author: "webDisplayTools" - -rig: "./configs/rigs/cshl_g6_2x10_ball.yaml" - -plugins: - - name: "fictrac" - type: "class" - matlab: - class: "FicTracPlugin" - config: - bridge_url: "ws://localhost:8765" - fictrac_port: 60000 - proto: "udp" - offset: 0 - -variables: - # ── Speed knobs ──────────────────────────────────────────────────────────── - # Change the number here (or in the Edit view's Variables panel) to rescale a - # whole block. Open-loop trials have a single duration field (no wait), so - # open_dur is unambiguous. The closed-loop trial binds BOTH its trialParams - # duration and its bracketing wait to cl_dur, so they move together. - open_dur: &open_dur 10 # Block 1 open-loop grating trials (10 s each) - cl_dur: &cl_dur 30 # Block 2 closed-loop rotation trials (30 s each) - -experiment: - - name: "open_loop_gratings" - trials: - - "sq_fwd_05" - - "sq_fwd_15" - - "sq_fwd_30" - - "sq_rev_05" - - "sq_rev_15" - - "sq_rev_30" - - "sine_fwd_05" - - "sine_fwd_15" - - "sine_fwd_30" - - "sine_rev_05" - - "sine_rev_15" - - "sine_rev_30" - repetitions: 2 - - name: "closed_loop_ball" - trials: - - "closed_loop_rotation" - repetitions: 3 - - "shutdown" - -conditions: - # NOTE: no explicit fictrac connect/disconnect. The runner auto-connects the - # bridge at run start, and — crucially — must NOT disconnect it at the end: - # the recorded-run log is exported from the bridge AFTER the sequence finishes, - # so a protocol-level disconnect would close the bridge before the log commits - # (that broke the log on the first bench run). Let the run own the lifecycle. - - # ---- BLOCK 1: open-loop drifting gratings (Mode 2) ------------------------- - # frame_rate is the frame-advance rate in Hz; its SIGN is the drift direction - # (+ forward, − reverse). No `wait`: the runner holds the trial for `duration` - # (= open_dur) host-side, so each open-loop trial is a single trialParams with - # one duration knob. - - # grating_sq (pattern 2) — forward - - name: "sq_fwd_05" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: 5 - gain: 0 - - name: "sq_fwd_15" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: 15 - gain: 0 - - name: "sq_fwd_30" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: 30 - gain: 0 - # grating_sq (pattern 2) — reverse (negative frame_rate) - - name: "sq_rev_05" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: -5 - gain: 0 - - name: "sq_rev_15" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: -15 - gain: 0 - - name: "sq_rev_30" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sq" - pattern_ID: 2 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: -30 - gain: 0 - - # grating_sine (pattern 3) — forward - - name: "sine_fwd_05" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sine" - pattern_ID: 3 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: 5 - gain: 0 - - name: "sine_fwd_15" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sine" - pattern_ID: 3 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: 15 - gain: 0 - - name: "sine_fwd_30" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sine" - pattern_ID: 3 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: 30 - gain: 0 - # grating_sine (pattern 3) — reverse (negative frame_rate) - - name: "sine_rev_05" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sine" - pattern_ID: 3 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: -5 - gain: 0 - - name: "sine_rev_15" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sine" - pattern_ID: 3 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: -15 - gain: 0 - - name: "sine_rev_30" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "grating_sine" - pattern_ID: 3 - duration: *open_dur - mode: 2 - frame_index: 0 - frame_rate: -30 - gain: 0 - - # ---- BLOCK 2: closed-loop rotation (Mode 3 + FicTrac) ---------------------- - # A 200-frame azimuthal rotation loaded static in Mode 3; FicTrac heading then - # drives the frame index for `cl_dur` s (host streams SET_FRAME_POSITION via - # the bridge). gain 1.8 = 360°/200 frames, so the arena rotates with the fly. - # The `wait` is load-bearing here — it holds closed-loop ACTIVE for cl_dur - # before stopClosedLoop fires (drop it and the stop would fire immediately). - - name: "closed_loop_rotation" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *cl_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - - type: "wait" - duration: *cl_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- shutdown -------------------------------------------------------------- - # Blank the arena only. Do NOT disconnect the bridge here — the run-log export - # runs after the sequence, and needs the bridge still connected. - - name: "shutdown" - commands: - - type: "controller" - command_name: "stopDisplay" - - type: "controller" - command_name: "allOff" diff --git a/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_blue_gs16.pat b/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_blue_gs16.pat deleted file mode 100644 index 8de7ef74..00000000 Binary files a/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_blue_gs16.pat and /dev/null differ diff --git a/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_green_gs16.pat b/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_green_gs16.pat deleted file mode 100644 index 38c9d004..00000000 Binary files a/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_green_gs16.pat and /dev/null differ diff --git a/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_violet_gs16.pat b/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_violet_gs16.pat deleted file mode 100644 index 81f8cf63..00000000 Binary files a/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_violet_gs16.pat and /dev/null differ diff --git a/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_yellow_gs16.pat b/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_yellow_gs16.pat deleted file mode 100644 index b5602629..00000000 Binary files a/protocols/g6_2p_color_led_sweep_patterns/G6_2x10_toprow_grat20_yellow_gs16.pat and /dev/null differ diff --git a/protocols/g6_2p_color_led_sweep_patterns/README.txt b/protocols/g6_2p_color_led_sweep_patterns/README.txt deleted file mode 100644 index 9d7971a2..00000000 --- a/protocols/g6_2p_color_led_sweep_patterns/README.txt +++ /dev/null @@ -1,15 +0,0 @@ -Patterns for protocols/g6_2p_color_led_sweep.yaml (G6 2x10 four-color panels, GS16 — lit pixels 15, 20 frames each; -built by scripts/make-color-optomotor-patterns.js --gs16 — regenerate rather than edit). GS16 so the controller -re-streams at 300 Hz (its GS16 default), as in the 22 Sept 2026 2P duty-sweep runs; the GS2 set would default to 1000 Hz. -Upload the whole folder from Arena Studio (Console → Patterns → Add ▾ → this folder); the Studio resolves -patterns by NAME, so the pattern_IDs in the YAML only matter as the fallback when the SD listing is unavailable. - -Each file: square-wave grating, period 20 px = 36°, 10 px on / 10 px off, one pixel of shift toward increasing -column index per frame (20 frames = one period; playback wraps), TOP panel row only (pixel rows 20..39), and only -the pixels of ONE LED color bank lit (2×2-cell bank map of js/panel-color.js, layout "four-color"). -Frame_rate +80 in mode 2 = 4 Hz temporal frequency (80 px/s ÷ 20 px); negative reverses. - -G6_2x10_toprow_grat20_violet_gs16.pat — bank 0 = violet 405 nm; frames 20, lit px/frame 500, stray px 0, 81338 bytes OK -G6_2x10_toprow_grat20_blue_gs16.pat — bank 1 = blue 470 nm; frames 20, lit px/frame 500, stray px 0, 81338 bytes OK -G6_2x10_toprow_grat20_green_gs16.pat — bank 2 = green 525 nm; frames 20, lit px/frame 500, stray px 0, 81338 bytes OK -G6_2x10_toprow_grat20_yellow_gs16.pat — bank 3 = yellow-orange 590 nm; frames 20, lit px/frame 500, stray px 0, 81338 bytes OK diff --git a/protocols/g6_2x10_2p_diagnostics.yaml b/protocols/g6_2x10_2p_diagnostics.yaml deleted file mode 100644 index 0809e7f4..00000000 --- a/protocols/g6_2x10_2p_diagnostics.yaml +++ /dev/null @@ -1,226 +0,0 @@ -version: 3 - -# ============================================================================ -# 2P line-sync DIAGNOSTICS — G6 2x10 arena on the Bergamo (Sept 2026). ≈ 52 s. -# -# Companion to g6_2x10_2p_duty_sweep.yaml. Same wiring and ScanImage setup -# (panel mode 2, DIO 1 = out_debug_framescan → Aux trigger 1 on a free D2.x, -# DIO 2 = in_trigger ← line clock; see the sweep file's header). The SWEEP is -# re-run unchanged for the hardware-only controls (objective capped, arena -# covered, SiPM bias series). THIS file varies the pattern CONTENT at a fixed -# duty, which the sweep cannot do: -# -# The residual offset in the SiPM channels is expected to follow the TOTAL -# light the display emits. Full-field GS16 patterns make that a clean test: -# ramp_128 grey level 0→15 in 16 steps of 0.5 s (ascending) → offset vs emitted light, one trial -# steps_128 the same 16 levels in a fixed pseudo-random order → same, immune to slow drift -# flash_128 full field 0/15 square wave at 1 Hz → the stimulus-locked offset step and its -# build-up/decay kinetics, 8 repeats to average -# sparse_128 10 % of the LEDs at full, static → is 10 % of LEDs at full the same as all LEDs -# at ~10 % (ramp level 1–2)? per-LED vs total light -# flash_255 the 1 Hz flash at duty 255 → does the duty-255 anomaly (Ch3) follow lit -# fraction / on-time; followed by 6 s all-off -# so the suspected slow tail can be followed -# to zero with margin -# -# Patterns: protocols/g6_2x10_2p_diagnostics_patterns/ (built by -# scripts/make-2p-diagnostic-patterns.js; README.txt lists the per-frame mean -# level of each). Upload the folder once from Arena Studio: Console → Patterns → -# Add ▾ → From course repo… → this protocol's folder. The Studio resolves patterns -# by NAME; the pattern_ID numbers below are only the fallback (alphabetical SD -# index after the four existing patterns) and the run-view preflight will flag -# a mismatch. Keep the four existing patterns on the card. -# -# Epochs in the TIFF header (auxTrigger0 blocks separated by the allOff gaps), in -# order: ramp_128, steps_128, flash_128, sparse_128, flash_255, then 6 s off. -# Within ramp/steps, level k occupies [t0 + 0.5 k, t0 + 0.5 (k+1)) s of the block -# (frame_rate 2). Within flash, frames alternate 0.5 s dark / 0.5 s full. -# Analysis: check_aux_triggers.py prints the blocks; compare the core mean per -# level with the display-off frames of the same file (see quick_checks_review.py -# for the local-reference and frame-block-SE approach). -# -# ALIGNMENT of the level changes inside a block. Three marks are available: -# 1. The aux-trigger block start (first J3 pulse of the block) + k × 0.5 s. -# The controller steps frames on its own clock (frame_rate 2), so this is -# good to a few ms (host start jitter) — enough for 0.5 s levels. -# 2. Ch3 itself (ungated, red-channel detector): it sees the LED light of every -# row flash, so each level change is a step in the Ch3 row mean at the exact -# line where the new frame was applied (126 µs resolution). Use this for the -# flash kinetics — it is the cleanest mark and needs no extra wiring. -# 3. The setAnalogOut markers below (0.5–2.5 V per condition, arena J27). Only -# recorded if the vDAQ Data Recorder logs an AI (docs/development/ -# 2p-scanimage-signal-logging.md § 2c, in the private 2P rig repo); host-timed like (1), so they add a -# cross-check, not precision. A frame-locked AO ("frame_number" role) is not -# validated on this controller yet — do not rely on it. -# ============================================================================ - -experiment_info: - name: "2P line-sync diagnostics: brightness steps, flash, sparse (G6 2x10)" - author: "mreiser" - date_created: "2026-09-23" - -rig: "./configs/rigs/bergamo_g6_2x10_2p.yaml" - -# The runner must implement the controller: block below (a runner that does not — MATLAB -# today — refuses to run this file instead of running it in the wrong panel mode). -requires: [controller_block] - -# Sticky controller settings asserted by Arena Studio before EVERY run and recorded in the -# run header (docs/development/controller-settings-strategy.md). Replaces the manual -# "set panel mode 2 by hand" step: the Studio reads the mode, sets it if needed, reads it -# back, and refuses to run if it cannot. -controller: - panel_mode: triggered # 2 — LEDs only inside the line-clock LOW gap - refresh_policy: line_sync_safe # cap the 300 Hz re-stream at the rig's limits.max_refresh_hz - panel_firmware: "2p" # SD footer "2p-[-d]" = the eintlow_2p line-sync build - -plugins: [] - -variables: - - &ramp_pattern "G6_2x10_ff_ramp16" # 16 frames, full field, level = frame index - - &ramp_id 6 - - &steps_pattern "G6_2x10_ff_steps_pr16" # 16 frames, full field, levels in pseudo-random order - - &steps_id 7 - - &flash_pattern "G6_2x10_ff_flash_0_15" # 2 frames: full field dark / full - - &flash_id 5 - - &sparse_pattern "G6_2x10_sparse10" # 1 frame: 10 % of LEDs at 15 - - &sparse_id 8 - - &level_hz 2 # frames/s: 0.5 s per grey level; 16 levels → 8 s; flash → 1 Hz - - &pre_dur 3 - - &ramp_dur 8 # = 16 levels / level_hz: exactly one pass - - &flash_dur 8 # 8 flashes at 1 Hz - - &sparse_dur 5 - - &flash255_dur 6 - - &iti_dur 2 # allOff between steps: pauses the J3 pulses → epoch marker - - &long_off 6 # allOff after the last step: slow-tail test - - &duty_50 128 - - &duty_100 255 # diagnostics only; experiments stay ≤ ~190 - -experiment: - - "pre_blank" - - name: "diagnostics" - trials: ["ramp_128", "steps_128", "flash_128", "sparse_128", "flash_255"] - repetitions: 1 - intertrial: "iti" - - "post_blank" - -conditions: - - name: "pre_blank" - commands: - - type: "controller" - command_name: "allOff" - - type: "controller" - command_name: "setAnalogOut" - mv: 0 - - type: "wait" - duration: *pre_dur - - - name: "ramp_128" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 500 - - type: "controller" - command_name: "trialParams" - pattern: *ramp_pattern - pattern_ID: *ramp_id - duration: *ramp_dur - mode: 2 - frame_index: 0 - frame_rate: *level_hz - gain: 0 - duty: *duty_50 - - type: "wait" - duration: *ramp_dur - - - name: "steps_128" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 1000 - - type: "controller" - command_name: "trialParams" - pattern: *steps_pattern - pattern_ID: *steps_id - duration: *ramp_dur - mode: 2 - frame_index: 0 - frame_rate: *level_hz - gain: 0 - duty: *duty_50 - - type: "wait" - duration: *ramp_dur - - - name: "flash_128" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 1500 - - type: "controller" - command_name: "trialParams" - pattern: *flash_pattern - pattern_ID: *flash_id - duration: *flash_dur - mode: 2 - frame_index: 0 - frame_rate: *level_hz - gain: 0 - duty: *duty_50 - - type: "wait" - duration: *flash_dur - - # static: frame_rate 0 holds frame 0 (the sparse frame) - - name: "sparse_128" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 2000 - - type: "controller" - command_name: "trialParams" - pattern: *sparse_pattern - pattern_ID: *sparse_id - duration: *sparse_dur - mode: 2 - frame_index: 0 - frame_rate: 0 - gain: 0 - duty: *duty_50 - - type: "wait" - duration: *sparse_dur - - - name: "flash_255" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 2500 - - type: "controller" - command_name: "trialParams" - pattern: *flash_pattern - pattern_ID: *flash_id - duration: *flash255_dur - mode: 2 - frame_index: 0 - frame_rate: *level_hz - gain: 0 - duty: *duty_100 - - type: "wait" - duration: *flash255_dur - - # Between steps: display dark AND controller refresh stopped (J3 pulses pause). - - name: "iti" - commands: - - type: "controller" - command_name: "allOff" - - type: "wait" - duration: *iti_dur - - # The long all-off after flash_255: recovery / slow-tail measurement. End with allOff, not stopDisplay. - - name: "post_blank" - commands: - - type: "controller" - command_name: "allOff" - - type: "controller" - command_name: "setAnalogOut" - mv: 0 - - type: "wait" - duration: *long_off diff --git a/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_flash_0_15.pat b/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_flash_0_15.pat deleted file mode 100644 index 986eb79d..00000000 Binary files a/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_flash_0_15.pat and /dev/null differ diff --git a/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_ramp16.pat b/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_ramp16.pat deleted file mode 100644 index 1cd30e28..00000000 Binary files a/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_ramp16.pat and /dev/null differ diff --git a/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_steps_pr16.pat b/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_steps_pr16.pat deleted file mode 100644 index 0f332059..00000000 Binary files a/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_ff_steps_pr16.pat and /dev/null differ diff --git a/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_sparse10.pat b/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_sparse10.pat deleted file mode 100644 index 8e3014f6..00000000 Binary files a/protocols/g6_2x10_2p_diagnostics_patterns/G6_2x10_sparse10.pat and /dev/null differ diff --git a/protocols/g6_2x10_2p_diagnostics_patterns/README.txt b/protocols/g6_2x10_2p_diagnostics_patterns/README.txt deleted file mode 100644 index e9a9efaf..00000000 --- a/protocols/g6_2x10_2p_diagnostics_patterns/README.txt +++ /dev/null @@ -1,11 +0,0 @@ -Patterns for protocols/g6_2x10_2p_diagnostics.yaml (G6 2x10, GS16, built by scripts/make-2p-diagnostic-patterns.js). -Upload the whole folder from Arena Studio (Console → Patterns → Add ▾ → this protocol's folder); the Studio resolves patterns by NAME. - -G6_2x10_ff_ramp16.pat — full field, grey level = frame index 0..15 (ascending ramp). mode 2, frame_rate 2 → 8 s per pass - G6_2x10_ff_ramp16.pat frames 16 mean/full per frame [0.00 0.07 0.13 0.20 0.27 0.33 0.40 0.47 0.53 0.60 0.67 0.73 0.80 0.87 0.93 1.00] lit fraction [0.00 1.00] OK -G6_2x10_ff_steps_pr16.pat — full field, the 16 grey levels in pseudo-random order 8,12,14,15,7,3,1,0,9,4,2,10,5,11,13,6. mode 2, frame_rate 2 → 8 s per pass - G6_2x10_ff_steps_pr16.pat frames 16 mean/full per frame [0.53 0.80 0.93 1.00 0.47 0.20 0.07 0.00 0.60 0.27 0.13 0.67 0.33 0.73 0.87 0.40] lit fraction [1.00 0.00] OK -G6_2x10_ff_flash_0_15.pat — full field off/on square wave. mode 2, frame_rate 2 → 1 Hz flash (0.5 s dark, 0.5 s full) - G6_2x10_ff_flash_0_15.pat frames 2 mean/full per frame [0.00 1.00] lit fraction [0.00 1.00] OK -G6_2x10_sparse10.pat — 10 % of pixels at level 15, rest dark, static (frame_rate 0). Compare with ff_ramp16 held at level 1–2 - G6_2x10_sparse10.pat frames 1 mean/full per frame [0.10] lit fraction [0.10] OK diff --git a/protocols/g6_2x10_2p_duty_sweep.yaml b/protocols/g6_2x10_2p_duty_sweep.yaml deleted file mode 100644 index a67adeac..00000000 --- a/protocols/g6_2x10_2p_duty_sweep.yaml +++ /dev/null @@ -1,267 +0,0 @@ -version: 3 - -# ============================================================================ -# 2P line-sync duty sweep — G6 2x10 arena on the Bergamo (Sept 2026). -# -# Purpose: quantify display-light leak into the SiPM channels as a function of -# panel duty_cycle, with unambiguous timing marks recorded by ScanImage. -# Replaces the hand-driven 22 Sept 2026 run (HANDOVER_300Hz_display_leak.md). -# Total ≈ 62 s. Any pattern works; the default is the built-in 200-frame GS16 -# rotation so the arena is visibly running. Swap `pattern`/`pattern_ID` for the -# dark-bar pattern if it is on the card (keep it Gray_16 — see note 3). -# -# BEFORE PRESSING RUN (Arena Studio, once per connection — not YAML-able): -# 1. Panel display mode 2 (Triggered) is now ASSERTED by the Studio from the controller: -# block below (and by the Bergamo rig default at connect) — no manual step. Wire: 02 1B 02 -# NOT 3 (Gated): Gated restarts at row 0 on every gate drop and only the -# top rows of each panel ever light with a line-clock gate. -# 2. Digital IO 1 role = out_debug_framescan. Wire: 03 AC 01 03 -# DO1 (BNC J3) is then HIGH for the duration of every SPI frame transfer -# (one ~0.7 ms pulse per controller refresh tick). Record it in ScanImage. -# Digital IO 2 (J4) stays in_trigger — it IS the line-clock → EINT route. -# 3. Refresh rate (Debug ▾, unlock): 300 Hz for GS16 is the default. Do NOT -# run a Gray_2 pattern without setting refresh ≤ 300 (GS2 default is -# 1000 Hz; every re-streamed frame costs the panel ~1–2 line edges). -# 4. Telemetry ring: on (default) — cc/cf/cs rows land in the run log. -# -# WHAT TO RECORD IN SCANIMAGE (vDAQ) — MINIMAL VERSION, everything lands in the TIFF -# -# We only STORE data. Nothing here triggers ScanImage: start the Grab by hand, -# then press Run. The aux trigger below does not start/stop/gate anything — -# it writes edge timestamps into frames you are already saving. -# -# Wiring (one BNC): arena J3 ("Digital IO 1 (5V)", role out_debug_framescan, -# a ~0.7 ms 5 V pulse per SPI frame transfer, 300 Hz) -# → a FREE vDAQ breakout digital input on its OWN cable -# (Bergamo: D2.1 — D2.0 carries the resonant scanner sync; -# never T into an existing cable: a J3 splice into the sync -# on 2026-09-23 stopped frame formation whenever the display -# was on). The line-clock cable into arena J4 stays as it is. -# ScanImage (one dropdown): Resource Configuration → your imaging system → -# Triggers tab → "Aux trigger 1" = that port (D2.1). Save. -# Ch2/Ch3 as before. GUI "Aux trigger 1" = header auxTrigger0. -# What gets logged: every TIFF frame's header (ImageDescription) gains -# "Aux Trigger 1 = [t1 t2 …]" — the times (s, 8 ns resolution, -# same FPGA clock as frameTimestamps_sec) of each frame -# transfer that happened during that frame: ~5–6 per 18 ms -# frame while the arena streams, [] while it is off. -# How epochs are marked: the 2 s allOff between steps (the "iti" condition) -# disarms the controller refresh timer and pushes a dark -# frame, so the J3 pulses PAUSE for 2 s. In the header -# record each duty step is a block of timestamps separated -# by a gap; block order = the order in `experiment:` below. -# Read it back: Python ScanImageTiffReader(p).description(n) or -# MATLAB scanimage.util.opentif(file) / imfinfo → ImageDescription. -# Expect "Aux Trigger 1 = [ … ]" non-empty on frames during a -# step, [] during iti/blank. All-[] = wrong port, or arena -# DIO 1 not in out_debug_framescan (Studio raw hex 01 AD lists roles). -# Optional (only if a photodiode is at the rig): photodiode → PCIe rear SMB -# AI3 (= Channel 4, keep < 2 Vpp) → Channels window → Save. -# The only way to tell a residual at low duty apart from SiPM -# recovery after the in-gap flash. -# Not needed for this test: line clock (same FPGA as the pixel clock → image -# columns already ARE its phase; shape known from the scope), -# frame clock, AO marker, Data Recorder .h5. Full version: -# reiserlab/scanimage-2026-bergamo procedures/webdisplaytools-2p/2p-scanimage-signal-logging.md. The -# setAnalogOut steps below are harmless if unrecorded. -# Ch3 is NOT a display sync; it is the leak signal you are measuring. -# -# Notes -# 1. duty: 0 in trialParams means "pattern's stored duty", NOT off. Off is allOff. -# 2. duty values 25/64/128/191/255 ≈ 10/25/50/75/100 % of 255, 8 s each. -# With the 2P panel firmware (BCM base 1 µs) a duty-255 row is ~15 µs, -# so EVERY step fits inside the ~18 µs line-clock LOW gap and the sweep -# maps the usable brightness range end to end (255 is the tight-margin -# case). With the 9014b5b firmware (3 µs base) steps above duty ~85 -# overrun into the imaged part of the line — the same sweep run on both -# firmwares is the comparison. -# 3. GS16 pattern → 300 Hz refresh. frame2_h_ccw_200f and grating_sine are -# GS16; all_on and grating_sq are GS2. -# 4. End with allOff, not stopDisplay: in the free-running Triggered firmware -# a stopped controller leaves the last frame lit, one row per line. -# ============================================================================ - -experiment_info: - name: "2P line-sync duty sweep (G6 2x10)" - author: "mreiser" - date_created: "2026-09-22" - -rig: "./configs/rigs/bergamo_g6_2x10_2p.yaml" - -# The runner must implement the controller: block below (a runner that does not — MATLAB -# today — refuses to run this file instead of running it in the wrong panel mode). -requires: [controller_block] - -# Sticky controller settings asserted by Arena Studio before EVERY run and recorded in the -# run header (docs/development/controller-settings-strategy.md). Replaces the manual -# "set panel mode 2 by hand" step: the Studio reads the mode, sets it if needed, reads it -# back, and refuses to run if it cannot. -controller: - panel_mode: triggered # 2 — LEDs only inside the line-clock LOW gap - refresh_policy: line_sync_safe # cap the 300 Hz re-stream at the rig's limits.max_refresh_hz - panel_firmware: "2p" # SD footer "2p-[-d]" = the eintlow_2p line-sync build - -plugins: [] - -variables: - - &pattern_name "frame2_h_ccw_200f" - - &pattern_id 4 - - &rotate_hz 20 # frames/s; 200-frame pattern → one revolution per 10 s - - &blank_dur 4 - - &step_dur 7 - - &iti_dur 2 # allOff between steps: stops the 300 Hz re-stream → gap in the J3 aux-trigger record - - &duty_10 25 - - &duty_25 64 - - &duty_50 128 - - &duty_75 191 - - &duty_100 255 # ~15 µs row on the 2P firmware: tight-margin case; overruns on 9014b5b - -experiment: - - "pre_blank" - - name: "duty_sweep" - trials: ["duty_10", "duty_25", "duty_50", "duty_75", "duty_100", "static_50"] - repetitions: 1 - intertrial: "iti" - - "post_blank" - -conditions: - # Display dark, AO marker 0 V. Baseline for the SiPM channels. - - name: "pre_blank" - commands: - - type: "controller" - command_name: "allOff" - - type: "controller" - command_name: "setAnalogOut" - mv: 0 - - type: "wait" - duration: *blank_dur - - - name: "duty_10" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 500 - - type: "controller" - command_name: "trialParams" - pattern: *pattern_name - pattern_ID: *pattern_id - duration: *step_dur - mode: 2 - frame_index: 0 - frame_rate: *rotate_hz - gain: 0 - duty: *duty_10 - - type: "wait" - duration: *step_dur - - - name: "duty_25" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 1000 - - type: "controller" - command_name: "trialParams" - pattern: *pattern_name - pattern_ID: *pattern_id - duration: *step_dur - mode: 2 - frame_index: 0 - frame_rate: *rotate_hz - gain: 0 - duty: *duty_25 - - type: "wait" - duration: *step_dur - - - name: "duty_50" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 1500 - - type: "controller" - command_name: "trialParams" - pattern: *pattern_name - pattern_ID: *pattern_id - duration: *step_dur - mode: 2 - frame_index: 0 - frame_rate: *rotate_hz - gain: 0 - duty: *duty_50 - - type: "wait" - duration: *step_dur - - - name: "duty_75" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 2000 - - type: "controller" - command_name: "trialParams" - pattern: *pattern_name - pattern_ID: *pattern_id - duration: *step_dur - mode: 2 - frame_index: 0 - frame_rate: *rotate_hz - gain: 0 - duty: *duty_75 - - type: "wait" - duration: *step_dur - - - name: "duty_100" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 2500 - - type: "controller" - command_name: "trialParams" - pattern: *pattern_name - pattern_ID: *pattern_id - duration: *step_dur - mode: 2 - frame_index: 0 - frame_rate: *rotate_hz - gain: 0 - duty: *duty_100 - - type: "wait" - duration: *step_dur - - # Same pattern and duty as duty_50 but frame_rate 0 (static frame). The - # controller still re-streams at the refresh rate, so any difference from - # duty_50 isolates pattern-content change from re-streaming. - - name: "static_50" - commands: - - type: "controller" - command_name: "setAnalogOut" - mv: 3000 - - type: "controller" - command_name: "trialParams" - pattern: *pattern_name - pattern_ID: *pattern_id - duration: *step_dur - mode: 2 - frame_index: 0 - frame_rate: 0 - gain: 0 - duty: *duty_50 - - type: "wait" - duration: *step_dur - - # Between steps: display dark AND controller refresh stopped, so the J3 - # frame-transfer pulses pause — the epoch boundary marker in the TIFF header. - - name: "iti" - commands: - - type: "controller" - command_name: "allOff" - - type: "wait" - duration: *iti_dur - - - name: "post_blank" - commands: - - type: "controller" - command_name: "allOff" - - type: "controller" - command_name: "setAnalogOut" - mv: 0 - - type: "wait" - duration: *blank_dur diff --git a/protocols/index.json b/protocols/index.json index 0de3b79e..aa85e4c6 100644 --- a/protocols/index.json +++ b/protocols/index.json @@ -13,26 +13,6 @@ "label": "FicTrac closed-loop (Mode 3)", "path": "./protocols/fictrac_closed_loop.yaml" }, - { - "key": "fictrac_direction_test", - "label": "FicTrac closed-loop + open-loop direction/speed test (~5.5 min)", - "path": "./protocols/fictrac_direction_test.yaml" - }, - { - "key": "fictrac_bias_test", - "label": "FicTrac closed-loop bias/disturbance test (~4 min)", - "path": "./protocols/fictrac_bias_test.yaml" - }, - { - "key": "led_activation_quadrant_test", - "label": "Conditional LED activation — graded zones test (Mode-3 closed loop, ~2 min)", - "path": "./protocols/led_activation_quadrant_test.yaml" - }, - { - "key": "fictrac_coupling_bias_fly_test", - "label": "Closed-loop coupling × bias — fly test (Mode 3, natural coupling −1, ~13 min)", - "path": "./protocols/fictrac_coupling_bias_fly_test.yaml" - }, { "key": "opto_intensity_runtime_test", "label": "Opto intensity as a runtime variable (Mode 2, ledDrive; ~2 min)", @@ -43,31 +23,6 @@ "label": "Full experiment (blocks, plugins, ITI)", "path": "./protocols/full_experiment.yaml" }, - { - "key": "g6_2x10_2p_duty_sweep", - "label": "2P line-sync duty sweep — G6 2×10 on the Bergamo (~62 s, 2 s allOff between steps)", - "path": "./protocols/g6_2x10_2p_duty_sweep.yaml" - }, - { - "key": "g6_2x10_2p_diagnostics", - "label": "2P line-sync diagnostics — full-field brightness ramp/steps, 1 Hz flash, 10 % sparse, 6 s all-off (~52 s; upload its _patterns folder first)", - "path": "./protocols/g6_2x10_2p_diagnostics.yaml" - }, - { - "key": "bias_camera_closed_loop_poc", - "label": "BIAS camera + FicTrac closed loop through the ROS 2 bridge (LAB-303 spike, ~40 s)", - "path": "./protocols/bias_camera_closed_loop_poc.yaml" - }, - { - "key": "bias_camera_closed_loop_poc_ww18", - "label": "BIAS camera + FicTrac closed loop, ROS 2 bridge — ww18 variant (site 2x10 grating_sq, slot 2)", - "path": "./protocols/bias_camera_closed_loop_poc_ww18.yaml" - }, - { - "key": "bias_camera_closed_loop_poc_ww18_soak10", - "label": "BIAS + FicTrac closed loop, ROS 2 bridge - ww18 10-min soak (2 x 300 s, grating_sq slot 2)", - "path": "./protocols/bias_camera_closed_loop_poc_ww18_soak10.yaml" - }, { "key": "g6_color_optomotor_test", "label": "Color optomotor test — one LED color at a time, top row, 25/50/100 % duty (G6 four-color, BIAS, ~18 min)", @@ -77,11 +32,6 @@ "key": "g6_color_optomotor_smoke", "label": "Color optomotor SMOKE — 10 s per pattern, duty 100 %, BIAS check (~2 min)", "path": "./protocols/g6_color_optomotor_smoke.yaml" - }, - { - "key": "g6_2p_color_led_sweep", - "label": "2P color LED-intensity sweep — one ScanImage grab per color, 6 duty levels × 3 s at 20 fps (G6 four-color, ScanImage JSON connector, ~2.5 min)", - "path": "./protocols/g6_2p_color_led_sweep.yaml" } ] } diff --git a/protocols/led_activation_quadrant_test.yaml b/protocols/led_activation_quadrant_test.yaml deleted file mode 100644 index b19fc5b2..00000000 --- a/protocols/led_activation_quadrant_test.yaml +++ /dev/null @@ -1,212 +0,0 @@ -version: 3 - -# ============================================================================ -# Conditional LED activation — graded zones test (web runner, Mode-3 closed loop) -# -# Tests the per-trial `led_activation` on trialParams (Arena Studio v0.86: graded -# ZONES with linear ramp edges over a baseline; v0.59's hard-edged `on_ranges` -# still works as sugar). Reuses the SAME built-in SD pattern the closed-loop -# block uses — NO new patterns needed: -# 4 frame2_h_ccw_200f (a 200-frame full-azimuth rotation) -# -# The 200 frames map 1:1 onto azimuth (1.8°/frame = 360°/200 px), so the pattern -# divides into four 50-frame QUADRANTS: -# Q1 frames 0– 49 ( 0– 90°) -# Q2 frames 50– 99 ( 90–180°) -# Q3 frames 100–149 (180–270°) -# Q4 frames 150–199 (270–360°) -# In closed loop the displayed frame index tracks the fly's heading, so gating -# the LED on quadrant zones drives it as the fly rotates through them. The -# index the LED follows is the DISPLAYED one (after any bias), i.e. where the -# stimulus actually is on the arena. -# -# HOW IT DRIVES THE LED: `startClosedLoop` enables the bridge's frame apply — -# that same event stream is what feeds led_activation, so the LED gating only -# runs INSIDE the startClosedLoop → wait → stopClosedLoop bracket. (This is also -# the bench check for "is closed-loop apply active?" — if the arena rotates with -# the fly, the LED gating is receiving frames too.) -# -# FOUR conditions, each 30 s (`hold_dur`): -# 1. led_one_quadrant — hard edges: LED on in Q2 only (on_ranges sugar) -# 2. led_opposite — hard edges: LED on in Q1 + Q3 (two bands; 4 flips / rev) -# 3. led_ramped — one zone, 10-frame (18°) ramps in/out around Q2 -# 4. led_baseline_probe — dim baseline everywhere + a brighter ramped probe zone -# that WRAPS through frame 0 (Q4 → Q1) -# -# Needs the FicTrac bridge (`pixi run bridge`); with no fly, `pixi run sim` -# supplies synthetic heading (`--model fly` for a fixating one). Requires fw #33+ -# (duty/AO) and a BuckPuck LED on Analog Out. Web-only — MATLAB does not read -# led_activation. Bench check: photodiode on the LED — the ramps must be -# monotone with no flicker, and the `cf` rows' max req_age_us must not grow -# while a ramp is active (the LED writes yield to frame writes). -# ============================================================================ - -experiment_info: - name: "Conditional LED activation — graded zones test" - author: "webDisplayTools" - -rig: "./configs/rigs/cshl_g6_2x10_ball.yaml" - -plugins: - - name: "fictrac" - type: "class" - matlab: - class: "FicTracPlugin" - config: - bridge_url: "ws://localhost:8765" - fictrac_port: 60000 - proto: "udp" - offset: 0 - -variables: - # Change ONE number to rescale. hold_dur drives each trial's trialParams - # duration AND its bracketing wait (they must agree — see the waits rule). - hold_dur: &hold_dur 30 # seconds of closed-loop hold per condition - led_pct: &led_pct 20 # LED brightness % inside a zone (BuckPuck curve; fractional OK) - base_pct: &base_pct 2 # dim baseline % for the baseline+probe condition - # Ramp endpoints are literal frame indices below (10-frame ramps = 18° at - # 1.8°/frame); bind them to anchors from the Studio's 🔗 if you sweep them. - -experiment: - - "led_one_quadrant" - - "led_opposite" - - "led_ramped" - - "led_baseline_probe" - - "shutdown" - -conditions: - # ---- 1) single quadrant, hard edges (legacy on_ranges sugar) --------------- - # LED on across Q2 (frames 50–99), off elsewhere. As the fly's heading sweeps - # through Q2 the LED switches on entering frame 50 and off leaving frame 99. - # `on_ranges: [[50, 99]]` ≡ `zones: [{level, ramp_in: [50, 50], ramp_out: [100, 100]}]`. - - name: "led_one_quadrant" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *hold_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - led_activation: - level: *led_pct - on_ranges: - - [50, 99] - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: -1 - - type: "wait" - duration: *hold_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- 2) two opposite quadrants, hard edges --------------------------------- - # LED on across Q1 (0–49) and Q3 (100–149) — four transitions per revolution, - # exercising the multi-range on_ranges list. - - name: "led_opposite" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *hold_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - led_activation: - level: *led_pct - on_ranges: - - [0, 49] - - [100, 149] - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: -1 - - type: "wait" - duration: *hold_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- 3) one zone with linear ramps ----------------------------------------- - # Dark at frame 40, full `led_pct` by frame 50, held through 99, back to dark - # at frame 110. A fly dithering on a boundary now just modulates the LED a - # little instead of chattering it (this is what hysteresis used to be for). - - name: "led_ramped" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *hold_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - led_activation: - baseline: 0 - zones: - - level: *led_pct - ramp_in: [40, 50] # baseline at 40 → level at 50 - ramp_out: [100, 110] # level at 100 → baseline at 110 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: -1 - - type: "wait" - duration: *hold_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- 4) dim baseline + brighter probe zone that wraps through frame 0 ------ - # The LED sits at `base_pct` everywhere and ramps up to `led_pct` in a zone - # centred on frame 0 (Q4 → Q1): ramp in 170→180, hold 180..19 (through the - # 199/0 seam), ramp out 20→30. Checks the wrap unrolling and that ramps start - # from the BASELINE, not from dark. - - name: "led_baseline_probe" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "frame2_h_ccw_200f" - pattern_ID: 4 - duration: *hold_dur - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - led_activation: - baseline: *base_pct - zones: - - level: *led_pct - ramp_in: [170, 180] - ramp_out: [20, 30] - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - params: - coupling: -1 - - type: "wait" - duration: *hold_dur - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - # ---- shutdown ------------------------------------------------------------- - # Blank the arena (also forces the LED off via the runner's activator - # teardown on stopDisplay). Do NOT disconnect the bridge — the run-log export - # runs after the sequence and needs it connected. - - name: "shutdown" - commands: - - type: "controller" - command_name: "stopDisplay" - - type: "controller" - command_name: "allOff" diff --git a/protocols/led_activation_quadrant_test_patterns/001_all_on.pat b/protocols/led_activation_quadrant_test_patterns/001_all_on.pat deleted file mode 100644 index ed8f96e9..00000000 Binary files a/protocols/led_activation_quadrant_test_patterns/001_all_on.pat and /dev/null differ diff --git a/protocols/led_activation_quadrant_test_patterns/002_grating_sq.pat b/protocols/led_activation_quadrant_test_patterns/002_grating_sq.pat deleted file mode 100644 index 65ca95b6..00000000 Binary files a/protocols/led_activation_quadrant_test_patterns/002_grating_sq.pat and /dev/null differ diff --git a/protocols/led_activation_quadrant_test_patterns/003_grating_sine.pat b/protocols/led_activation_quadrant_test_patterns/003_grating_sine.pat deleted file mode 100644 index 9f5b4fdc..00000000 Binary files a/protocols/led_activation_quadrant_test_patterns/003_grating_sine.pat and /dev/null differ diff --git a/protocols/led_activation_quadrant_test_patterns/004_frame2_h_ccw_200f.pat b/protocols/led_activation_quadrant_test_patterns/004_frame2_h_ccw_200f.pat deleted file mode 100644 index 8e0b0fa9..00000000 Binary files a/protocols/led_activation_quadrant_test_patterns/004_frame2_h_ccw_200f.pat and /dev/null differ diff --git a/protocols/mode3_drill_1trial.yaml b/protocols/mode3_drill_1trial.yaml deleted file mode 100644 index de624540..00000000 --- a/protocols/mode3_drill_1trial.yaml +++ /dev/null @@ -1,67 +0,0 @@ -version: 3 - -# ============================================================================ -# One-trial Mode-3 drill — for the recovery drill before an overnight soak and -# for the lab's injected-stall test (lab-test-day-windows-2026-09-14.md, test B): -# a single 180 s closed-loop trial on the 813 KB bar with the simulator, nothing -# else (≈ 36k reads at 200 Hz — more than one of the old ~24.5k-read stall periods, -# so the injected legacy arm has time to stall). Run it as a Test run; the banner at the end is the result. -# before: await Studio.setSdDiag(3) (inject the stall arm) -# after: await Studio.setSdDiag(0) -# ============================================================================ - -experiment_info: - name: "Mode-3 drill (one 180 s bar trial)" - author: "webDisplayTools" - -rig: "./configs/rigs/cshl_g6_2x10_ball.yaml" - -plugins: - - name: "fictrac" - type: "class" - matlab: - class: "FicTracPlugin" - config: - bridge_url: "ws://localhost:8765" - fictrac_port: 60000 - proto: "udp" - offset: 0 - -experiment: - - "drill_setup" - - "cl_bar_200f" - - "drill_shutdown" - -conditions: - - name: "drill_setup" - commands: - - type: "plugin" - plugin_name: "fictrac" - command_name: "connect" - - # The 813 KB, 200-frame bar on the CSHL course card is p3's pattern (036_p3_heisenberg_ts.pat); - # names resolve against the card listing, pattern_ID is only the fallback. - - name: "cl_bar_200f" - commands: - - type: "controller" - command_name: "trialParams" - pattern: "p3_heisenberg_ts" - pattern_ID: 36 - duration: 180 - mode: 3 - frame_index: 0 - frame_rate: 0 - gain: 0 - - type: "plugin" - plugin_name: "fictrac" - command_name: "startClosedLoop" - - type: "wait" - duration: 180 - - type: "plugin" - plugin_name: "fictrac" - command_name: "stopClosedLoop" - - - name: "drill_shutdown" - commands: - - type: "controller" - command_name: "stopDisplay" diff --git a/rig/HANDOVER-okey-cokey-2026-10-21.md b/rig/HANDOVER-okey-cokey-2026-10-21.md deleted file mode 100644 index c1ef0e8a..00000000 --- a/rig/HANDOVER-okey-cokey-2026-10-21.md +++ /dev/null @@ -1,102 +0,0 @@ -# Handover: okey-cokey rig, BIAS test + Foxglove dashboard (2026-10-21) - -For a Claude Code session running **on the okey-cokey lab PC** (Windows; the room with the G6 2×10 -four-color arena, an overhead BIAS camera, freely walking flies, no FicTrac). Michael's Mac has no -remote path into the rig PCs, so start the session there: open Claude Code in any folder (the -checkout does not exist yet) and paste the kickoff prompt below as the first message. - -**This PC starts bare** (no Foxglove, no webDisplayTools checkout, maybe no git or pixi). Setup is -its own checklist, [`okey-cokey-pc-setup.md`](okey-cokey-pc-setup.md), done first. Claude Code -cannot install itself: a person installs the Claude desktop app (or Git for Windows, then the CLI) -over Remote Desktop; from there a Claude session can do most of the checklist and asks for the rest. -Without Claude on the PC, the checklist and the run sheet work by hand too. - -Then two jobs, in this order, both on ONE run sheet: -[`okey-cokey-pc-2026-10-21.md`](okey-cokey-pc-2026-10-21.md). - -1. **Test** today's protocol and BIAS on this PC. -2. **Test the camera + G6 Foxglove dashboard** on the real rig, live and on the run's replay. It was - BUILT and tested off-rig on Michael's Mac the same day - ([`docs/okey-cokey-foxglove-dashboard-plan.md`](docs/okey-cokey-foxglove-dashboard-plan.md), the - status block at the top); nothing of it has met this rig, its camera or a real `.ufmf` from it. - -## Kickoff prompt (paste on the lab PC) - -``` -Okey-cokey rig work, LAB-303. This PC may be bare (no webDisplayTools checkout, no Foxglove, maybe -no git or pixi). Start with the setup checklist; read it on GitHub until the repo is cloned: -https://github.com/reiserlab/webDisplayTools/blob/claude/okey-cokey-rig/rig/okey-cokey-pc-setup.md -It clones the repo to C:\w on branch claude/okey-cokey-rig. Then read -C:\w\rig\HANDOVER-okey-cokey-2026-10-21.md and run C:\w\rig\okey-cokey-pc-2026-10-21.md -in order, keeping C:\w\rig\okey-cokey-pc-results-2026-10-21.md as you go (one row per step: -pass/fail, timings, sizes, every error verbatim; the setup facts first). -Ask me (Michael) before every download or install, any admin/UAC step, and anything that needs a -click: the BIAS GUI, the Chrome serial-port picker, the IR backlight, Foxglove, putting flies in. -If BIAS is not on this PC, stop and tell me. Rules are in the handover's "Constraints" section. -Commit fixes and the results file to claude/okey-cokey-rig and push; open no PR. -``` - -## Where things stand - -- **Branch `claude/okey-cokey-rig`** = release candidate 2026-10-20 rc1 (Studio v0.97, the ROS 2 - graph, the BIAS connector; on Next) plus: - - `claude/okey-cokey-pairs`: `protocols/g6_color_optomotor_test.yaml` + `_smoke.yaml` in **Grace's - trial shape (2026-10-21)** — each trial a CW/CCW pair, 15 s CW then 15 s CCW with no gap, then 10 s - dark (also across block boundaries). Full run 24 pairs, 16.1 min; smoke 4 pairs (10 s per - direction, 5 s dark), 1.7 min. Parsed and flattened with the real modules; `pixi run test -- - protocol` 106/106. - - the dashboard (plan doc status block): `nodes/stimulus_node.py` + `stimulus_core.py` + - `arena_display.py` (expected/observed display in the four-color LEDs, arena rate/lag/health from - controller telemetry), BIAS rate/drops in `viz_node.py`, `rig.py --no-fictrac --stimulus`, the - replay (`fox-json --stimulus`, `tools/ufmf.py`), `foxglove/arena-okey-cokey.json`. Mac: 54/54 + - 10/10 + harness 54/54. -- **Not on any Pages tier**: Next serves the candidate *without* the new protocol shape. Serve this - checkout locally (`pixi run python .claude\nocache-server.py 8092` from `C:\w`) or open the YAML via Protocol ▾ → Open…. -- **Background reading**: the ww18 results, `windows-results-ww18-2026-10-15.md` (every Windows finding - is in its table; most are fixed on this branch). -- **Settled**: the okey-cokey controller runs telemetry firmware; BIAS is NOT changed now (no live - camera in Foxglove — the BIAS window sits beside it; the replay has the camera). -- **Record in job 1**: which BIAS build (the Atlas10 fork reports drop counters, the Spinnaker build - may not: those panels then read "no data", never 0). - -## What could go wrong on this rig (look here first) - -- **Windows paths and the pattern/protocol lookup**: the stimulus node finds `.pat` under - the repo's `protocols/` + `patterns/` and the run's protocol by its `protocol_filename` under - `protocols/`. A protocol opened from elsewhere (Downloads) still runs, but its patterns' NAMES may - not resolve → a gray hatched placeholder and "pattern N" in the health line. Fix with - `--stimulus-args "--protocol-dir --pattern-dir "`, not code. -- **The camera's clock**: the replay places camera frames with the run's `getTimestamp` replies. - `camera.offset_ms` in the converter's report should be stable; if `camera.error` says no reply - had a value > 0, the protocol's first getTimestamp came too early (BIAS reads 0 before its first - frame). -- **uFMF from this camera**: the reader follows BIAS's writer (v4, MONO8, index or scan). A different - coding (e.g. RGB) is refused with a clear error: record it. -- **foxglove_bridge on Windows**: never run on Windows yet (ww18 did not install `viz`). If it fails - to start, the rig prints it and the rest runs; record the error verbatim. -- **Load**: the stimulus node renders 800×160 PNGs at 15 Hz (a few ms each on the Mac). If the - bridge's latency numbers (`connector_status`) get worse with it on, record both. - -## Constraints - -- **Never touch Production or Next**, never merge to `main`, open no PR. Code reaches `main` only - through a release candidate, which Michael builds (`docs/development/release-process.md`). -- **No Studio changes** (`arena_studio.html`, `js/*`), **no bridge 3.4 changes** - (`fictrac-bridge/bridge.py`), **no BIAS changes**. Everything lives in `rig/`. If a Studio - change turns out to be needed, stop and write it up instead. -- Tests: `pixi run test`, `pixi run test-viz`, `pixi run test-stimulus` and `pixi run test-stimulus-live` - inside `rig/` (the last needs the rig stopped), plus `pixi run test` at the repo root, all green - before every commit. Prettier only on `*.js`, never on HTML. -- Windows habits from ww18: write text with `newline="\n"`; take durations from `perf_counter`; never - run `bias_sim` or the harness's sim while the real BIAS is open on 5010; Ctrl-C the rig to stop it - (the SIGBREAK handler closes the bag cleanly). -- Capture an error's full text before you try to fix it. Commit small fixes to - `claude/okey-cokey-rig` with a message that names the step. -- American English in code, docs and commits. - -## What to send back - -The results file (both jobs): the PC facts, BIAS build, both runs' checks, the two test counts on -Windows, a screenshot of the live dashboard mid-rotation, the replay converter's printed report, -and anything that needed a manual step. Push `claude/okey-cokey-rig`, and end with a short -summary Michael can paste into the LAB-303 ticket. diff --git a/rig/README.md b/rig/README.md index a1a90aab..45352b2d 100644 --- a/rig/README.md +++ b/rig/README.md @@ -57,7 +57,7 @@ Pieces, individually (each is also a `pixi run` task; `--help` on every script): | `sim` | `rig-fictrac-sim` (rig_nodes.sims.fictrac_sim, moved from `fictrac-bridge/`; `-- --model fly --kp 2` for a fixating fly) | | `kinefly-sim` / `bias-sim` | synthetic KF/1 producer; a fake BIAS program (HTTP, state machine, writes the video file) | | `studio-emulator` | the Studio runner's exact bridge conversation, for machines without a browser | -| `stimulus` | `nodes/stimulus_node.py`: the arena's EXPECTED display (the trial's `.pat` frame × duty in the panels' LED colors) and its OBSERVED display (controller telemetry) as PNG images, plus arena rate / lag / health, from the live `/arena/browser`; `pixi run rig -- --stimulus` (included by `--foxglove`). Layout `foxglove/arena-okey-cokey.json` (camera + G6, no FicTrac); the same layout on a finished run: `pixi run fox-json -- --stimulus --panel-layout four-color` (adds `/camera/image` from the run's uFMF). Plan + status: `docs/okey-cokey-foxglove-dashboard-plan.md` | +| `stimulus` | `nodes/stimulus_node.py`: the arena's EXPECTED display (the trial's `.pat` frame × duty in the panels' LED colors) and its OBSERVED display (controller telemetry) as PNG images, plus arena rate / lag / health, from the live `/arena/browser`; `pixi run rig -- --stimulus` (included by `--foxglove`). Layout `foxglove/arena-okey-cokey.json` (camera + G6, no FicTrac); the same layout on a finished run: `pixi run fox-json -- --stimulus --panel-layout four-color` (adds `/camera/image` from the run's uFMF). Plan + status: `rigs/ww14/okey-cokey-foxglove-dashboard-plan.md` in the private `reiserlab/arena-experiments` (rig records left this repo on 2026-10-29, LAB-227) | | `test-stimulus` / `test-stimulus-live` | the dashboard's pure parts (panel colors pinned to `js/panel-color.js`, renderer, tracker on synthetic + a real 80 fps trial, uFMF reader, replay converter); the live path through router + bridge + stimulus node (~25 s, needs ports 7447/8765 free) | Environment set by the workspace (`[activation.env]`): `RMW_IMPLEMENTATION=rmw_zenoh_cpp`, @@ -168,7 +168,7 @@ starts at its first frame, ≈ 300 ms later — take the alignment stamp ≥ 0.5 startRecording, or anchor on the first frame. The harness (`pixi run test`) exercises the same path through the emulator: 32 / 32. -One detour worth knowing: the stock `fictrac_bias_test.yaml` names `frame2_h_ccw_200f` at `pattern_ID 4`; this card's slot 4 is a 1-frame color test pattern, the runner read `frames: 1` from the card (correctly) and the display sat on frame 0. The protocol was re-pointed at the card's 20-frame grating — a card-content mismatch the Studio already warns about, not a bridge issue. +One detour worth knowing: the stock `fictrac_bias_test.yaml` (now `protocols/shared/` in the private `reiserlab/arena-experiments`) names `frame2_h_ccw_200f` at `pattern_ID 4`; this card's slot 4 is a 1-frame color test pattern, the runner read `frames: 1` from the card (correctly) and the display sat on frame 0. The protocol was re-pointed at the card's 20-frame grating — a card-content mismatch the Studio already warns about, not a bridge issue. **Still not verified:** Windows execution (`win-64` only resolves and locks; the rig-PC session is scripted step by step in `WINDOWS-TEST.md`), and the macOS router with the stock IPv6 config (the @@ -282,7 +282,7 @@ without further changes; **"ROS Studio" = that Studio served from the `/ros/` Pa Studio v0.95 served from `localhost:8092` (this branch), `pixi run rig -- --sim --bias` (model fly at 50 Hz, typed FicTrac topic, real BIAS Mac port on :5010, bridge node recording with the `crashsafe` profile), the G6 2×10 bench controller over Web Serial (Michael clicked the port -picker), protocol `protocols/bias_camera_closed_loop_poc.yaml` (6 steps: camera start → 15 s +picker), protocol `protocols/ww18/bias_camera_closed_loop_poc.yaml` from the private `reiserlab/arena-experiments` (in this repo's `protocols/` until 2026-10-29; 6 steps: camera start → 15 s closed loop → camera sync → 15 s closed loop + 90 °/s bias → camera stop → shutdown). | | result | diff --git a/rig/WINDOWS-TEST.md b/rig/WINDOWS-TEST.md index b783a7c2..a62804a4 100644 --- a/rig/WINDOWS-TEST.md +++ b/rig/WINDOWS-TEST.md @@ -52,7 +52,7 @@ pass/fail, timings, sizes and every error verbatim. 3. Follow rig/WINDOWS-TEST.md in order. Steps 1–3 need no hardware and can run unattended; stop and ask me to be at the keyboard before step 4 (BIAS) and step 5 (Studio + controller over Web Serial). Stop bridge 3.4 first if it is running. -4. Results: rig/windows-results-ww18-.md committed to the branch and pushed; add a +4. Results: a `windows-results-ww18-.md` committed to the private `reiserlab/arena-experiments` under `rigs/ww18/` (rig records left this repo on 2026-10-29, LAB-227); add a "Windows results" row to the test-plan table in PR #254's description. Small fixes may go on the branch (message names the step); never touch Production or Next. ``` @@ -70,7 +70,7 @@ Windows build with its FLIR camera, the G6 controller on USB, optionally FicTrac Visual Studio 2022 Build Tools (Desktop C++) is installed — step 1b measures the typed-message build with and without it. -**Windows notes from the first run (ww18, 2026-10-15 — `windows-results-ww18-2026-10-15.md`):** +**Windows notes from the first run (ww18, 2026-10-15 — `rigs/ww18/windows-results-ww18-2026-10-15.md` in `reiserlab/arena-experiments`):** - **Short build path for the typed build**: under `C:\Users\\Documents\GitHub\…` it hits Windows' 260-character path limit (`C1083`). Either clone to under ~45 characters before `\rig`, or, from any @@ -83,7 +83,7 @@ build with and without it. since 538cc85: 1b / 7 install on ww18 (35 s, harness 48 / 48). - **Check the controller's SD card first**: an empty SD listing / "pattern missing" can mean no card is mounted at all (ww18's old card). The site library's 2×10 set (`patterns/g6_2x10/patterns/`) plus - `protocols/bias_camera_closed_loop_poc_ww18*.yaml` (slot 2 `grating_sq`) work on any 2×10 card loaded that way. + `protocols/ww18/bias_camera_closed_loop_poc_ww18*.yaml` (`reiserlab/arena-experiments`; slot 2 `grating_sq`) work on any 2×10 card loaded that way. - **FicTrac** (pixi-built): start it with `build\Release\fictrac.bat ` from the config folder — `fictrac.exe` alone exits silently with `0xC0000135` (DLL not found). - **BIAS Atlas10 build** (atlas-bench): HTTP server on by default at 5010; uFMF by default, so `.avi` names @@ -224,7 +224,7 @@ cd .. # repo root python .claude\nocache-server.py 8092 ``` -Chrome: `http://localhost:8092/arena_studio.html?p=bias_camera_closed_loop_poc&rig=&mode=run` +Chrome: `http://localhost:8092/arena_studio.html?repo=reiserlab/arena-experiments&p=protocols/ww18/bias_camera_closed_loop_poc.yaml&rig=&mode=run` (signed in; the protocol moved to the private repo on 2026-10-29) with the rig key from `configs\rigs\index.json` that matches this arena (`cshl_g6_2x10_ball` for a 2×10 ball rig). Then: diff --git a/rig/config/bias/okey-cokey-bfs-u3-51s5m.json b/rig/config/bias/okey-cokey-bfs-u3-51s5m.json deleted file mode 100644 index ac2ce960..00000000 --- a/rig/config/bias/okey-cokey-bfs-u3-51s5m.json +++ /dev/null @@ -1,253 +0,0 @@ -{ - "camera": { - "format7Settings": { - "mode": "0", - "pixelFormat": "MONO8", - "roi": { - "height": 2048, - "offsetX": 0, - "offsetY": 0, - "width": 2448 - } - }, - "frameRate": "Format7", - "guid": "24203703", - "model": "Blackfly S BFS-U3-51S5M", - "properties": { - "autoExposure": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - }, - "brightness": { - "absoluteControl": true, - "absoluteValue": 0.0, - "autoActive": false, - "on": true, - "onePush": false, - "present": true, - "value": 33 - }, - "focus": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - }, - "frameRate": { - "absoluteControl": true, - "absoluteValue": 73.3232421875, - "autoActive": false, - "on": false, - "onePush": false, - "present": true, - "value": 95 - }, - "gain": { - "absoluteControl": true, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": true, - "value": 0 - }, - "gamma": { - "absoluteControl": true, - "absoluteValue": 0.800048828125, - "autoActive": false, - "on": true, - "onePush": false, - "present": true, - "value": 14 - }, - "hue": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - }, - "iris": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - }, - "pan": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - }, - "saturation": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - }, - "sharpness": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - }, - "shutter": { - "absoluteControl": true, - "absoluteValue": 998.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": true, - "value": 0 - }, - "temperature": { - "absoluteControl": true, - "absoluteValue": 52.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": true, - "value": 70 - }, - "tilt": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - }, - "triggerDelay": { - "absoluteControl": true, - "absoluteValue": 13.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": true, - "value": 0 - }, - "triggerMode": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": true, - "value": 0 - }, - "whiteBalance": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "valueBlue": 0, - "valueRed": 0 - }, - "zoom": { - "absoluteControl": false, - "absoluteValue": 0.0, - "autoActive": false, - "on": false, - "onePush": false, - "present": false, - "value": 0 - } - }, - "triggerType": "Internal", - "vendor": "Point Grey Research", - "videoMode": "Format7" - }, - "configuration": { - "directory": "C:/Users/labadmin/Documents", - "fileName": "bias_config" - }, - "display": { - "colorMap": "None", - "orientation": { - "flipHorizontal": false, - "flipVertical": false - }, - "rotation": 0, - "updateFrequency": 15.0 - }, - "logging": { - "autoNamingOptions": { - "cameraIdentifier": "CameraNumber", - "includeCameraIdentifier": true, - "includeTimeAndDate": true, - "includeVersionNumber": true, - "timeAndDateFormat": "\u0027date\u0027_yyyy_MM_dd_\u0027time\u0027_hh_mm_ss" - }, - "directory": "C:/rec/okey-cokey", - "enabled": false, - "fileName": "bias_video", - "format": "ufmf", - "settings": { - "avi": { - "codec": "MJPG", - "frameSkip": 1 - }, - "bmp": { - "frameSkip": 1 - }, - "fmf": { - "frameSkip": 1 - }, - "jpg": { - "compressionThreads": 10, - "frameSkip": 1, - "mjpg": true, - "mjpgMaxFramePerFile": 5000000, - "mjpgMaxFramePerFileFlag": false, - "quality": 90 - }, - "ufmf": { - "backgroundThreshold": 40, - "boxLength": 30, - "compressionThreads": 15, - "dilate": { - "on": true, - "windowSize": 1 - }, - "frameSkip": 1, - "medianUpdateCount": 100, - "medianUpdateInterval": 50 - } - } - }, - "server": { - "enabled": true, - "port": 5010 - }, - "timer": { - "enabled": false, - "settings": { - "duration": 40 - } - } -} diff --git a/rig/docs/okey-cokey-foxglove-dashboard-plan.md b/rig/docs/okey-cokey-foxglove-dashboard-plan.md deleted file mode 100644 index 055cf91e..00000000 --- a/rig/docs/okey-cokey-foxglove-dashboard-plan.md +++ /dev/null @@ -1,176 +0,0 @@ -# Plan: a purpose-built Foxglove dashboard for the okey-cokey rig (camera + G6, no FicTrac) - -> **Status 2026-10-21: phases 1, 1b and 2 BUILT** on branch `claude/okey-cokey-rig` (off -> `claude/okey-cokey-pairs`, itself the 2026-10-20 candidate + Grace's protocol shape). Phase 3 -> (live camera) NOT built, by decision: BIAS stays unchanged for now. The okey-cokey controller -> runs telemetry firmware (Michael), so the observed side is live. Part of LAB-303. -> -> | built | where | -> |---|---| -> | `/arena/browser` published live | `nodes/bridge_node.py` (one publisher; the bag record is unchanged) | -> | tracker: expected/observed index, lag, display rate, health | `nodes/stimulus_core.py` (pure) | -> | renderer + Python port of the panel colors | `nodes/arena_display.py` (pinned cell by cell to `js/panel-color.js` under Node) | -> | live node | `nodes/stimulus_node.py`; `pixi run rig -- --stimulus` (`--foxglove` includes it), `--no-fictrac` for this rig | -> | BIAS rate / drops / rtt / writer queue | `rig_nodes.bias_health` (reiserlab/rig-nodes), `viz_node.py` (`--sources bias` hides the unused FicTrac/Kinefly/arena-frame statuses) | -> | replay: same topics from a finished bag + `/camera/image` from the uFMF | `nodes/dashboard_replay.py` via `pixi run fox-json -- --stimulus`; `tools/ufmf.py` (BIAS v4 layout, index or scan) | -> | layout | `foxglove/arena-okey-cokey.json` (15 panels) | -> -> Tested: `pixi run test-stimulus` 54/54 (incl. a REAL 80 fps trial from the 2026-10-14 smoke run: -> 800+ frames, every lag 0, 80 Hz); `pixi run test-stimulus-live` 10/10 (router + bridge + -> stimulus node, the real trial played into the Studio WebSocket); ROS harness `pixi run test` -> 54/54 and `test-viz` unchanged; `rig.py --no-fictrac --bias-sim --bias --foxglove` brings every -> dashboard topic up with foxglove_bridge on :8766; uFMF reader on three real BIAS movies -> (1920×1080, up to 2.1 GB, indexed). On the real 2026-10-14 bag the replay MCAP is 612 kB for -> 2.5 min. **Not verified:** the layout opened in the Foxglove app (written to Foxglove's layout -> format by hand; its topics are checked by the test), a live run on the okey-cokey rig, a real -> uFMF from that rig's camera through the replay. -> -> Deviations from the plan below: (1) the arena status is the stimulus node's own -> `arena/display` diagnostic instead of re-feeding viz_node's FicTrac-frame `arena` entry, which -> keeps meaning closed-loop frames; (2) an OBSERVED display image (`/arena/display_observed`) -> was added beside the expected one; (3) `/viz/arena/observed_idx` and `lag_frames` are one sample -> per displayed frame (live and replay alike); (4) the expected display's stall check is STALE only -> while a pattern plays, since the telemetry ring has nothing to send in the dark; (5) the tiff -> tool's PNG writer was not refactored into the shared module (left as it was). - -## Goal - -One Foxglove layout for a freely-walking-fly rig (overhead BIAS camera + G6 2×10 four-color arena, no -FicTrac) showing three things: - -1. **The camera.** -2. **The expected display**: what the arena should be showing right now (pattern, frame, color, duty), - rendered as an image of the unrolled arena. -3. **Health and rate** for the arena and for BIAS: is each one alive, at what rate, and has anything - dropped. - -The same layout should work **live** (foxglove_bridge during the run) and on **replay** (the run's MCAP -afterwards). - -## What exists today (release 2026-10-20, `rig/`) - -| Need | Today | Gap | -|---|---|---| -| BIAS health | `/bias/status` at 5 Hz (`bias_node.py`): reachable, capturing, logging, frameCount, framesPerSec, rtt_ms; on Atlas10 also frameIdGaps, cameraSkipped, loggerSkipped, writerSkipped, logQueueSize. `viz_node` turns these into `/viz/bias/*` and a `bias` entry in `/diagnostics`. `foxglove/arena-bias.json` shows them. | Mostly there. Missing: a rate from the frameCount slope (independent of BIAS's own estimate) and the drop counters on a plot. | -| Arena health | `viz_node`'s `arena` entry is fed only by `/arena/frame`, which only FicTrac lines produce. | **Always "never seen" on this rig.** | -| What the arena is doing | Every Studio message (trialParams `a` echoes, runner status events with `conditionName` / `params` / `durationSec`, controller telemetry `cc`/`cf`/`cs` row batches) reaches `bridge_node`. | It is **written to the bag only** (`/arena/browser`, `/arena/event` via `rec.write_raw`), never published, so nothing live can subscribe. The `a` hex is truncated to 8 bytes (no gain/duration). | -| Expected display image | `tools/patfile.py` reads `.pat`; `tools/tiff_run_to_mcap.py` already renders `/arena/display` as a `foxglove.CompressedImage` (200×40, upscaled, labeled RECONSTRUCTED). | Offline only, and wired to the 2P color-sweep file names and DO1 timing. Monochrome. | -| Camera image | Nothing. BIAS's HTTP API has no frame grab (only status, timestamps, frame count, video file). | **No live frames anywhere.** After the run, the `.ufmf` plus the per-frame stamps on `/bias/frames` (recorded at stopRecording) are enough to rebuild a timed image stream. | - -## Design - -### A. Make the arena's activity a live topic (bridge_node, small) - -`bridge_node` publishes what it already records: - -- `/arena/browser`: String JSON, verbatim, the same record it writes to the bag (QoS reliable, depth 50). - It stays in the bag as now. - -That one change lets any node watch the run without touching the Studio or bridge 3.4. - -### B. `stimulus_node.py`: expected display + arena health (new node, the core of the work) - -It subscribes to `/arena/browser` and keeps the current stimulus state: - -- From runner status events and the trialParams echoes: condition name, pattern name, mode, frame - rate, start frame, duty, and the trial start time (the echo's `ms`, i.e. when the command left the - browser). -- Expected frame for Mode 2: `idx = (init + round(fps · (t − t_start))) mod n_frames`. `allOff` = dark. - Mode 3 is out of scope here (it needs FicTrac). -- Observed frame: telemetry `cf` rows carry the frame the controller actually put up (idx + pattern - per frame change). Arrival is in ~10 Hz batches, so the observed stream lags by ~100 ms, but each row - has its own controller timestamp. - -It publishes: - -| Topic | Type | Rate | What | -|---|---|---|---| -| `/arena/stimulus` | String JSON (later typed) | on change | `{condition, pattern, mode, fps, init, duty, t_start}` | -| `/arena/display` | `foxglove.CompressedImage` (PNG) | 10–15 Hz | the expected display: unrolled 200×40, ×4 upscale, top row = top | -| `/viz/arena/expected_idx`, `/viz/arena/observed_idx` | Float64 | 10–15 Hz / per row | for one plot of the two indices together | -| `/viz/arena/display_rate_hz` | Float64 | 1 Hz | frame changes per second from `cf` rows; should equal \|fps\| (80) during a trial and 0 in the dark | -| `/viz/arena/lag_frames` | Float64 | 1 Hz | expected − observed (mod n), the "is the arena keeping up" number | -| `/diagnostics` entry `arena` | DiagnosticStatus | 1 Hz | OK / WARN (rate off by > 5 %, lag > 2 frames, a `display_gap` or flagged `trial_quality`) / STALE (no telemetry for > 2 s while a run is open) / ERROR (non-ok command status) | - -**Color, not green.** The four-color panels light one bank per pattern, so a green ramp would show the -wrong thing. Port the minimum of `js/panel-color.js` to Python (the layout table, and -`bankAt(row, col) = 2·((row+1)%2) + (col%2)` in host coordinates), and pin it to the JS module with a -test. Duty scales the brightness. Without that test, the two copies will drift. - -**Patterns.** The node needs the `.pat` files. It looks them up by name under a `--pattern-dir` (the -protocol's `*_patterns/` folder) and caches them. If a pattern is missing, the image shows the name on -a gray placeholder; the run is never refused. - -**If the firmware has no telemetry** (GET_FIRMWARE_VERSION flag bit 2 off, or the poller is off), there -are no `cf` rows: show the expected display only, observed = "unknown", and arena health from command -status alone. It must never claim "OK" without evidence (the same rule as `trial-quality.js`). - -### C. BIAS health additions (viz_node, small) - -- `/viz/bias/fps_from_count`: frameCount slope over the last 2 s of 5 Hz polls, next to BIAS's own - `framesPerSec`. -- `/viz/bias/drops`: the sum of the Atlas10 counters (frameIdGaps + cameraSkipped + loggerSkipped + - writerSkipped) as a plot; WARN on any increase while logging. Absent keys (Spinnaker build) mean - "not reported", not 0. -- `/viz/bias/rtt_ms` from the status polls; WARN at > 100 ms. - -### D. Camera - -- **Replay (phase 2).** `ufmf_to_mcap.py` reads the run's `.ufmf` and writes a sidecar MCAP with - `/camera/image` as `foxglove.CompressedImage` JPEG, timed by the `/bias/frames` stamps from the run - bag, downsampled (for example 10 Hz, half resolution) so the file stays small. Foxglove opens the run - bag and the sidecar together. uFMF needs a reader: write a small one (the format is documented, and - motmot's reader is Python 2-era), and test it on a ww18 recording. -- **Live (phase 3, only if wanted).** BIAS has to push frames out: a small BIAS patch (in our - atlas-bench fork) publishing a rate-limited JPEG preview over ZMQ. A node then mirrors it as - `/camera/image`, copying `scanimage_frames_node.py` (rate limit + loss stats). Until then, keep the - BIAS window beside Foxglove during the run. - -### E. The layout: `foxglove/arena-okey-cokey.json` - -``` -┌──────────────────────────────┬─────────────────────────────────────┐ -│ Image /camera/image │ Image /arena/display (expected) │ -│ (replay; live after phase 3) │ caption: condition · pattern · duty │ -├──────────────┬───────────────┼─────────────────────────────────────┤ -│ Indicators │ Plot: BIAS fps (own + from count), drops │ -│ BIAS reach/ ├─────────────────────────────────────────────────────┤ -│ capture/log; │ Plot: arena expected vs observed idx; display rate │ -│ arena state ├─────────────────────────────────────────────────────┤ -│ │ StateTransitions: /arena/stimulus condition │ -├──────────────┴──────────────────┬──────────────────────────────────┤ -│ DiagnosticSummary (/diagnostics)│ RawMessages /bias/reply │ -└─────────────────────────────────┴──────────────────────────────────┘ -``` - -### F. Replay parity - -The derived topics (B, C) do not exist in bags recorded before this work, and live ones are not -recorded unless we choose to. Do both: - -- Record `/arena/stimulus` live (it is small). Do not record `/arena/display`: it is rebuilt from - stimulus + patterns. -- Extend `nodes/foxglove_json.py` (the offline converter) to run the same `stimulus_node` logic over - the bag's `/arena/browser` records and emit `/arena/display` and the `/viz/arena/*` topics. Then the - layout works on any run, including today's. - -The logic lives in one pure module (`nodes/stimulus_core.py`) used both by the live node and by the -converter, the same split as `studio-replay.js`. - -## Phases and size - -| Phase | What | Size | Gate | -|---|---|---|---| -| 1 | A + B (no color port yet) + C + layout; harness test with the Studio emulator sending trialParams + `cf` rows | ~1 day | live on the Mac bench with the 2×10 arena | -| 1b | Python color port + test pinned to `js/panel-color.js` | ~2 h | the four banks show their colors | -| 2 | F (offline converter) + D replay (uFMF reader → sidecar MCAP) | ~1 day | today's okey-cokey run opens with camera + expected display in sync | -| 3 | D live (BIAS ZMQ preview patch + node) | ~1–2 days, BIAS C++ | only if a live camera in Foxglove is worth more than the BIAS window | - -## Open questions - -1. ~~Is a live camera in Foxglove needed?~~ Not now (2026-10-21): BIAS stays unchanged; replay has the camera. -2. ~~Telemetry firmware on the okey-cokey controller?~~ Yes (Michael, 2026-10-21). -3. Which BIAS build is on the okey-cokey PC: the Atlas10 fork has the drop counters, the Spinnaker - build may not (the panels then read "no data", never 0). -4. Typed messages: `viz_node` subscribes to `/arena/frame` as String, which breaks in the `typed` env. - That doesn't affect this rig (no `/arena/frame`), but fix it when touching viz_node. diff --git a/rig/okey-cokey-pc-2026-10-21.md b/rig/okey-cokey-pc-2026-10-21.md deleted file mode 100644 index 10b3171a..00000000 --- a/rig/okey-cokey-pc-2026-10-21.md +++ /dev/null @@ -1,167 +0,0 @@ -# Okey-cokey rig PC — BIAS + G6 color optomotor test, 2026-10-21 - -The run sheet for today's test on the new lab PC in the okey-cokey room (freely walking flies, overhead -BIAS camera, G6 2×10 four-color arena, **no FicTrac**). It is `WINDOWS-TEST.md` cut down to what this -rig needs, with the ww18 findings of 2026-10-15 (`windows-results-ww18-2026-10-15.md`) applied up front. -Record results in a sibling `okey-cokey-pc-results-2026-10-21.md` in the same shape as the ww18 file. - -Branch: `claude/okey-cokey-rig` = the 2026-10-20 release candidate (Studio v0.97, ROS 2 graph, -BIAS connector) + the CW/CCW-pair protocols Grace asked for (`claude/okey-cokey-pairs`) + the camera + -G6 Foxglove dashboard (`docs/okey-cokey-foxglove-dashboard-plan.md`). Neither is on a Pages tier, so -serve the checkout locally (step 4). - -## 0. Setup (bare PC → ready) - -Do [`okey-cokey-pc-setup.md`](okey-cokey-pc-setup.md) first: PC access, git / pixi / Chrome / -Foxglove, BIAS + camera SDK, the controller, the clone at `C:\w` on `claude/okey-cokey-rig`, -the three `pixi install`s, both dashboard tests and a no-flies smoke of the whole stack. The steps -below assume it is done. - -- [ ] IR backlight on by hand (Arduino). The web runner does not drive the backlight yet. - -## 1. BIAS itself (10 min) - -- [ ] **Which BIAS build?** FLIR/Spinnaker build or the atlas-bench Atlas10 fork (ww18 ran the fork). - Note the exe path and camera model. -- [ ] External-control server on, port **5010** for camera 0: - `curl.exe "http://127.0.0.1:5010/?get-status"` returns `"success" : true`. Note any extra keys. -- [ ] Logging format = **ufmf** in BIAS's own settings (BIAS writes its configured format whatever - extension it is sent; ww18 B1). BIAS v0.73.1 (Spinnaker) resets to jpg on **every** restart (ww14), - so after each BIAS start run - `powershell -ExecutionPolicy Bypass -File tools\bias-ufmf.ps1 -VideoDir