This commit is contained in:
gac-x05la
2025-03-17 18:27:47 +01:00
parent 6c0405ec7a
commit 9051e1a9ee
4 changed files with 69 additions and 81 deletions
@@ -126,6 +126,7 @@ gfcam:
prefix: 'X02DA-CAM-GF2:' prefix: 'X02DA-CAM-GF2:'
backend_url: 'http://sls-daq-001:8080' backend_url: 'http://sls-daq-001:8080'
auto_soft_enable: true auto_soft_enable: true
std_daq_live: 'tcp://129.129.95.111:20000'
deviceTags: deviceTags:
- camera - camera
- trigger - trigger
@@ -146,13 +147,13 @@ gfdaq:
deviceTags: deviceTags:
- std-daq - std-daq
- gfcam - gfcam
enabled: true enabled: false
onFailure: buffer onFailure: buffer
readOnly: false readOnly: false
readoutPriority: monitored readoutPriority: monitored
softwareTrigger: false softwareTrigger: false
daq_stream0: gf_stream0:
description: stdDAQ preview (2 every 555) description: stdDAQ preview (2 every 555)
deviceClass: tomcat_bec.devices.StdDaqPreviewDetector deviceClass: tomcat_bec.devices.StdDaqPreviewDetector
deviceConfig: deviceConfig:
@@ -160,56 +161,41 @@ daq_stream0:
deviceTags: deviceTags:
- std-daq - std-daq
- gfcam - gfcam
enabled: true enabled: false
onFailure: buffer onFailure: buffer
readOnly: false readOnly: false
readoutPriority: monitored readoutPriority: monitored
softwareTrigger: false softwareTrigger: false
daq_stream1: # pcocam:
description: stdDAQ preview (1 at 5 Hz) # description: PCO.edge camera client
deviceClass: tomcat_bec.devices.StdDaqPreviewDetector # deviceClass: tomcat_bec.devices.PcoEdge5M
deviceConfig: # deviceConfig:
url: 'tcp://129.129.95.111:20001' # prefix: 'X02DA-CCDCAM2:'
deviceTags: # deviceTags:
- std-daq # - camera
- gfcam # - trigger
enabled: true # - pcocam
onFailure: buffer # enabled: true
readOnly: false # onFailure: buffer
readoutPriority: monitored # readOnly: false
softwareTrigger: false # readoutPriority: monitored
# softwareTrigger: true
# pcodaq:
pcocam: # description: GigaFrost stdDAQ client
description: PCO.edge camera client # deviceClass: tomcat_bec.devices.StdDaqClient
deviceClass: tomcat_bec.devices.PcoEdge5M # deviceConfig:
deviceConfig: # ws_url: 'ws://129.129.95.111:8081'
prefix: 'X02DA-CCDCAM2:' # rest_url: 'http://129.129.95.111:5010'
deviceTags: # deviceTags:
- camera # - std-daq
- trigger # - pcocam
- pcocam # enabled: true
enabled: true # onFailure: buffer
onFailure: buffer # readOnly: false
readOnly: false # readoutPriority: monitored
readoutPriority: monitored # softwareTrigger: false
softwareTrigger: true
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
- pcocam
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: monitored
softwareTrigger: false
pco_stream0: pco_stream0:
description: stdDAQ preview (2 every 555) description: stdDAQ preview (2 every 555)
@@ -59,6 +59,7 @@ class GigaFrostBase(Device):
file_path = Cpt(Signal, kind=Kind.config, value="") file_path = Cpt(Signal, kind=Kind.config, value="")
file_prefix = Cpt(Signal, kind=Kind.config, value="") file_prefix = Cpt(Signal, kind=Kind.config, value="")
num_images = Cpt(Signal, kind=Kind.config, value=1) num_images = Cpt(Signal, kind=Kind.config, value=1)
num_images_counter = Cpt(Signal, kind=Kind.hinted, value=0)
# GF specific interface # GF specific interface
acquire_block = Cpt(Signal, kind=Kind.config, value=0) acquire_block = Cpt(Signal, kind=Kind.config, value=0)
@@ -484,8 +484,9 @@ class GigaFrostCamera(PSIDeviceBase, GigaFrostBase):
self.backend.shutdown() self.backend.shutdown()
super().destroy() super().destroy()
# def _on_preview_update(self, img:np.ndarray): def _on_preview_update(self, img:np.ndarray, header: dict):
# self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, obj=self, value=img) self.num_images_counter.put(header['frame'], force=True)
self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, obj=self, value=img)
# def acq_done(self) -> DeviceStatus: # def acq_done(self) -> DeviceStatus:
# """ # """
+33 -33
View File
@@ -13,13 +13,15 @@ ZMQ_TOPIC_FILTER = b""
class StdDaqPreview: class StdDaqPreview:
USER_ACCESS = ["start", "stop"] USER_ACCESS = ["start", "stop", "image"]
_socket = None
_zmq_thread = None
_shutdown_event = threading.Event()
_throttle = 0.2
image = None
def __init__(self, url: str, cb: Callable): def __init__(self, url: str, cb: Callable):
self.url = url self.url = url
self._socket = None
self._shutdown_event = threading.Event()
self._zmq_thread = None
self._on_update_callback = cb self._on_update_callback = cb
def connect(self): def connect(self):
@@ -40,6 +42,11 @@ class StdDaqPreview:
self._socket.connect(self.url) self._socket.connect(self.url)
def start(self): def start(self):
# Only one consumer thread
if self._zmq_thread:
self.stop()
self._shutdown_event.clear()
self._zmq_thread = threading.Thread( self._zmq_thread = threading.Thread(
target=self._zmq_update_loop, daemon=True, name="StdDaq_live_preview" target=self._zmq_update_loop, daemon=True, name="StdDaq_live_preview"
) )
@@ -49,13 +56,23 @@ class StdDaqPreview:
self._shutdown_event.set() self._shutdown_event.set()
if self._zmq_thread: if self._zmq_thread:
self._zmq_thread.join() self._zmq_thread.join()
self._zmq_thread = None
def _zmq_update_loop(self): def _zmq_update_loop(self):
if self._socket is None:
self.connect()
t_last = time.time()
while not self._shutdown_event.is_set(): while not self._shutdown_event.is_set():
if self._socket is None:
self.connect()
try: try:
self._poll() # pylint: disable=no-member
r = self._socket.recv_multipart(flags=zmq.NOBLOCK)
# Throttle parsing and callbacks
t_curr = time.time()
if t_curr - t_last > self._throttle:
self._parse_data(r)
t_last = t_curr
except ValueError: except ValueError:
# Happens when ZMQ partially delivers the multipart message # Happens when ZMQ partially delivers the multipart message
pass pass
@@ -63,33 +80,10 @@ class StdDaqPreview:
# Happens when receive queue is empty # Happens when receive queue is empty
time.sleep(0.1) time.sleep(0.1)
def _poll(self):
"""
Poll the ZMQ socket for new data. It will throttle the data update and
only subscribe to the topic for a single update. This is not very nice
but it seems like there is currently no option to set the update rate on
the backend.
"""
if self._shutdown_event.wait(0.2):
return
try:
# subscribe to the topic
self._socket.setsockopt(zmq.SUBSCRIBE, ZMQ_TOPIC_FILTER)
# pylint: disable=no-member
r = self._socket.recv_multipart(flags=zmq.NOBLOCK)
self._parse_data(r)
finally:
# Unsubscribe from the topic
self._socket.setsockopt(zmq.UNSUBSCRIBE, ZMQ_TOPIC_FILTER)
def _parse_data(self, data): def _parse_data(self, data):
# Length and throtling checks # Length and throtling checks
if len(data) != 2: if len(data) != 2:
logger.warning(f"Received malformed array of length {len(data)}") logger.warning(f"Received incomplete ZMQ message of length {len(data)}")
# Unpack the Array V1 reply to metadata and array data # Unpack the Array V1 reply to metadata and array data
meta, img_data = data meta, img_data = data
@@ -98,11 +92,17 @@ class StdDaqPreview:
header = json.loads(meta) header = json.loads(meta)
if header["type"] == "uint16": if header["type"] == "uint16":
image = np.frombuffer(img_data, dtype=np.uint16) image = np.frombuffer(img_data, dtype=np.uint16)
elif header["type"] == "uint8":
image = np.frombuffer(img_data, dtype=np.uint8)
else: else:
raise ValueError(f"Unexpected type {header['type']}") raise ValueError(f"Unexpected type {header['type']}")
if image.size != np.prod(header["shape"]): if image.size != np.prod(header["shape"]):
err = f"Unexpected array size of {image.size} for header: {header}" err = f"Unexpected array size of {image.size} for header: {header}"
raise ValueError(err) raise ValueError(err)
image = image.reshape(header["shape"]) image = image.reshape(header["shape"])
logger.info(f"Live update: frame {header['frame']}") # Print diadnostics and run callback
self._on_update_callback(image) logger.info(
f"Live update: frame {header['frame']}\tShape: {header['shape']}\t"
f"Mean: {np.mean(image):.3f}"
)
self._on_update_callback(image, header)