From a36c8237da866ec9b030bafa6c1ceef8df6286af Mon Sep 17 00:00:00 2001 From: gac-x05la Date: Mon, 10 Feb 2025 18:18:08 +0100 Subject: [PATCH] PCO Edge in regular beamline config --- .../device_configs/microxas_test_bed.yaml | 38 +++++++- tomcat_bec/devices/__init__.py | 2 + tomcat_bec/devices/gigafrost/pcoedgecamera.py | 97 ++++++++++++++----- 3 files changed, 110 insertions(+), 27 deletions(-) diff --git a/tomcat_bec/device_configs/microxas_test_bed.yaml b/tomcat_bec/device_configs/microxas_test_bed.yaml index d364070..8a98eee 100644 --- a/tomcat_bec/device_configs/microxas_test_bed.yaml +++ b/tomcat_bec/device_configs/microxas_test_bed.yaml @@ -176,15 +176,43 @@ daq_stream1: readoutPriority: monitored softwareTrigger: false -pco_stream0: - description: Raw camera stream from PCO.edge - deviceClass: tomcat_bec.devices.StdDaqPreviewDetector + +pcocam: + description: PCO.edge camera client + deviceClass: tomcat_bec.devices.PcoEdge5M deviceConfig: - url: 'tcp://129.129.106.124:8080' + prefix: 'X02DA-CCDCAM2:' + deviceTags: + - camera + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: monitored + softwareTrigger: false + +pcodaq: + description: GigaFrost stdDAQ client + deviceClass: tomcat_bec.devices.StdDaqClient + deviceConfig: + ws_url: 'ws://129.129.95.111:8081' + rest_url: 'http://129.129.95.111:5010' deviceTags: - std-daq enabled: true onFailure: buffer readOnly: false - readoutPriority: async + readoutPriority: monitored + softwareTrigger: false + +pco_stream0: + description: stdDAQ preview (2 every 555) + deviceClass: tomcat_bec.devices.StdDaqPreviewDetector + deviceConfig: + url: 'tcp://129.129.95.111:20010' + deviceTags: + - std-daq + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: monitored softwareTrigger: false diff --git a/tomcat_bec/devices/__init__.py b/tomcat_bec/devices/__init__.py index ead4971..a9d2c1d 100644 --- a/tomcat_bec/devices/__init__.py +++ b/tomcat_bec/devices/__init__.py @@ -11,5 +11,7 @@ from .grashopper_tomcat import GrashopperTOMCAT from .psimotor import EpicsMotorMR, EpicsMotorEC from .gigafrost.gigafrostcamera import GigaFrostCamera +from .gigafrost.pcoedgecamera import PcoEdge5M + from .gigafrost.stddaq_client import StdDaqClient from .gigafrost.stddaq_preview import StdDaqPreviewDetector diff --git a/tomcat_bec/devices/gigafrost/pcoedgecamera.py b/tomcat_bec/devices/gigafrost/pcoedgecamera.py index edbfa5e..ed095eb 100644 --- a/tomcat_bec/devices/gigafrost/pcoedgecamera.py +++ b/tomcat_bec/devices/gigafrost/pcoedgecamera.py @@ -28,7 +28,7 @@ class PcoEdgeCameraMixin(CustomDeviceMixin): """Configure and arm PCO.Edge camera for acquisition """ - # Gigafrost can finish a run without explicit unstaging + # PCO can finish a run without explicit unstaging if self.parent.state not in ("IDLE"): logger.warning(f"Trying to stage the camera from state {self.parent.state}, unstaging it first!") self.parent.unstage() @@ -36,25 +36,30 @@ class PcoEdgeCameraMixin(CustomDeviceMixin): # Fish out our configuration from scaninfo (via explicit or generic addressing) scanparam = self.parent.scaninfo.scan_msg.info - alias = self.parent.parent.name if self.parent.parent is not None else self.parent.name - # logger.warning(f"[{alias}] Scan parameters:\n{scanparam}") d = {} if 'kwargs' in scanparam: scanargs = scanparam['kwargs'] - if 'image_width' in scanargs and scanargs['image_width']!=None: + if 'num_images_total' in scanargs and scanargs['num_images_total'] is not None: + d['images_total'] = scanargs['num_images_total'] + if 'image_width' in scanargs and scanargs['image_width'] is not None: d['image_width'] = scanargs['image_width'] - if 'image_height' in scanargs and scanargs['image_height']!=None: + if 'image_height' in scanargs and scanargs['image_height'] is not None: d['image_height'] = scanargs['image_height'] - if 'exp_time' in scanargs and scanargs['exp_time']!=None: + if 'exp_time' in scanargs and scanargs['exp_time'] is not None: d['exposure_time_ms'] = scanargs['exp_time'] - if 'exp_period' in scanargs and scanargs['exp_period']!=None: + if 'exp_period' in scanargs and scanargs['exp_period'] is not None: d['exposure_period_ms'] = scanargs['exp_period'] - # if 'exp_burst' in scanargs and scanargs['exp_burst']!=None: + # if 'exp_burst' in scanargs and scanargs['exp_burst'] is not None: # d['exposure_num_burst'] = scanargs['exp_burst'] - # if 'acq_mode' in scanargs and scanargs['acq_mode']!=None: + # if 'acq_mode' in scanargs and scanargs['acq_mode'] is not None: # d['acq_mode'] = scanargs['acq_mode'] # elif self.parent.scaninfo.scan_type == "step": # d['acq_mode'] = "default" + if 'pco_store_mode' in scanargs and scanargs['pco_store_mode'] is not None: + d['store_mode'] = scanargs['pco_store_mode'] + if 'pco_data_format' in scanargs and scanargs['pco_data_format'] is not None: + d['data_format'] = scanargs['pco_data_format'] + # Perform bluesky-style configuration if len(d) > 0: @@ -136,6 +141,24 @@ class HelgeCameraBase(PSIDeviceBase): camError = Component(EpicsSignalRO, "ERRCODE", auto_monitor=True, kind=Kind.config) camWarning = Component(EpicsSignalRO, "WARNCODE", auto_monitor=True, kind=Kind.config) + # ######################################################################## + # Buffer configuration + bufferRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config) + bufferStoreMode = Component(EpicsSignal, "STOREMODE", auto_monitor=True, kind=Kind.config) + fileRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config) + + buffer_used = Component(EpicsSignalRO, "PIC_BUFFER", auto_monitor=True, kind=Kind.normal) + buffer_size = Component(EpicsSignalRO, "PIC_MAX", auto_monitor=True, kind=Kind.normal) + + # ######################################################################## + # File saving interface + cam_data_rate = Component(EpicsSignalRO, "CAMRATE", auto_monitor=True, kind=Kind.normal) + file_data_rate = Component(EpicsSignalRO, "FILERATE", auto_monitor=True, kind=Kind.normal) + file_savestart = Component(EpicsSignal, "SAVESTART", put_complete=True, kind=Kind.config) + file_savestop = Component(EpicsSignal, "SAVESTOP", put_complete=True, kind=Kind.config) + file_format = Component(EpicsSignal, "FILEFORMAT", put_complete=True, kind=Kind.config) + file_transfer = Component(EpicsSignal, "FTRANSFER", put_complete=True, kind=Kind.config) + # ######################################################################## # Configuration state maschine with separate transition states camStatusCode = Component(EpicsSignalRO, "STATUSCODE", auto_monitor=True, kind=Kind.config) @@ -176,19 +199,43 @@ class HelgeCameraBase(PSIDeviceBase): raise ReadOnlyError("State is a ReadOnly property") def configure(self, d: dict = {}) -> tuple: - """ Configure the base Helge camera device""" + """ Configure the base Helge camera device + + Parameters as 'd' dictionary + ---------------------------- + num_images : int + Number of images to be taken during each scan. Meaning depends on + store mode. + exposure_time_ms : float + Exposure time [ms], usually gets set back to 20 ms + exposure_period_ms : float + Exposure period [ms], up to 200 ms. + store_mode : str + Buffer operation mode + *'Recorder' to record in buffer + *'FIFO buffer' for continous streaming + data_format : str + Usually set to 'ZEROMQ' + """ if self.state not in ("IDLE"): raise RuntimeError(f"Can't change configuration from state {self.state}") # If Bluesky style configure if d is not None: # Commonly changed settings + if 'num_images' in d: + self.file_savestop.set(d['num_images']).wait() if 'exposure_time_ms' in d: self.acquire_time.set(d['exposure_time_ms']).wait() if 'exposure_period_ms' in d: - # acquire_time = d['exposure_time_ms'] if 'exposure_time_ms' in d else self.acquire_time.get() self.acquire_delay.set(d['exposure_period_ms']).wait() - + if 'exposure_period_ms' in d: + self.acquire_delay.set(d['exposure_period_ms']).wait() + if 'store_mode' in d: + self.bufferStoreMode.set(d['store_mode']).wait() + if 'data_format' in d: + self.file_format.set(d['data_format']).wait() + # State machine # Initial: BUSY and SET both low # 0. Write 1 to SET_PARAM @@ -231,17 +278,29 @@ class HelgeCameraBase(PSIDeviceBase): status = SubscriptionStatus(self.camStatusCode, isIdle, timeout=5, settle_time=0.2) status.wait() + # Data streaming is stopped by setting the max index to 0 + self.file_savestop.set(0).wait() -class PcoEdgeBase(HelgeCameraBase): + + def bluekickoff(self): + """ Start data transfer + + TODO: Need to revisit this once triggering is complete + """ + self.file_transfer.set(1).wait() + + + +class PcoEdge5M(HelgeCameraBase): """Ophyd baseclass for PCO.Edge cameras This class provides wrappers for Helge's camera IOCs around SwissFEL and for high performance SLS 2.0 cameras. Theese are mostly PCO cameras running on a special Windows IOC host with lots of RAM and CPU power. - """ + """ custom_prepare_cls = PcoEdgeCameraMixin - USER_ACCESS = ["bluestage", "blueunstage"] + USER_ACCESS = ["bluestage", "blueunstage", "bluekickoff"] # ######################################################################## # Additional status info @@ -266,12 +325,6 @@ class PcoEdgeBase(HelgeCameraBase): pxRoiY_lo = Component(EpicsSignal, "REGIONY_START", put_complete=True, auto_monitor=True, kind=Kind.config) pxRoiY_hi = Component(EpicsSignal, "REGIONY_END", put_complete=True, auto_monitor=True, kind=Kind.config) - # ######################################################################## - # Buffer configuration - bufferRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config) - bufferStoreMode = Component(EpicsSignalRO, "STOREMODE", auto_monitor=True, kind=Kind.config) - fileRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config) - def configure(self, d: dict = {}) -> tuple: """ Camera configuration instructions: @@ -302,7 +355,7 @@ class PcoEdgeBase(HelgeCameraBase): Binning along image height [pixels] acq_mode : str, not yet implemented Select one of the pre-configured trigger behavior - """ + """ if d is not None: # Need to be smart how we set the ROI.... # Image sensor is 2560x2160 (X and Y)