diff --git a/src/aare/daq/config.py b/src/aare/daq/config.py index 61196563..4b353ae0 100644 --- a/src/aare/daq/config.py +++ b/src/aare/daq/config.py @@ -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 diff --git a/src/aare/daq/daq.py b/src/aare/daq/daq.py index 67c47868..1a105633 100644 --- a/src/aare/daq/daq.py +++ b/src/aare/daq/daq.py @@ -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), diff --git a/src/aare/daq/devices.py b/src/aare/daq/devices.py index 0455beea..bf2ee040 100644 --- a/src/aare/daq/devices.py +++ b/src/aare/daq/devices.py @@ -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: diff --git a/src/aare/daq/server.py b/src/aare/daq/server.py index faf1bfbb..2665f44d 100644 --- a/src/aare/daq/server.py +++ b/src/aare/daq/server.py @@ -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" diff --git a/src/aare/devices/aerotech.py b/src/aare/devices/aerotech.py index 47ae6cad..2d236f7e 100644 --- a/src/aare/devices/aerotech.py +++ b/src/aare/devices/aerotech.py @@ -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 diff --git a/src/aare/gui/panels/abr_tweak_panel.py b/src/aare/gui/panels/abr_tweak_panel.py index 8741f0dd..9eb589cf 100644 --- a/src/aare/gui/panels/abr_tweak_panel.py +++ b/src/aare/gui/panels/abr_tweak_panel.py @@ -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() diff --git a/src/aare/gui/threads/daq_worker.py b/src/aare/gui/threads/daq_worker.py index 36fb7464..619d6cec 100644 --- a/src/aare/gui/threads/daq_worker.py +++ b/src/aare/gui/threads/daq_worker.py @@ -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()