mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2025-06-24 19:51:09 +02:00
More Helgecams
This commit is contained in:
@ -15,7 +15,7 @@ import numpy as np
|
||||
|
||||
|
||||
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
|
||||
for high performance SLS 2.0 cameras.
|
||||
@ -30,6 +30,14 @@ class HelgeCameraBase(Device):
|
||||
camType = Component(EpicsSignalRO, "QUERY", kind=Kind.omitted)
|
||||
camBoard = Component(EpicsSignalRO, "BOARD", 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
|
||||
@ -43,28 +51,21 @@ class HelgeCameraBase(Device):
|
||||
pxNumX = Component(EpicsSignalRO, "WIDTH", auto_monitor=True, kind=Kind.config)
|
||||
pxNumY = Component(EpicsSignalRO, "HEIGHT", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
# ########################################################################
|
||||
# Acquisition commands
|
||||
|
||||
|
||||
# ########################################################################
|
||||
# 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)
|
||||
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
|
||||
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)
|
||||
camSetParamBusy = Component(EpicsSignalRO, "BUSY_SET_PARAM", 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)
|
||||
#camInitBusy = Component(EpicsSignalRO, "INIT_BUSY", auto_monitor=True, kind=Kind.config)
|
||||
camInitBusy = Component(EpicsSignalRO, "INIT_BUSY", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
# ########################################################################
|
||||
# Acquisition configuration
|
||||
@ -83,23 +84,86 @@ class HelgeCameraBase(Device):
|
||||
|
||||
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:
|
||||
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):
|
||||
""" State transitions are only allowed when the IOC is not busy """
|
||||
if self.camCameraBusy.value or self.camInitBusy.value or self.camSetParamBusy.value:
|
||||
raise RuntimeErrror("Failed to stage, the camera appears busy.")
|
||||
def stage(self) -> None:
|
||||
""" 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()
|
||||
|
||||
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):
|
||||
""" State transitions are only allowed when the IOC is not busy """
|
||||
""" Stop the running acquisition and unstage the device"""
|
||||
self.camStatusCmd.set("Idle").wait()
|
||||
|
||||
super().unstage()
|
||||
|
||||
|
||||
# Automatically connect to test camera if directly invoked
|
||||
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