From 40c8d90072269e863f7ac1b2ac5e56fb14d2a98a Mon Sep 17 00:00:00 2001 From: Mirko Holler Date: Wed, 28 Oct 2026 17:30:11 +0100 Subject: [PATCH] refactor(aerotech): aerotech_cont_grid always runs as a snake Remove the snake argument and the raster path (flyback with PSO disarm/re-arm); snake is the only mode used. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01M8zoDyJxbBLmjZCEPUo5FM --- csaxs_bec/devices/aerotech/README.md | 5 ++-- .../scans/scans_v4/aerotech_cont_grid.py | 18 +++--------- .../test_aerotech_cont_grid_scan.py | 28 ------------------- 3 files changed, 6 insertions(+), 45 deletions(-) diff --git a/csaxs_bec/devices/aerotech/README.md b/csaxs_bec/devices/aerotech/README.md index 498e19a7..5e6f3a47 100644 --- a/csaxs_bec/devices/aerotech/README.md +++ b/csaxs_bec/devices/aerotech/README.md @@ -59,11 +59,11 @@ Documentation: A3200 6.04.004 help (`A3200.chm`, topic "ASCII Command Interface" ```python scans.aerotech_cont_grid(dev.aerox, 0, 1, 0.01, dev.aeroy, 0, 1, 0.1, exp_time=0.01, - snake=True, trigger_source="ddg", settle_time=0.15) + trigger_source="ddg", settle_time=0.15) ``` - fast axis X moves continuously at `fast_step_size / exp_time`, the step axis Y steps between - lines; `snake=True` (default) alternates the line direction + lines; the lines always run as a snake (alternating direction) - line start triggered by position: one PSO window per scan, `[fast_start - v * shutter_delay, exposure_end + v * shutter_delay]`, armed once in pre_scan. Its output rises on entering the window: at the low edge on forward lines, at @@ -86,7 +86,6 @@ scans.aerotech_cont_grid(dev.aerox, 0, 1, 0.01, dev.aeroy, 0, 1, 0.1, exp_time=0 ramp at 1 mm/s / 10 mm/s^2). In a snake the run-out of a line is the run-up of the next. - at the turnarounds X does not wait for In Position (`wait_in_position` signal off during the scan); X reaches 1 um within ~60 ms but 10 nm only after 0.5-2 s -- `snake=False`: the flyback passes the PSO window, so the PSO is switched off during it - `capture_positions=True` (default False until commissioned): device `aerocapture` latches the encoder counters on each detector trigger (DATAACQ on UINT1, up to 16 kHz, 4 kHz with MXR sources); after each line they are read back (~2600 values/s over the ASCII interface) diff --git a/csaxs_bec/scans/scans_v4/aerotech_cont_grid.py b/csaxs_bec/scans/scans_v4/aerotech_cont_grid.py index 94487b70..95fe5976 100644 --- a/csaxs_bec/scans/scans_v4/aerotech_cont_grid.py +++ b/csaxs_bec/scans/scans_v4/aerotech_cont_grid.py @@ -1,8 +1,8 @@ """ Continuous grid scan for Aerotech A3200 stages with a position triggered line start. -The fast axis moves continuously, the step axis steps between lines. By default the lines -run as a snake (alternating direction). The start of each line is triggered in hardware by +The fast axis moves continuously, the step axis steps between lines. The lines always run +as a snake (alternating direction). The start of each line is triggered in hardware by the A3200 PSO output of the fast axis: one PSO window per scan, [fast_start - v * shutter_delay, exposure_end + v * shutter_delay] @@ -114,7 +114,6 @@ class AerotechContGrid(ScanBase): "Acquisition Parameters": [ "exp_time", "relative", - "snake", "trigger_source", "capture_positions", ], @@ -133,7 +132,6 @@ class AerotechContGrid(ScanBase): stepper_step_size: Annotated[float, ScanArgument(display_name="Step Step Size", description="Step size of the step axis in units of the motor.")], exp_time: DefaultArgType.ExposureTime, relative: DefaultArgType.Relative = False, - snake: bool = True, trigger_source: str | None = "ddg", trigger_edge: str = "falling", trigger_device: str = "pandatrig", @@ -161,7 +159,6 @@ class AerotechContGrid(ScanBase): stepper_step_size (float): Step size of the step axis in units of the motor. exp_time (float): Exposure time (period of the detector gates) in seconds. relative (bool): Whether the positions are relative to the current position. Default is False. - snake (bool): Alternate the direction of the lines. Default is True. trigger_source (str | None): "ddg" (beamline, DDG1/MCS), "panda" (PandABox line trigger) or None (no trigger hardware). Default is "ddg". trigger_edge (str): DDG1 external trigger edge, "falling" (PSO open collector / differential output) or "rising" (opto output). Default is "falling". trigger_device (str): Name of the PandaLineTrigger device for trigger_source="panda". Default is "pandatrig". @@ -198,7 +195,6 @@ class AerotechContGrid(ScanBase): self.stepper_step_size = stepper_step_size self.relative = relative self.exp_time = exp_time - self.snake = snake self.trigger_source = trigger_source self.readout_time = readout_time self.shutter_delay = shutter_delay @@ -323,13 +319,7 @@ class AerotechContGrid(ScanBase): for line_index in range(line["num_lines"]): stepper_pos = self.positions[line_index][1] if line_index > 0: - if self.snake: - self.actions.set([self.stepper_axis], [stepper_pos]).wait() - else: - # the flyback passes the PSO window: no line trigger while returning - self.fast_axis.disarm_line_trigger() - self.actions.set(self.motors, [line["line_start"], stepper_pos]).wait() - self.fast_axis.arm_line_trigger(line["trigger_low"], line["trigger_high"]) + self.actions.set([self.stepper_axis], [stepper_pos]).wait() target = line["line_end"] if self._is_forward(line_index) else line["line_start"] self.at_each_point( motors=[self.fast_axis], positions=np.array([target]), line_index=line_index @@ -399,7 +389,7 @@ class AerotechContGrid(ScanBase): ####################################################### def _is_forward(self, line_index: int) -> bool: - return not self.snake or line_index % 2 == 0 + return line_index % 2 == 0 def _wait_mcs_frames(self, line_index: int, timeout: float = 2.0, poll: float = 0.05): """ diff --git a/tests/tests_scans/test_aerotech_cont_grid_scan.py b/tests/tests_scans/test_aerotech_cont_grid_scan.py index bfdf8e8c..9b516008 100644 --- a/tests/tests_scans/test_aerotech_cont_grid_scan.py +++ b/tests/tests_scans/test_aerotech_cont_grid_scan.py @@ -140,12 +140,6 @@ def test_snake_positions(v4_scan_assembler, device_manager): assert np.allclose(computed[5:10, 0], [1.0, 0.5, 0.0, -0.5, -1.0]) -def test_raster_positions(v4_scan_assembler, device_manager): - scan = _assemble(v4_scan_assembler, device_manager, snake=False) - scan.prepare_scan() - assert np.allclose(scan.positions, [[1.0, -2.0], [1.0, 0.0], [1.0, 2.0]]) - - def test_shutter_delay_without_ddg(v4_scan_assembler, device_manager): scan = _assemble(v4_scan_assembler, device_manager, trigger_source=None) scan.prepare_scan() @@ -281,28 +275,6 @@ def test_snake_scan_core(v4_scan_assembler, device_manager, nth_done_status_mock scan.fast_axis.disarm_line_trigger.assert_not_called() -def test_raster_scan_core_disarms_pso_for_flyback( - v4_scan_assembler, device_manager, nth_done_status_mock -): - scan = _ready_for_core( - _assemble(v4_scan_assembler, device_manager, trigger_source=None, snake=False), - nth_done_status_mock, - ) - scan.scan_core() - - line = scan._line - targets = [list(c.args[1]) for c in scan.actions.set.call_args_list] - assert targets == [ - [line["line_end"]], - [line["line_start"], 0.0], - [line["line_end"]], - [line["line_start"], 2.0], - [line["line_end"]], - ] - assert scan.fast_axis.disarm_line_trigger.call_count == 2 - assert scan.fast_axis.arm_line_trigger.call_count == 2 - - def test_panda_lines_are_checked(v4_scan_assembler, device_manager, nth_done_status_mock): scan = _ready_for_core( _assemble(v4_scan_assembler, device_manager, trigger_source="panda"), nth_done_status_mock