More Helgecams

This commit is contained in:
2024-03-26 15:03:48 +01:00
committed by mohacsi_i
parent bd9eac5540
commit e5ebdf3732
3 changed files with 210 additions and 77 deletions

View File

@ -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__":

View 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")

View File

@ -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"])