DAQLib: Introduce a dedicated structure for Diffraction geometry

This commit is contained in:
2025-06-12 16:49:27 +02:00
parent e79a323f70
commit 0af369fd12
5 changed files with 81 additions and 67 deletions
-47
View File
@@ -1,47 +0,0 @@
import math
def cal_res_A(detector_distance_mm: float,
wavelength_angstrom: float,
radius_mm: float) -> float:
"""
Calculate maximum achievable resolution in Angstroms.
Args:
detector_distance_mm: Distance from crystal to detector in mm
wavelength_angstrom: X-ray wavelength in Angstroms
radius_mm: Detector radius in mm
Returns:
Resolution in Angstroms
"""
# Calculate theta using arctangent of opposite/adjacent
theta = math.atan(radius_mm / detector_distance_mm) / 2.0
# Using Bragg's law: n𝜆 = 2d*sin(θ)
# Solving for d: d = 𝜆/(2*sin(θ))
resolution = wavelength_angstrom / (2 * math.sin(theta))
return resolution
def calc_dtz_mm(desired_resolution_angstrom: float,
wavelength_angstrom: float,
radius_mm: float) -> float:
"""
Calculate required detector distance for desired resolution.
Args:
desired_resolution_angstrom: Desired resolution in Angstroms
wavelength_angstrom: X-ray wavelength in Angstroms
radius_mm: Detector radius in mm
Returns:
Required detector distance in mm
"""
# From Bragg's law: sin(θ) = 𝜆/(2d)
theta = math.asin(wavelength_angstrom / (2 * desired_resolution_angstrom))
# Using tangent: tan(2θ) = opposite/adjacent
# Therefore: distance = radius/tan(2θ)
distance = radius_mm / math.tan(2 * theta)
return distance
@@ -0,0 +1,46 @@
import math
from typing import Annotated, Tuple
from pydantic import Field, BaseModel
class DiffractionGeometry(BaseModel):
energy_keV: Annotated[float, Field(gt=1.0, lt=100.0)]
dtz_mm: Annotated[float, Field(gt=10.0, lt=5000.0)]
pixel_size_mm: Annotated[float, Field(ge=0.05, le=0.5)]
beam_center_pxl: Tuple[float, float]
detector_size_pxl: Tuple[int, int]
@property
def detector_max_radius_pxl(self):
x0 = self.detector_size_pxl[0] - self.beam_center_pxl[0]
x1 = self.beam_center_pxl[0]
y0 = self.detector_size_pxl[1] - self.beam_center_pxl[1]
y1 = self.beam_center_pxl[1]
return max(abs(x0), abs(x1), abs(y0), abs(y1))
@property
def detector_radius_mm(self):
return self.detector_max_radius_pxl * self.pixel_size_mm
@property
def wavelength_angstrom(self):
return 12.398 / self.energy_keV
@property
def max_resolution_angstrom(self):
return self.resolution_angstrom(self.dtz_mm)
def resolution_angstrom(self, exp_dtz_mm: float) -> float:
if exp_dtz_mm <= 0:
raise ValueError("Detector distance must be positive")
theta = math.atan(self.detector_radius_mm / exp_dtz_mm) / 2.0
return self.wavelength_angstrom / (2 * math.sin(theta))
def calc_dtz_mm(self, exp_resolution_angstrom: float) -> float:
if exp_resolution_angstrom <= 0:
raise ValueError("Resolution must be positive")
theta = math.asin(self.wavelength_angstrom / (2 * exp_resolution_angstrom))
return self.detector_radius_mm / math.tan(2 * theta)
+2 -6
View File
@@ -4,6 +4,7 @@ from typing import Annotated, Literal, Tuple, List, Optional
from pydantic import BaseModel, Field
from aaredaqlib.coordinate import Coordinate
from aaredaqlib.diffraction_geometry import DiffractionGeometry
from aaredaqlib.sample_geometry import SampleGeometryModel
class TokenData(BaseModel):
@@ -145,8 +146,6 @@ class AutofocusSettings(BaseModel):
z_steps: int
class BeamlineStatus(BaseModel):
energy_keV: float
dtz_mm: float
ring_current_mA: float
light: Annotated[float, Field(ge=0.0, le=100.0)]
cryojet_K: float
@@ -161,16 +160,13 @@ class SessionStatus(BaseModel):
class DAQStatusModel(BaseModel):
geom: SampleGeometryModel
diffraction: DiffractionGeometry
bl: BeamlineStatus
state: BeamlineStateEnum
busy: bool
sample: SampleShortInfo | None = None
session: SessionStatus
class BeamCenterModel(BaseModel):
x: float | None = 0.0
y: float | None = 0.0
class BeamlineSettingsModel(BaseModel):
# Float properties
cryojet_park_position: float | None = 12.0
+18 -10
View File
@@ -1,6 +1,7 @@
import base64
import io
import json
from typing import Tuple
import numpy as np
import redis
@@ -13,7 +14,7 @@ from aaredaqlib.models import (
def_loop_centering_zoom,
SampleShortInfo,
SessionStatus,
BeamlineStateEnum, SessionsStateEnum, BeamCenterModel,
BeamlineStateEnum, SessionsStateEnum,
)
from aaredaqlib.beamline import MXBeamline
@@ -148,17 +149,24 @@ class BeamlineConfig:
)
@property
def beam_center(self, dtz: float | None = None) -> BeamCenterModel:
tmp = self.__client.get(f"{self.__bl}:beam_center")
if tmp is None:
return BeamCenterModel()
data_dict = json.loads(tmp)
model = BeamCenterModel(**data_dict)
return model
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: BeamCenterModel):
self.__client.set(f"{self.__bl}:beam_center", data.model_dump_json())
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])
def __get_settings(self) -> BeamlineSettingsModel:
tmp = self.__client.get(f"{self.__bl}:settings")
+15 -4
View File
@@ -8,6 +8,7 @@ import redis
from aaredaq.autofocus import calculate_focus_measure
from aaredaqlib.coordinate import Coordinate, SmargonCoordinate
from aaredaqlib.diffraction_geometry import DiffractionGeometry
from aaredaqlib.models import (
SampleShortInfo,
PuckLoadedInfo,
@@ -398,7 +399,8 @@ class AareDAQ:
y=self.__devs.gmy.value - abr_pos.y,
z=self.__devs.gmz.value - abr_pos.z,
),
aerotech_meas=self.__devs.abr_pos
aerotech_meas=self.__devs.abr_pos,
detector_radius_mm = 115.0 # PILATUS4 2M / JUNGFRAU 9M
)
def get_beam_center(self):
@@ -711,10 +713,18 @@ class AareDAQ:
self.__devs.shutter = False
@property
def beamline_status(self) -> BeamlineStatus:
return BeamlineStatus(
def diffraction_geometry(self) -> DiffractionGeometry:
return DiffractionGeometry(
energy_keV=self.__devs.energy_kev,
dtz_mm=self.__devs.dtz.value,
detector_size_pxl=(1553,1630),
pixel_size_mm=0.150, #PILATUS 4
beam_center_pxl=self.__cfg.beam_center
)
@property
def beamline_status(self) -> BeamlineStatus:
return BeamlineStatus(
ring_current_mA=self.__devs.ring_current,
light=self.light,
cryojet_K=self.__devs.cryojet_temp,
@@ -732,6 +742,7 @@ class AareDAQ:
geom=self.sample_geometry,
bl=self.beamline_status,
sample=self.sample,
session=SessionStatus()
session=SessionStatus(),
diffraction=self.diffraction_geometry
)