Determine beam centre from detector coords #166
+1
-1
@@ -7,7 +7,7 @@ requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"uv",
|
||||
"gunicorn",
|
||||
"aarecommon>=0.2.2",
|
||||
"aarecommon>=0.5.1",
|
||||
"pydantic>=2.11",
|
||||
"numpy",
|
||||
"jfjoch_client==1.0.0rc146",
|
||||
|
||||
@@ -13,7 +13,7 @@ def get_beamline_dispatch() -> BeamlineDispatch:
|
||||
case MXBeamline.X06DA:
|
||||
from .x06da import X06daDispatch
|
||||
|
||||
return X06daDispatch()
|
||||
return X06daDispatch(MXBeamline.X06DA)
|
||||
case MXBeamline.X06SA:
|
||||
from .x06sa import X06saDispatch
|
||||
|
||||
@@ -21,4 +21,4 @@ def get_beamline_dispatch() -> BeamlineDispatch:
|
||||
case MXBeamline.X10SA:
|
||||
from .x10sa import X10saDispatch
|
||||
|
||||
return X10saDispatch()
|
||||
return X10saDispatch(MXBeamline.X10SA)
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import json
|
||||
import os
|
||||
from abc import ABC
|
||||
from importlib.resources import files
|
||||
|
||||
from aare.beamline_dispatch.protocols import AuthDispatch, BeamlineDispatch
|
||||
from aarecommon.config.beamline import MXBeamline
|
||||
from aarecommon.math.beam_center import BeamCenterFromDetectorStage
|
||||
from aarecommon.models.beam_centre import BeamCentre
|
||||
|
||||
from aare.beamline_dispatch.protocols import AuthDispatch, BeamlineDispatch, Geometry
|
||||
|
||||
|
||||
class DefaultAuthDispatch(AuthDispatch):
|
||||
@@ -12,13 +19,31 @@ class DefaultAuthDispatch(AuthDispatch):
|
||||
return key
|
||||
|
||||
|
||||
class DefaultDispatch(BeamlineDispatch):
|
||||
class DefaultGeometry(Geometry):
|
||||
def __init__(self, beamline: MXBeamline) -> None:
|
||||
super().__init__()
|
||||
with open(str(files("aarecommon.config") / "beamline_configs" / "beam_centres.json")) as f:
|
||||
measured = json.loads(f.read())[beamline.value.lower()]
|
||||
self._model = BeamCentre.model_validate(measured)
|
||||
self._beamline = beamline
|
||||
|
||||
@property
|
||||
def beam_centre_model(self) -> BeamCenterFromDetectorStage:
|
||||
return self._model.model
|
||||
|
||||
|
||||
class DefaultDispatch(BeamlineDispatch, ABC):
|
||||
"""Default implementation for anything which can vary between beamlines and/or simulation.
|
||||
Should be safe and fail rather than assuming anything."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, beamline: MXBeamline) -> None:
|
||||
self._auth = DefaultAuthDispatch()
|
||||
self._geo = DefaultGeometry(beamline=beamline)
|
||||
|
||||
@property
|
||||
def auth(self):
|
||||
return self._auth
|
||||
|
||||
@property
|
||||
def geo(self):
|
||||
return self._geo
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any
|
||||
|
||||
from aarecommon.math.beam_center import BeamCenterFromDetectorStage
|
||||
|
||||
|
||||
class AuthDispatch(ABC):
|
||||
@abstractmethod
|
||||
@@ -28,6 +30,12 @@ class BecMacros(ABC):
|
||||
def mono_pitch_scan(plot=True): ...
|
||||
|
||||
|
||||
class Geometry(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def beam_centre_model(self) -> BeamCenterFromDetectorStage: ...
|
||||
|
||||
|
||||
class BeamlineDispatch(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
@@ -35,3 +43,6 @@ class BeamlineDispatch(ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def bec_macros(self) -> BecMacros: ...
|
||||
@property
|
||||
@abstractmethod
|
||||
def geo(self) -> Geometry: ...
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from typing import Any
|
||||
|
||||
from aarecommon.config.beamline import MXBeamline
|
||||
|
||||
from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch
|
||||
from aare.beamline_dispatch.protocols import BecMacros
|
||||
|
||||
@@ -45,8 +47,8 @@ class X06daBecMacros(BecMacros):
|
||||
|
||||
|
||||
class X06daDispatch(DefaultDispatch):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, beamline: MXBeamline) -> None:
|
||||
super().__init__(beamline=beamline)
|
||||
self._bec_macros = X06daBecMacros()
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from typing import Any
|
||||
|
||||
from aarecommon.config.beamline import MXBeamline
|
||||
|
||||
from aare.beamline_dispatch.default.beamline_dispatch import DefaultDispatch
|
||||
from aare.beamline_dispatch.protocols import BecMacros
|
||||
|
||||
@@ -39,8 +41,8 @@ class X10SaBecMacros(BecMacros):
|
||||
|
||||
|
||||
class X10saDispatch(DefaultDispatch):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, beamline: MXBeamline) -> None:
|
||||
super().__init__(beamline=beamline)
|
||||
self._bec_macros = X10SaBecMacros()
|
||||
|
||||
@property
|
||||
|
||||
@@ -632,25 +632,6 @@ class BeamlineConfig:
|
||||
lens_factor = DEFAULT_LENS_MAGNIFICATION / lens_magnification
|
||||
return float(np.log(lens_factor / (b * target_pixel_in_mm)) / a)
|
||||
|
||||
@property
|
||||
def beam_center(self) -> tuple[float, float]:
|
||||
tmp_x = self._client.get(f"{self._bl}:beam_center_x")
|
||||
tmp_y = self._client.get(f"{self._bl}:beam_center_y")
|
||||
if tmp_x:
|
||||
val_x = float(tmp_x)
|
||||
else:
|
||||
val_x = 0
|
||||
if tmp_y:
|
||||
val_y = float(tmp_y)
|
||||
else:
|
||||
val_y = 0
|
||||
return val_x, val_y
|
||||
|
||||
@beam_center.setter
|
||||
def beam_center(self, data: tuple[float, float]):
|
||||
self._client.set(f"{self._bl}:beam_center_x", data[0])
|
||||
self._client.set(f"{self._bl}:beam_center_y", data[1])
|
||||
|
||||
@property
|
||||
def beam_size_mm(self) -> Coordinate:
|
||||
tmp_x = self._client.get(f"{self._bl}:beam_size_x")
|
||||
|
||||
+23
-50
@@ -67,6 +67,7 @@ from aarecommon.models.rotation_scan import CompletedRotationScan, RotationScanR
|
||||
from aarecommon.models.tell import TellPhaseEnum, TellStateModel
|
||||
from aareDB import SampleEventType
|
||||
|
||||
from aare.beamline_dispatch.protocols import BeamlineDispatch
|
||||
from aare.daq import workflows
|
||||
from aare.daq.aaredb import AareWrapper
|
||||
from aare.daq.config import ABR_POS_MOUNT, BeamlineConfig, BeamlineStateEnum
|
||||
@@ -270,10 +271,11 @@ class AareDAQ:
|
||||
AUTO_RASTER_MIN_CELL_SIZE_MM = 0.005
|
||||
AUTO_RASTER_SKIP_IF_EXCEED_MAX_IMAGE_THRESHOLD = True
|
||||
|
||||
def __init__(self, cfg: BeamlineConfig, bl: MXBeamline):
|
||||
def __init__(self, cfg: BeamlineConfig, bl: MXBeamline, dispatch: BeamlineDispatch):
|
||||
self.last_time = 0.0
|
||||
self._dispatch = dispatch
|
||||
self._cfg = cfg
|
||||
self._devs = BeamlineDevices(bl)
|
||||
self._devs = BeamlineDevices(bl, dispatch)
|
||||
self._mlbox = MlBox(bl)
|
||||
self._jfjoch = JFJochWrapper(bl)
|
||||
self._bl = bl.value.upper()
|
||||
@@ -2261,14 +2263,6 @@ class AareDAQ:
|
||||
)
|
||||
return sample_geom
|
||||
|
||||
@property
|
||||
def beam_center(self) -> tuple[float, float]:
|
||||
return self._cfg.beam_center
|
||||
|
||||
@beam_center.setter
|
||||
def beam_center(self, val: tuple[float, float]):
|
||||
self._cfg.beam_center = val
|
||||
|
||||
@property
|
||||
def beam_size_mm(self) -> Coordinate:
|
||||
return self._cfg.beam_size_mm
|
||||
@@ -3151,46 +3145,25 @@ class AareDAQ:
|
||||
|
||||
@property
|
||||
def diffraction_geometry(self) -> DiffractionGeometry:
|
||||
try:
|
||||
metadata = self._cached_detector_metadata()
|
||||
width = int(metadata.get("detector_width", 1))
|
||||
height = int(metadata.get("detector_height", 1))
|
||||
pixel_size_mm = float(metadata.get("pixel_size_mm", 0.15))
|
||||
detector_description = str(metadata.get("detector_description", "unavailable"))
|
||||
detector_serial_number = str(metadata.get("detector_serial_number", "unavailable"))
|
||||
energy = self._devs.energy_kev
|
||||
dtz = self._devs.dtz
|
||||
beam_center = self._cfg.beam_center
|
||||
return DiffractionGeometry(
|
||||
energy_keV=energy,
|
||||
dtz_mm=dtz,
|
||||
detector_size_pxl=(width, height),
|
||||
pixel_size_mm=pixel_size_mm,
|
||||
beam_center_pxl=beam_center,
|
||||
detector_description=detector_description,
|
||||
detector_serial_number=detector_serial_number,
|
||||
poni_rot1_rad=-0.001396263,
|
||||
poni_rot2_rad=-0.003839724,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Falling back to default diffraction geometry because cached detector metadata is unavailable: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
energy = self._devs.energy_kev
|
||||
dtz = self._devs.dtz
|
||||
beam_center = self._cfg.beam_center
|
||||
return DiffractionGeometry(
|
||||
energy_keV=energy,
|
||||
dtz_mm=dtz,
|
||||
detector_size_pxl=(1, 1),
|
||||
pixel_size_mm=0.15,
|
||||
beam_center_pxl=beam_center,
|
||||
detector_description="unavailable",
|
||||
detector_serial_number="unavailable",
|
||||
poni_rot1_rad=-0.001396263,
|
||||
poni_rot2_rad=-0.003839724,
|
||||
)
|
||||
metadata = self._cached_detector_metadata()
|
||||
width = int(metadata.get("detector_width", 1))
|
||||
height = int(metadata.get("detector_height", 1))
|
||||
pixel_size_mm = float(metadata.get("pixel_size_mm", 0.15))
|
||||
detector_description = str(metadata.get("detector_description", "unavailable"))
|
||||
detector_serial_number = str(metadata.get("detector_serial_number", "unavailable"))
|
||||
energy = self._devs.energy_kev
|
||||
dtz = self._devs.dtz
|
||||
return DiffractionGeometry(
|
||||
energy_keV=energy,
|
||||
dtz_mm=dtz,
|
||||
detector_size_pxl=(width, height),
|
||||
pixel_size_mm=pixel_size_mm,
|
||||
beam_center_pxl=self._devs.detector_beam_centre_px,
|
||||
detector_description=detector_description,
|
||||
detector_serial_number=detector_serial_number,
|
||||
poni_rot1_rad=-0.001396263,
|
||||
poni_rot2_rad=-0.003839724,
|
||||
)
|
||||
|
||||
@property
|
||||
def beamline_status(self) -> BeamlineStatus:
|
||||
|
||||
+20
-6
@@ -1,10 +1,6 @@
|
||||
# Abstractions of devices for beamline
|
||||
import time
|
||||
|
||||
# Each "standard" device needs three elements:
|
||||
# - property to read device value
|
||||
# - setter with option to do sync/async move
|
||||
# - property setter, which assumes that sync move is done (excl. zoom, which is async by default)
|
||||
from aarecommon.config.beamline import cfg_get
|
||||
from aarecommon.config.logger import setup_logger
|
||||
from aarecommon.config.logger_events import log_timing
|
||||
@@ -13,6 +9,11 @@ from aarecommon.models.beamline import MXBeamline
|
||||
from aarecommon.models.models import BeamlineStateEnum, SampleCameraSettings, StagePositionEnum
|
||||
from epics import PV
|
||||
|
||||
# Each "standard" device needs three elements:
|
||||
# - property to read device value
|
||||
# - setter with option to do sync/async move
|
||||
# - property setter, which assumes that sync move is done (excl. zoom, which is async by default)
|
||||
from aare.beamline_dispatch.protocols import BeamlineDispatch
|
||||
from aare.devices import aerotech, smargon
|
||||
from aare.devices.area_detector import AutoEnum, epicsAD
|
||||
from aare.devices.bec_worker import BECClientWorker
|
||||
@@ -27,7 +28,8 @@ logger = setup_logger("aareDAQ")
|
||||
|
||||
|
||||
class BeamlineDevices:
|
||||
def __init__(self, beamline: MXBeamline):
|
||||
def __init__(self, beamline: MXBeamline, dispatch: BeamlineDispatch):
|
||||
self._dispatch = dispatch
|
||||
self._beamline = beamline
|
||||
BEAMLINE = beamline.value.upper()
|
||||
self.tell = make_tell_client(beamline)
|
||||
@@ -42,6 +44,7 @@ class BeamlineDevices:
|
||||
|
||||
# faster to define the dtz object here than in functions and then use
|
||||
self._dtz = self.bec_worker.dev.det_z
|
||||
self._dty = self.bec_worker.dev.det_y
|
||||
self.dtz_mod = cfg_get("daq.detector_distance_limit_modifier", 1.0)
|
||||
# TODO convert epics pvs to BEC
|
||||
self._sample_cam = epicsAD(f"{BEAMLINE}-ES-MS:")
|
||||
@@ -156,6 +159,13 @@ class BeamlineDevices:
|
||||
def lamp_light(self) -> float:
|
||||
return self._front_light.value
|
||||
|
||||
@property
|
||||
def detector_beam_centre_px(self) -> tuple[float, float]:
|
||||
beam_center_x, beam_center_y = self._dispatch.geo.beam_centre_model.predict(
|
||||
self.dtz, self.dty
|
||||
)
|
||||
return float(beam_center_x[0]), float(beam_center_y[0])
|
||||
|
||||
@lamp_light.setter
|
||||
def lamp_light(self, v: float):
|
||||
self.set_front_light(v, wait=False)
|
||||
@@ -267,7 +277,11 @@ class BeamlineDevices:
|
||||
# Detector Z
|
||||
@property
|
||||
def dtz(self) -> float:
|
||||
return self._dtz.read()["det_z"]["value"]
|
||||
return self._dtz.user_setpoint.get()
|
||||
|
||||
@property
|
||||
def dty(self) -> float:
|
||||
return self._dty.user_setpoint.get()
|
||||
|
||||
@dtz.setter
|
||||
def dtz(self, value: float):
|
||||
|
||||
+1
-20
@@ -108,7 +108,7 @@ async def lifespan(application: FastAPI):
|
||||
# ── Core objects (Redis, EPICS PVs, BEC, TELL, JFJoch, etc.) ──
|
||||
bl = mx_beamline()
|
||||
cfg = BeamlineConfig(bl)
|
||||
daq = AareDAQ(cfg, bl)
|
||||
daq = AareDAQ(cfg, bl, bl_dispatch)
|
||||
cfg.state = daq.read_current_state_from_bec()
|
||||
|
||||
try:
|
||||
@@ -932,25 +932,6 @@ async def clear_beam_mark(token: str = Depends(oauth2_scheme)):
|
||||
return "OK"
|
||||
|
||||
|
||||
@app.post("/beamline/beam_center")
|
||||
async def beam_center(x: float, y: float, token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
Set the beam center position. Staff only.
|
||||
|
||||
Args:
|
||||
x: X coordinate in pixels.
|
||||
y: Y coordinate in pixels.
|
||||
token: OAuth2 access token.
|
||||
|
||||
Returns:
|
||||
"OK" on success.
|
||||
"""
|
||||
logger.debug(f"Beam Center {x}, {y}")
|
||||
auth.check_jwt_staff(cfg, auth.parse_token(token))
|
||||
daq.beam_center = (x, y)
|
||||
return "OK"
|
||||
|
||||
|
||||
@app.post("/beamline/beam_size_mm")
|
||||
async def beam_size_mm(x: float, y: float, token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
|
||||
@@ -414,7 +414,7 @@ class BECClientWorker:
|
||||
logger.warning(
|
||||
f"Energy change may have failed, current energy: {self.check_current_energy()} eV"
|
||||
)
|
||||
if beamline is MXBeamline.X10SA:
|
||||
if self.beamline is MXBeamline.X10SA:
|
||||
additonal_text = [
|
||||
f"New dcm_bragg position: {self.dev.dcm_bragg.position:4g} mrad",
|
||||
f"New dcm_pitch position: {self.dev.dcm_pitch.position:4g} ",
|
||||
|
||||
@@ -2,7 +2,6 @@ from aarecommon.models.models import DAQStatusModel
|
||||
from PySide6.QtCore import Signal, Slot
|
||||
from PySide6.QtWidgets import QGridLayout, QLabel, QWidget
|
||||
|
||||
from aare.gui.widgets.number_line_edit import NumberLineEdit
|
||||
from aare.gui.widgets.title_label import section_title
|
||||
|
||||
|
||||
@@ -16,23 +15,16 @@ class BeamCenterWidget(QWidget):
|
||||
|
||||
grid_layout.addWidget(section_title("Beam center (detector)", self), 0, 0, 1, 5)
|
||||
|
||||
self.x = NumberLineEdit(-4000, 4000, 0, parent=self)
|
||||
self.x.newValue.connect(self.beam_center_edited)
|
||||
|
||||
self.y = NumberLineEdit(-4000, 4000, 0, parent=self)
|
||||
self.y.newValue.connect(self.beam_center_edited)
|
||||
self._x = QLabel("...")
|
||||
self._y = QLabel("...")
|
||||
|
||||
grid_layout.addWidget(QLabel("x:"), 1, 0)
|
||||
grid_layout.addWidget(self.x, 1, 1)
|
||||
grid_layout.addWidget(self._x, 1, 1)
|
||||
grid_layout.addWidget(QLabel("y:"), 1, 2)
|
||||
grid_layout.addWidget(self.y, 1, 3)
|
||||
grid_layout.addWidget(QLabel("pxl"), 1, 4)
|
||||
grid_layout.addWidget(self._y, 1, 3)
|
||||
grid_layout.addWidget(QLabel("px"), 1, 4)
|
||||
|
||||
@Slot(DAQStatusModel)
|
||||
def update_daq_status(self, s: DAQStatusModel):
|
||||
self.x.update_value(s.diffraction.beam_center_pxl[0])
|
||||
self.y.update_value(s.diffraction.beam_center_pxl[1])
|
||||
|
||||
@Slot(float)
|
||||
def beam_center_edited(self, _: float):
|
||||
self.beam_center.emit(self.x.value, self.y.value)
|
||||
self._x.setText(f"{s.diffraction.beam_center_pxl[0]:.1f}")
|
||||
self._y.setText(f"{s.diffraction.beam_center_pxl[1]:.1f}")
|
||||
|
||||
Reference in New Issue
Block a user