This commit is contained in:
gac-x05la
2025-04-30 13:31:10 +02:00
parent 5d5eef11f6
commit f688cfca0f
3 changed files with 39 additions and 29 deletions

View File

@@ -9,16 +9,17 @@ import numpy as np
from ophyd.status import SubscriptionStatus, DeviceStatus
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from bec_lib.logger import bec_logger
from tomcat_bec.devices.gigafrost.pcoedge_base import PcoEdgeBase
from tomcat_bec.devices.gigafrost.std_daq_preview import StdDaqPreview
from tomcat_bec.devices.gigafrost.std_daq_client import StdDaqClient, StdDaqStatus
from bec_lib.logger import bec_logger
logger = bec_logger.logger
# pylint: disable=too-many-instance-attributes
class PcoEdge5M(PSIDeviceBase, PcoEdgeBase):
"""Ophyd baseclass for Helge camera IOCs
@@ -55,19 +56,13 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase):
destructive. to prevent this, we don't have EPICS preview
"""
# pylint: disable=too-many-instance-attributes
USER_ACCESS = [
"complete",
"backend",
"live_preview",
"arm",
"disarm",
]
USER_ACCESS = ["complete", "backend", "live_preview", "arm", "disarm"]
# Placeholders for stdDAQ and livestream clients
backend = None
live_preview = None
# pylint: disable=too-many-arguments
def __init__(
self,
prefix="",
@@ -106,7 +101,7 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase):
else:
logger.error("No stdDAQ stream address provided, launching without preview!")
def configure(self, d: dict = {}) -> tuple:
def configure(self, d: dict = None) -> tuple:
"""Configure the base Helge camera device
Parameters as 'd' dictionary
@@ -215,7 +210,7 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase):
self.acquire.set("Idle").wait()
# Data streaming is stopped by setting the max index to 0
# FIXME: This might interrupt data transfer
# FIXME: This will interrupt data transfer
self.file_savestop.set(0).wait()
def destroy(self):
@@ -226,7 +221,7 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase):
def _on_preview_update(self, img: np.ndarray, header: dict):
"""Send preview stream and update frame index counter"""
# FIXME: There's also a recorded images counter provided by the stdDAQ writer
# FIXME: There's also a recorded images counter provided by the stdDAQ writer
self.num_images_counter.put(header["frame"], force=True)
self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, obj=self, value=img)
@@ -280,16 +275,16 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase):
d["exposure_time_ms"] = scan_args["exp_time"]
if "exp_period" in scan_args and scan_args["exp_period"] is not None:
d["exposure_period_ms"] = scan_args["exp_period"]
if 'exp_burst' in scan_args and scan_args['exp_burst'] is not None:
d['exposure_num_burst'] = scan_args['exp_burst']
if "exp_burst" in scan_args and scan_args["exp_burst"] is not None:
d["exposure_num_burst"] = scan_args["exp_burst"]
if "acq_time" in scan_args and scan_args["acq_time"] is not None:
d["exposure_time_ms"] = scan_args["acq_time"]
if "acq_period" in scan_args and scan_args["acq_period"] is not None:
d["exposure_period_ms"] = scan_args["acq_period"]
if 'acq_burst' in scan_args and scan_args['acq_burst'] is not None:
d['exposure_num_burst'] = scan_args['acq_burst']
if 'acq_mode' in scan_args and scan_args['acq_mode'] is not None:
d['acq_mode'] = scan_args['acq_mode']
if "acq_burst" in scan_args and scan_args["acq_burst"] is not None:
d["exposure_num_burst"] = scan_args["acq_burst"]
if "acq_mode" in scan_args and scan_args["acq_mode"] is not None:
d["acq_mode"] = scan_args["acq_mode"]
# elif self.scaninfo.scan_type == "step":
# d['acq_mode'] = "default"
if "pco_store_mode" in scan_args and scan_args["pco_store_mode"] is not None:
@@ -361,6 +356,7 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase):
# Wait until the buffer fills up with enough images
t_expected = (self.acquire_time.get() + self.acquire_delay.get()) * self.file_savestop.get()
def wait_acquisition(*, value, timestamp, **_):
num_target = self.file_savestop.get()
# logger.warning(f"{value} of {num_target}")
@@ -381,7 +377,7 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase):
# against values from the previous cycle, i.e. pass automatically.
t_start = time.time()
def wait_sending(*, old_value, value, timestamp, **kwargs):
def wait_sending(*, old_value, value, timestamp, **_):
t_elapsed = timestamp - t_start
# logger.warning(f"{old_value}\t{value}\t{t_elapsed}")
return old_value == 1 and value == 0 and t_elapsed > 0

View File

@@ -46,7 +46,9 @@ class StdDaqStatus(str, enum.Enum):
WAITING_FOR_FIRST_IMAGE = "waiting_for_first_image"
# pylint: disable=too-many-instance-attributes
class StdDaqClient:
"""Standalone stdDAQ client class"""
USER_ACCESS = ["status", "count", "start", "stop", "get_config", "set_config", "reset"]
@@ -93,9 +95,10 @@ class StdDaqClient:
self, status: DeviceStatus, success: list[StdDaqStatus], error: list[StdDaqStatus]
):
"""
Add a DeviceStatus callback for the StdDAQ. The status will be updated when the StdDAQ status changes and
set to finished when the status matches one of the specified success statuses and to exception when the status
matches one of the specified error statuses.
Add a DeviceStatus callback for the StdDAQ. The status will be updated
when the StdDAQ status changes and set to finished when the status
matches one of the specified success statuses and to exception when the
status matches one of the specified error statuses.
Args:
status (DeviceStatus): DeviceStatus object
@@ -106,7 +109,12 @@ class StdDaqClient:
@typechecked
def start(
self, file_path: str, file_prefix: str, num_images: int, timeout: float = 20, wait=True
self,
file_path: str,
file_prefix: str,
num_images: int,
timeout: float = 20,
wait: bool = True,
) -> StatusBase | None:
"""Start acquisition on the StdDAQ.
@@ -122,8 +130,9 @@ class StdDaqClient:
self.wait_for_connection()
status = StatusBase()
# NOTE: CREATING_FILE --> IDLE is a known error, the exact cause is unknown, nut might be botched overwrite protection
# Changing file_prefix often solves the problem, but still allows overwrites
# NOTE: CREATING_FILE --> IDLE is a known error, the exact cause is unknown,
# Might be botched overwrite protection (solved by changing file_prefix)
# In previous versions there was also a mutex ownership problem
self.add_status_callback(
status, success=["waiting_for_first_image"], error=["rejected", "idle"]
)

View File

@@ -14,11 +14,11 @@ ZMQ_TOPIC_FILTER = b""
class StdDaqPreview:
"""Standalone stdDAQ preview class"""
USER_ACCESS = ["start", "stop", "image", "frameno"]
_socket = None
_zmq_thread = None
_monitor_mutex = threading.Lock()
_shutdown_event = threading.Event()
_throttle = 0.2
image = None
frameno = None
@@ -26,6 +26,9 @@ class StdDaqPreview:
def __init__(self, url: str, cb: Callable):
self.url = url
self._on_update_callback = cb
# Must be here otherwise they're static (shared between class instances)
self._monitor_mutex = threading.Lock()
self._shutdown_event = threading.Event()
def connect(self):
"""Connect to te StDAQs PUB-SUB streaming interface
@@ -45,6 +48,7 @@ class StdDaqPreview:
self._socket.connect(self.url)
def start(self):
"""Start the preview thread"""
# Only one consumer thread
if self._zmq_thread:
self.stop()
@@ -56,6 +60,7 @@ class StdDaqPreview:
self._zmq_thread.start()
def stop(self):
"""Stop the preview and disconnect from ZMQ stream"""
self._shutdown_event.set()
if self._zmq_thread:
self._zmq_thread.join()
@@ -124,5 +129,5 @@ class StdDaqPreview:
f"Mean: {np.mean(image):.3f}"
)
self.image = image
self.frameno = header['frame']
self.frameno = header["frame"]
self._on_update_callback(image, header)