daq: send commanded dtz (not live readback) to JFJoch/DB + wait for arrival
Build and Publish / test (push) Failing after 2m10s
Build and Publish / build (push) Skipped
Build and Publish / Build and Deploy Docs (push) Skipped

The detector_distance_mm sent to JFJoch and written to the DB came from the
live detector readback (diffraction.dtz_mm) captured before the detector
finished moving, so a scan requested at e.g. 100 mm could be recorded at the
old position (e.g. 800 mm).

- JFJoch + DB now read the validated request target (request.dtz) instead of
  the live diffraction.dtz_mm; diffraction.dtz_mm stays live for the
  local-contact readout.
- jfjoch: raise if request.dtz is unset (setup_datacollection must validate it
  first) instead of silently falling back to the live readback.
- raster + rotation services: wait for the detector to reach request.dtz
  (set_dtz wait=True) right before triggering the scan, since the sa2dc move is
  non-blocking. (Raster setup-before-measure reorder landed earlier in daq.py.)
- tests: request.dtz now required in jfjoch/aaredb tests; xtal-snapshot exposure
  assertion updated to 1 ms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
appleb_mandClaude Opus 4.8 committed 2026-06-25 16:39:06 +02:00
1 parent f7b5008be5
commit ec45bf3729
7 files changed
+33 -15

No files matched your search

+2 -2
View File
@@ -202,7 +202,7 @@ class AareWrapper:
model=d.diffraction.detector_description,
type="photon-counting",
serialNumber=d.diffraction.detector_serial_number,
detectorDistance_mm=d.diffraction.dtz_mm,
detectorDistance_mm=r.dtz,
resolution_at_edge_Ang=d.diffraction.max_resolution_angstrom,
beamCenterX_px=d.diffraction.beam_center_pxl[0],
beamCenterY_px=d.diffraction.beam_center_pxl[1],
@@ -268,7 +268,7 @@ class AareWrapper:
model=d.diffraction.detector_description,
type="photon-counting",
serialNumber=d.diffraction.detector_serial_number,
detectorDistance_mm=round(d.diffraction.dtz_mm,3),
detectorDistance_mm=round(r.dtz,3),
resolution_at_edge_Ang=round(d.diffraction.max_resolution_angstrom,3),
beamCenterX_px=round(d.diffraction.beam_center_pxl[0],2),
beamCenterY_px=round(d.diffraction.beam_center_pxl[1],2),
@@ -317,6 +317,11 @@ class RasterService:
self.logger.debug(
f"Starting grid scan with {request.n_x}x{request.n_y} points, exp time {request.exp_time_s}s"
)
if not self.ctx.deps.cfg.simulated_detector:
# The detector move (sa2dc) is non-blocking; ensure it has reached
# the requested distance before triggering the scan, so data is
# collected at the dtz already reported to JFJoch/DB.
self.ctx.deps.devs.set_dtz(request.dtz, wait=True)
self.ctx.deps.devs.aerotech.grid_scan(
grid_elem_size_y_um=request.grid_size_mm.y * 1000,
grid_elem_size_x_um=request.grid_size_mm.x * 1000,
+8 -1
View File
@@ -41,6 +41,10 @@ class RotationService:
if not self.ctx.deps.cfg.simulated_detector:
self.ctx.deps.jfjoch.wait_till_running(timeout=60.0)
# The detector move (sa2dc) is non-blocking; ensure it has reached the
# requested distance before triggering the scan, so data is collected
# at the dtz already reported to JFJoch/DB.
self.ctx.deps.devs.set_dtz(request.dtz, wait=True)
if request.screening:
self.ctx.deps.devs.aerotech.screening_scan(
@@ -84,8 +88,11 @@ class RotationService:
def run(self, request: RotationScanRequest) -> CompletedRotationScan:
sample = self.ctx.sample
self._prepare_detector(request)
# Validate + set the requested dtz (cfg.dtz, requires request.dtz) BEFORE
# configuring JFJoch, so JFJoch receives the commanded target rather than
# the live, still-in-transit detector position.
self.ctx.services.datacollection.prepare(request)
self._prepare_detector(request)
if sample is not None and sample.db_id is not None and self.ctx.services.events is not None:
self.ctx.services.events.send(sample.db_id, SampleEventType.COLLECTING)
+6 -2
View File
@@ -125,14 +125,18 @@ class JFJochWrapper:
wedge = None
images = r.steps
trigger = 1
if r.dtz is None:
raise ValueError(
"Scan request has no detector distance (dtz); setup_datacollection "
"must validate and set request.dtz before JFJoch is configured"
)
#build common dataset settings
dataset_settings = jfjoch_client.DatasetSettings(
beam_x_pxl=s.diffraction.beam_center_pxl[0],
beam_y_pxl=s.diffraction.beam_center_pxl[1],
ntrigger= trigger,
images_per_trigger=images,
detector_distance_mm=s.diffraction.dtz_mm,
detector_distance_mm=r.dtz,
file_prefix=f"{data_folder}/{r.file_prefix}",
incident_energy_keV=s.diffraction.energy_keV,
sample_name=sample,
+5 -5
View File
@@ -157,7 +157,7 @@ def test_upload_jpg(mock_post, mock_api, mock_bl):
def test_create_rotation_run(mock_sample, mock_api, mock_bl, sample_info, daq_status):
wrapper = AareWrapper(bl=mock_bl)
# Standard rotation
req = RotationScanRequest(exp_time_s=0.1, incr_omega_deg=0.1, steps=100, file_prefix="test_prefix")
req = RotationScanRequest(exp_time_s=0.1, incr_omega_deg=0.1, steps=100, file_prefix="test_prefix", dtz=200.0)
wrapper.create_rotation_run(sample_info, req, daq_status)
mock_sample.return_value.create_experiment_parameters_for_sample.assert_called_once()
@@ -170,7 +170,7 @@ def test_create_rotation_run(mock_sample, mock_api, mock_bl, sample_info, daq_st
@patch("aareDB.SamplesRunnerApi")
def test_create_rotation_run_screening(mock_sample, mock_api, mock_bl, sample_info, daq_status):
wrapper = AareWrapper(bl=mock_bl)
req = RotationScanRequest(exp_time_s=0.1, incr_omega_deg=0.1, steps=100, file_prefix="test_prefix", screening=True, wedge_omega_deg=5.0)
req = RotationScanRequest(exp_time_s=0.1, incr_omega_deg=0.1, steps=100, file_prefix="test_prefix", screening=True, wedge_omega_deg=5.0, dtz=200.0)
wrapper.create_rotation_run(sample_info, req, daq_status)
mock_sample.return_value.create_experiment_parameters_for_sample.assert_called_once()
@@ -178,7 +178,7 @@ def test_create_rotation_run_screening(mock_sample, mock_api, mock_bl, sample_in
@patch("aareDB.SamplesRunnerApi")
def test_create_rotation_run_error(mock_sample, mock_api, mock_bl, sample_info, daq_status, caplog):
wrapper = AareWrapper(bl=mock_bl)
req = RotationScanRequest(exp_time_s=0.1, incr_omega_deg=0.1, steps=100, file_prefix="test_prefix")
req = RotationScanRequest(exp_time_s=0.1, incr_omega_deg=0.1, steps=100, file_prefix="test_prefix", dtz=200.0)
mock_sample.return_value.create_experiment_parameters_for_sample.side_effect = Exception("Rotation error")
wrapper.create_rotation_run(sample_info, req, daq_status)
assert "Rotation error" in caplog.text
@@ -187,7 +187,7 @@ def test_create_rotation_run_error(mock_sample, mock_api, mock_bl, sample_info,
@patch("aareDB.SamplesRunnerApi")
def test_create_gridscan_run(mock_sample, mock_api, mock_bl, sample_info, daq_status):
wrapper = AareWrapper(bl=mock_bl)
req = RasterGridRequest(exp_time_s=0.1, n_x=10, n_y=10, grid_size_mm=Coordinate(x=0.01, y=0.01), smargon_top_left=None, file_prefix="test_grid_prefix")
req = RasterGridRequest(exp_time_s=0.1, n_x=10, n_y=10, grid_size_mm=Coordinate(x=0.01, y=0.01), smargon_top_left=None, file_prefix="test_grid_prefix", dtz=200.0)
wrapper.create_gridscan_run(sample_info, req, daq_status)
mock_sample.return_value.create_experiment_parameters_for_sample.assert_called_once()
@@ -200,7 +200,7 @@ def test_create_gridscan_run(mock_sample, mock_api, mock_bl, sample_info, daq_st
@patch("aareDB.SamplesRunnerApi")
def test_create_gridscan_run_error(mock_sample, mock_api, mock_bl, sample_info, daq_status, caplog):
wrapper = AareWrapper(bl=mock_bl)
req = RasterGridRequest(exp_time_s=0.1, n_x=10, n_y=10, grid_size_mm=Coordinate(x=0.01, y=0.01), smargon_top_left=None, file_prefix="test_grid_prefix")
req = RasterGridRequest(exp_time_s=0.1, n_x=10, n_y=10, grid_size_mm=Coordinate(x=0.01, y=0.01), smargon_top_left=None, file_prefix="test_grid_prefix", dtz=200.0)
mock_sample.return_value.create_experiment_parameters_for_sample.side_effect = Exception("Grid error")
wrapper.create_gridscan_run(sample_info, req, daq_status)
assert "Grid error" in caplog.text
+2 -2
View File
@@ -118,7 +118,7 @@ def test_sa2xtal_snapshot(mock_devs, mock_cfg):
mock_devs.bec_worker.move_to = MagicMock()
sa2xtal_snapshot(mock_devs, mock_cfg)
assert mock_devs.samcam_settings.exposure == 0.0005
assert mock_devs.samcam_settings.exposure == 0.001
_assert_bec_moved(mock_devs, BeamlineState.XTAL_SNAPSHOT)
@@ -128,7 +128,7 @@ def test_dc2xtal_snapshot(mock_devs, mock_cfg):
mock_devs.bec_worker.move_to = MagicMock()
dc2xtal_snapshot(mock_devs, mock_cfg)
assert mock_devs.samcam_settings.exposure == 0.0005
assert mock_devs.samcam_settings.exposure == 0.001
_assert_bec_moved(mock_devs, BeamlineState.XTAL_SNAPSHOT)
+5 -3
View File
@@ -121,7 +121,8 @@ def test_measure_rotation(jfjoch_wrapper, mock_jfjoch_client):
incr_omega_deg=0.1,
steps=100,
exp_time_s=0.01,
file_prefix="test_prefix"
file_prefix="test_prefix",
dtz=200.0,
)
s = create_mock_daq_status()
s.sample = SampleShortInfo(sample_name="S1", user="p12345", db_id=1, dewar_name="D1", puck_name="P1", run_number=1, pin=1)
@@ -132,7 +133,7 @@ def test_measure_rotation(jfjoch_wrapper, mock_jfjoch_client):
def test_measure_rotation_error(jfjoch_wrapper, mock_jfjoch_client):
mock_jfjoch_client.start_post.side_effect = Exception("API Error")
r = RotationScanRequest(exp_time_s=0.01, incr_omega_deg=0.1, steps=10)
r = RotationScanRequest(exp_time_s=0.01, incr_omega_deg=0.1, steps=10, dtz=200.0)
s = create_mock_daq_status()
s.sample = SampleShortInfo(sample_name="S1", user="p12345", db_id=1, dewar_name="D1", puck_name="P1", run_number=1, pin=1)
@@ -147,7 +148,8 @@ def test_measure_raster(jfjoch_wrapper, mock_jfjoch_client):
grid_size_mm=Coordinate(x=0.01, y=0.01),
exp_time_s=0.01,
file_prefix="raster_prefix",
smargon_top_left=SmargonCoordinate(sh_mm=Coordinate(x=0, y=0))
smargon_top_left=SmargonCoordinate(sh_mm=Coordinate(x=0, y=0)),
dtz=200.0,
)
s = create_mock_daq_status()
s.sample = SampleShortInfo(sample_name="S1", user="p12345", db_id=1, dewar_name="D1", puck_name="P1", run_number=1, pin=1)