fix(aerotech): readout_time in scan_info, statistics report the real line period
CI for csaxs_bec / test (push) Successful in 3m31s

- scan_info / data file recorded readout_time 0.0 although the gates and the
  centres used the trigger hardware's 0.2 ms; now the value the scan uses
- statistics: "line mean" was line start .. trigger checked (0.60 s for a
  0.78 s line in scan 8477) and "scan" ran from pre_scan only (10.0 s for a
  13.5 s file); now line period start to start incl. capture readout and step,
  with motion / readout split, and the total of the lines labelled as such

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M8zoDzDfsCTY2o5bT3J7Wv
This commit is contained in:
x01dcandClaude Opus 5.5 committed 2026-10-29 13:42:37 +01:00
1 parent 96c30dbd3a
commit 51ec4fcad8
2 files changed
+33 -9

No files matched your search

+21 -8
View File
@@ -272,8 +272,10 @@ class AerotechContGrid(ScanBase):
self.capture = devices[capture_device]
self._captured_lines: list[dict] = []
self._step_status = None
self._line_times: list[float] = []
self._scan_start_time = None
self._line_times: list[float] = [] # line start .. trigger checked
self._line_starts: list[float] = []
self._readout_times: list[float] = [] # capture readout per line
self._last_line_end = None
self.motors = [fast_axis, stepper_axis]
self.frames_per_line = 0
@@ -324,6 +326,7 @@ class AerotechContGrid(ScanBase):
frames_per_trigger=self.frames_per_line,
computed_positions=positions,
num_lines=num_lines,
readout_time=self._line["readout_time"],
)
if self.mcs is not None:
@@ -367,7 +370,6 @@ class AerotechContGrid(ScanBase):
)
self.panda.arm()
self.actions.pre_scan_all_devices()
self._scan_start_time = time.time()
@scan_hook
def scan_core(self):
@@ -401,6 +403,7 @@ class AerotechContGrid(ScanBase):
then check that the line was triggered and read the captured positions.
"""
line_start_time = time.time()
self._line_starts.append(line_start_time)
self.capture.arm(self.frames_per_line)
move_status = self.actions.set(motors, positions, wait=False)
next_line = line_index + 1 if line_index + 1 < self._line["num_lines"] else None
@@ -423,7 +426,10 @@ class AerotechContGrid(ScanBase):
self.panda.wait_lines_done(line_index + 1, timeout=self._line_duration() + 2)
self._line_times.append(time.time() - line_start_time)
readout_start = time.time()
self._captured_lines.append(self.capture.read_line(centre_shift=self._centre_shift()))
self._last_line_end = time.time()
self._readout_times.append(self._last_line_end - readout_start)
@scan_hook
def post_scan(self):
@@ -538,12 +544,19 @@ class AerotechContGrid(ScanBase):
f" backward - forward [um]: mean {diff.mean():+.2f}, slope {slope:+.2f} / mm,"
f" peak-peak {np.ptp(diff):.2f}",
]
if self._line_times:
total = time.time() - self._scan_start_time if self._scan_start_time else 0.0
if self._line_starts and self._last_line_end is not None:
# line period: start to start of the next line (motion, trigger check, capture
# readout, wait for the step); the last line ends with its capture readout
ends = self._line_starts[1:] + [self._last_line_end]
periods = np.subtract(ends, self._line_starts)
lines_total = self._last_line_end - self._line_starts[0]
text.append(
f" times: scan {total:.1f} s, line mean {np.mean(self._line_times):.2f} s"
f" (min {np.min(self._line_times):.2f}, max {np.max(self._line_times):.2f}),"
f" exposure per line {n * self.exp_time:.2f} s"
f" times: lines {lines_total:.1f} s (first line start to last line end,"
f" without staging / premove / close), line period mean {periods.mean():.2f} s"
f" (min {periods.min():.2f}, max {periods.max():.2f}), of which motion and"
f" trigger check {np.mean(self._line_times):.2f} s, capture readout"
f" {np.mean(self._readout_times):.2f} s; exposure per line"
f" {n * self.exp_time:.2f} s"
)
return "\n".join(text)
@@ -418,11 +418,22 @@ def test_statistics_of_a_symmetric_snake(v4_scan_assembler, device_manager):
bwd = {"enc6_centre": list((cells + 3e-3)[::-1]), "enc2_centre": [0.5] * 5}
scan._captured_lines = [fwd, bwd, fwd]
scan._line_times = [1.0, 1.2, 1.1]
scan._readout_times = [0.2, 0.2, 0.2]
scan._line_starts = [100.0, 101.5, 103.0] # periods 1.5, 1.5, last line ends at 104.4
scan._last_line_end = 104.4
text = scan._statistics()
assert "forward mean +1.00" in text
assert "backward mean +3.00" in text
assert "backward - forward [um]: mean +2.00" in text and "peak-peak 0.00" in text
assert "line mean 1.10 s" in text
assert "lines 4.4 s" in text
assert "line period mean 1.47 s (min 1.40, max 1.50)" in text
assert "motion and trigger check 1.10 s, capture readout 0.20 s" in text
def test_readout_time_in_scan_info(v4_scan_assembler, device_manager):
scan = _assemble(v4_scan_assembler, device_manager)
scan.prepare_scan()
assert scan.scan_info.readout_time == pytest.approx(2e-4)
def test_on_exception_restores_ddg_pso_and_axis(v4_scan_assembler, device_manager):