BIG AEROTECH UPGRADE: transferred to aerotech api. Ported several functions from Coordinate to aerotech coordinate. Nees thorough testing as this could lead to strange behaviour

This commit is contained in:
appleb_m committed 2026-03-24 11:11:02 +01:00
1 parent 5634554c00
commit 9a0f7abba0
7 files changed
+232 -79

No files matched your search

+12 -6
View File
@@ -6,7 +6,7 @@ from typing import Tuple, List
import numpy as np
import redis
import redis_lock
from aare.common.coordinate import Coordinate
from aare.common.coordinate import Coordinate, AerotechCoordinate
from aare.common.models import (
BeamlineSettingsModel,
BeamMarkCoeffModel,
@@ -23,8 +23,13 @@ from aare.common.logger_config import setup_logger
from aare.common.exception_handler import BeamlineBusyException
ABR_POS_ALIGN_DEF = Coordinate(x=-18, y=-0.266, z=0)
ABR_POS_MOUNT = Coordinate(x=0, y=0, z=0)#Coordinate(x=-18, y=0, z=0)
#TODO WHAT SHOULD THIS BE?
ABR_POS_ALIGN_DEF = AerotechCoordinate(at_mm=Coordinate(x=-18, y=-0.266, z=0))
ABR_POS_MOUNT = AerotechCoordinate(
at_mm=Coordinate(x=0, y=0, z=0),
omega_deg=0
)
#Coordinate(x=-18, y=0, z=0)
ABR_OMEGA_MOUNT = 0.0
logger = setup_logger("aareDAQ")
@@ -74,6 +79,7 @@ class BeamlineConfig:
else:
host = f"{self.__bl}-redis.psi.ch"
self.__client = redis.Redis(host=host, port=6379, db=0, decode_responses=True)
self.simulated_detector = bl is MXBeamline.SIMULATED
# Session and authentication management
@@ -458,16 +464,16 @@ class BeamlineConfig:
self.__client.set(f"{self.__bl}:{self.zoom_setting_string(mode)}", data.model_dump_json())
@property
def abr_meas_pos(self) -> Coordinate:
def abr_meas_pos(self) -> AerotechCoordinate:
tmp = self.__client.get(f"{self.__bl}:abr_meas_pos")
if tmp is None:
return ABR_POS_ALIGN_DEF
data_dict = json.loads(tmp)
return Coordinate(**data_dict)
return AerotechCoordinate(**data_dict)
@abr_meas_pos.setter
def abr_meas_pos(self, data: Coordinate):
def abr_meas_pos(self, data: AerotechCoordinate):
self.__client.set(f"{self.__bl}:abr_meas_pos", data.model_dump_json())
@property
+170 -29
View File
@@ -1,4 +1,4 @@
import copy
import secrets
import time
import traceback
@@ -11,6 +11,7 @@ from typing import List, Tuple, Optional, Callable
import cv2
import numpy as np
from jfjoch_client import ScanResult, ScanResultImagesInner
import aare.common.face_detection as fd
from aare.daq import workflows
@@ -21,7 +22,7 @@ from aare.daq.config import BeamlineStateEnum
from aare.daq.devices import BeamlineDevices
from aare.daq.mlbox import MlBox
from aare.common.beamline import MXBeamline
from aare.common.coordinate import Coordinate, SmargonCoordinate
from aare.common.coordinate import Coordinate, SmargonCoordinate, AerotechCoordinate
from aare.common.diffraction_geometry import DiffractionGeometry
from aare.common.logger_config import setup_logger
from aare.common.models import (
@@ -34,6 +35,7 @@ from aare.common.models import (
from aare.common.raster_grid import RasterGridRequest, CompletedRasterGrid, CompletedRasterGridElem
from aare.common.rotation_scan import RotationScanRequest, CompletedRotationScan
from aare.common.sample_geometry import SampleGeometryModel
from aare.daq.spreadsheetupdater import beamline
from aare.devices.area_detector import AutoEnum
from aare.devices.jfjoch import JFJochWrapper
from aare.devices.mx_lib import clean_filename
@@ -358,10 +360,10 @@ class AareDAQ:
def samcam_settings(self, s: SampleCameraSettings):
self.__devs.samcam_settings = s
def tweak_abr_meas_pos(self, c: Coordinate):
def tweak_abr_meas_pos(self, c: AerotechCoordinate):
self.__cfg.set_busy(BeamlineStateEnum.SampleAlignment)
try:
new_meas_pos = self.__cfg.abr_meas_pos + c
new_meas_pos = AerotechCoordinate(at_mm=self.__cfg.abr_meas_pos.at_mm + c.at_mm)
self.__cfg.abr_meas_pos = new_meas_pos
self.__devs.aerotech_pos = new_meas_pos
self.__saved_box = None
@@ -426,7 +428,6 @@ class AareDAQ:
def __mount(self, target: SampleShortInfo | None):
self.__devs.smargon_move_home()
self.__devs.aerotech_pos = ABR_POS_MOUNT
self.__devs.aerotech_omega = ABR_OMEGA_MOUNT
#collimator should be down!!!
self.__devs.tell.check_enable_motion()
self.__devs.tell.wait_not_busy()
@@ -691,30 +692,97 @@ class AareDAQ:
else:
return None
def __setup_datacollection(self, request: RasterGridRequest | RasterGridRequest):
if request.dtz is not None:
logger.info(f'requesting dtz to move to {request.dtz}')
self.__cfg.dtz = request.dtz
if self.sample is not None and self.sample.db_id is not None:
self.save_screenshot_db(self.sample.db_id, f"{self.sample.db_id}_before_data_collection")
def __raster(self, r: RasterGridRequest):# -> CompletedRasterGridElem:
max_time = r.exp_time_s * r.n_x * r.n_y + 60
row_time = r.exp_time_s * r.n_x
row_width_mm = r.grid_size_mm.x * r.n_x
self.__set_state(BeamlineStateEnum.DataCollection)
save_smargon_position = self.__devs.smargon_pos
if r.smargon_top_left is not None:
delta_mm = self.sample_geometry.smargon_nudge(Coordinate(x=r.grid_size_mm.x / 2, y=r.grid_size_mm.y / 2))
self.__devs.set_smargon_pos(SmargonCoordinate(sh_mm=r.smargon_top_left.sh_mm + delta_mm,
phi_deg=r.smargon_top_left.phi_deg,
chi_deg=r.smargon_top_left.chi_deg))
self.__devs.aerotech_omega = r.omega_deg
if request.transmission is not None:
logger.info(f'requesting transmission to move to {request.transmission}')
self.__devs.transmission = request.transmission
if hasattr(request, 'start') and request.start is not None:
self.__devs.smargon_pos = request.start
elif hasattr(request, 'smargon_top_left') and request.smargon_top_left is not None:
self.__devs.set_smargon_pos(SmargonCoordinate(sh_mm=request.smargon_top_left.sh_mm,
phi_deg=request.smargon_top_left.phi_deg,
chi_deg=request.smargon_top_left.chi_deg))
#if request.transmission is not None:
# self.__devs.transmission.wait()
self.__devs.smargon_wait(timeout=180)
return
def _build_fake_scan_result(self, *, file_prefix: str | None, image_count: int) -> ScanResult:
images = [
ScanResultImagesInner(
number=i,
efficiency=1.0,
bkg=0.0,
spots=0,
index=0,
mos=0.0,
b=0.0,
)
for i in range(max(1, image_count))
]
return ScanResult(file_prefix=file_prefix, images=images)
def _build_fake_rotation_result(self, request: RotationScanRequest) -> CompletedRotationScan:
result = self._build_fake_scan_result(
file_prefix=request.file_prefix,
image_count=request.steps,
)
return CompletedRotationScan(
request=copy.deepcopy(request),
result=result,
)
def _build_fake_raster_result(self, request: RasterGridRequest) -> CompletedRasterGridElem:
result = self._build_fake_scan_result(
file_prefix=request.file_prefix,
image_count=request.n_x * request.n_y,
)
return CompletedRasterGridElem(
request=copy.deepcopy(request),
result=result,
centre_of_mass=None,
)
def __raster(self, request: RasterGridRequest) -> CompletedRasterGridElem:
self.__devs.aerotech_omega = request.omega_deg
self.__setup_datacollection(request=request)
status = self.status
print(f"raster status {status}")
print(f'raster grid request: {r}')
logger.info(f"raster status {status}")
logger.info(f'raster grid request: {request}')
total_time = request.exp_time_s*request.n_x*request.n_y
#self.__jfjoch.measure_raster(r, status)
self.__devs.aerotech.run_grid_scan(cell_height_mm=r.grid_size_mm.y, num_rows=r.n_x,
row_width_mm=row_width_mm, time_per_row_s=row_time, task_id=3)
self.__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,
grid_elem_count_x=request.n_x,
grid_elem_count_y=request.n_y,
time_sec=request.exp_time_s,
run_async=True)
self.__devs.aerotech.wait_till_done(timeout=int(round(total_time*2,0)))
self.__devs.aerotech_pos = self.__cfg.abr_meas_pos
#result = self.__jfjoch.wait_till_done(60)
return None
#return CompletedRasterGridElem(request=copy.deepcopy(r), result=, centre_of_mass=None)
#return None
return self._build_fake_raster_result(request=request)
#return CompletedRasterGridElem(request=copy.deepcopy(request), result=result, centre_of_mass=None)
def measure_raster(self, r: RasterGridRequest, auto: bool) -> CompletedRasterGrid:
self.__cfg.try_set_busy(timeout=ceil(360))
@@ -723,25 +791,98 @@ class AareDAQ:
result = self.__auto_center(r)
else:
raster_result = self.__raster(r)
#result = CompletedRasterGrid(r=[raster_result])
result = CompletedRasterGrid(r=[raster_result])
self.__set_state(BeamlineStateEnum.SampleAlignment)
self.__cfg.state_busy = False
return CompletedRasterGrid(r=[])
#return result
#return CompletedRasterGrid(r=[])
return result
except Exception as e:
try:
self.__aare.axc_failed(self.sample)
except Exception as axc_e:
logger.error(f"Exception while reporting AXC failure: {axc_e}")
self.__set_state(BeamlineStateEnum.SampleAlignment)
if not self.busy:
self.__cfg.try_set_busy(timeout=ceil(360))
if self.status.state != BeamlineStateEnum.SampleAlignment:
self.__set_state(BeamlineStateEnum.SampleAlignment)
self.__cfg.state_busy = False
raise e
def __rotation(self, request: RotationScanRequest) -> CompletedRotationScan:
return None
omega_start = self.omega
status = self.status
#self.__aare.create_rotation_run(self.sample, request, status)
total_time = request.exp_time_s * request.steps
try:
# if self.__cfg.simulated_detector:
# logger.info("Simulated detector mode enabled; skipping JFJoch start.")
# result = self._build_fake_rotation_result(request)
#else:
#self.__jfjoch.measure_rotation(request, status, self.__cfg.xrf)
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:
#logger.info(f"rotation scan {request}")
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,
)
#logger.info(f"rotation scan in progress {request}")
#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.time_sec / 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)
#logger.info(f"wait till rotation scan done {request}")
self.__devs.aerotech.wait_till_done(timeout=int(round(total_time + 60,0)))
#logger.info(f"move aerotech to omega start: {omega_start}")
self.__devs.aerotech_omega = omega_start
#result = self.__jfjoch.wait_till_done(60)
# self.__aare.sample_collected(self.sample)
# if result is None:
# logger.warning("JFJoch returned no ScanResult; using fake result for rotation scan.")
# result = self._build_fake_rotation_result(request)
if self.sample is not None and self.sample.db_id is not None:
self.save_screenshot_db(self.sample.db_id, "after_dc")
# try:
# self.__aare.ingest_scan(sample=self.sample, result=result,
# geom=self.sample_geometry, beam_mark_pxl=self.__cfg.get_beam_mark(self.zoom))
#
# except Exception as e:
# logger.error(f"Exception ingesting scan: {e}")
except Exception as e:
logger.error(f"Exception during rotation scan: {e}")
#result = self._build_fake_rotation_result(request)
return self._build_fake_rotation_result(request)
#return CompletedRotationScan(request=copy.deepcopy(request), result=result)
def measure_rotation(self, request: RotationScanRequest) -> CompletedRotationScan:
total_time = request.exp_time_s * request.steps
logger.info(f"received rotation scan request: {request}, total time: {total_time}s, steps: {request.steps}")
self.__cfg.try_set_busy(timeout=ceil(total_time + 360))
try:
@@ -813,8 +954,8 @@ class AareDAQ:
@property
def sample_geometry(self) -> SampleGeometryModel:
zoom = self.__devs.zoom
aerotech_pos_ref = self.__cfg.abr_meas_pos
aerotech_pos = self.__devs.aerotech_pos
aerotech_pos_ref = self.__cfg.abr_meas_pos.at_mm
aerotech_pos = self.__devs.aerotech_pos.at_mm
sample_geom = SampleGeometryModel(
beam_location_pxl=self.__cfg.beam_mark_coeff.apply(zoom),
pixel_in_mm=self.__cfg.pixel_to_mm(zoom),
+31 -24
View File
@@ -5,15 +5,11 @@
# - setter with option to do sync/async move
# - property setter, which assumes that sync move is done (excl. zoom, which is async by default)
import time
from enum import Enum
import numpy as np
from epics import PV
from fontTools.feaLib.ast import deviceToString
from aare.common.beamline import MXBeamline
from aare.common.coordinate import Coordinate, SmargonCoordinate
from aare.common.coordinate import SmargonCoordinate, AerotechCoordinate
from aare.common.models import SampleCameraSettings, StagePositionEnum
from aare.devices import smargon, aerotech
from aare.devices.area_detector import epicsAD, AutoEnum
@@ -27,8 +23,7 @@ class BeamlineDevices:
def __init__(self, beamline: MXBeamline):
BEAMLINE = beamline.value.upper()
self.tell = make_tell_client(beamline)
self.__aerotech = aerotech.AerotechControllerEpics(beamline)
self.aerotech = aerotech.AerotechController(controller_ip="129.129.118.96")
self.aerotech = aerotech.AerotechController(beamline)
self.__smargon = smargon.Smargon(beamline)
self.__ring_current_pv = PV(f"ARS07-DPCT-0100:CURR")
@@ -262,8 +257,6 @@ class BeamlineDevices:
"""
return int(self.__sample_cam.uid.get())
# Detector Z
@property
def dtz(self) -> float:
@@ -284,32 +277,46 @@ class BeamlineDevices:
def dtz_high(self) -> float:
return self.__dtz.get("HLM")
# Aertoech Automation1
@property
def aerotech_pos(self) -> Coordinate:
return Coordinate(x=self.__aerotech.gmx.readback,
y=self.__aerotech.gmy.readback, z=self.__aerotech.gmz.readback)
def aerotech_pos(self) -> AerotechCoordinate:
#TODO add u/omega????
return self.aerotech.get_position()
@aerotech_pos.setter
def aerotech_pos(self, pos: Coordinate):
# self.__aerotech.gmx.move(pos.x, wait=False)
# self.__aerotech.gmy.move(pos.y, wait=False)
# self.__aerotech.gmz.move(pos.z, wait=False)
self.__aerotech.gmx.move(pos.x, wait=True)
self.__aerotech.gmy.move(pos.y, wait=True)
self.__aerotech.gmz.move(pos.z, wait=True)
#TODO add wait pos?
def aerotech_pos(
self,
coord: AerotechCoordinate,
/,
wait: bool = True,
incremental: bool = False,
):
self.aerotech.position(
coord,
wait=wait,
incremental=incremental,
)
@property
def aerotech_omega(self) -> float:
return self.__aerotech.omega.readback
return self.aerotech.status().u.pos
@aerotech_omega.setter
def aerotech_omega(self, val: float):
self.set_aerotech_omega(val, wait=True)
def set_aerotech_omega(self, val: float, /, wait: bool = True):
self.__aerotech.omega.move(val, wait=wait)
def set_aerotech_omega(
self,
val: float,
/,
wait: bool = True,
incremental: bool = False,
):
target = AerotechCoordinate(omega_deg=val)
return self.aerotech.position(
target,
wait=wait,
incremental=incremental,
)
@property
def aerotech_lock(self) -> bool:
+2 -3
View File
@@ -7,7 +7,7 @@ import json
import cv2
import urllib3
import uvicorn
from aare.common.coordinate import SmargonCoordinate, Coordinate
from aare.common.coordinate import SmargonCoordinate, Coordinate, AerotechCoordinate
from aare.common.error_codes import export_error_codes, export_error_codes_grouped
from aare.common.logger_config import setup_logger
from aare.common.models import SampleShortInfo, DAQStatusModel, BeamlineStateEnum, BeamlineSettingsModel, \
@@ -205,8 +205,7 @@ async def smargon(val: SmargonCoordinate, token: str = Depends(oauth2_scheme)):
@app.post("/beamline/tweak_abr_meas_pos")
async def tweak_abr_meas_pos(val: Coordinate, token: str = Depends(oauth2_scheme)):
logger.debug(f"Setting abr to {val}")
async def tweak_abr_meas_pos(val: AerotechCoordinate, token: str = Depends(oauth2_scheme)):
auth.check_jwt_staff(cfg, auth.parse_token(token))
daq.tweak_abr_meas_pos(val)
return "OK"
+8 -8
View File
@@ -44,7 +44,7 @@ class AerotechController(object):
y=at_mm.y if at_mm is not None else None,
z=at_mm.z if at_mm is not None else None,
u=coord.omega_deg,
var_async=not wait,
run_async=not wait,
incremental=incremental,
)
@@ -112,12 +112,12 @@ class AerotechController(object):
def rotation_scan(self, rotation_deg: float | int,
time_sec: float | int,
start_pos_deg: float | int,
var_async: bool = False):
run_async: bool = False):
payload = RotationRequest(
rotation_deg=rotation_deg,
time_sec=time_sec,
start_pos_deg=start_pos_deg,
var_async=var_async
run_async=run_async
)
if self.__simulated:
return payload
@@ -130,16 +130,16 @@ class AerotechController(object):
time_sec: int|float,
grid_elem_size_x_um: Optional[Union[float, int]] = None,
grid_elem_count_x: Optional[int] = None,
var_async: Optional[bool] = False
run_async: Optional[bool] = 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,
var_async=var_async,
run_async=run_async,
)
if self.__simulated:
return payload
@@ -150,14 +150,14 @@ class AerotechController(object):
wedge_deg: float | int,
time_sec: float | int,
steps: int,
var_async: bool = False
run_async: bool = False
):
payload = ScreenRequest(
rotation_deg=rotation_deg,
wedge_deg=wedge_deg,
time_sec=time_sec,
steps=steps,
var_async=var_async
run_async=run_async
)
if self.__simulated:
return payload
+6 -6
View File
@@ -1,7 +1,7 @@
from PySide6.QtCore import Signal, Slot
from PySide6.QtGui import Qt
from PySide6.QtWidgets import QWidget, QGridLayout, QLabel, QPushButton
from aare.common.coordinate import Coordinate
from aare.common.coordinate import Coordinate, AerotechCoordinate
from aare.common.models import DAQStatusModel
from aare.gui.widgets.button_with_payload import ButtonWithPayload
@@ -11,7 +11,7 @@ from aare.gui.widgets.title_label import TitleLabel
DEFAULT_ABR_STEP_UM = 5
class AbrTweakButtons(QWidget):
abr_tweak = Signal(Coordinate)
abr_tweak = Signal(AerotechCoordinate)
def __init__(self, step_mm, parent=None):
super().__init__(parent)
@@ -66,14 +66,14 @@ class AbrTweakButtons(QWidget):
@Slot(dict)
def abr_button(self, payload: dict):
self.abr_tweak.emit(Coordinate(x=self.__step_mm * payload["x"], y=self.__step_mm * payload["y"], z=self.__step_mm * payload["z"]))
self.abr_tweak.emit(AerotechCoordinate(at_mm=Coordinate(x=self.__step_mm * payload["x"], y=self.__step_mm * payload["y"], z=self.__step_mm * payload["z"])))
@Slot(float)
def set_step(self, val_um: float):
self.__step_mm = val_um / 1000.0
class AbrTweakWidget(QWidget):
abr_tweak = Signal(Coordinate)
abr_tweak = Signal(AerotechCoordinate)
abr_save = Signal()
abr_goto_meas = Signal()
@@ -109,8 +109,8 @@ class AbrTweakWidget(QWidget):
def goto_button_pressed(self):
self.abr_goto_meas.emit()
@Slot(Coordinate)
def abr_button_pressed(self, c: Coordinate):
@Slot(AerotechCoordinate)
def abr_button_pressed(self, c: AerotechCoordinate):
self.abr_tweak.emit(c)
@Slot()
+3 -3
View File
@@ -8,7 +8,7 @@ from PySide6.QtCore import Signal, QUrl, Slot, QTimer, QObject, QByteArray
from PySide6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from jfjoch_client import ScanResult, ScanResultImagesInner
from aare.common.coordinate import SmargonCoordinate, Coordinate
from aare.common.coordinate import SmargonCoordinate, Coordinate, AerotechCoordinate
from aare.common.error_codes import export_error_codes
from aare.common.models import DAQStatusModel, SampleShortInfoList, SampleShortInfo, SampleCameraSettings, \
AutofocusSettings, SimpleScanParameters, FluorescenceSpectrumParameterModel, FluorescenceSpectrumOutputModel
@@ -800,8 +800,8 @@ class DAQWorker(QObject):
return
self.generic_post("scan/smart_params", p.model_dump_json())
@Slot(Coordinate)
def abr_tweak(self, c: Coordinate):
@Slot(AerotechCoordinate)
def abr_tweak(self, c: AerotechCoordinate):
self.generic_post("beamline/tweak_abr_meas_pos", c.model_dump_json())
@Slot()