Captured 2026-09-15 directly from the omny-panda hardware via GetState() (ophyd_devices/devices/panda_box/utility_scripts.py), one per Design: omny.ini (Mode A) and omny_freerun.ini (Mode B). Mode B was speed-tested successfully up to 20 kHz same day, though the saved omny_freerun.ini itself reflects CLOCK1.PERIOD=0.0001 (10 kHz) -- flagged in the setup doc as a discrepancy to double-check rather than assumed away. Setup doc updated with the exact save/load commands and a diff summary of the two layouts' mode-specific differences (PCAP.GATE/TRIG source, CLOCK1.ENABLE, capture types). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TsfqLcPWspjJ6vedMyCxco
13 KiB
PandABox (omny_panda) burst acquisition: two operation modes
Status: Mode B confirmed working end-to-end against real PandA hardware (2026-09-15, omny-panda.psi.ch) — correct per-channel raw values, masked
gate_detector_active, andframe_counter-based completion all verified, then speed-tested up to 20 kHz with no reported issues (though see the caveat under "Saving / restoring layouts" — the savedomny_freerun.inilayout itself was captured at 10 kHz, not 20 kHz).ptycho_flomni.yamlcurrently hasomny_panda_continuous(Mode B) active — note the PandA's Design was switched back toOMNYon 2026-09-15 solely to capture theomny.inilayout file below, so double-check which Design is actually loaded on the physical PandA before running a scan against either config.
Context
flomni burst scans acquire frames_per_trigger sub-frames per scan point.
Historically, the PandABox (omny_panda, device class PandaBoxOMNY in
csaxs_bec/devices/panda_box/panda_box_omny.py) reports one PCAP-aggregated
statistic (Mean/Min/Max/Sum/Diff) per gate, discarding the time structure
within a gate. This document describes the two supported operation modes —
the existing per-gate aggregate mode, and a new continuous free-running raw
mode — and how the PandA hardware and ptycho_flomni.yaml device config must
be set up for each.
Mode selection is a single software flag (raw_stream_mode in the device
config); it does not by itself reconfigure the PandA hardware. The PandA
layout must independently match the intended mode (see "Open question" at
the end) before that mode can be used.
Mode comparison
| Mode A: legacy (default) | Mode B: free-running (new) | |
|---|---|---|
| Selected by | raw_stream_mode unset / false |
raw_stream_mode: true |
| PCAP capture fields | Mean/Min/Max/Sum/Diff per gate |
Value (raw instantaneous sample) + gate bit + frame counter |
| Gate/arm timing | Pulsed per point / sub-frame (rate = scan rate × frames_per_trigger) |
PCAP held open continuously for the burst window, free-running at CLOCK1's configured rate (started at 100 Hz for initial testing on 2026-09-15; will be ramped up as testing progresses) |
_acquisition_group (BEC async metadata) |
fly / monitored / burst, chosen from scan_type/frames_per_trigger |
free_running |
| Message publishing | One .data.put() per PandA network frame, unchanged |
Coalesced client-side: flushed every raw_stream_flush_row_count rows or raw_stream_flush_interval seconds, whichever comes first (mitigates Redis/HDF5 message-rate risk — see the acquisition plan's data-path notes) |
Completion check (on_complete) |
Exact count: waits for *PCAP.CAPTURED? to reach num_points * frames_per_trigger |
Exact count: waits for COUNTER1.OUT? (frame_counter) to reach num_points * frames_per_trigger. *PCAP.CAPTURED? can't be used here — CLOCK1 drives PCAP.gate/PCAP.trig continuously (see "Continuous gate/arm" below), so the captured count never naturally stops increasing while armed; COUNTER1 instead counts completed detector exposures directly (it's clocked off the detector-trigger's falling edge), so it reaches the expected total exactly when the last exposure finishes |
| Data completeness verification | Implicit in the exact-count completion check | Same mechanism as Mode A: frame_counter (COUNTER1) is both the completion signal and the post-hoc completeness record — a gap in an otherwise-monotonic sequence in the HDF5 data means dropped rows even though the expected total was reached |
Both modes are implemented in the same PandaBoxOMNY class; raw_stream_mode
defaults to false so any device config that doesn't set it keeps today's
exact behavior unchanged.
Physical wiring (Mode B only)
The detector-trigger signal is patched into TTL input 1 (TTLIN1) on the
PandA front panel. That same TTLIN1.VAL signal feeds both downstream
consumers (see "Block configuration" below): it is the source counted by
COUNTER1 (the frame counter) and the source captured into the gate bit.
Block configuration
Mode A (legacy) — reference baseline
The blocks already configured and aliased today, for reference / so this
layout can be restored unambiguously (see ptycho_flomni.yaml):
FMC_IN.VAL1/FMC_IN.VAL2(capacitive sensor analog inputs) —Min,Max,Meancapture fields.INENC1-INENC4.VAL(position-capture encoder inputs) —Min,Max,Meancapture fields.PCAP.GATE_DURATION—Value.- PCAP gate is pulsed once per point / sub-frame by the existing external trigger electronics (unchanged by this work).
Mode B (new) — free-running raw capture
-
Gate-bit input: the detector-trigger signal, wired into
TTLIN1(see "Physical wiring" above).TTLIN1.VALis captured intoPCAP.BITS0, bit offset 0 — confirmed 2026-09-15 against the omny-panda hardware via:TTLIN1.VAL.CAPTURE_WORD? -> OK =PCAP.BITS0 TTLIN1.VAL.OFFSET? -> OK =0PCAP.BITS0is a shared 32-bit capture word — other capturedbit_outsignals (several TTLIN/LVDSIN/encoder bits are also flagged for capture on this layout) may occupy other bit positions of the same word, so the rawPCAP.BITS0.Valueis not itself a clean 0/1.PandaBoxOMNYmasks it down to bit 0 inraw_stream_mode(see_GATE_BIT_OFFSETinpanda_box_omny.py) before publishing it asgate_detector_active— treat that masking as required, not optional, unless it's confirmed nothing else sharesBITS0. Re-run the query above (read-only, safe against live hardware) if the PandA layout is ever rebuilt, since bit assignment is fixed by the FPGA build and not guaranteed stable across rebuilds. -
Frame counter:
COUNTER1, triggered offTTLIN1.VAL's falling edge (see layout below) — i.e. it increments once per completed detector exposure, not once per PCAP capture tick as originally guessed here. Captured withCAPTURE=Value; this is both the data-completeness signal and the completion-detection signal (frame_counterinsignal_alias, see below, and "Completion check" in the mode-comparison table above) — once the last expected exposure's trigger pulse has fallen,COUNTER1.OUTreachesnum_points * frames_per_triggerexactly. -
Raw analog/position capture:
FMC_IN.VAL1,FMC_IN.VAL2and all fourINENC1-4.VALare switched from aggregate capture toCAPTURE=Valueon this layout (confirmed 2026-09-15) — i.e. every analog/encoder channel goes raw together, not a subset. This means the current PandA layout cannot simultaneously produce Mode A's aggregate (Min/Max/Mean) data — see "Open question" below. -
Continuous gate/arm:
CLOCK1, enabled by the constantONE, drivesPCAP'sgateandtriginputs continuously (instead of the external per-point trigger used in Mode A), so PCAP free-runs atCLOCK1's configured rate. Testing started at 100 Hz on 2026-09-15 and will be ramped up; there is no PandA-side layout change needed to change the rate, onlyCLOCK1's period.TTLIN1 (TTL input 1, detector trigger) ├──▶ COUNTER1.trig, falling edge (Up/Down pulse counter) ──▶ COUNTER1.OUT = frame_counter (CAPTURE=Value) │ = completion signal too: reaches num_points * frames_per_trigger when the last exposure finishes └──▶ captured into PCAP.BITS0 bit 0 ──▶ masked in software = gate_detector_active CLOCK1 (enabled by constant ONE) ──▶ PCAP.gate, PCAP.trig (continuous arm/capture)
Saving / restoring layouts
Use ophyd_devices/devices/panda_box/utility_scripts.py to save the current
PandA configuration to a .ini layout file and reload it later, so a
configuration survives a PandA power cycle. Both layouts are saved in this
repo under csaxs_bec/devices/panda_box/layouts/ — omny.ini (Mode A) and
omny_freerun.ini (Mode B), captured 2026-09-15 directly from the PandA
design named OMNY/omny_freerun respectively (switch the PandA's active
Design first, then save):
python ophyd_devices/devices/panda_box/utility_scripts.py \
--host omny-panda.psi.ch --save-layout csaxs_bec/devices/panda_box/layouts/omny.ini
python ophyd_devices/devices/panda_box/utility_scripts.py \
--host omny-panda.psi.ch --save-layout csaxs_bec/devices/panda_box/layouts/omny_freerun.ini
To restore either, swap --save-layout for --load-layout with the same
file. Diffing the two confirms the mode-specific differences documented
above: PCAP.GATE/PCAP.TRIG are TTLIN1.VAL in Mode A vs CLOCK1.OUT in
Mode B; CLOCK1.ENABLE is ZERO (off) in Mode A vs ONE in Mode B;
INENC1-4.VAL.CAPTURE/FMC_IN.VAL1/VAL2.CAPTURE are Min Max Mean in
Mode A vs Value in Mode B; PCAP.BITS0.CAPTURE/COUNTER1.OUT.CAPTURE are
No in Mode A vs Value in Mode B.
omny_freerun.ini was captured with CLOCK1.PERIOD=0.0001 (100 µs → 10 kHz)
— note this doesn't match the 20 kHz reported as tested successfully the
same day, so it may not be the exact layout state the 20 kHz test ran at;
worth double-checking before relying on this file as "the" validated
high-rate layout.
Signal alias mapping
The physical PandA block feeding each BEC signal name, kept here and in
csaxs_bec/device_configs/ptycho_flomni.yaml (omny_panda.deviceConfig.signal_alias for
Mode A, omny_panda_continuous.deviceConfig.signal_alias for Mode B) in sync as a single
source of truth:
| PandA block | Mode | BEC signal name |
|---|---|---|
FMC_IN.VAL1.Min/.Max/.Mean |
A | cap_voltage_fzp_y_min/_max/_mean |
FMC_IN.VAL2.Min/.Max/.Mean |
A | cap_voltage_fzp_x_min/_max/_mean |
INENC1.VAL.Min/.Max/.Mean |
A | interf_st_fzp_y_min/_max/_mean |
INENC2.VAL.Min/.Max/.Mean |
A | interf_st_fzp_x_min/_max/_mean |
INENC3.VAL.Min/.Max/.Mean |
A | interf_st_rotz_min/_max/_mean |
INENC4.VAL.Min/.Max/.Mean |
A | interf_st_rotx_min/_max/_mean |
PCAP.GATE_DURATION.Value |
A, B | pcap_gate_duration_value |
FMC_IN.VAL1/VAL2.Value, INENC1-4.VAL.Value |
B | cap_voltage_fzp_y/_x, interf_st_fzp_y/_x/_rotz/_rotx |
PCAP.BITS0.Value (bit 0 = TTLIN1.VAL, confirmed 2026-09-15; masked in software, see above) |
B | gate_detector_active |
COUNTER1.OUT.Value (confirmed 2026-09-15, triggered off TTLIN1.VAL's falling edge) |
B | frame_counter |
The BEC-side data schema produced by Mode B (what ends up in the HDF5 async
dataset) is documented separately in
docs/developer/panda_box_free_running_data_schema.md.
Switching between modes
omny_panda (Mode A) and omny_panda_continuous (Mode B) need different PandA hardware
layouts and can't run simultaneously (see "Open question" below), so exactly one should be
active in ptycho_flomni.yaml at a time, matching whichever layout is currently loaded on
the PandA — comment out the other one's block entirely, don't rely on enabled: false
alone.
That's a correction from earlier in this project: enabled: false on a device BEC has never
connected to is an inert placeholder — connect_device in
bec_server/device_server/devices/devicemanager.py is only called when enabled — so the
theory was that both entries could stay uncommented and switching would just be flipping the
flag. In practice (2026-09-15), disabling omny_panda_continuous this way was not
sufficient once it had already been connected in the running session: both devices ended up
active simultaneously against the same physical PandA. A device that's already connected
doesn't appear to get disconnected just because a later config reload marks it disabled.
Commenting the block out (so BEC never even constructs the device object) is the reliable
way to guarantee it's gone; if in doubt, confirm the other device has actually dropped its
connection (e.g. via a device server restart) rather than trusting enabled: false alone.
Confirmed end-to-end 2026-09-15: Mode B tested working (correct per-channel values, masked
gate_detector_active, frame_counter-based completion), then switched back to Mode A
(commented out) for A/B comparison testing.
Open question, now partially resolved
Can both modes coexist under a single static PandA layout — i.e. is mode
switching purely the raw_stream_mode software flag, with the PandA always
capturing both the aggregate and raw fields simultaneously — or do Mode A
and Mode B require two distinct saved layouts that must be loaded before a
scan depending on the desired mode?
As currently wired for testing (2026-09-15), the answer looks like two
distinct layouts are required: FMC_IN.VAL1/VAL2 and INENC1-4.VAL are
each set to CAPTURE=Value (single selection), not simultaneously
CAPTURE=Value,Min,Max,Mean — so this layout cannot also produce Mode A's
aggregate statistics at the same time. This has not yet been deliberately
tested against the alternative (whether the PandA capture UI actually
supports selecting Value together with Min/Max/Mean on the same
field) — if that turns out to work, a single layout could serve both modes.
Once Mode B testing is far enough along to save a named layout (see
"Saving / restoring layouts" below), settle this explicitly and record the
answer plus, if two layouts are needed, the load procedure to switch between
them.