Code quality fixes

This commit is contained in:
gac-x05la
2024-07-30 17:39:53 +02:00
committed by mohacsi_i
parent 242598d252
commit 938e411b58
7 changed files with 105 additions and 200 deletions

View File

@@ -124,35 +124,8 @@ gfclient:
readoutPriority: monitored
softwareTrigger: true
daq:
description: Standard DAQ controls
deviceClass: tomcat_bec.devices.gigafrost.stddaq_ws.StdDaqWsClient
deviceConfig:
ws_url: 'ws://xbl-daq-29:8080'
rest_url: 'http://xbl-daq-29:5000'
deviceTags:
- std-daq
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: monitored
softwareTrigger: false
daqcfg:
description: Standard DAQ config
deviceClass: tomcat_bec.devices.gigafrost.stddaq_rest.StdDaqRestClient
deviceConfig:
rest_url: 'http://xbl-daq-29:5000'
deviceTags:
- std-daq
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: monitored
softwareTrigger: false
daq_stream0:
description: Standard DAQ preview stream 2 frames every 1000
description: Standard DAQ preview stream 2 frames every 1000 image
deviceClass: tomcat_bec.devices.gigafrost.stddaq_preview.StdDaqPreview
deviceConfig:
url: 'tcp://129.129.95.38:20000'

View File

@@ -12,7 +12,7 @@ from enum import IntEnum
gf_valid_enable_modes = ("soft", "external", "soft+ext", "always")
gf_valid_exposure_modes = ("external", "timer", "soft")
gf_valid_trigger_modes = ("auto", "external", "timer", "soft")
gf_valid_fix_nframe_modes = ("off", "start", "end", "start+end")
gf_valid_fix_nframe_modes = ("off", "start", "end", "start+end")
# STATUS

View File

@@ -6,9 +6,8 @@ Created on Thu Jun 27 17:28:43 2024
@author: mohacsi_i
"""
import sys
from time import sleep
from ophyd import Signal, SignalRO, Device, Component, EpicsSignal, EpicsSignalRO, Kind, DeviceStatus
from ophyd import Signal, Component, EpicsSignal, EpicsSignalRO, Kind, DeviceStatus
from ophyd.device import Staged
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
@@ -37,23 +36,24 @@ except ModuleNotFoundError:
class GigaFrostCameraMixin(CustomDetectorMixin):
"""Mixin class to setup TOMCAT specific implementations of the detector.
This class will be called by the custom_prepare_cls attribute of the detector class.
"""
This class will be called by the custom_prepare_cls attribute of the
detector class.
"""
def _define_backend_ip(self):
if self.parent.backendUrl.get() == const.BE3_DAFL_CLIENT: # xbl-daq-33
return const.BE3_NORTH_IP, const.BE3_SOUTH_IP
elif self.parent.backendUrl.get() == const.BE999_DAFL_CLIENT:
if self.parent.backendUrl.get() == const.BE999_DAFL_CLIENT:
return const.BE999_NORTH_IP, const.BE999_SOUTH_IP
else:
raise RuntimeError(f"Backend {self.parent.backendUrl.get()} not recognized. {(const.GF1, const.GF2, const.GF3)}")
raise RuntimeError(f"Backend {self.parent.backendUrl.get()} not recognized.")
def _define_backend_mac(self):
if self.parent.backendUrl.get() == const.BE3_DAFL_CLIENT: # xbl-daq-33
return const.BE3_NORTH_MAC, const.BE3_SOUTH_MAC
elif self.parent.backendUrl.get() == const.BE999_DAFL_CLIENT:
if self.parent.backendUrl.get() == const.BE999_DAFL_CLIENT:
return const.BE999_NORTH_MAC, const.BE999_SOUTH_MAC
else:
raise RuntimeError(f"Backend {self.parent.backendUrl.get()} not recognized. {(const.GF1, const.GF2, const.GF3)}")
raise RuntimeError(f"Backend {self.parent.backendUrl.get()} not recognized.")
def _set_udp_header_table(self):
"""Set the communication parameters for the camera module"""
@@ -123,10 +123,10 @@ class GigaFrostCameraMixin(CustomDetectorMixin):
self.parent.macSouth.put(s, force=True)
# Set udp header table
self._set_udp_header_table()
self.parent.state.put(const.GfStatus.INIT, force=True)
return super().on_init()
def on_stage(self) -> None:
"""Specify actions to be executed during stage
@@ -372,7 +372,6 @@ class GigaFrostCamera(PSIDetectorBase):
*,
name,
auto_soft_enable=False,
timeout=10,
backend_url=const.BE999_DAFL_CLIENT,
kind=None,
read_attrs=None,
@@ -450,6 +449,9 @@ class GigaFrostCamera(PSIDetectorBase):
* 4: Invert pixel values, but do not apply any linearity correction
* 5: Apply the full linearity correction
"""
# Stop acquisition
self.unstage()
# If Bluesky style configure
if d is not None:
nimages = d.get('nimages', 10)
@@ -464,20 +466,14 @@ class GigaFrostCamera(PSIDetectorBase):
scanid = d.get('scanid', 0)
correction_mode = d.get('correction_mode', 5)
# Stop acquisition
self.unstage()
self.cmdStartCamera.set(0).wait()
if self.autoSoftEnable.get():
self.cmdSoftEnable.set(0).wait()
# change settings
self.cfgExposure.set(exposure).wait()
self.cfgFramerate.set(period).wait()
self.cfgRoiX.set(pixel_width).wait()
self.cfgRoiY.set(pixel_height).wait()
self.cfgScanId.set(scanid).wait()
self.cfgCntNum.set(nimages).wait()
self.cfgCorrMode.set(correction_mode).wait()
# change settings
self.cfgExposure.set(exposure).wait()
self.cfgFramerate.set(period).wait()
self.cfgRoiX.set(pixel_width).wait()
self.cfgRoiY.set(pixel_height).wait()
self.cfgScanId.set(scanid).wait()
self.cfgCntNum.set(nimages).wait()
self.cfgCorrMode.set(correction_mode).wait()
# Commit parameter
self.cmdSetParam.set(1).wait()
@@ -498,12 +494,12 @@ class GigaFrostCamera(PSIDetectorBase):
mode_external = self.cfgTrigExpExt.get()
if mode_soft and not mode_timer and not mode_external:
return "soft"
elif not mode_soft and mode_timer and not mode_external:
if not mode_soft and mode_timer and not mode_external:
return "timer"
elif not mode_soft and not mode_timer and mode_external:
if not mode_soft and not mode_timer and mode_external:
return "external"
else:
return None
return None
@exposure_mode.setter
def exposure_mode(self, exp_mode):
@@ -514,11 +510,6 @@ class GigaFrostCamera(PSIDetectorBase):
exp_mode : {'external', 'timer', 'soft'}
The exposure mode to be set.
"""
if exp_mode not in const.gf_valid_exposure_modes:
raise ValueError(
f"Invalid exposure mode! Valid modes are:\n" "{const.gf_valid_exposure_modes}"
)
if exp_mode == "external":
self.cfgTrigExpExt.set(1).wait()
self.cfgTrigExpSoft.set(0).wait()
@@ -531,6 +522,11 @@ class GigaFrostCamera(PSIDetectorBase):
self.cfgTrigExpExt.set(0).wait()
self.cfgTrigExpSoft.set(1).wait()
self.cfgTrigExpTimer.set(0).wait()
else:
raise ValueError(
f"Invalid exposure mode! Valid modes are:\n{const.gf_valid_exposure_modes}"
)
# Commit parameters
self.cmdSetParam.set(1).wait()
@@ -548,14 +544,14 @@ class GigaFrostCamera(PSIDetectorBase):
if not start_bit and not end_bit:
return "off"
elif start_bit and not end_bit:
if start_bit and not end_bit:
return "start"
elif not start_bit and end_bit:
if not start_bit and end_bit:
return "end"
elif start_bit and end_bit:
if start_bit and end_bit:
return "start+end"
else:
return None
return None
@fix_nframes_mode.setter
def fix_nframes_mode(self, mode):
@@ -566,11 +562,6 @@ class GigaFrostCamera(PSIDetectorBase):
mode : {'off', 'start', 'end', 'start+end'}
The fixed number of frames mode to be applied.
"""
if mode not in const.gf_valid_fix_nframe_modes:
raise ValueError(
f"Invalid fixed number of frames mode! Valid modes are:\n{const.gf_valid_fix_nframe_modes}"
)
self._fix_nframes_mode = mode
if self._fix_nframes_mode == "off":
self.cfgCntStartBit.set(0).wait()
@@ -584,6 +575,11 @@ class GigaFrostCamera(PSIDetectorBase):
elif self._fix_nframes_mode == "start+end":
self.cfgCntStartBit.set(1).wait()
self.cfgCntEndBit.set(1).wait()
else:
raise ValueError(
f"Invalid fixed number of frames mode! Valid modes are:\n{const.gf_valid_fix_nframe_modes}"
)
# Commit parameters
self.cmdSetParam.set(1).wait()
@@ -603,14 +599,14 @@ class GigaFrostCamera(PSIDetectorBase):
mode_soft = self.cfgTrigSoft.get()
if mode_auto:
return "auto"
elif mode_soft:
if mode_soft:
return "soft"
elif mode_timer:
if mode_timer:
return "timer"
elif mode_external:
if mode_external:
return "external"
else:
return None
return None
@trigger_mode.setter
def trigger_mode(self, mode):
@@ -621,11 +617,6 @@ class GigaFrostCamera(PSIDetectorBase):
mode : {'auto', 'external', 'timer', 'soft'}
The GigaFRoST trigger mode.
"""
if mode not in const.gf_valid_trigger_modes:
raise ValueError(
"Invalid trigger mode! Valid modes are:\n" "{const.gf_valid_trigger_modes}"
)
if mode == "auto":
self.cfgTrigAuto.set(1).wait()
self.cfgTrigSoft.set(0).wait()
@@ -646,6 +637,11 @@ class GigaFrostCamera(PSIDetectorBase):
self.cfgTrigSoft.set(1).wait()
self.cfgTrigTimer.set(0).wait()
self.cfgTrigExt.set(0).wait()
else:
raise ValueError(
"Invalid trigger mode! Valid modes are:\n{const.gf_valid_trigger_modes}"
)
# Commit parameters
self.cmdSetParam.set(1).wait()
@@ -670,8 +666,8 @@ class GigaFrostCamera(PSIDetectorBase):
return "always"
elif mode_external and not mode_soft and not mode_auto:
return "external"
else:
return None
return None
@enable_mode.setter
def enable_mode(self, mode):
@@ -684,7 +680,7 @@ class GigaFrostCamera(PSIDetectorBase):
"""
if mode not in const.gf_valid_enable_modes:
raise ValueError(
"Invalid enable mode! Valid modes are:\n" "{const.gf_valid_enable_modes}"
"Invalid enable mode! Valid modes are:\n{const.gf_valid_enable_modes}"
)
if mode == "soft":

View File

@@ -1,13 +1,12 @@
# -*- coding: utf-8 -*-
"""
GigaFrost camera class module
GigaFrost client module that combines camera and DAQ
Created on Thu Jun 27 17:28:43 2024
@author: mohacsi_i
"""
from time import sleep
from ophyd import Device, Component, EpicsSignal, EpicsSignalRO, Kind, DeviceStatus
from ophyd import Component, DeviceStatus
from ophyd.device import Staged
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
@@ -17,87 +16,26 @@ from ophyd_devices.interfaces.base_classes.psi_detector_base import (
try:
import gfconstants as const
except ModuleNotFoundError:
import tomcat_bec.devices.gigafrost.gfconstants as const
try:
from StdDaqClient import StdDaqClient
except ModuleNotFoundError:
from tomcat_bec.devices.gigafrost.stddaq_ws import StdDaqWsClient
try:
from gigafrostcamera import GigaFrostCamera
except ModuleNotFoundError:
import tomcat_bec.devices.gigafrost.gfconstants as const
from tomcat_bec.devices.gigafrost.stddaq_ws import StdDaqClient
from tomcat_bec.devices.gigafrost.gigafrostcamera import GigaFrostCamera
try:
from bec_lib import bec_logger
logger = bec_logger.logger
except ModuleNotFoundError:
import logging
logger = logging.getLogger("GfCam")
class GigaFrostClientMixin(CustomDetectorMixin):
"""Mixin class to setup TOMCAT specific implementations of the detector.
This class will be called by the custom_prepare_cls attribute of the detector class.
"""
def _define_backend_ip(self):
if self.parent.backendUrl.get() == const.BE3_DAFL_CLIENT: # xbl-daq-33
return const.BE3_NORTH_IP, const.BE3_SOUTH_IP
elif self.parent.backendUrl.get() == const.BE999_DAFL_CLIENT:
return const.BE999_NORTH_IP, const.BE999_SOUTH_IP
else:
raise RuntimeError(f"Backend not recognized. {(const.GF1, const.GF2, const.GF3)}")
def _define_backend_mac(self):
if self.parent.backendUrl.get() == const.BE3_DAFL_CLIENT: # xbl-daq-33
return const.BE3_NORTH_MAC, const.BE3_SOUTH_MAC
elif self.parent.backendUrl.get() == const.BE999_DAFL_CLIENT:
return const.BE999_NORTH_MAC, const.BE999_SOUTH_MAC
else:
raise RuntimeError(f"Backend not recognized. {(const.GF1, const.GF2, const.GF3)}")
def _set_udp_header_table(self):
"""Set the communication parameters for the camera module"""
self.parent.cfgConnectionParam.set(self._build_udp_header_table()).wait()
def _build_udp_header_table(self):
"""Build the header table for the communication"""
udp_header_table = []
for i in range(0, 64, 1):
for j in range(0, 8, 1):
dest_port = 2000 + 8 * i + j
source_port = 3000 + j
if j < 4:
extend_header_table(
udp_header_table, self.parent.macSouth, self.parent.ipSouth, dest_port, source_port
)
else:
extend_header_table(
udp_header_table, self.parent.macNorth, self.parent.ipNorth, dest_port, source_port
)
return udp_header_table
def on_init(self) -> None:
"""Initialize the camera, set channel values
on_init is automatically called during __init__ of the sub devices.
"""
return super().on_init()
def on_stage(self) -> None:
"""
Specify actions to be executed during stage in preparation for a scan.
@@ -180,7 +118,7 @@ class GigaFrostClient(PSIDetectorBase):
USER_ACCESS = [""]
cam = Component(GigaFrostCamera, prefix="X02DA-CAM-GF2:", name="cam")
daq = Component(StdDaqWsClient, name="daq")
daq = Component(StdDaqClient, name="daq")
def __init__(
self,
@@ -215,11 +153,15 @@ class GigaFrostClient(PSIDetectorBase):
def configure(self, d: dict=None, **kwargs):
"""Configure the next scan with the GigaFRoST camera
def configure(self, d: dict=None):
"""Configure the next scan with the GigaFRoST camera and standard DAQ backend
Parameters
----------
ntotal : int, optional
Total mumber of images to be taken during the whole scan. Set to -1
for an unlimited number of images (limited by the ringbuffer size and
backend speed). (default = 10000)
nimages : int, optional
Number of images to be taken during each scan. Set to -1 for an
unlimited number of images (limited by the ringbuffer size and
@@ -263,10 +205,10 @@ class GigaFrostClient(PSIDetectorBase):
px_gf_h = self.cam.cfgRoiY.get()
if px_daq_h != px_gf_h or px_daq_w != px_gf_w:
raise RuntimeError(f"Different image size configured on GF and the DAQ")
raise RuntimeError("Different image size configured on GF and the DAQ")
return super().stage()
def trigger(self) -> DeviceStatus:
status = super().trigger()
return status

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
Standard DAQ class module
Standard DAQ preview image stream module
Created on Thu Jun 27 17:28:43 2024
@@ -8,10 +8,10 @@ Created on Thu Jun 27 17:28:43 2024
"""
import json
import enum
import zmq
import numpy as np
from time import sleep, time
from threading import Thread
import zmq
import numpy as np
from ophyd import Device, Signal, Component, Kind
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
CustomDetectorMixin,
@@ -89,24 +89,6 @@ class StdDaqPreview(Device):
sleep(1)
self._socket.connect(self.url.get())
def configure(self, throttle: float = 0.2) -> tuple:
"""Set the DAQ preview parameters
Note that there's not much to do except for additional throtling if the
preview data stream is too fast. Perhaps later we can add some online
processing to ophyd.
Example:
----------
std.configure(throttle=0.2)
Parameters
----------
throttle : float, optional
Additional throtling for the ophyd device. (default = 0.05 sec)
"""
self._throttle = throttle
def stage(self) -> list:
"""Start listening for preview data stream"""
self.connect()
@@ -120,7 +102,7 @@ class StdDaqPreview(Device):
self._stop_polling = True
return super().unstage()
def stop(self):
def stop(self, success=False):
"""Stop a running preview"""
self.unstage()
@@ -154,7 +136,10 @@ class StdDaqPreview(Device):
self.image.put(image, force=True)
self._run_subs(sub_type=self.SUB_MONITOR, value=image)
t_last=t_curr
logger.info(f"[{self.name}] Updated frame {header['frame']}\tShape: {header['shape']}\tMean: {np.mean(image):.3f}")
logger.info(
f"[{self.name}] Updated frame {header['frame']}\t"
f"Shape: {header['shape']}\tMean: {np.mean(image):.3f}"
)
except ValueError:
# Happens when ZMQ partially delivers the multipart message
pass

View File

@@ -1,15 +1,11 @@
# -*- coding: utf-8 -*-
"""
Standard DAQ class module
Standard DAQ configuration interface module through the latrest REST API
Created on Thu Jun 27 17:28:43 2024
@author: mohacsi_i
"""
import sys
import json
from time import sleep
from threading import Thread
from ophyd import Device, Signal, Component, Kind
import requests
@@ -62,7 +58,11 @@ class StdDaqRestClient(Device):
def read_daq_config(self):
"""Read the current configuration from the JSON file
"""
r = requests.get(self._url_base + '/api/config/get', params={'config_file': "/etc/std_daq/configs/gf1.json", 'user':"ioc"})
r = requests.get(
self._url_base + '/api/config/get',
params={'config_file': "/etc/std_daq/configs/gf1.json", 'user':"ioc"},
timeout=2
)
if r.status_code != 200:
raise ConnectionError(f"[{self.name}] Error {r.status_code}:\t{r.text}")
@@ -111,15 +111,20 @@ class StdDaqRestClient(Device):
raise RuntimeError("Pleae read config before editing")
config = self._build_config()
params = {"user": "ioc", "config_file": "/etc/std_daq/configs/gf1.json"}
r = requests.post(self._url_base +'/api/config/set', params=params, json=config, headers={"Content-Type": "application/json"})
r = requests.post(
self._url_base +'/api/config/set',
params=params,
json=config,
timeout=2,
headers={"Content-Type": "application/json"}
)
if r.status_code != 200:
raise ConnectionError(f"[{self.name}] Error {r.status_code}:\t{r.text}")
return r
def configure(self, d: dict=None):
"""Configure the next scan with the GigaFRoST camera
@@ -134,7 +139,7 @@ class StdDaqRestClient(Device):
# If Bluesky style configure
if d is not None:
# Only reconfigure if we're instructed
# Only reconfigure if we're instructed
if ('pixel_width' in d) or ('pixel_height' in d) or ('image_width' in d) or ('image_height' in d):
pixel_width = d.get('pixel_width', 2016)
pixel_height = d.get('pixel_height', 2016)

View File

@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
"""
Standard DAQ class module
Standard DAQ control interface module through the websocket API
Created on Thu Jun 27 17:28:43 2024
@author: mohacsi_i
"""
import sys
import json
from time import sleep
from threading import Thread
@@ -50,7 +49,12 @@ class StdDaqWsClient(Device):
cfg = Component(StdDaqRestClient, kind=Kind.config)
def __init__(
self, *args, ws_url: str = "ws://localhost:8080", rest_url="http://localhost:5000", parent: Device = None, **kwargs
self,
*args,
ws_url: str = "ws://localhost:8080",
rest_url="http://localhost:5000",
parent: Device = None,
**kwargs
) -> None:
self.__class__.__dict__['cfg'].kwargs['rest_url'] = rest_url
@@ -60,6 +64,7 @@ class StdDaqWsClient(Device):
self._mon = None
# Connect ro the DAQ
self._client = None
self.connect()
def connect(self):
@@ -84,7 +89,7 @@ class StdDaqWsClient(Device):
self._mon = Thread(target=self.poll, daemon=True)
self._mon.start()
def configure(self, d: dict={}) -> tuple:
def configure(self, d: dict=None) -> tuple:
"""Set the standard DAQ parameters for the next run
Note that full reconfiguration is not possible with the websocket
@@ -197,7 +202,7 @@ class StdDaqWsClient(Device):
try:
message = json.loads(msg)
self.status.put(message["status"], force=True)
except (ConnectionClosedError, ConnectionClosedOK) as ex:
except (ConnectionClosedError, ConnectionClosedOK):
return
except Exception as ex:
print(ex)
@@ -208,7 +213,6 @@ class StdDaqWsClient(Device):
class StdDaqClient(StdDaqWsClient):
"""Just an alias"""
pass
# Automatically connect to MicroSAXS testbench if directly invoked