From 9476fde13ab427eba61bd7a5776e8b71aca92b0a Mon Sep 17 00:00:00 2001 From: e21206 Date: Thu, 17 Aug 2023 18:10:35 +0200 Subject: [PATCH 1/2] feat: add pilatus_2 ophyd class to repository --- ophyd_devices/epics/devices/pilatus_csaxs.py | 210 +++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 ophyd_devices/epics/devices/pilatus_csaxs.py diff --git a/ophyd_devices/epics/devices/pilatus_csaxs.py b/ophyd_devices/epics/devices/pilatus_csaxs.py new file mode 100644 index 0000000..5db685c --- /dev/null +++ b/ophyd_devices/epics/devices/pilatus_csaxs.py @@ -0,0 +1,210 @@ +import json +import os +import requests +import numpy as np + +from typing import List + +from ophyd.areadetector import ADComponent as ADCpt, PilatusDetectorCam, DetectorBase +from ophyd.areadetector.plugins import FileBase + +from bec_lib.core import BECMessage, MessageEndpoints +from bec_lib.core.file_utils import FileWriterMixin + + +class PilatusDetectorCamEx(PilatusDetectorCam, FileBase): + pass + + +class PilatusCsaxs(DetectorBase): + """ + + in device config, device_access needs to be set true to inject the device manager + """ + + _html_docs = ["PilatusDoc.html"] + cam = ADCpt(PilatusDetectorCamEx, "cam1:") + + def __init__( + self, + prefix="", + *, + name, + kind=None, + read_attrs=None, + configuration_attrs=None, + parent=None, + device_manager=None, + **kwargs, + ): + self.device_manager = device_manager + self.username = "e21206" # TODO get from config + # self.username = self.device_manager.producer.get(MessageEndpoints.account()).decode() + super().__init__( + prefix=prefix, + name=name, + kind=kind, + read_attrs=read_attrs, + configuration_attrs=configuration_attrs, + parent=parent, + **kwargs, + ) + # TODO how to get base_path + self.service_cfg = {"base_path": f"/sls/X12SA/data/{self.username}/Data10/pilatus_2"} + self.filewriter = FileWriterMixin(self.service_cfg["base_path"]) + self.num_frames = 0 + self.readout = 0.003 # 3 ms + self.triggermode = 0 # 0 : internal, scan must set this if hardware triggered + + def _get_current_scan_msg(self) -> BECMessage.ScanStatusMessage: + msg = self.device_manager.producer.get(MessageEndpoints.scan_status()) + return BECMessage.ScanStatusMessage.loads(msg) + + def stage(self) -> List[object]: + # TODO remove + # scan_msg = self._get_current_scan_msg() + # self.metadata = { + # "scanID": scan_msg.content["scanID"], + # "RID": scan_msg.content["info"]["RID"], + # "queueID": scan_msg.content["info"]["queueID"], + # } + scan_number = 10 # scan_msg.content["info"]["scan_number"] + self.exp_time = 0.5 # scan_msg.content["info"]["exp_time"] + self.num_frames = 1 # scan_msg.content["info"]["num_points"] + # self.username = self.device_manager.producer.get(MessageEndpoints.account()).decode() + + # set pilatus threshol + self._set_threshold() + + # set Epic PVs for filewriting + self.file_path.set(f"/dev/shm/zmq/") + self.file_name.set(f"{self.username}_2_") + self.auto_increment.set(1) # auto increment + self.file_number.set(0) # first iter + self.file_format.set(0) # 0: TIFF + self.file_template.set("%s%s_%5.5d.cbf") + + # compile zmq stream for data transfer + scan_dir = self.filewriter._get_scan_directory( + scan_bundle=1000, scan_number=scan_number, leading_zeros=5 + ) + self.destination_path = os.path.join( + self.service_cfg["base_path"] + ) # os.path.join(self.service_cfg["base_path"], scan_dir) + data_msg = { + "source": [ + { + "searchPath": "/", + "searchPattern": "glob:*.cbf", + "destinationPath": self.destination_path, + } + ] + } + headers = {"Content-Type": "application/json", "Accept": "application/json"} + + res = requests.put( + url="http://x12sa-pd-2:8080/stream/pilatus_2", + data=json.dumps(data_msg), + headers=headers, + ) + + if not res.ok: + res.raise_for_status() + + # prepare writer + data_msg = [ + "zmqWriter", + self.username, + { + "addr": "tcp://x12sa-pd=1:8888", + "dst": ["file"], + "numFrm": self.num_frames, + "timeout": 2000, + "ifType": "PULL", + "user": self.username, + }, + ] + + res = requests.put( + url="http://xbl-daq-34:8091/pilatus_1/run", + data=json.dumps(data_msg), + headers=headers, + ) + + if not res.ok: + res.raise_for_status() + + self._set_acquisition_params( + exp_time=self.exp_time, + readout=self.readout, + num_frames=self.num_frames, + triggermode=self.triggermode, + ) + + return super().stage() + + def unstage(self) -> List[object]: + headers = {"Content-Type": "application/json", "Accept": "application/json"} + data_msg = [ + "zmqWriter", + self.username, + { + "frmCnt": self.num_frames, + "timeout": 2000, + "ifType": "PULL", + "user": self.username, + }, + ] + + res = requests.put( + url="http://xbl-daq-34:8091/pilatus_1/run", + data=json.dumps(data_msg), + headers=headers, + ) + # Reset triggermode to internal + self.triggermode = 0 + + if not res.ok: + res.raise_for_status() + return super().unstage() + + def _set_threshold(self) -> None: + # TODO readout mono, monitor threshold and set it if mokev is different + # mokev = self.device_manager.devices.mokev.obj.read()["mokev"]["value"] + # TODO remove + mokev = 20 + pil_threshold = self.cam.threshold_energy.read() + if not np.isclose(mokev / 2, pil_threshold, rtol=0.05): + self.cam.threshold_energy.set(mokev / 2) + + def _set_acquisition_params( + self, exp_time: float, readout: float, num_frames: int, triggermode: int + ) -> None: + """set acquisition parameters on the detector + + Args: + exp_time (float): exposure time + readout (float): readout time + num_frames (int): images per scan + triggermode (int): + 0 Internal + 1 Ext. Enable + 2 Ext. Trigger + 3 Mult. Trigger + 4 Alignment + Returns: + None + """ + self.cam.acquire_time.set(exp_time) + self.cam.acquire_period.set(exp_time + readout) + self.cam.num_images.set(num_frames) + self.cam.num_exposures.set(1) + self.cam.trigger_mode.set(triggermode) + + def acquire(self) -> None: + self.cam.acquire.set(1) + + def stop(self, *, success=False) -> None: + self.cam.acquire.set(0) + super().stop(success=success) + self._stopped = True From 28b91eeda22af03c3709861ff7fb045fe5b2cf9b Mon Sep 17 00:00:00 2001 From: e21206 Date: Thu, 17 Aug 2023 19:06:37 +0200 Subject: [PATCH 2/2] fix: simple end-to-end test works at beamline --- ophyd_devices/epics/devices/pilatus_csaxs.py | 37 ++++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/ophyd_devices/epics/devices/pilatus_csaxs.py b/ophyd_devices/epics/devices/pilatus_csaxs.py index 5db685c..a9f639f 100644 --- a/ophyd_devices/epics/devices/pilatus_csaxs.py +++ b/ophyd_devices/epics/devices/pilatus_csaxs.py @@ -10,6 +10,9 @@ from ophyd.areadetector.plugins import FileBase from bec_lib.core import BECMessage, MessageEndpoints from bec_lib.core.file_utils import FileWriterMixin +from bec_lib.core import bec_logger + +logger = bec_logger.logger class PilatusDetectorCamEx(PilatusDetectorCam, FileBase): @@ -50,8 +53,8 @@ class PilatusCsaxs(DetectorBase): **kwargs, ) # TODO how to get base_path - self.service_cfg = {"base_path": f"/sls/X12SA/data/{self.username}/Data10/pilatus_2"} - self.filewriter = FileWriterMixin(self.service_cfg["base_path"]) + self.service_cfg = {"base_path": f"/sls/X12SA/data/{self.username}/Data10/pilatus_2/"} + self.filewriter = FileWriterMixin(self.service_cfg) self.num_frames = 0 self.readout = 0.003 # 3 ms self.triggermode = 0 # 0 : internal, scan must set this if hardware triggered @@ -68,25 +71,26 @@ class PilatusCsaxs(DetectorBase): # "RID": scan_msg.content["info"]["RID"], # "queueID": scan_msg.content["info"]["queueID"], # } - scan_number = 10 # scan_msg.content["info"]["scan_number"] + self.scan_number = 10 # scan_msg.content["info"]["scan_number"] self.exp_time = 0.5 # scan_msg.content["info"]["exp_time"] - self.num_frames = 1 # scan_msg.content["info"]["num_points"] + self.num_frames = 3 # scan_msg.content["info"]["num_points"] + # TODO remove # self.username = self.device_manager.producer.get(MessageEndpoints.account()).decode() # set pilatus threshol self._set_threshold() # set Epic PVs for filewriting - self.file_path.set(f"/dev/shm/zmq/") - self.file_name.set(f"{self.username}_2_") - self.auto_increment.set(1) # auto increment - self.file_number.set(0) # first iter - self.file_format.set(0) # 0: TIFF - self.file_template.set("%s%s_%5.5d.cbf") + self.cam.file_path.set(f"/dev/shm/zmq/") + self.cam.file_name.set(f"{self.username}_2_{self.scan_number:05d}") + self.cam.auto_increment.set(1) # auto increment + self.cam.file_number.set(0) # first iter + self.cam.file_format.set(0) # 0: TIFF + self.cam.file_template.set("%s%s_%5.5d.cbf") # compile zmq stream for data transfer scan_dir = self.filewriter._get_scan_directory( - scan_bundle=1000, scan_number=scan_number, leading_zeros=5 + scan_bundle=1000, scan_number=self.scan_number, leading_zeros=5 ) self.destination_path = os.path.join( self.service_cfg["base_path"] @@ -100,6 +104,7 @@ class PilatusCsaxs(DetectorBase): } ] } + logger.info(data_msg) headers = {"Content-Type": "application/json", "Accept": "application/json"} res = requests.put( @@ -116,7 +121,7 @@ class PilatusCsaxs(DetectorBase): "zmqWriter", self.username, { - "addr": "tcp://x12sa-pd=1:8888", + "addr": "tcp://x12sa-pd-2:8888", "dst": ["file"], "numFrm": self.num_frames, "timeout": 2000, @@ -126,7 +131,7 @@ class PilatusCsaxs(DetectorBase): ] res = requests.put( - url="http://xbl-daq-34:8091/pilatus_1/run", + url="http://xbl-daq-34:8091/pilatus_2/run", data=json.dumps(data_msg), headers=headers, ) @@ -155,6 +160,7 @@ class PilatusCsaxs(DetectorBase): "user": self.username, }, ] + logger.info(data_msg) res = requests.put( url="http://xbl-daq-34:8091/pilatus_1/run", @@ -172,8 +178,9 @@ class PilatusCsaxs(DetectorBase): # TODO readout mono, monitor threshold and set it if mokev is different # mokev = self.device_manager.devices.mokev.obj.read()["mokev"]["value"] # TODO remove - mokev = 20 - pil_threshold = self.cam.threshold_energy.read() + mokev = 16 + # TODO refactor naming from name, pilatus_2 + pil_threshold = self.cam.threshold_energy.read()["pilatus_2_cam_threshold_energy"]["value"] if not np.isclose(mokev / 2, pil_threshold, rtol=0.05): self.cam.threshold_energy.set(mokev / 2)