DAQLib: Introduce a dedicated structure for Diffraction geometry
This commit is contained in:
@@ -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)
|
||||
@@ -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
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user