Working GFclient class
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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!!!
|
||||
|
||||
Reference in New Issue
Block a user