mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2025-06-27 21:21:08 +02:00
More Helgecams
This commit is contained in:
@ -15,7 +15,7 @@ import numpy as np
|
|||||||
|
|
||||||
|
|
||||||
class HelgeCameraBase(Device):
|
class HelgeCameraBase(Device):
|
||||||
""" Ophyd baseclass for Helge camera IOCs
|
"""Ophyd baseclass for Helge camera IOCs
|
||||||
|
|
||||||
This class provides wrappers for Helge's camera IOCs around SwissFEL and
|
This class provides wrappers for Helge's camera IOCs around SwissFEL and
|
||||||
for high performance SLS 2.0 cameras.
|
for high performance SLS 2.0 cameras.
|
||||||
@ -30,6 +30,14 @@ class HelgeCameraBase(Device):
|
|||||||
camType = Component(EpicsSignalRO, "QUERY", kind=Kind.omitted)
|
camType = Component(EpicsSignalRO, "QUERY", kind=Kind.omitted)
|
||||||
camBoard = Component(EpicsSignalRO, "BOARD", kind=Kind.config)
|
camBoard = Component(EpicsSignalRO, "BOARD", kind=Kind.config)
|
||||||
#camSerial = Component(EpicsSignalRO, "SERIALNR", kind=Kind.config)
|
#camSerial = Component(EpicsSignalRO, "SERIALNR", kind=Kind.config)
|
||||||
|
|
||||||
|
# ########################################################################
|
||||||
|
# Acquisition commands
|
||||||
|
busy = Component(EpicsSignalRO, "BUSY", auto_monitor=True, kind=Kind.config)
|
||||||
|
camState = Component(EpicsSignalRO, "SS_CAMERA", auto_monitor=True, kind=Kind.config)
|
||||||
|
camStatusCmd = Component(EpicsSignal, "CAMERASTATUS", put_complete=True, kind=Kind.config)
|
||||||
|
camProgress = Component(EpicsSignalRO, "CAMPROGRESS", auto_monitor=True, kind=Kind.config)
|
||||||
|
camRate = Component(EpicsSignalRO, "CAMRATE", auto_monitor=True, kind=Kind.config)
|
||||||
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
# Image size settings
|
# Image size settings
|
||||||
@ -43,28 +51,21 @@ class HelgeCameraBase(Device):
|
|||||||
pxNumX = Component(EpicsSignalRO, "WIDTH", auto_monitor=True, kind=Kind.config)
|
pxNumX = Component(EpicsSignalRO, "WIDTH", auto_monitor=True, kind=Kind.config)
|
||||||
pxNumY = Component(EpicsSignalRO, "HEIGHT", auto_monitor=True, kind=Kind.config)
|
pxNumY = Component(EpicsSignalRO, "HEIGHT", auto_monitor=True, kind=Kind.config)
|
||||||
|
|
||||||
# ########################################################################
|
|
||||||
# Acquisition commands
|
|
||||||
|
|
||||||
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
# Polled CamStatus
|
# Polled CamStatus
|
||||||
busy = Component(EpicsSignalRO, "BUSY", auto_monitor=True, kind=Kind.config)
|
|
||||||
camState = Component(EpicsSignalRO, "SS_CAMERA", auto_monitor=True, kind=Kind.config)
|
|
||||||
|
|
||||||
camError = Component(EpicsSignalRO, "ERRCODE", auto_monitor=True, kind=Kind.config)
|
camError = Component(EpicsSignalRO, "ERRCODE", auto_monitor=True, kind=Kind.config)
|
||||||
camWarning = Component(EpicsSignalRO, "WARNCODE", auto_monitor=True, kind=Kind.config)
|
camWarning = Component(EpicsSignalRO, "WARNCODE", auto_monitor=True, kind=Kind.config)
|
||||||
camProgress = Component(EpicsSignalRO, "CAMPROGRESS", auto_monitor=True, kind=Kind.config)
|
|
||||||
camRate = Component(EpicsSignalRO, "CAMRATE", auto_monitor=True, kind=Kind.config)
|
|
||||||
|
|
||||||
# Weird state maschine with separate transition states
|
# Weird state maschine with separate transition states
|
||||||
camStatusCmd = Component(EpicsSignal, "CAMERASTATUS", put_complete=True, kind=Kind.config)
|
camStatusCode = Component(EpicsSignalRO, "STATUSCODE", auto_monitor=True, kind=Kind.config)
|
||||||
|
camRemoved = Component(EpicsSignalRO, "REMOVAL", auto_monitor=True, kind=Kind.config)
|
||||||
|
|
||||||
camSetParam = Component(EpicsSignalRO, "SET_PARAM", auto_monitor=True, kind=Kind.config)
|
camSetParam = Component(EpicsSignalRO, "SET_PARAM", auto_monitor=True, kind=Kind.config)
|
||||||
camSetParamBusy = Component(EpicsSignalRO, "BUSY_SET_PARAM", auto_monitor=True, kind=Kind.config)
|
camSetParamBusy = Component(EpicsSignalRO, "BUSY_SET_PARAM", auto_monitor=True, kind=Kind.config)
|
||||||
camCamera = Component(EpicsSignalRO, "CAMERA", auto_monitor=True, kind=Kind.config)
|
camCamera = Component(EpicsSignalRO, "CAMERA", auto_monitor=True, kind=Kind.config)
|
||||||
#camCameraBusy = Component(EpicsSignalRO, "CAMERA_BUSY", auto_monitor=True, kind=Kind.config)
|
camCameraBusy = Component(EpicsSignalRO, "CAMERA_BUSY", auto_monitor=True, kind=Kind.config)
|
||||||
camInit= Component(EpicsSignalRO, "INIT", auto_monitor=True, kind=Kind.config)
|
camInit= Component(EpicsSignalRO, "INIT", auto_monitor=True, kind=Kind.config)
|
||||||
#camInitBusy = Component(EpicsSignalRO, "INIT_BUSY", auto_monitor=True, kind=Kind.config)
|
camInitBusy = Component(EpicsSignalRO, "INIT_BUSY", auto_monitor=True, kind=Kind.config)
|
||||||
|
|
||||||
# ########################################################################
|
# ########################################################################
|
||||||
# Acquisition configuration
|
# Acquisition configuration
|
||||||
@ -83,23 +84,86 @@ class HelgeCameraBase(Device):
|
|||||||
|
|
||||||
image = Component(EpicsSignalRO, "FPICTURE", kind=Kind.omitted)
|
image = Component(EpicsSignalRO, "FPICTURE", kind=Kind.omitted)
|
||||||
|
|
||||||
|
# File interface
|
||||||
|
camFileFormat = Component(EpicsSignal, "FILEFORMAT", put_complete=True, kind=Kind.config)
|
||||||
|
camFilePath = Component(EpicsSignal, "FILEPATH", put_complete=True, kind=Kind.config)
|
||||||
|
camFileName = Component(EpicsSignal, "FILENAME", put_complete=True, kind=Kind.config)
|
||||||
|
camFileNr = Component(EpicsSignal, "FILENR", put_complete=True, kind=Kind.config)
|
||||||
|
camFilePath = Component(EpicsSignal, "FILEPATH", put_complete=True, kind=Kind.config)
|
||||||
|
camFileTransferStart = Component(EpicsSignal, "FTRANSFER", put_complete=True, kind=Kind.config)
|
||||||
|
camFileTransferStop = Component(EpicsSignal, "SAVESTOP", put_complete=True, kind=Kind.config)
|
||||||
|
|
||||||
def configure(self, exposure_time=None):
|
|
||||||
|
|
||||||
|
def configure(self, d: dict = {}) -> tuple:
|
||||||
|
if self.state in ["OFFLINE", "REMOVED", "RUNNING"]:
|
||||||
|
raise RuntimeError(f"Can't change configuration from state {self.state}")
|
||||||
|
|
||||||
|
exposure_time = d['exptime']
|
||||||
|
|
||||||
if exposure_time is not None:
|
if exposure_time is not None:
|
||||||
self.acqExpTime.set(exposure_time).wait()
|
self.acqExpTime.set(exposure_time).wait()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
if self.camSetParamBusy.value:
|
||||||
|
return "BUSY"
|
||||||
|
if self.camStatusCode.value==2 and self.camInit.value==1:
|
||||||
|
return "IDLE"
|
||||||
|
if self.camStatusCode.value==6 and self.camInit.value==1:
|
||||||
|
return "RUNNING"
|
||||||
|
if self.camRemoval.value==0 and self.camInit.value==0:
|
||||||
|
return "OFFLINE"
|
||||||
|
if self.camRemoval.value:
|
||||||
|
return "REMOVED"
|
||||||
|
return "UNKNOWN"
|
||||||
|
|
||||||
|
|
||||||
|
@state.setter
|
||||||
|
def state(self):
|
||||||
|
raise ReadOnlyError("State is a ReadOnly property")
|
||||||
|
|
||||||
|
|
||||||
def stage(self):
|
def stage(self) -> None:
|
||||||
""" State transitions are only allowed when the IOC is not busy """
|
""" Start acquisition"""
|
||||||
if self.camCameraBusy.value or self.camInitBusy.value or self.camSetParamBusy.value:
|
|
||||||
raise RuntimeErrror("Failed to stage, the camera appears busy.")
|
# State transitions are only allowed when the IOC is not busy
|
||||||
|
if self.state not in ("OFFLINE", "BUSY", "REMOVED", "RUNNING"):
|
||||||
|
raise RuntimeError(f"Camera in in state: {self.state}")
|
||||||
|
|
||||||
|
# Start the acquisition
|
||||||
self.camStatusCmd.set("Running").wait()
|
self.camStatusCmd.set("Running").wait()
|
||||||
|
|
||||||
|
super().stage()
|
||||||
|
|
||||||
|
def kickoff(self, settle_time=0.2) -> DeviceStatus:
|
||||||
|
""" Start acquisition"""
|
||||||
|
|
||||||
|
# State transitions are only allowed when the IOC is not busy
|
||||||
|
if self.state not in ("OFFLINE", "BUSY", "REMOVED", "RUNNING"):
|
||||||
|
raise RuntimeError(f"Camera in in state: {self.state}")
|
||||||
|
|
||||||
|
# Start the acquisition
|
||||||
|
self.camStatusCmd.set("Running").wait()
|
||||||
|
|
||||||
|
# Subscribe and wait for update
|
||||||
|
def isRunning(*args, old_value, value, timestamp, **kwargs):
|
||||||
|
# result = bool(value==6 and self.camInit.value==1)
|
||||||
|
result = bool(self.state=="RUNNING")
|
||||||
|
return result
|
||||||
|
status = SubscriptionStatus(self.camStatusCode, isRunning, settle_time=0.2)
|
||||||
|
return status
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
""" Stop the running acquisition """
|
||||||
|
self.camStatusCmd.set("Idle").wait()
|
||||||
|
|
||||||
def unstage(self):
|
def unstage(self):
|
||||||
""" State transitions are only allowed when the IOC is not busy """
|
""" Stop the running acquisition and unstage the device"""
|
||||||
self.camStatusCmd.set("Idle").wait()
|
self.camStatusCmd.set("Idle").wait()
|
||||||
|
|
||||||
|
super().unstage()
|
||||||
|
|
||||||
|
|
||||||
# Automatically connect to test camera if directly invoked
|
# Automatically connect to test camera if directly invoked
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
127
ophyd_devices/epics/devices/helgecams/StdDaqBase.py
Normal file
127
ophyd_devices/epics/devices/helgecams/StdDaqBase.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import enum
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import os
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from ophyd import EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
||||||
|
from ophyd import Device
|
||||||
|
from ophyd import ADComponent as ADCpt
|
||||||
|
|
||||||
|
from std_daq_client import StdDaqClient
|
||||||
|
|
||||||
|
from bec_lib import threadlocked
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from bec_lib import messages
|
||||||
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
|
|
||||||
|
from ophyd_devices.epics.devices.psi_detector_base import PSIDetectorBase, CustomDetectorMixin
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class StdDaqBase(Device):
|
||||||
|
"""
|
||||||
|
Standalone standard_daq base class from the Eiger 9M.
|
||||||
|
"""
|
||||||
|
std_numpoints = None
|
||||||
|
std_filename = None
|
||||||
|
|
||||||
|
def __init__(self, prefix="", *, name, stddaq_filepath, kind=None, stddaq_rest_url="http://xbl-daq-29:5000", stddaq_timeout=5, read_attrs=None, configuration_attrs=None, parent=None, **kwargs):
|
||||||
|
|
||||||
|
self.std_rest_url = stddaq_rest_url
|
||||||
|
self.std_timeout = stddaq_timeout
|
||||||
|
self.std_filepath = stddaq_filepath
|
||||||
|
|
||||||
|
# Std client
|
||||||
|
self.std_client = StdDaqClient(url_base=self.std_rest_url)
|
||||||
|
|
||||||
|
# Stop writer
|
||||||
|
self.std_client.stop_writer()
|
||||||
|
|
||||||
|
# Change e-account
|
||||||
|
#eacc = self.parent.scaninfo.username
|
||||||
|
#self.update_std_cfg("writer_user_id", int(eacc.strip(" e")))
|
||||||
|
|
||||||
|
signal_conditions = [(lambda: self.std_client.get_status()["state"], "READY")]
|
||||||
|
if not self.wait_for_signals(
|
||||||
|
signal_conditions=signal_conditions,
|
||||||
|
timeout=self.std_timeout,
|
||||||
|
all_signals=True,
|
||||||
|
):
|
||||||
|
raise TimeoutError(f"Std client not in READY state, returns: {self.std_client.get_status()}")
|
||||||
|
|
||||||
|
def update_std_cfg(self, cfg_key: str, value: Any) -> None:
|
||||||
|
"""
|
||||||
|
Update std_daq config
|
||||||
|
|
||||||
|
Checks that the new value matches the type of the former entry.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg_key (str) : config key of value to be updated
|
||||||
|
value (Any) : value to be updated for the specified key
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Load config from client and check old value
|
||||||
|
cfg = self.std_client.get_config()
|
||||||
|
old_value = cfg.get(cfg_key)
|
||||||
|
if old_value is None:
|
||||||
|
raise KeyError( f"Tried to change entry for key {cfg_key} in std_config that does not exist")
|
||||||
|
if not isinstance(value, type(old_value)):
|
||||||
|
raise TypeError(f"Type of new value {type(value)}:{value} does not match old value {type(old_value)}:{old_value}")
|
||||||
|
|
||||||
|
# Update config with new value and send back to client
|
||||||
|
cfg.update({cfg_key: value})
|
||||||
|
self.std_client.set_config(cfg)
|
||||||
|
|
||||||
|
def configure(self, d: dict):
|
||||||
|
"""Configure the standard_daq"""
|
||||||
|
|
||||||
|
filename = str(d['filename']).split(".")[0]
|
||||||
|
|
||||||
|
self.std_numpoints = int(d['numpoints'])
|
||||||
|
self.std_filename = f"{self.std_filepath}/{filename}.h5"
|
||||||
|
|
||||||
|
|
||||||
|
def stage(self) -> None:
|
||||||
|
"""Start acquiring"""
|
||||||
|
|
||||||
|
self.stop()
|
||||||
|
try:
|
||||||
|
self.std_client.start_writer_async(
|
||||||
|
{"output_file": self.std_filename, "n_images": int(self.std_numpoints)}
|
||||||
|
)
|
||||||
|
except Exception as ex:
|
||||||
|
time.sleep(5)
|
||||||
|
if self.std_client.get_status()["state"] == "READY":
|
||||||
|
raise TimeoutError(f"Timeout of start_writer_async with {ex}") from ex
|
||||||
|
|
||||||
|
# Check status of std_daq
|
||||||
|
signal_conditions = [(lambda: self.std_client.get_status()["acquisition"]["state"], "WAITING_IMAGES")]
|
||||||
|
if not self.wait_for_signals(
|
||||||
|
signal_conditions=signal_conditions,
|
||||||
|
timeout=self.std_timeout,
|
||||||
|
check_stopped=False,
|
||||||
|
all_signals=True,
|
||||||
|
):
|
||||||
|
raise TimeoutError(f"Timeout of 5s reached for std_daq start_writer_async with std_daq client status {self.std_client.get_status()}")
|
||||||
|
|
||||||
|
|
||||||
|
def unstage(self) -> None:
|
||||||
|
"""Close file writer"""
|
||||||
|
self.std_client.stop_writer()
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""Close file writer"""
|
||||||
|
self.std_client.stop_writer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
daq = StdDaqBase(name="daq", stddaq_filepath="~/Data10")
|
@ -1,58 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
Created on Wed Dec 6 11:33:54 2023
|
|
||||||
|
|
||||||
@author: mohacsi_i
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ophyd import Device, Component, EpicsMotor, EpicsSignal, EpicsSignalRO, Kind
|
|
||||||
from ophyd.status import Status, SubscriptionStatus, StatusBase
|
|
||||||
from time import sleep
|
|
||||||
import warnings
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
STDAQ_REST_ADDR = "http://127.0.0.1:5001"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data = {"sources":"eiger", "n_images":10, "output_file":"/tmp/test.h5"}
|
|
||||||
headers = {'Content-type': 'application/json'}
|
|
||||||
r = requests.post(url = "http://127.0.0.1:5000/write_sync", json=data, headers=headers)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class StdDaqBase(Device):
|
|
||||||
""" Ophyd baseclass for Helge camera IOCs
|
|
||||||
|
|
||||||
This class provides wrappers for Helge's camera IOCs around SwissFEL and
|
|
||||||
for high performance SLS 2.0 cameras.
|
|
||||||
|
|
||||||
The IOC's operatio
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
# ########################################################################
|
|
||||||
# General hardware info
|
|
||||||
camType = Component(EpicsSignalRO, "QUERY", kind=Kind.omitted)
|
|
||||||
camBoard = Component(EpicsSignalRO, "BOARD", kind=Kind.config)
|
|
||||||
#camSerial = Component(EpicsSignalRO, "SERIALNR", kind=Kind.config)
|
|
||||||
|
|
||||||
|
|
||||||
def configure(self, d: dict = {}):
|
|
||||||
self._n_images = d['n_images'] if 'n_images' in d else None
|
|
||||||
self._sources = d['sources'] if 'sources' in d else None
|
|
||||||
self._output_file = d['output_file'] if 'output_file' in d else None
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def kickoff(self):
|
|
||||||
data = {"sources": self._sources, "n_images": self._n_images, "output_file": self._output_file}
|
|
||||||
headers = {'Content-type': 'application/json'}
|
|
||||||
r = requests.post(url = "http://127.0.0.1:5000/write_async", json=data, headers=headers)
|
|
||||||
self.req_id = req_id = str(r.json()["request_id"])
|
|
Reference in New Issue
Block a user