Small updates from the SAXS beamtime #264

Open
appel_c wants to merge 6 commits from updates-from-the-beamline-fix-eiger into main
8 changed files with 235 additions and 23 deletions
@@ -270,10 +270,10 @@ class ROIMappingBox(QGroupBox):
layout.setColumnStretch(2, 1)
# An unknown calibration must not silently become a one-motor-unit-per-pixel scan.
self.camera_scale_pixel_x = spinbox(value=0.0, single_step=0.1, parent=self)
self.camera_scale_pixel_x = spinbox(value=0.0, single_step=0.1, decimals=6, parent=self)
self.offset_cam_xray_x = spinbox(value=0.0, single_step=0.1, parent=self)
self.direction_x = direction_combo(self)
self.camera_scale_pixel_y = spinbox(value=0.0, single_step=0.1, parent=self)
self.camera_scale_pixel_y = spinbox(value=0.0, single_step=0.1, decimals=6, parent=self)
self.offset_cam_xray_y = spinbox(value=0.0, single_step=0.1, parent=self)
self.direction_y = direction_combo(self)
self.mapping_lock_toggle = ToggleSwitch(self, checked=False)
@@ -13,6 +13,7 @@ from typing import Any, Literal
from uuid import uuid4
from bec_lib import bec_logger
from bec_lib.endpoints import MessageEndpoints
from bec_lib.messages import VariableMessage
from bec_qthemes import material_icon
from bec_widgets.utils.bec_widget import BECWidget
@@ -115,6 +116,11 @@ class SAXSWidget(BECWidget, QWidget):
self._sync_mapping_reference_to_display()
self._refresh_crosshair_pin_if_enabled()
# Connect to ScanStatusUpdates
self.bec_dispatcher.connect_slot(
slot=self._deactivate_toggle_live_mode, topics = MessageEndpoints.scan_status()
)
# ------------------------------------------------------------------
# UI construction
# ------------------------------------------------------------------
@@ -229,6 +235,7 @@ class SAXSWidget(BECWidget, QWidget):
"step_size",
"readback",
"device_box",
"setpoint",
}:
widget.hide()
@@ -283,8 +290,22 @@ class SAXSWidget(BECWidget, QWidget):
self._clear_reference_crosshair_pin()
# self.image.toggle_crosshair(False)
@SafeSlot(dict,dict)
def _deactivate_toggle_live_mode(self, content, _metadata) -> None:
status = content.get("status", "")
if status == "open":
enabled = False
else:
enabled = True
toggle_cam_action = self.image.toolbar.components.get_action("toggle_cam")
if toggle_cam_action is not None:
toggle_cam_action.action.setEnabled(enabled)
def _toggle_camera_live_mode(self, enabled: bool) -> None:
# If enabled, set the camera to live mode, if disabled, stop the live mode.
dev_manager = self.bec_dispatcher.client.device_manager
if dev_manager.scan_info.msg is not None and dev_manager.scan_info.msg.status in ["open"]:
return # Ignore commands if scan is running
device_name = getattr(self.image, "device", "")
if device_name and device_name in self.dev:
device = self.dev[device_name]
+16 -16
View File
@@ -1,25 +1,25 @@
eiger_1_5:
description: Eiger 1.5M in-vacuum detector
deviceClass: csaxs_bec.devices.jungfraujoch.eiger_1_5m.Eiger1_5M
deviceConfig:
detector_distance: 2150
beam_center: [860, 1219]
onFailure: raise
enabled: True
readoutPriority: async
softwareTrigger: False
# eiger_9:
# description: Eiger 9M detector
# deviceClass: csaxs_bec.devices.jungfraujoch.eiger_9m.Eiger9M
# eiger_1_5:
# description: Eiger 1.5M in-vacuum detector
# deviceClass: csaxs_bec.devices.jungfraujoch.eiger_1_5m.Eiger1_5M
# deviceConfig:
# detector_distance: 2200
# beam_center: [870, 1203]
# detector_distance: 2150
# beam_center: [860, 1219]
# onFailure: raise
# enabled: True
# readoutPriority: async
# softwareTrigger: False
eiger_9:
description: Eiger 9M detector
deviceClass: csaxs_bec.devices.jungfraujoch.eiger_9m.Eiger9M
deviceConfig:
detector_distance: 6975.225
beam_center: [1149.71, 1450.89]
onFailure: raise
enabled: True
readoutPriority: async
softwareTrigger: False
# ids_cam:
# description: IDS camera for live image acquisition
# deviceClass: csaxs_bec.devices.ids_cameras.IDSCamera
+8 -2
View File
@@ -16,11 +16,17 @@ endstation:
detectors:
- !include ./bl_detectors.yaml
xeye:
- !include ./xeye.yaml
ssaxs:
- !include ./ssaxs.yaml
#sastt:
# - !include ./sastt.yaml
flomni:
- !include ./ptycho_flomni.yaml
# flomni:
# - !include ./ptycho_flomni.yaml
#omny:
# - !include ./ptycho_omny.yaml
+75
View File
@@ -0,0 +1,75 @@
samx:
description: Owis motor stage samx
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES01
motor_resolution: 0.00125
base_velocity: 0.0625
velocity: 10
backlash_distance: 0.125
acceleration: 0.2
user_offset_dir: 0
deviceTags:
- cSAXS
- owis_samx
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
samy:
description: Owis motor stage samy
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES02
motor_resolution: 0.00125
base_velocity: 0.0625
velocity: 10
backlash_distance: 0.125
acceleration: 0.2
user_offset_dir: 0
deviceTags:
- cSAXS
- owis_samy
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
micz:
description: Owis motor stage micz
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES03
motor_resolution: 0.000125
base_velocity: 0.00625
velocity: 1
backlash_distance: 0.0125
acceleration: 0.2
user_offset_dir: 0
deviceTags:
- cSAXS
- owis_micz
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
cam_sample:
description: Camera sample
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
deviceConfig:
camera_id: 5
bits_per_pixel: 24
num_rotation_90: 3
transpose: false
force_monochrome: false
m_n_colormode: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: async
# userParameter:
# pixel_calibration: 0.00044247787610619477 # mm/pixel (= 0.05 / 113)
+56
View File
@@ -0,0 +1,56 @@
eyex:
description: Owis motor stage eyex
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES04
motor_resolution: 0.00125
base_velocity: 0.0625
velocity: 1
backlash_distance: 0.125
acceleration: 0.2
user_offset_dir: 0
deviceTags:
- cSAXS
- owis_eyex
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
eyey:
description: Owis motor stage eyey
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
deviceConfig:
prefix: X12SA-ES2-ES05
motor_resolution: 0.00125
base_velocity: 0.0625
velocity: 1
backlash_distance: 0.125
acceleration: 0.2
user_offset_dir: 0
deviceTags:
- cSAXS
- owis_eyey
onFailure: buffer
enabled: true
readoutPriority: baseline
softwareTrigger: false
cam_xeye:
description: Camera Xray eye ID1
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
deviceConfig:
camera_id: 1
bits_per_pixel: 24
num_rotation_90: 2
transpose: true
force_monochrome: true
m_n_colormode: 1
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: async
# userParameter:
# pixel_calibration: 0.00044247787610619477 # mm/pixel (= 0.05 / 113)
+52 -3
View File
@@ -41,6 +41,7 @@ import yaml
from bec_lib.file_utils import get_full_path
from bec_lib.logger import bec_logger
from bec_server.scan_server.scans.scan_base import ScanInfo as ScanServerScanInfo
from jfjoch_client.models.azim_int_settings import AzimIntSettings
from jfjoch_client.models.dataset_settings import DatasetSettings
from jfjoch_client.models.detector_settings import DetectorSettings
from jfjoch_client.models.detector_state import DetectorState
@@ -85,7 +86,13 @@ class Eiger(PSIDeviceBase):
**kwargs: Additional keyword arguments.
"""
USER_ACCESS = ["set_detector_distance", "set_beam_center"]
USER_ACCESS = [
"set_detector_distance",
"set_beam_center",
"print_detector_distance_and_center",
"set_azi_settings",
"get_azi_settings",
]
file_event = Cpt(FileEventSignal, name="file_event")
preview_image = Cpt(PreviewSignal, name="preview_image", ndim=2)
@@ -160,6 +167,45 @@ class Eiger(PSIDeviceBase):
"""
self.detector_distance = distance
def print_detector_distance_and_center(self):
print(f"Detector Distance : {self.detector_distance}")
print(f"Beam cetner position (x,y) : {self.beam_center}")
def get_azi_settings(self):
return self.jfj_client.api.config_azim_int_get(_request_timeout=5).model_dump()
def set_azi_settings(
self,
pol_cor: bool,
solid_angle_corr: bool,
high_q_recip_a: float,
low_q_recip_a: float,
q_spacing: float,
azimuthal_bins: int = 16,
force_cpu: bool = True,
) -> None:
"""
Set the azimuthal integration settings.
Args:
pol_cor (bool): Apply polarization correction for azimuthal integration.
solid_angle_corr (bool): Apply solid angle correction for azimuthal integration.
high_q_recip_a (float): High q value in reciprocal angstroms.
low_q_recip_a (float): Low q value in reciprocal angstroms.
q_spacing (float): Q spacing.
azimuthal_bins (int): Number of azimuthal (phi) bins; 1 = standard 1D azimuthal integration.
"""
model = AzimIntSettings(
polarization_corr=pol_cor,
solid_angle_corr=solid_angle_corr,
high_q_recip_a=high_q_recip_a,
low_q_recip_a=low_q_recip_a,
q_spacing=q_spacing,
azimuthal_bins=azimuthal_bins,
force_cpu=force_cpu,
)
self.jfj_client.api.config_azim_int_put(azim_int_settings=model, _request_timeout=5)
# pylint: disable=missing-function-docstring
@property
def beam_center(self) -> tuple[float, float]:
@@ -273,6 +319,9 @@ class Eiger(PSIDeviceBase):
logger.info(f"Device {self.name} on_stage called.")
start_time = time.time()
self.scan_parameters = fetch_scan_info(self.scan_info)
if self.jfj_client.detector_state != DetectorState.IDLE:
self.jfj_client.stop(request_timeout=0.5)
self.jfj_client.wait_for_idle(timeout=5)
# TODO Reactivate
try:
@@ -323,8 +372,8 @@ class Eiger(PSIDeviceBase):
image_time_us=int(exp_time * 1e6),
ntrigger=self._num_triggers,
file_prefix=path,
beam_x_pxl=int(self._beam_center[0]),
beam_y_pxl=int(self._beam_center[1]),
beam_x_pxl=float(self._beam_center[0]),
beam_y_pxl=float(self._beam_center[1]),
detector_distance_mm=self.detector_distance,
incident_energy_ke_v=self._incident_energy,
)
+5
View File
@@ -398,8 +398,13 @@ def test_eiger_on_stage(eiger_1_5m):
with (
mock.patch.object(eiger.jfj_client, "wait_for_idle", return_value=True),
mock.patch.object(eiger.jfj_client, "start") as mock_start,
mock.patch.object(eiger.jfj_client, "stop") as mock_stop,
mock.patch.object(
eiger.jfj_client.api, "status_get", return_value=BrokerStatus(state="Idle")
),
):
eiger.stage()
mock_stop.assert_not_called()
assert (
eiger._full_path
== f"{scan_msg.info['file_components'][0]}_{eiger.name}_master.{scan_msg.info['file_components'][1]}"