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:
@ -12,118 +12,67 @@ import warnings
|
||||
import numpy as np
|
||||
|
||||
|
||||
|
||||
|
||||
class HelgeCameraBase(Device):
|
||||
class HelgeCameraCore(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
|
||||
The IOC's operation is a bit arcane.
|
||||
|
||||
|
||||
|
||||
"""
|
||||
USER_ACCESS = ["kickoff"]
|
||||
# ########################################################################
|
||||
# General hardware info
|
||||
camType = Component(EpicsSignalRO, "QUERY", kind=Kind.omitted)
|
||||
camBoard = Component(EpicsSignalRO, "BOARD", kind=Kind.config)
|
||||
#camSerial = Component(EpicsSignalRO, "SERIALNR", kind=Kind.config)
|
||||
camError = Component(EpicsSignalRO, "ERRCODE", auto_monitor=True, kind=Kind.config)
|
||||
camWarning = Component(EpicsSignalRO, "WARNCODE", auto_monitor=True, 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
|
||||
# Priority is: binning -> roi -> final size
|
||||
pxBinX = Component(EpicsSignalRO, "BINX", auto_monitor=True, kind=Kind.config)
|
||||
pxBinY = Component(EpicsSignalRO, "BINY", auto_monitor=True, kind=Kind.config)
|
||||
pxRoiX_lo = Component(EpicsSignalRO, "REGIONX_START", auto_monitor=True, kind=Kind.config)
|
||||
pxRoiX_hi = Component(EpicsSignalRO, "REGIONX_END", auto_monitor=True, kind=Kind.config)
|
||||
pxRoiY_lo = Component(EpicsSignalRO, "REGIONY_START", auto_monitor=True, kind=Kind.config)
|
||||
pxRoiY_hi = Component(EpicsSignalRO, "REGIONY_END", 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)
|
||||
|
||||
# ########################################################################
|
||||
# Polled CamStatus
|
||||
camError = Component(EpicsSignalRO, "ERRCODE", auto_monitor=True, kind=Kind.config)
|
||||
camWarning = Component(EpicsSignalRO, "WARNCODE", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
# Weird state maschine with separate transition states
|
||||
# Polled state maschine with separate transition states
|
||||
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, "BUSY_CAMERA", 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, "BUSY_INIT", auto_monitor=True, kind=Kind.config)
|
||||
camRemoved = Component(EpicsSignalRO, "REMOVAL", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
# ########################################################################
|
||||
# Acquisition configuration
|
||||
acqMode = Component(EpicsSignalRO, "ACQMODE", auto_monitor=True, kind=Kind.config)
|
||||
acqExpTime = Component(EpicsSignalRO, "EXPOSURE", auto_monitor=True, kind=Kind.config)
|
||||
acqDelay = Component(EpicsSignalRO, "DELAY", auto_monitor=True, kind=Kind.config)
|
||||
acqTriggerEna = Component(EpicsSignalRO, "TRIGGER", auto_monitor=True, kind=Kind.config)
|
||||
#acqTriggerSource = Component(EpicsSignalRO, "TRIGGERSOURCE", auto_monitor=True, kind=Kind.config)
|
||||
#acqTriggerEdge = Component(EpicsSignalRO, "TRIGGEREDGE", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
|
||||
bufferRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config)
|
||||
bufferStoreMode = Component(EpicsSignalRO, "STOREMODE", auto_monitor=True, kind=Kind.config)
|
||||
fileRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
|
||||
image = Component(EpicsSignalRO, "FPICTURE", kind=Kind.omitted)
|
||||
camStateString = Component(EpicsSignalRO, "SS_CAMERA", string=True, auto_monitor=True, kind=Kind.config)
|
||||
|
||||
# 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, 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):
|
||||
def state(self) -> str:
|
||||
""" Single word camera state"""
|
||||
if self.camSetParamBusy.value:
|
||||
return "BUSY"
|
||||
return "BUSY"
|
||||
if self.camStatusCode.value==2 and self.camInit.value==1:
|
||||
return "IDLE"
|
||||
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==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 configure(self, d: dict = {}) -> tuple:
|
||||
if self.state in ["OFFLINE", "REMOVED", "RUNNING"]:
|
||||
raise RuntimeError(f"Can't change configuration from state {self.state}")
|
||||
|
||||
def stage(self) -> None:
|
||||
""" Start acquisition"""
|
||||
|
||||
@ -163,6 +112,193 @@ class HelgeCameraBase(Device):
|
||||
self.camStatusCmd.set("Idle").wait()
|
||||
|
||||
super().unstage()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class HelgeCameraBase(HelgeCameraCore):
|
||||
"""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. Theese are mostly PCO cameras running
|
||||
on a special Windows IOC host with lots of RAM and CPU power.
|
||||
|
||||
The IOC's operation is a bit arcane, and is documented on the "read the code"
|
||||
level. However the most important part is the state machine of 7+1 PV signals:
|
||||
INIT
|
||||
BUSY_INIT
|
||||
SET_PARAM
|
||||
BUSY_SET_PARAM
|
||||
CAMERA
|
||||
BUSY_CAMERA
|
||||
CAMERASTATUSCODE
|
||||
CAMERASTATUS
|
||||
|
||||
|
||||
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["describe", "shape", "bin", "roi"]
|
||||
# ########################################################################
|
||||
# Additional status info
|
||||
busy = Component(EpicsSignalRO, "BUSY", auto_monitor=True, kind=Kind.config)
|
||||
camState = Component(EpicsSignalRO, "SS_CAMERA", 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)
|
||||
|
||||
# ########################################################################
|
||||
# Acquisition configuration
|
||||
acqMode = Component(EpicsSignalRO, "ACQMODE", auto_monitor=True, kind=Kind.config)
|
||||
acqExpTime = Component(EpicsSignalRO, "EXPOSURE", auto_monitor=True, kind=Kind.config)
|
||||
acqDelay = Component(EpicsSignalRO, "DELAY", auto_monitor=True, kind=Kind.config)
|
||||
acqTriggerEna = Component(EpicsSignalRO, "TRIGGER", auto_monitor=True, kind=Kind.config)
|
||||
#acqTriggerSource = Component(EpicsSignalRO, "TRIGGERSOURCE", auto_monitor=True, kind=Kind.config)
|
||||
#acqTriggerEdge = Component(EpicsSignalRO, "TRIGGEREDGE", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
# ########################################################################
|
||||
# Image size settings
|
||||
# Priority is: binning -> roi -> final size
|
||||
pxBinX = Component(EpicsSignalRO, "BINX", auto_monitor=True, kind=Kind.config)
|
||||
pxBinY = Component(EpicsSignalRO, "BINY", auto_monitor=True, kind=Kind.config)
|
||||
pxRoiX_lo = Component(EpicsSignalRO, "REGIONX_START", auto_monitor=True, kind=Kind.config)
|
||||
pxRoiX_hi = Component(EpicsSignalRO, "REGIONX_END", auto_monitor=True, kind=Kind.config)
|
||||
pxRoiY_lo = Component(EpicsSignalRO, "REGIONY_START", auto_monitor=True, kind=Kind.config)
|
||||
pxRoiY_hi = Component(EpicsSignalRO, "REGIONY_END", 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)
|
||||
|
||||
|
||||
# ########################################################################
|
||||
# Buffer configuration
|
||||
bufferRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config)
|
||||
bufferStoreMode = Component(EpicsSignalRO, "STOREMODE", auto_monitor=True, kind=Kind.config)
|
||||
fileRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
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, d: dict = {}) -> tuple:
|
||||
"""
|
||||
Camera configuration instructions:
|
||||
After setting the corresponding PVs, one needs to process SET_PARAM and wait until
|
||||
BUSY_SET_PARAM goes high and low, followed by SET_PARAM goes high and low. This will
|
||||
both send the settings to the camera and allocate the necessary buffers in the correct
|
||||
size and shape (that takes time). Starting the exposure with CAMERASTATUS will also
|
||||
call SET_PARAM, but it might take long.
|
||||
"""
|
||||
old = self.read_configuration()
|
||||
super().configure(d)
|
||||
|
||||
exposure_time = d['exptime']
|
||||
if exposure_time is not None:
|
||||
self.acqExpTime.set(exposure_time).wait()
|
||||
|
||||
if "roi" in d:
|
||||
roi = d["roi"]
|
||||
if not isinstance(roi, (list, tuple)):
|
||||
raise ValueError(f"Unknown ROI data type {type(roi)}")
|
||||
if not len(roi[0])==2 and len(roi[1])==2:
|
||||
raise ValueError(f"Unknown ROI shape: {roi}")
|
||||
self.pxRoiX_lo.set(roi[0][0]).wait()
|
||||
self.pxRoiX_hi.set(roi[0][1]).wait()
|
||||
self.pxRoiY_lo.set(roi[1][0]).wait()
|
||||
self.pxRoiY_hi.set(roi[1][1]).wait()
|
||||
|
||||
if "bin" in d:
|
||||
binning = d["bin"]
|
||||
if not isinstance(binning, (list, tuple)):
|
||||
raise ValueError(f"Unknown BINNING data type {type(binning)}")
|
||||
if not len(binning)==2:
|
||||
raise ValueError(f"Unknown ROI shape: {binning}")
|
||||
self.pxBinX.set(binning[0]).wait()
|
||||
self.pxBinY.set(binning[1]).wait()
|
||||
|
||||
# State machine
|
||||
# Initial: BUSY and SET both low
|
||||
# 1. BUSY set to high
|
||||
# 2. BUSY goes low, SET goes high
|
||||
# 3. SET goes low
|
||||
self.camSetParam.set(1).wait()
|
||||
def risingEdge(*args, old_value, value, timestamp, **kwargs):
|
||||
return bool(not old_value and value)
|
||||
def fallingEdge(*args, old_value, value, timestamp, **kwargs):
|
||||
return bool(old_value and not value)
|
||||
# Subscribe and wait for update
|
||||
status = SubscriptionStatus(self.camSetParam, fallingEdge, settle_time=0.5)
|
||||
status.wait()
|
||||
new = self.read_configuration()
|
||||
return (old, new)
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return (self.pxNumX.value, self.pxNumY.value)
|
||||
|
||||
@shape.setter
|
||||
def shape(self):
|
||||
raise ReadOnlyError("Shape is a ReadOnly property")
|
||||
|
||||
@property
|
||||
def bin(self):
|
||||
return (self.pxBinX.value, self.pxBinY.value)
|
||||
|
||||
@bin.setter
|
||||
def bin(self):
|
||||
raise ReadOnlyError("Bin is a ReadOnly property")
|
||||
|
||||
@property
|
||||
def roi(self):
|
||||
return ((self.pxRoiX_lo.value, self.pxRoiX_hi.value), (self.pxRoiY_lo.value, self.pxRoiY_hi.value))
|
||||
|
||||
@roi.setter
|
||||
def roi(self):
|
||||
raise ReadOnlyError("Bin is a ReadOnly property")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Automatically connect to test camera if directly invoked
|
||||
|
Reference in New Issue
Block a user