Expose burst acquisition in the app; refresh scan_format acquisition docs
Wires the burst_mode flag through QtScanController to ScanEngine and adds a checkbox to the scan panel. The setting persists via ScanDefaults like the other scan fields, defaulting to off — per-row acquisition stays the default path until burst mode has run on the rig and the gate-off preflight has settled which TRIGOUT value idles the pin low. scan_format.md — the acquisition settings table had drifted from the code it claimed to describe: it attributed the settings to sc3_aui_app.py (they moved to core/scope_sras.py in the Phase 2 extraction), listed a 1.24 V trigger level and 0 % offset where the code sets 0.500 V and HORizontal:POSition 30, and did not mention the logic-AND scan trigger at all. Corrected, pointed at the module that actually owns them, and noted that none of it affects byte layout — only where the acoustic packet lands inside a frame. Added an acquisition-paths section: the two paths write byte-identical files and the choice is a runtime flag that is not recorded in the file, so a reader never needs to care which produced it. Documents where row boundaries come from in a burst and that either path squares rows up to n_frames. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ class ScanDefaults:
|
|||||||
oscope_ip: str = "192.168.0.1"
|
oscope_ip: str = "192.168.0.1"
|
||||||
save_dir: str = str(DEFAULTS_PATH.parent / "scans")
|
save_dir: str = str(DEFAULTS_PATH.parent / "scans")
|
||||||
helios_port: str = "/dev/ttyUSB2"
|
helios_port: str = "/dev/ttyUSB2"
|
||||||
|
burst_mode: bool = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, path: Path = DEFAULTS_PATH) -> "ScanDefaults":
|
def load(cls, path: Path = DEFAULTS_PATH) -> "ScanDefaults":
|
||||||
|
|||||||
+3
-2
@@ -32,7 +32,7 @@ class QtScanController(QObject):
|
|||||||
paused_changed = pyqtSignal(bool) # True while paused at a row boundary
|
paused_changed = pyqtSignal(bool) # True while paused at a row boundary
|
||||||
|
|
||||||
def __init__(self, stage, scope, rotator, plan, out_path,
|
def __init__(self, stage, scope, rotator, plan, out_path,
|
||||||
resume=None, on_scan_active=None):
|
resume=None, on_scan_active=None, burst_mode=False):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._prompt_event = threading.Event()
|
self._prompt_event = threading.Event()
|
||||||
self._on_scan_active = on_scan_active
|
self._on_scan_active = on_scan_active
|
||||||
@@ -47,7 +47,8 @@ class QtScanController(QObject):
|
|||||||
prompt=self._blocking_prompt,
|
prompt=self._blocking_prompt,
|
||||||
)
|
)
|
||||||
self._engine = ScanEngine(stage, scope, rotator, plan, out_path,
|
self._engine = ScanEngine(stage, scope, rotator, plan, out_path,
|
||||||
resume=resume, callbacks=callbacks)
|
resume=resume, callbacks=callbacks,
|
||||||
|
burst_mode=burst_mode)
|
||||||
|
|
||||||
# ── Engine control (called from the GUI thread) ───────────────────────────
|
# ── Engine control (called from the GUI thread) ───────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -997,6 +997,16 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="burst_mode_check">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Acquire as many whole rows per FastFrame acquisition as the scope can hold, and transfer each burst in one CURVe? transaction. The stage trigger output is gated off for the flyback between rows.</string>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Burst acquisition (multi-row FastFrame)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="start_scan_btn">
|
<widget class="QPushButton" name="start_scan_btn">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|||||||
@@ -917,6 +917,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.row_spacing_edit.setText("0.250")
|
self.row_spacing_edit.setText("0.250")
|
||||||
self.scan_prefix_edit.setText("scan")
|
self.scan_prefix_edit.setText("scan")
|
||||||
self.scan_save_dir_edit.setText(DEFAULTS.save_dir)
|
self.scan_save_dir_edit.setText(DEFAULTS.save_dir)
|
||||||
|
self.burst_mode_check.setChecked(DEFAULTS.burst_mode)
|
||||||
|
|
||||||
# Hide the old T3R manual controls; the connect toggle becomes the panel button.
|
# Hide the old T3R manual controls; the connect toggle becomes the panel button.
|
||||||
for w in (
|
for w in (
|
||||||
@@ -977,6 +978,7 @@ class MainWindow(QMainWindow):
|
|||||||
self.t3r_comport_edit.editingFinished.connect(self._persist_defaults)
|
self.t3r_comport_edit.editingFinished.connect(self._persist_defaults)
|
||||||
self.bbd202_comport_edit.editingFinished.connect(self._persist_defaults)
|
self.bbd202_comport_edit.editingFinished.connect(self._persist_defaults)
|
||||||
self.oscope_ip_edit.editingFinished.connect(self._persist_defaults)
|
self.oscope_ip_edit.editingFinished.connect(self._persist_defaults)
|
||||||
|
self.burst_mode_check.toggled.connect(self._persist_defaults)
|
||||||
|
|
||||||
# Camera
|
# Camera
|
||||||
self.show_camera_toggle.toggled.connect(self._on_camera_toggle)
|
self.show_camera_toggle.toggled.connect(self._on_camera_toggle)
|
||||||
@@ -1134,6 +1136,7 @@ class MainWindow(QMainWindow):
|
|||||||
DEFAULTS.bbd_port = self.bbd202_comport_edit.text().strip()
|
DEFAULTS.bbd_port = self.bbd202_comport_edit.text().strip()
|
||||||
DEFAULTS.oscope_ip = self.oscope_ip_edit.text().strip()
|
DEFAULTS.oscope_ip = self.oscope_ip_edit.text().strip()
|
||||||
DEFAULTS.save_dir = self.scan_save_dir_edit.text().strip()
|
DEFAULTS.save_dir = self.scan_save_dir_edit.text().strip()
|
||||||
|
DEFAULTS.burst_mode = self.burst_mode_check.isChecked()
|
||||||
DEFAULTS.save()
|
DEFAULTS.save()
|
||||||
|
|
||||||
# ── Camera toggle ─────────────────────────────────────────────────────────
|
# ── Camera toggle ─────────────────────────────────────────────────────────
|
||||||
@@ -1257,6 +1260,7 @@ class MainWindow(QMainWindow):
|
|||||||
# a worker concern, not the engine's.
|
# a worker concern, not the engine's.
|
||||||
on_scan_active=lambda active: setattr(
|
on_scan_active=lambda active: setattr(
|
||||||
self._bbd_worker, "scanning_active", active),
|
self._bbd_worker, "scanning_active", active),
|
||||||
|
burst_mode=self.burst_mode_check.isChecked(),
|
||||||
)
|
)
|
||||||
self._scan_worker.moveToThread(self._scan_thread)
|
self._scan_worker.moveToThread(self._scan_thread)
|
||||||
self._scan_thread.started.connect(self._scan_worker.run)
|
self._scan_thread.started.connect(self._scan_worker.run)
|
||||||
|
|||||||
+41
-11
@@ -183,18 +183,48 @@ using that angle's `x_start` from the Per-Angle Geometry Table (not
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Acquisition Settings (fixed by sc3_aui_app.py)
|
## Acquisition Settings (fixed by core/scope_sras.py)
|
||||||
|
|
||||||
| Parameter | Value |
|
| Parameter | Value |
|
||||||
|-----------------------|------------------------------|
|
|-----------------------|------------------------------------------|
|
||||||
| Oscilloscope trigger | CH2, rising edge, 1.24 V |
|
| Setup trigger | CH2, rising edge, 0.500 V (`TRIG_LEVEL_V`) |
|
||||||
| Trigger offset | 0 % (trigger at left edge) |
|
| Scan trigger | Logic AND, CH2 HIGH ∧ CH3 HIGH, 0.500 V |
|
||||||
| Sample rate | 6.25 GS/s (160 ps/sample) |
|
| Horizontal position | 30 (`HORizontal:POSition`) |
|
||||||
| Channels recorded | CH1, CH3, CH4 |
|
| Sample rate | 6.25 GS/s (160 ps/sample) |
|
||||||
| Stage X velocity | 100 mm/s |
|
| Transfer format | `DATa:ENCdg RIBinary`, `DATa:WIDth 1` |
|
||||||
| Stage X acceleration | 1500 mm/s² |
|
| Channels recorded | CH1, CH3, CH4 |
|
||||||
| Stage X trigger out | Logic-high at max velocity |
|
| Stage X velocity | 100 mm/s |
|
||||||
| Acquisition mode | FastFrame, Normal trigger |
|
| Stage X acceleration | 1500 mm/s² |
|
||||||
|
| Stage X trigger out | Logic-high at max velocity (`TRIGOUT_MAXV`) |
|
||||||
|
| Acquisition mode | FastFrame, Normal trigger |
|
||||||
|
|
||||||
|
None of these are stored in the file, so they do not affect byte layout — but
|
||||||
|
they do set where the acoustic packet lands inside each frame. Read them from
|
||||||
|
`core/scope_sras.py`; earlier revisions of this table drifted from the code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acquisition Paths
|
||||||
|
|
||||||
|
Two acquisition strategies write **byte-identical** files; the choice is a
|
||||||
|
runtime flag (`ScanEngine(burst_mode=…)`, exposed as a checkbox in the app) and
|
||||||
|
is not recorded in the file.
|
||||||
|
|
||||||
|
| | Per-row (default) | Burst |
|
||||||
|
|---|---|---|
|
||||||
|
| FastFrame acquisitions | one per row | one per `floor(max_frames / n_frames)` rows |
|
||||||
|
| Curve transfers | one per channel per row | one per channel per burst |
|
||||||
|
| Stage X trigger out | armed for the whole scan | armed per acquiring pass, dropped for the flyback |
|
||||||
|
|
||||||
|
Burst mode runs a single acquisition across several rows, so the return move
|
||||||
|
must not trigger: the trigger output is dropped before each flyback and
|
||||||
|
re-armed for each acquiring pass. Row boundaries inside the burst come from
|
||||||
|
`ACQuire:NUMFRAMESACQuired?` sampled after each pass — the burst itself carries
|
||||||
|
no row markers. See `core/scope_burst.py`.
|
||||||
|
|
||||||
|
Either path squares each row up to the declared `n_frames` (zero-padding a
|
||||||
|
short row, dropping the tail of a long one), because the format has no per-row
|
||||||
|
length field and a mismatch would shift every later row.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user