Fix/automation jfjoch aarescan failures #197

Merged
perl_d merged 6 commits from fix/automation_jfjoch_aarescan_failures into main 2026-09-09 10:29:13 +02:00
6 changed files with 125 additions and 94 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ dependencies = [
"psi-pshell==2.1.0",
"bec_lib>=3.130.3",
"bec-ipython-client>=3.130.3",
"aarescan-client==1.0.0rc5",
"aarescan-client>=1.0.0rc14",
"aarelcinfer-client==0.1.1a8",
"matplotlib>=3.10.3",
"httpx>=0.28.1",
+34 -69
View File
@@ -956,7 +956,7 @@ class AareDAQ:
sample_id = getattr(sample, "db_id", None)
logger.exception(
"Operation failed",
f"Operation {operation.value} failed with error {error}",
extra={
"operation": operation.value,
"event_type": str(event_type),
@@ -965,6 +965,8 @@ class AareDAQ:
"additional_comment": additional_comment,
},
)
logger.warning("cleaning up detector state after failed operation...")
self._jfjoch.cancel(wait_for_done=True)
if sample is None or sample_id is None:
logger.error(f"Error in {operation.value}: {error}")
@@ -1236,6 +1238,15 @@ class AareDAQ:
success=False, payload=payload, error=e, comment=additional_comment
)
def _validate_grid_params(self, request: RasterGridRequest):
self._devs.aerotech.validate_grid_scan(
grid_elem_size_y_um=request.grid_size_mm.y * 1000,
grid_elem_size_x_um=request.grid_size_mm.x * 1000,
grid_elem_count_x=request.n_x,
grid_elem_count_y=request.n_y,
time_sec=request.exp_time_s,
)
def _execute_raster_sequence(
self, grid_request: RasterGridRequest, auto_center: bool = False
) -> CompletedRasterGrid | None:
@@ -1377,6 +1388,27 @@ class AareDAQ:
)
return None
def _validate_rotation_params(self, request: RotationScanRequest):
total_time = request.exp_time_s * request.steps
logger.debug("Validating rotation parameters with AareScan")
if request.screening:
if request.wedge_omega_deg is None:
raise ValueError("Must supply a wedge angle for screening requests")
self._devs.aerotech.validate_screening_scan(
rotation_deg=request.incr_omega_deg,
wedge_deg=request.wedge_omega_deg,
time_sec=total_time,
steps=request.steps,
run_async=True,
)
else:
self._devs.aerotech.rotation_scan(
rotation_deg=request.steps * request.incr_omega_deg,
time_sec=total_time,
start_pos_deg=request.start_omega_deg,
run_async=True,
)
def _execute_rotation_sequence(
self, rotation_request: RotationScanRequest
) -> CompletedRotationScan | None:
@@ -1394,6 +1426,7 @@ class AareDAQ:
logger.warning(f"Exposure shorter than default of {min_exp_time} s! Adjusting.")
rotation_request.exp_time_s = min_exp_time
try:
self._validate_rotation_params(rotation_request)
return self._create_rotation_service().run(rotation_request)
except JFJochCommunicationError as e:
logger.error(f"Rotation sequence failed due to JFJoch Communication error: {e}")
@@ -1990,74 +2023,6 @@ class AareDAQ:
self._set_state(BeamlineStateEnum.SampleAlignment)
raise
def _rotation(self, request: RotationScanRequest) -> CompletedRotationScan:
omega_start = self.omega
status = self.status
if request.exp_time_s < 0.004:
logger.warning("Exposure time too short for PXII rotation scan")
request.exp_time_s = 0.004
total_time = request.exp_time_s * request.steps
if self.sample is not None and self.sample.db_id is not None:
self._aare.create_rotation_run(self.sample, request, status)
if not self._cfg.simulated_detector:
try:
self._jfjoch.wait_till_running(timeout=60.0)
except Exception as e:
self._raise_if_critical_jfjoch_detector_error(e, command="wait_till_running")
raise
try:
if request.screening:
self._devs.aerotech.screening_scan(
rotation_deg=request.steps * request.incr_omega_deg,
wedge_deg=request.wedge_omega_deg,
time_sec=total_time,
steps=request.steps,
run_async=True,
)
else:
self._devs.aerotech.rotation_scan(
rotation_deg=request.steps * request.incr_omega_deg,
time_sec=total_time,
start_pos_deg=request.start_omega_deg,
run_async=True,
)
# Is this for helical scans...? do we do smargon scans?
if request.start is not None and request.end is not None:
smargon_time_step = request.exp_time_s / float(request.steps)
pos_step = (request.end.sh_mm - request.start.sh_mm) * (
1.0 / float(request.steps)
)
for i in range(request.steps):
self._devs.smargon.target = SmargonCoordinate(
sh_mm=request.start.sh_mm + pos_step * i
)
time.sleep(smargon_time_step)
self._devs.aerotech.wait_till_done(timeout=int(round(total_time + 60, 0)))
self._devs.aerotech_omega = omega_start
if self._cfg.simulated_detector:
logger.warning("Detector in simulation mode, returning fake zero rotation result.")
return self._build_fake_rotation_result(request)
else:
try:
scan_result = self._jfjoch.wait_till_done(60)
except Exception as e:
self._raise_if_critical_jfjoch_detector_error(e, command="wait_till_done")
raise
return CompletedRotationScan(request=copy.deepcopy(request), result=scan_result)
except JFJochCommunicationError as e:
logger.error(f"Exception during rotation scan related to JFJoch: {e}")
raise
except Exception as e:
logger.error(f"Exception during rotation scan: {e}")
raise
def measure_rotation(self, request: RotationScanRequest) -> CompletedRotationScan:
"""
Execute a rotation scan.
+5 -15
View File
@@ -421,19 +421,12 @@ class RasterService:
if self.ctx.sample is not None and self.ctx.sample.db_id is not None:
self.ctx.deps.aare.create_gridscan_run(self.ctx.sample, request, status)
if not self.ctx.deps.cfg.simulated_detector:
self.ctx.deps.jfjoch.wait_till_running(timeout=60.0)
else:
self.logger.info("Simulated detector mode enabled; faking jfjoch intilalisation.")
self.ctx.deps.jfjoch.wait_till_running(timeout=60.0)
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.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,
@@ -747,12 +740,9 @@ class RasterService:
grid.omega_deg = geom.omega_deg
status = self.ctx.status
if not self.ctx.deps.cfg.simulated_detector:
self.logger.info("initialise detector")
self.ctx.deps.jfjoch.measure_raster(grid, status)
self.logger.info("detector initialised")
else:
self.logger.info("Simulated detector mode enabled; using fake raster result.")
self.logger.info("initialise detector")
self.ctx.deps.jfjoch.measure_raster(grid, status)
self.logger.info("detector initialised")
if self.ctx.services.state is None:
raise RuntimeError("RasterService requires services.state")
+82 -7
View File
@@ -190,10 +190,7 @@ class AerotechController:
start_pos_deg=start_pos_deg,
run_async=run_async,
)
logger.debug(f"Payload sent to AareScan for rotation: {payload}")
if self._simulated:
return payload
logger.debug(f"Payload sent to AareScan for rotation execution: {payload}")
try:
return self._api.rotation_scan_post(payload)
except Exception as e:
@@ -204,6 +201,26 @@ class AerotechController:
operation="POST",
) from e
def validate_rotation_scan(
self, rotation_deg: float, time_sec: float, start_pos_deg: float, run_async: bool = False
):
payload = RotationRequest(
rotation_deg=rotation_deg,
time_sec=time_sec,
start_pos_deg=start_pos_deg,
run_async=run_async,
)
logger.debug(f"Payload sent to AareScan for validating rotation: {payload}")
try:
return self._api.validate_rotation_scan_post(payload)
except Exception as e:
raise AerotechCommunicationError(
f"Aerotech rotation scan failed validation: {e}",
endpoint="rotation_scan_post",
base_url=self._base,
operation="POST",
) from e
def grid_scan(
self,
grid_elem_count_y: int,
@@ -221,14 +238,44 @@ class AerotechController:
time_sec=time_sec,
run_async=run_async,
)
logger.debug(f"Payload sent to AareScan for grid_scan: {payload}")
logger.debug(f"Payload sent to AareScan for grid_scan execution: {payload}")
if self._simulated:
return payload
try:
return self._api.grid_scan_post(payload)
except Exception as e:
raise AerotechCommunicationError(
"Aerotech grid scan failed",
f"Aerotech grid scan failed: {e}",
endpoint="grid_scan_post",
base_url=self._base,
operation="POST",
) from e
def validate_grid_scan(
self,
grid_elem_count_y: int,
grid_elem_size_y_um: float,
time_sec: float,
grid_elem_size_x_um: float,
grid_elem_count_x: int,
run_async: bool | None = False,
):
payload = GridRequest(
grid_elem_count_x=grid_elem_count_x,
grid_elem_count_y=grid_elem_count_y,
grid_elem_size_x_um=grid_elem_size_x_um,
grid_elem_size_y_um=grid_elem_size_y_um,
time_sec=time_sec,
run_async=run_async,
)
logger.debug(f"Payload sent to AareScan for grid_scan validation: {payload}")
if self._simulated:
return payload
try:
return self._api.validate_grid_scan_post(payload)
except Exception as e:
raise AerotechCommunicationError(
f"Aerotech grid scan validation failed: {e}",
endpoint="grid_scan_post",
base_url=self._base,
operation="POST",
@@ -252,7 +299,7 @@ class AerotechController:
if self._simulated:
return payload
try:
logger.info("sending screening scan request to aerotech")
logger.info(f"sending screening scan request to aerotech with payload {payload}")
return self._api.screening_post(payload)
except Exception as e:
raise AerotechCommunicationError(
@@ -262,6 +309,34 @@ class AerotechController:
operation="POST",
) from e
def validate_screening_scan(
self,
rotation_deg: float,
wedge_deg: float,
time_sec: float,
steps: int,
run_async: bool = False,
):
payload = ScreenRequest(
rotation_deg=rotation_deg,
wedge_deg=wedge_deg,
time_sec=time_sec,
steps=steps,
run_async=run_async,
)
try:
logger.info(
f"sending screening scan request to aarescan for validation with payload {payload}"
)
return self._api.screening_post(payload)
except Exception as e:
raise AerotechCommunicationError(
f"Aerotech screening validation failed: {e}",
endpoint="screening_post",
base_url=self._base,
operation="POST",
) from e
if __name__ == "__main__":
beamline = mx_beamline()
+3 -1
View File
@@ -114,7 +114,7 @@ class JFJochWrapper:
) from e
@needs_init
def cancel(self, on_init: bool = False):
def cancel(self, on_init: bool = False, wait_for_done: bool = False):
try:
self._api.cancel_post()
except Exception as e:
@@ -124,6 +124,8 @@ class JFJochWrapper:
operation="POST",
endpoint="cancel_post",
) from e
if wait_for_done:
self._api.wait_till_done_post(1)
@needs_init
def is_idle(self) -> bool:
-1
View File
@@ -425,7 +425,6 @@ class TellClient:
def get_mounted_sample(self) -> SampleDewarAddress | None:
ret = self.get_setting("mounted_sample_position").strip()
logger.debug(f"Get TELL setting 'mounted_sample_position' returned: {ret}")
if not ret:
return None