Per-angle background capture: v7/v11 .sras layout
A multi-angle scan runs for hours, but every angle was referenced against
one background captured before the first row of the first angle. That
reference has drifted by the last angle, and comparing angles — the whole
point of a multi-angle scan — was comparing each one against a noise floor
measured at whichever angle came first.
Every angle now captures its own. Before each angle's rows, the operator is
prompted to switch the Genesis laser off, the engine averages a fresh CH1
record, and the operator switches it back on. The data block therefore reads
[background][scan][background][scan] …, one pair per angle.
Format v7 (scan) and v11 (SAW check) carry the background inside the data
block, one length-prefixed block ahead of each angle's rows; the single
block that sat between the preambles and the data is gone. Per-angle offsets
now come from a walk of the data block at parse time rather than arithmetic
over the geometry table, and an angle whose background is not fully on disk
is the frontier — nothing of it was written yet.
v6/v10 files still read: SrasFile hands their one background to every angle,
so readers never branch on the version. Nothing writes them, and a resume
refuses them, since a re-acquired angle writes a block the old layout has no
room for. A resumed v7 angle rewrites its background in place, and the
engine checks the new block fits the room the file has before writing it —
anything else would shift every row behind it.
Two fixes made along the way:
* QtScanController never accepted file_version, so every scan launched
from the app raised TypeError at construction.
* angle_status() left its cursor parked at the frontier, so every angle
past it reported the frontier's own data_offset — which handed a resumed
scan the same write position for several angles. Two recorded offsets in
tests/golden/sras_expected.json are corrected accordingly.
The v6 goldens stay as parser fixtures; the writer is now locked against
bytes the test lays out from scan_format.md itself.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+12
-5
@@ -6,7 +6,7 @@ without the other:
|
||||
|
||||
*Acquisition* — ``middle_row_plan`` reduces a full ScanPlan to a single row
|
||||
per angle, the row-wise middle of the ROI. ScanEngine runs the result
|
||||
exactly like any other scan and writes it as a v10 .sras file
|
||||
exactly like any other scan and writes it as a v11 .sras file
|
||||
(``sras_format.VERSION_SAW_CHECK``), so a check costs one row-time per angle
|
||||
instead of the hours a full multi-angle scan takes.
|
||||
|
||||
@@ -55,7 +55,7 @@ def middle_row_plan(plan: ScanPlan) -> ScanPlan:
|
||||
|
||||
An even row count has no exact middle; the upper of the two central rows
|
||||
is taken (``n_rows // 2``), which is also the row the viewer picks when it
|
||||
reads the middle row out of a full v6 scan.
|
||||
reads the middle row out of a full scan.
|
||||
"""
|
||||
if plan.n_angles == 0:
|
||||
raise ScanGeometryError("Cannot build a SAW check from a plan with no angles")
|
||||
@@ -181,18 +181,24 @@ class AlignmentSummary:
|
||||
|
||||
|
||||
def frequency_traces(sras: SrasFile, *, dc_threshold_mv: float = 0.0,
|
||||
background: np.ndarray | None = None,
|
||||
subtract_background: bool = False,
|
||||
gate_start_ns: float | None = None,
|
||||
gate_end_ns: float | None = None,
|
||||
calib: ChannelCalibration | None = None,
|
||||
on_progress=lambda done, total: None) -> list[AngleTrace]:
|
||||
"""Peak SAW frequency along the middle row of every angle in ``sras``.
|
||||
|
||||
Works on a v10 check (one row per angle, so the middle row is the only
|
||||
row) and on a full v6 scan alike — the same middle row the check would
|
||||
Works on a v11 check (one row per angle, so the middle row is the only
|
||||
row) and on a full scan alike — the same middle row the check would
|
||||
have acquired is pulled out of the scan, which is what lets a finished
|
||||
scan be re-examined with the check's own read-out.
|
||||
|
||||
``subtract_background`` takes each angle's own background out of its
|
||||
frames (v6/v10 files have only the one, which every angle then shares).
|
||||
Doing it per angle is the point of the per-angle capture: comparing
|
||||
angles is exactly what this read-out is for, so they must not be
|
||||
referenced against one background taken at whichever angle came first.
|
||||
|
||||
Angles with nothing on disk (an aborted file) are skipped rather than
|
||||
reported as flat zero.
|
||||
"""
|
||||
@@ -210,6 +216,7 @@ def frequency_traces(sras: SrasFile, *, dc_threshold_mv: float = 0.0,
|
||||
row = middle_row_index(st.n_rows_available)
|
||||
view = sras.load_angle(st.index, n_rows=st.n_rows_available)[row:row + 1]
|
||||
|
||||
background = sras.background_array(st.index) if subtract_background else None
|
||||
img = compute_rf_image(view, calib, freq_axis, dc_threshold_mv,
|
||||
background=background,
|
||||
gate_start_ns=gate_start_ns, gate_end_ns=gate_end_ns,
|
||||
|
||||
Reference in New Issue
Block a user