Working GFclient class

This commit is contained in:
gac-x05la
2024-07-30 16:01:18 +02:00
committed by mohacsi_i
parent 6e98d5789c
commit 242598d252
5 changed files with 75 additions and 105 deletions

View File

@@ -6,6 +6,7 @@ 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.device import Staged
@@ -175,22 +176,17 @@ class GigaFrostCameraMixin(CustomDetectorMixin):
Specify actions to be executed upon receiving trigger signal.
Return a DeviceStatus object or None
"""
status = DeviceStatus(self.parent)
if self.parent.infoBusyFlag.get() in (0, 'IDLE'):
raise RuntimeError('GigaFrost must be running before triggering')
# Soft triggering based on operation mode
if self.parent.autoSoftEnable.get() and self.parent.trigger_mode=='auto' and self.parent.enable_mode=='soft':
# BEC teststand operation mode: posedge of SoftEnable if Started
self.parent.cmdSoftEnable.set(0).wait()
self.parent.cmdSoftEnable.set(1).wait()
sleep_time = self.parent.cfgFramerate.value*self.parent.cfgCntNum.value*0.001+0.050
# There's no status readback from the camera, so we just wait
sleep(sleep_time)
logger.info(f"[GF2] Slept for: {sleep_time} seconds")
else:
self.parent.cmdSoftTrigger.set(1).wait()
status.set_finished()
return status
class GigaFrostCamera(PSIDetectorBase):
@@ -411,6 +407,18 @@ class GigaFrostCamera(PSIDetectorBase):
self.state.put(const.GfStatus.NEW, force=True)
return super()._init()
def trigger(self) -> DeviceStatus:
super().trigger()
# There's no status readback from the camera, so we just wait
status = DeviceStatus(self)
sleep_time = self.cfgExposure.value*self.cfgCntNum.value*0.001+0.050
sleep(sleep_time)
logger.info(f"[GF2] Slept for: {sleep_time} seconds")
status.set_finished()
return status
def configure(self, d: dict=None):
"""Configure the next scan with the GigaFRoST camera

View File

@@ -32,11 +32,18 @@ except ModuleNotFoundError:
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):
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.
@@ -83,51 +90,10 @@ class GigafrostClientMixin(CustomDetectorMixin):
def on_init(self) -> None:
"""Initialize the camera, set channel values"""
## Stop acquisition
self.parent.cmdStartCamera.set(0).wait()
"""Initialize the camera, set channel values
### set entry to UDP table
# number of UDP ports to use
self.parent.cfgUdpNumPorts.set(2).wait()
# number of images to send to each UDP port before switching to next
self.parent.cfgUdpNumFrames.set(5).wait()
# offset in UDP table - where to find the first entry
self.parent.cfgUdpHtOffset.set(0).wait()
# activate changes
self.parent.cmdWriteService.set(1).wait()
# Configure software triggering if needed
if self.parent._auto_soft_enable:
# trigger modes
self.parent.cfgCntStartBit.set(1).wait()
self.parent.cfgCntEndBit.set(0).wait()
# set modes
self.parent.enable_mode = "soft"
self.parent.trigger_mode = "auto"
self.parent.exposure_mode = "timer"
# line swap - on for west, off for east
self.parent.cfgLineSwapSW.set(1).wait()
self.parent.cfgLineSwapNW.set(1).wait()
self.parent.cfgLineSwapSE.set(0).wait()
self.parent.cfgLineSwapNE.set(0).wait()
# Commit parameters
self.parent.cmdSetParam.set(1).wait()
# Initialize data backend
n, s = self._define_backend_ip()
self.parent.ipNorth.put(n, force=True)
self.parent.ipSouth.put(s, force=True)
n, s = self._define_backend_mac()
self.parent.macNorth.put(n, force=True)
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)
on_init is automatically called during __init__ of the sub devices.
"""
return super().on_init()
@@ -144,14 +110,13 @@ class GigafrostClientMixin(CustomDetectorMixin):
It must be safe to assume that the device is ready for the scan
to start immediately once this function is finished.
"""
if self.parent.infoBusyFlag.value:
raise RuntimeError("Camera is already busy, unstage it first!")
# Switch to acquiring
self.parent.cmdStartCamera.set(1).wait()
self.parent.state.put(const.GfStatus.ACQUIRING, force=True)
# Gigafrost can finish a run without explicit unstaging
self.parent._staged = Staged.no
#self.parent.daq.stage()
#self.parent.cam.stage()
def on_unstage(self) -> None:
"""
Specify actions to be executed during unstage.
@@ -160,11 +125,8 @@ class GigafrostClientMixin(CustomDetectorMixin):
and publishing the file location and file event message,
with flagged done to BEC.
"""
# Switch to idle
self.parent.cmdStartCamera.set(0).wait()
if self.parent.autoSoftEnable.get():
self.cmdSoftEnable.set(0).wait()
self.parent.state.put(const.GfStatus.STOPPED, force=True)
self.parent.cam.unstage()
self.parent.daq.unstage()
def on_stop(self) -> None:
"""
@@ -180,22 +142,7 @@ class GigafrostClientMixin(CustomDetectorMixin):
Specify actions to be executed upon receiving trigger signal.
Return a DeviceStatus object or None
"""
status = DeviceStatus(self.parent)
# Soft triggering based on operation mode
if self.parent.autoSoftEnable.get() and self.parent.trigger_mode=='auto' and self.parent.enable_mode=='soft':
# BEC teststand operation mode: posedge of SoftEnable if Started
self.parent.cmdSoftEnable.set(0).wait()
self.parent.cmdSoftEnable.set(1).wait()
sleep_time = self.parent.cfgFramerate.value*self.parent.cfgCntNum.value*0.001+0.050
# There's no status readback from the camera, so we just wait
sleep(sleep_time)
print(f"[GF2] Slept for: {sleep_time} seconds", file=sys.stderr)
else:
self.parent.cmdSoftTrigger.set(1).wait()
status.set_finished()
return status
return self.parent.cam.trigger()
class GigaFrostClient(PSIDetectorBase):
@@ -229,6 +176,8 @@ class GigaFrostClient(PSIDetectorBase):
FRAMERATE : Ignored in soft trigger mode, period becomes 2xexposure time
"""
# pylint: disable=too-many-instance-attributes
custom_prepare_cls = GigaFrostClientMixin
USER_ACCESS = [""]
cam = Component(GigaFrostCamera, prefix="X02DA-CAM-GF2:", name="cam")
daq = Component(StdDaqWsClient, name="daq")
@@ -279,10 +228,10 @@ class GigaFrostClient(PSIDetectorBase):
Exposure time [ms]. (default = 0.2)
period : float, optional
Exposure period [ms], ignored in soft trigger mode. (default = 1.0)
roix : int, optional
ROI size in the x-direction [pixels] (default = 2016)
roiy : int, optional
ROI size in the y-direction [pixels] (default = 2016)
pixel_width : int, optional
Image size in the x-direction [pixels] (default = 2016)
pixel_height : int, optional
Image size in the y-direction [pixels] (default = 2016)
scanid : int, optional
Scan identification number to be associated with the scan data
(default = 0)
@@ -297,6 +246,8 @@ class GigaFrostClient(PSIDetectorBase):
* 4: Invert pixel values, but do not apply any linearity correction
* 5: Apply the full linearity correction
"""
# Unstage camera (reconfiguration will anyway stop camera)
super().unstage()
# If Bluesky style configure
old = self.read_configuration()
self.cam.configure(d)
@@ -316,7 +267,9 @@ class GigaFrostClient(PSIDetectorBase):
return super().stage()
def trigger(self) -> DeviceStatus:
status = super().trigger()
return status
# Automatically connect to MicroSAXS testbench if directly invoked
if __name__ == "__main__":

View File

@@ -53,7 +53,7 @@ class StdDaqPreview(Device):
frame = Component(Signal, kind=Kind.normal)
image_shape = Component(Signal, kind=Kind.omitted)
value = Component(Signal, kind=Kind.hinted)
_throttle = 0.05
_throttle = 0.2
def __init__(
self, *args, url: str = "tcp://129.129.95.38:20000", parent: Device = None, **kwargs
@@ -89,7 +89,7 @@ class StdDaqPreview(Device):
sleep(1)
self._socket.connect(self.url.get())
def configure(self, throttle: float = 0.5) -> tuple:
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
@@ -98,7 +98,7 @@ class StdDaqPreview(Device):
Example:
----------
std.configure(throttle=0.05)
std.configure(throttle=0.2)
Parameters
----------
@@ -130,31 +130,31 @@ class StdDaqPreview(Device):
t_last = time()
try:
while True:
# Exit loop and finish monitoring
if self._stop_polling:
logger.info(f"[{self.name}]\tDetaching monitor")
break
try:
# pylint: disable=no-member
meta, data = self._socket.recv_multipart(flags=zmq.NOBLOCK)
header = json.loads(meta)
if header["type"]=="uint16":
image = np.frombuffer(data, dtype=np.uint16)
if image.size != np.prod(header['shape']):
raise ValueError(f"Unexpected array size of {image.size} for header: {header}")
image = image.reshape(header['shape'])
# Update image and update subscribers
t_curr = time()
t_elapsed = t_curr - t_last
if t_elapsed > self._throttle:
header = json.loads(meta)
if header["type"]=="uint16":
image = np.frombuffer(data, dtype=np.uint16)
if image.size != np.prod(header['shape']):
raise ValueError(f"Unexpected array size of {image.size} for header: {header}")
image = image.reshape(header['shape'])
# Update image and update subscribers
self.frame.put(header['frame'], force=True)
self.image_shape.put(header['shape'], force=True)
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}]\tUpdated frame {header['frame']}\tMean: {np.mean(image)}")
# Exit loop and finish monitoring
if self._stop_polling:
logger.info(f"[{self.name}]\tDetaching monitor")
break
logger.info(f"[{self.name}] Updated frame {header['frame']}\tShape: {header['shape']}\tMean: {np.mean(image):.3f}")
except ValueError:
# Happens when ZMQ partially delivers the multipart message
pass

View File

@@ -14,6 +14,15 @@ from ophyd import Device, Signal, Component, Kind
import requests
try:
from bec_lib import bec_logger
logger = bec_logger.logger
except ModuleNotFoundError:
import logging
logger = logging.getLogger("GfCam")
class StdDaqRestClient(Device):
"""Wrapper class around the new StdDaq REST interface.
@@ -137,7 +146,7 @@ class StdDaqRestClient(Device):
self.cfg_image_pixel_width.set(pixel_width).wait()
self.write_daq_config()
logger.info(f"[{self.name}] Reconfigured the StdDAQ")
new = self.read_configuration()
return old, new
@@ -159,7 +168,7 @@ class StdDaqRestClient(Device):
return super().unstage()
def stop(self):
def stop(self, success=False):
"""Stop op: Read the current configuration from the DAQ
"""
self.unstage()

View File

@@ -138,7 +138,7 @@ class StdDaqWsClient(Device):
self.status.put(reply["status"], force=True)
elif reply["status"] in ("rejected"):
raise RuntimeError(
f"Start command rejected (might be already running): {reply['reason']}"
f"Start StdDAQ command rejected (might be already running): {reply['reason']}"
)
self._mon = Thread(target=self.poll, daemon=True)
@@ -154,7 +154,7 @@ class StdDaqWsClient(Device):
_ = self.message(message, wait_reply=False)
return super().unstage()
def stop(self):
def stop(self, success=False):
""" Stop a running acquisition
WARN: This will also close the connection!!!