From a7bd4a220bd4622c9a3163fb08df2e5b0f4c6c5c Mon Sep 17 00:00:00 2001 From: x12sa Date: Wed, 15 Jul 2026 21:42:07 +0200 Subject: [PATCH 1/6] scanning SAXS, e23738 --- .../widgets/saxs_widget/saxs_widget.py | 1 + csaxs_bec/device_configs/bl_detectors.yaml | 32 ++++---- csaxs_bec/device_configs/main.yaml | 10 ++- csaxs_bec/device_configs/ssaxs.yaml | 75 +++++++++++++++++++ csaxs_bec/device_configs/xeye.yaml | 56 ++++++++++++++ 5 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 csaxs_bec/device_configs/ssaxs.yaml create mode 100644 csaxs_bec/device_configs/xeye.yaml diff --git a/csaxs_bec/bec_widgets/widgets/saxs_widget/saxs_widget.py b/csaxs_bec/bec_widgets/widgets/saxs_widget/saxs_widget.py index 64bd4dd..12aa947 100644 --- a/csaxs_bec/bec_widgets/widgets/saxs_widget/saxs_widget.py +++ b/csaxs_bec/bec_widgets/widgets/saxs_widget/saxs_widget.py @@ -229,6 +229,7 @@ class SAXSWidget(BECWidget, QWidget): "step_size", "readback", "device_box", + "setpoint", }: widget.hide() diff --git a/csaxs_bec/device_configs/bl_detectors.yaml b/csaxs_bec/device_configs/bl_detectors.yaml index a7cb3e2..0b5ca29 100644 --- a/csaxs_bec/device_configs/bl_detectors.yaml +++ b/csaxs_bec/device_configs/bl_detectors.yaml @@ -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: 2200 + beam_center: [870, 1203] + onFailure: raise + enabled: True + readoutPriority: async + softwareTrigger: False + # ids_cam: # description: IDS camera for live image acquisition # deviceClass: csaxs_bec.devices.ids_cameras.IDSCamera diff --git a/csaxs_bec/device_configs/main.yaml b/csaxs_bec/device_configs/main.yaml index 8f3fb43..4826172 100644 --- a/csaxs_bec/device_configs/main.yaml +++ b/csaxs_bec/device_configs/main.yaml @@ -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 diff --git a/csaxs_bec/device_configs/ssaxs.yaml b/csaxs_bec/device_configs/ssaxs.yaml new file mode 100644 index 0000000..3cce282 --- /dev/null +++ b/csaxs_bec/device_configs/ssaxs.yaml @@ -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) diff --git a/csaxs_bec/device_configs/xeye.yaml b/csaxs_bec/device_configs/xeye.yaml new file mode 100644 index 0000000..2539bdd --- /dev/null +++ b/csaxs_bec/device_configs/xeye.yaml @@ -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) -- 2.54.0 From 8f38053aeb2ee1478bffd9012b02d3a3dfd0cf1c Mon Sep 17 00:00:00 2001 From: x12sa Date: Fri, 17 Jul 2026 17:02:55 +0200 Subject: [PATCH 2/6] changes from the beamline --- .../widgets/saxs_widget/controls.py | 4 ++-- .../widgets/saxs_widget/saxs_widget.py | 20 +++++++++++++++++++ csaxs_bec/device_configs/bl_detectors.yaml | 4 ++-- csaxs_bec/devices/jungfraujoch/eiger.py | 13 +++++++++--- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/csaxs_bec/bec_widgets/widgets/saxs_widget/controls.py b/csaxs_bec/bec_widgets/widgets/saxs_widget/controls.py index 9e7d7d8..f64a815 100644 --- a/csaxs_bec/bec_widgets/widgets/saxs_widget/controls.py +++ b/csaxs_bec/bec_widgets/widgets/saxs_widget/controls.py @@ -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) diff --git a/csaxs_bec/bec_widgets/widgets/saxs_widget/saxs_widget.py b/csaxs_bec/bec_widgets/widgets/saxs_widget/saxs_widget.py index 12aa947..c84e6a9 100644 --- a/csaxs_bec/bec_widgets/widgets/saxs_widget/saxs_widget.py +++ b/csaxs_bec/bec_widgets/widgets/saxs_widget/saxs_widget.py @@ -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 # ------------------------------------------------------------------ @@ -284,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] diff --git a/csaxs_bec/device_configs/bl_detectors.yaml b/csaxs_bec/device_configs/bl_detectors.yaml index 0b5ca29..f49fc20 100644 --- a/csaxs_bec/device_configs/bl_detectors.yaml +++ b/csaxs_bec/device_configs/bl_detectors.yaml @@ -13,8 +13,8 @@ eiger_9: description: Eiger 9M detector deviceClass: csaxs_bec.devices.jungfraujoch.eiger_9m.Eiger9M deviceConfig: - detector_distance: 2200 - beam_center: [870, 1203] + detector_distance: 6975.225 + beam_center: [1149.71, 1450.89] onFailure: raise enabled: True readoutPriority: async diff --git a/csaxs_bec/devices/jungfraujoch/eiger.py b/csaxs_bec/devices/jungfraujoch/eiger.py index 042da1c..dc855cd 100644 --- a/csaxs_bec/devices/jungfraujoch/eiger.py +++ b/csaxs_bec/devices/jungfraujoch/eiger.py @@ -85,7 +85,7 @@ 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"] file_event = Cpt(FileEventSignal, name="file_event") preview_image = Cpt(PreviewSignal, name="preview_image", ndim=2) @@ -160,6 +160,10 @@ 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}") + # pylint: disable=missing-function-docstring @property def beam_center(self) -> tuple[float, float]: @@ -273,6 +277,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 +330,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, ) -- 2.54.0 From 07d54b82f82366283428b7023cf03ed08adc60da Mon Sep 17 00:00:00 2001 From: appel_c Date: Fri, 17 Jul 2026 17:12:54 +0200 Subject: [PATCH 3/6] add azimintsettings --- csaxs_bec/devices/jungfraujoch/eiger.py | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/csaxs_bec/devices/jungfraujoch/eiger.py b/csaxs_bec/devices/jungfraujoch/eiger.py index dc855cd..542c52a 100644 --- a/csaxs_bec/devices/jungfraujoch/eiger.py +++ b/csaxs_bec/devices/jungfraujoch/eiger.py @@ -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,12 @@ class Eiger(PSIDeviceBase): **kwargs: Additional keyword arguments. """ - USER_ACCESS = ["set_detector_distance", "set_beam_center", "print_detector_distance_and_center"] + USER_ACCESS = [ + "set_detector_distance", + "set_beam_center", + "print_detector_distance_and_center", + "set_azi_settings", + ] file_event = Cpt(FileEventSignal, name="file_event") preview_image = Cpt(PreviewSignal, name="preview_image", ndim=2) @@ -164,6 +170,36 @@ class Eiger(PSIDeviceBase): print(f"Detector Distance : {self.detector_distance}") print(f"Beam cetner position (x,y) : {self.beam_center}") + 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, + ) -> 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, + ) + 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]: -- 2.54.0 From c467a60ae9da78bd9abc7fcaedc5e74aa0916ef4 Mon Sep 17 00:00:00 2001 From: x12sa Date: Mon, 20 Jul 2026 13:14:50 +0200 Subject: [PATCH 4/6] fix(eiger): fix eiger check for IDLE in on_stage --- csaxs_bec/devices/jungfraujoch/eiger.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/csaxs_bec/devices/jungfraujoch/eiger.py b/csaxs_bec/devices/jungfraujoch/eiger.py index 542c52a..ca6329c 100644 --- a/csaxs_bec/devices/jungfraujoch/eiger.py +++ b/csaxs_bec/devices/jungfraujoch/eiger.py @@ -91,6 +91,7 @@ class Eiger(PSIDeviceBase): "set_beam_center", "print_detector_distance_and_center", "set_azi_settings", + "get_azi_settings", ] file_event = Cpt(FileEventSignal, name="file_event") @@ -170,6 +171,9 @@ class Eiger(PSIDeviceBase): 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, @@ -178,6 +182,7 @@ class Eiger(PSIDeviceBase): low_q_recip_a: float, q_spacing: float, azimuthal_bins: int = 16, + force_cpu:bool=True, ) -> None: """ Set the azimuthal integration settings. @@ -197,6 +202,7 @@ class Eiger(PSIDeviceBase): 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) -- 2.54.0 From 2bfd35c892ad1599760624c824acf00a94f54e9d Mon Sep 17 00:00:00 2001 From: appel_c Date: Mon, 20 Jul 2026 13:18:03 +0200 Subject: [PATCH 5/6] fix: fix formatting eiger --- csaxs_bec/devices/jungfraujoch/eiger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/csaxs_bec/devices/jungfraujoch/eiger.py b/csaxs_bec/devices/jungfraujoch/eiger.py index ca6329c..cdcf382 100644 --- a/csaxs_bec/devices/jungfraujoch/eiger.py +++ b/csaxs_bec/devices/jungfraujoch/eiger.py @@ -182,7 +182,7 @@ class Eiger(PSIDeviceBase): low_q_recip_a: float, q_spacing: float, azimuthal_bins: int = 16, - force_cpu:bool=True, + force_cpu: bool = True, ) -> None: """ Set the azimuthal integration settings. @@ -202,7 +202,7 @@ class Eiger(PSIDeviceBase): low_q_recip_a=low_q_recip_a, q_spacing=q_spacing, azimuthal_bins=azimuthal_bins, - force_cpu=force_cpu + force_cpu=force_cpu, ) self.jfj_client.api.config_azim_int_put(azim_int_settings=model, _request_timeout=5) -- 2.54.0 From b3488c7ffd8ecf1a6c1c4a16fc129cd438a6923c Mon Sep 17 00:00:00 2001 From: appel_c Date: Mon, 20 Jul 2026 13:44:57 +0200 Subject: [PATCH 6/6] test: fix eiger on_stage test --- tests/tests_devices/test_eiger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/tests_devices/test_eiger.py b/tests/tests_devices/test_eiger.py index 73ca78e..3e94b42 100644 --- a/tests/tests_devices/test_eiger.py +++ b/tests/tests_devices/test_eiger.py @@ -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]}" -- 2.54.0