PCO step scanning works
This commit is contained in:
1 parent
aea898e60f
commit
163ef3c7a5
7 files changed
+358
-171
No files matched your search
@@ -188,12 +188,13 @@ pcocam:
|
||||
prefix: 'X02DA-CCDCAM2:'
|
||||
deviceTags:
|
||||
- camera
|
||||
- trigger
|
||||
- pcocam
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
softwareTrigger: true
|
||||
|
||||
pcodaq:
|
||||
description: GigaFrost stdDAQ client
|
||||
|
||||
@@ -6,16 +6,20 @@ Created on Wed Dec 6 11:33:54 2023
|
||||
"""
|
||||
|
||||
from ophyd import Component, EpicsSignal, EpicsSignalRO, Kind
|
||||
from ophyd.status import SubscriptionStatus
|
||||
from ophyd.status import SubscriptionStatus, DeviceStatus
|
||||
import time
|
||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import PSIDetectorBase as PSIDeviceBase
|
||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import CustomDetectorMixin as CustomDeviceMixin
|
||||
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
|
||||
CustomDetectorMixin as CustomDeviceMixin,
|
||||
)
|
||||
|
||||
try:
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
except ModuleNotFoundError:
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("PcoEdgeCam")
|
||||
|
||||
|
||||
@@ -24,42 +28,43 @@ class PcoEdgeCameraMixin(CustomDeviceMixin):
|
||||
|
||||
This class will be called by the custom_prepare_cls attribute of the detector class.
|
||||
"""
|
||||
|
||||
def on_stage(self) -> None:
|
||||
"""Configure and arm PCO.Edge camera for acquisition
|
||||
"""
|
||||
"""Configure and arm PCO.Edge camera for acquisition"""
|
||||
|
||||
# PCO can finish a run without explicit unstaging
|
||||
if self.parent.state not in ("IDLE"):
|
||||
logger.warning(f"Trying to stage the camera from state {self.parent.state}, unstaging it first!")
|
||||
logger.warning(
|
||||
f"Trying to stage the camera from state {self.parent.state}, unstaging it first!"
|
||||
)
|
||||
self.parent.unstage()
|
||||
time.sleep(0.5)
|
||||
|
||||
# Fish out our configuration from scaninfo (via explicit or generic addressing)
|
||||
scanparam = self.parent.scaninfo.scan_msg.info
|
||||
d = {}
|
||||
if 'kwargs' in scanparam:
|
||||
scanargs = scanparam['kwargs']
|
||||
if 'num_images_total' in scanargs and scanargs['num_images_total'] is not None:
|
||||
d['images_total'] = scanargs['num_images_total']
|
||||
if 'image_width' in scanargs and scanargs['image_width'] is not None:
|
||||
d['image_width'] = scanargs['image_width']
|
||||
if 'image_height' in scanargs and scanargs['image_height'] is not None:
|
||||
d['image_height'] = scanargs['image_height']
|
||||
if 'exp_time' in scanargs and scanargs['exp_time'] is not None:
|
||||
d['exposure_time_ms'] = scanargs['exp_time']
|
||||
if 'exp_period' in scanargs and scanargs['exp_period'] is not None:
|
||||
d['exposure_period_ms'] = scanargs['exp_period']
|
||||
if "kwargs" in scanparam:
|
||||
scanargs = scanparam["kwargs"]
|
||||
if "exp_burst" in scanargs and scanargs["exp_burst"] is not None:
|
||||
d["exposure_num_burst"] = scanargs["exp_burst"]
|
||||
if "image_width" in scanargs and scanargs["image_width"] is not None:
|
||||
d["image_width"] = scanargs["image_width"]
|
||||
if "image_height" in scanargs and scanargs["image_height"] is not None:
|
||||
d["image_height"] = scanargs["image_height"]
|
||||
if "exp_time" in scanargs and scanargs["exp_time"] is not None:
|
||||
d["exposure_time_ms"] = scanargs["exp_time"]
|
||||
if "exp_period" in scanargs and scanargs["exp_period"] is not None:
|
||||
d["exposure_period_ms"] = scanargs["exp_period"]
|
||||
# if 'exp_burst' in scanargs and scanargs['exp_burst'] is not None:
|
||||
# d['exposure_num_burst'] = scanargs['exp_burst']
|
||||
# if 'acq_mode' in scanargs and scanargs['acq_mode'] is not None:
|
||||
# d['acq_mode'] = scanargs['acq_mode']
|
||||
# elif self.parent.scaninfo.scan_type == "step":
|
||||
# d['acq_mode'] = "default"
|
||||
if 'pco_store_mode' in scanargs and scanargs['pco_store_mode'] is not None:
|
||||
d['store_mode'] = scanargs['pco_store_mode']
|
||||
if 'pco_data_format' in scanargs and scanargs['pco_data_format'] is not None:
|
||||
d['data_format'] = scanargs['pco_data_format']
|
||||
|
||||
if "pco_store_mode" in scanargs and scanargs["pco_store_mode"] is not None:
|
||||
d["store_mode"] = scanargs["pco_store_mode"]
|
||||
if "pco_data_format" in scanargs and scanargs["pco_data_format"] is not None:
|
||||
d["data_format"] = scanargs["pco_data_format"]
|
||||
|
||||
# Perform bluesky-style configuration
|
||||
if len(d) > 0:
|
||||
@@ -70,29 +75,86 @@ class PcoEdgeCameraMixin(CustomDeviceMixin):
|
||||
self.parent.bluestage()
|
||||
|
||||
def on_unstage(self) -> None:
|
||||
"""Disarm the PCO.Edge camera
|
||||
"""
|
||||
"""Disarm the PCO.Edge camera"""
|
||||
self.parent.blueunstage()
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Stop the PCO.Edge camera
|
||||
"""
|
||||
"""Stop the PCO.Edge camera"""
|
||||
self.parent.blueunstage()
|
||||
|
||||
def on_trigger(self) -> None | DeviceStatus:
|
||||
"""Trigger mode operation
|
||||
|
||||
Use it to repeatedly record a fixed number of frames and send it to stdDAQ. The method waits
|
||||
for the acquisition and data transfer to complete.
|
||||
|
||||
NOTE: Maciej confirmed that sparse data is no problem to the stdDAQ.
|
||||
TODO: Optimize data transfer to launch at end and check completion at the beginning.
|
||||
"""
|
||||
logger.warning(f"triggering PCO")
|
||||
# Ensure that previous data transfer finished
|
||||
# def sentIt(*args, value, timestamp, **kwargs):
|
||||
# return value==0
|
||||
# status = SubscriptionStatus(self.parent.file_savebusy, sentIt, timeout=120)
|
||||
# status.wait()
|
||||
|
||||
# Not sure if it always sends the first batch of images or the newest
|
||||
def didWeReset(*args, old_value, value, timestamp, **kwargs):
|
||||
return (value < old_value) or (value == 0)
|
||||
|
||||
self.parent.buffer_clear.set(1).wait()
|
||||
status = SubscriptionStatus(self.parent.buffer_used, didWeReset, timeout=5)
|
||||
status.wait()
|
||||
|
||||
t_expected = (
|
||||
self.parent.acquire_time.get() + self.parent.acquire_delay.get()
|
||||
) * self.parent.file_savestop.get()
|
||||
|
||||
# Wait until the buffer fills up with enough images
|
||||
def areWeDoneYet(*args, old_value, value, timestamp, **kwargs):
|
||||
num_target = self.parent.file_savestop.get()
|
||||
# logger.warning(f"{value} of {num_target}")
|
||||
return bool(value >= num_target)
|
||||
|
||||
status = SubscriptionStatus(
|
||||
self.parent.buffer_used, areWeDoneYet, timeout=max(5, 5 * t_expected), settle_time=0.2
|
||||
)
|
||||
status.wait()
|
||||
|
||||
# Then start file transfer (need to get the save busy flag update)
|
||||
# self.parent.file_transfer.set(1, settle_time=0.2).wait()
|
||||
self.parent.file_transfer.set(1).wait()
|
||||
|
||||
# And wait until the images have been sent
|
||||
# NOTE: this does not wait for new value, the first check will be
|
||||
# against values from the previous cycle, i.e. pass automatically.
|
||||
t_start = time.time()
|
||||
|
||||
def haveWeSentIt(*args, old_value, value, timestamp, **kwargs):
|
||||
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
|
||||
|
||||
status = SubscriptionStatus(
|
||||
self.parent.file_savebusy, haveWeSentIt, timeout=120, settle_time=0.2
|
||||
)
|
||||
status.wait()
|
||||
logger.warning(f"done PCO")
|
||||
|
||||
|
||||
class HelgeCameraBase(PSIDeviceBase):
|
||||
"""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 operation is a bit arcane
|
||||
and there are different versions and cameras all around. So this device
|
||||
and there are different versions and cameras all around. So this device
|
||||
only covers the absolute basics.
|
||||
|
||||
Probably the most important part is the configuration state machine. As
|
||||
the SET_PARAMS takes care of buffer allocations it might take some time,
|
||||
as well as a full re-configuration is required every time we change the
|
||||
binning, roi, etc... This is automatically performed upon starting an
|
||||
exposure (if it heven't been done before).
|
||||
as well as a full re-configuration is required every time we change the
|
||||
binning, roi, etc... This is automatically performed upon starting an
|
||||
exposure (if it heven't been done before).
|
||||
|
||||
The status flag state machine during re-configuration is:
|
||||
BUSY low, SET low -> BUSY high, SET low -> BUSY low, SET high -> BUSY low, SET low
|
||||
@@ -112,12 +174,12 @@ class HelgeCameraBase(PSIDeviceBase):
|
||||
Note that in FIFO mode buffer reads are destructive, to prevent this, we don't have EPICS preview
|
||||
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
# ########################################################################
|
||||
# General hardware info (in AD nomenclature)
|
||||
manufacturer = Component(EpicsSignalRO, "QUERY", kind=Kind.config, doc="Camera model info")
|
||||
model = Component(EpicsSignalRO, "BOARD", kind=Kind.omitted, doc="Camera board info")
|
||||
model = Component(EpicsSignalRO, "BOARD", kind=Kind.omitted, doc="Camera board info")
|
||||
|
||||
# ########################################################################
|
||||
# Acquisition commands
|
||||
@@ -125,30 +187,41 @@ class HelgeCameraBase(PSIDeviceBase):
|
||||
|
||||
# ########################################################################
|
||||
# Acquisition configuration (in AD nomenclature)
|
||||
acquire_time = Component(EpicsSignal, "EXPOSURE", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
acquire_delay = Component(EpicsSignal, "DELAY", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
trigger_mode = Component(EpicsSignal, "TRIGGER", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
acquire_time = Component(
|
||||
EpicsSignal, "EXPOSURE", put_complete=True, auto_monitor=True, kind=Kind.config
|
||||
)
|
||||
acquire_delay = Component(
|
||||
EpicsSignal, "DELAY", put_complete=True, auto_monitor=True, kind=Kind.config
|
||||
)
|
||||
trigger_mode = Component(
|
||||
EpicsSignal, "TRIGGER", put_complete=True, auto_monitor=True, kind=Kind.config
|
||||
)
|
||||
|
||||
# ########################################################################
|
||||
# Image size configuration (in AD nomenclature)
|
||||
bin_x = Component(EpicsSignal, "BINX", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
bin_y = Component(EpicsSignal, "BINY", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
array_size_x = Component(EpicsSignalRO, "WIDTH", auto_monitor=True, kind=Kind.config, doc="Final image width")
|
||||
array_size_y = Component(EpicsSignalRO, "HEIGHT", auto_monitor=True, kind=Kind.config, doc="Final image height")
|
||||
array_size_x = Component(
|
||||
EpicsSignalRO, "WIDTH", auto_monitor=True, kind=Kind.config, doc="Final image width"
|
||||
)
|
||||
array_size_y = Component(
|
||||
EpicsSignalRO, "HEIGHT", auto_monitor=True, kind=Kind.config, doc="Final image height"
|
||||
)
|
||||
|
||||
# ########################################################################
|
||||
# General hardware info
|
||||
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)
|
||||
|
||||
# ########################################################################
|
||||
# Buffer configuration
|
||||
# Buffer configuration
|
||||
bufferRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config)
|
||||
bufferStoreMode = Component(EpicsSignal, "STOREMODE", auto_monitor=True, kind=Kind.config)
|
||||
fileRecMode = Component(EpicsSignalRO, "RECMODE", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
buffer_used = Component(EpicsSignalRO, "PIC_BUFFER", auto_monitor=True, kind=Kind.normal)
|
||||
buffer_size = Component(EpicsSignalRO, "PIC_MAX", auto_monitor=True, kind=Kind.normal)
|
||||
buffer_clear = Component(EpicsSignal, "CLEARMEM", put_complete=True, kind=Kind.omitted)
|
||||
|
||||
# ########################################################################
|
||||
# File saving interface
|
||||
@@ -158,15 +231,18 @@ class HelgeCameraBase(PSIDeviceBase):
|
||||
file_savestop = Component(EpicsSignal, "SAVESTOP", put_complete=True, kind=Kind.config)
|
||||
file_format = Component(EpicsSignal, "FILEFORMAT", put_complete=True, kind=Kind.config)
|
||||
file_transfer = Component(EpicsSignal, "FTRANSFER", put_complete=True, kind=Kind.config)
|
||||
file_savebusy = Component(EpicsSignalRO, "FILESAVEBUSY", auto_monitor=True, kind=Kind.normal)
|
||||
|
||||
# ########################################################################
|
||||
# Configuration state maschine with separate transition states
|
||||
camStatusCode = Component(EpicsSignalRO, "STATUSCODE", auto_monitor=True, kind=Kind.config)
|
||||
camSetParam = Component(EpicsSignal, "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)
|
||||
camCameraBusy = Component(EpicsSignalRO, "BUSY_CAMERA", 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, "BUSY_INIT", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
# ########################################################################
|
||||
@@ -175,22 +251,24 @@ class HelgeCameraBase(PSIDeviceBase):
|
||||
|
||||
# ########################################################################
|
||||
# Misc PVs
|
||||
#camRemoval = Component(EpicsSignalRO, "REMOVAL", auto_monitor=True, kind=Kind.config)
|
||||
camStateString = Component(EpicsSignalRO, "SS_CAMERA", string=True, auto_monitor=True, kind=Kind.config)
|
||||
# camRemoval = Component(EpicsSignalRO, "REMOVAL", auto_monitor=True, kind=Kind.config)
|
||||
camStateString = Component(
|
||||
EpicsSignalRO, "SS_CAMERA", string=True, auto_monitor=True, kind=Kind.config
|
||||
)
|
||||
|
||||
@property
|
||||
def state(self) -> str:
|
||||
""" Single word camera state"""
|
||||
"""Single word camera state"""
|
||||
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 "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:
|
||||
if self.camInit.value==0:
|
||||
# if self.camRemoval.value==0 and self.camInit.value==0:
|
||||
if self.camInit.value == 0:
|
||||
return "OFFLINE"
|
||||
#if self.camRemoval.value:
|
||||
# if self.camRemoval.value:
|
||||
# return "REMOVED"
|
||||
return "UNKNOWN"
|
||||
|
||||
@@ -199,103 +277,113 @@ class HelgeCameraBase(PSIDeviceBase):
|
||||
raise ReadOnlyError("State is a ReadOnly property")
|
||||
|
||||
def configure(self, d: dict = {}) -> tuple:
|
||||
""" Configure the base Helge camera device
|
||||
|
||||
Parameters as 'd' dictionary
|
||||
----------------------------
|
||||
num_images : int
|
||||
Number of images to be taken during each scan. Meaning depends on
|
||||
store mode.
|
||||
exposure_time_ms : float
|
||||
Exposure time [ms], usually gets set back to 20 ms
|
||||
exposure_period_ms : float
|
||||
Exposure period [ms], up to 200 ms.
|
||||
store_mode : str
|
||||
Buffer operation mode
|
||||
*'Recorder' to record in buffer
|
||||
*'FIFO buffer' for continous streaming
|
||||
data_format : str
|
||||
Usually set to 'ZEROMQ'
|
||||
"""Configure the base Helge camera device
|
||||
|
||||
Parameters as 'd' dictionary
|
||||
----------------------------
|
||||
num_images : int
|
||||
Number of images to be taken during each scan. Meaning depends on
|
||||
store mode.
|
||||
exposure_time_ms : float
|
||||
Exposure time [ms], usually gets set back to 20 ms
|
||||
exposure_period_ms : float
|
||||
Exposure period [ms], up to 200 ms.
|
||||
store_mode : str
|
||||
Buffer operation mode
|
||||
*'Recorder' to record in buffer
|
||||
*'FIFO buffer' for continous streaming
|
||||
data_format : str
|
||||
Usually set to 'ZEROMQ'
|
||||
"""
|
||||
if self.state not in ("IDLE"):
|
||||
raise RuntimeError(f"Can't change configuration from state {self.state}")
|
||||
|
||||
|
||||
# If Bluesky style configure
|
||||
if d is not None:
|
||||
# Commonly changed settings
|
||||
if 'num_images' in d:
|
||||
self.file_savestop.set(d['num_images']).wait()
|
||||
if 'exposure_time_ms' in d:
|
||||
self.acquire_time.set(d['exposure_time_ms']).wait()
|
||||
if 'exposure_period_ms' in d:
|
||||
self.acquire_delay.set(d['exposure_period_ms']).wait()
|
||||
if 'exposure_period_ms' in d:
|
||||
self.acquire_delay.set(d['exposure_period_ms']).wait()
|
||||
if 'store_mode' in d:
|
||||
self.bufferStoreMode.set(d['store_mode']).wait()
|
||||
if 'data_format' in d:
|
||||
self.file_format.set(d['data_format']).wait()
|
||||
if "exposure_num_burst" in d:
|
||||
self.file_savestop.set(d["exposure_num_burst"]).wait()
|
||||
if "exposure_time_ms" in d:
|
||||
self.acquire_time.set(d["exposure_time_ms"]).wait()
|
||||
if "exposure_period_ms" in d:
|
||||
self.acquire_delay.set(d["exposure_period_ms"]).wait()
|
||||
if "exposure_period_ms" in d:
|
||||
self.acquire_delay.set(d["exposure_period_ms"]).wait()
|
||||
if "store_mode" in d:
|
||||
self.bufferStoreMode.set(d["store_mode"]).wait()
|
||||
if "data_format" in d:
|
||||
self.file_format.set(d["data_format"]).wait()
|
||||
|
||||
# State machine
|
||||
# Initial: BUSY and SET both low
|
||||
# 0. Write 1 to SET_PARAM
|
||||
# 1. BUSY goes high, SET stays low
|
||||
# 0. Write 1 to SET_PARAM
|
||||
# 1. BUSY goes high, SET stays low
|
||||
# 2. BUSY goes low, SET goes high
|
||||
# 3. BUSY stays low, SET goes low
|
||||
# So we need a 'negedge' on SET_PARAM
|
||||
self.camSetParam.set(1).wait()
|
||||
|
||||
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, timeout=5, settle_time=0.5)
|
||||
status.wait()
|
||||
|
||||
def bluestage(self):
|
||||
"""Bluesky style stage: arm the detector
|
||||
"""
|
||||
"""Bluesky style stage: arm the detector"""
|
||||
logger.warning("Staging PCO")
|
||||
# Acquisition is only allowed when the IOC is not busy
|
||||
if self.state in ("OFFLINE", "BUSY", "REMOVED", "RUNNING"):
|
||||
raise RuntimeError(f"Camera in in state: {self.state}")
|
||||
|
||||
if (
|
||||
self.bufferStoreMode.get() in ("Recorder", 0)
|
||||
and self.file_savestop.get() > self.buffer_size.get()
|
||||
):
|
||||
self.logger.warning(
|
||||
"You're about to send some empty images, {self.file_savestop.get()} is above buffer size"
|
||||
)
|
||||
|
||||
# Start the acquisition (this sets parameers and starts acquisition)
|
||||
self.camStatusCmd.set("Running").wait()
|
||||
|
||||
# Subscribe and wait for update
|
||||
def isRunning(*args, old_value, value, timestamp, **kwargs):
|
||||
return bool(value==6)
|
||||
return bool(value == 6)
|
||||
|
||||
status = SubscriptionStatus(self.camStatusCode, isRunning, timeout=5, settle_time=0.2)
|
||||
status.wait()
|
||||
|
||||
def blueunstage(self):
|
||||
"""Bluesky style unstage: stop the detector
|
||||
"""
|
||||
"""Bluesky style unstage: stop the detector"""
|
||||
self.camStatusCmd.set("Idle").wait()
|
||||
self.custom_prepare.stop_monitor = True
|
||||
|
||||
# Subscribe and wait for update
|
||||
def isIdle(*args, old_value, value, timestamp, **kwargs):
|
||||
return bool(value==2)
|
||||
status = SubscriptionStatus(self.camStatusCode, isIdle, timeout=5, settle_time=0.2)
|
||||
status.wait()
|
||||
|
||||
# Data streaming is stopped by setting the max index to 0
|
||||
# FIXME: This might interrupt data transfer
|
||||
self.file_savestop.set(0).wait()
|
||||
|
||||
|
||||
def bluekickoff(self):
|
||||
""" Start data transfer
|
||||
|
||||
"""Start data transfer
|
||||
|
||||
TODO: Need to revisit this once triggering is complete
|
||||
"""
|
||||
self.file_transfer.set(1).wait()
|
||||
|
||||
# def complete(self):
|
||||
# """ Wait until the images have been sent"""
|
||||
# def areWeSending(*args, value, timestamp, **kwargs):
|
||||
# return not bool(value)
|
||||
# status = SubscriptionStatus(self.file_savebusy, haveWeSentIt, timeout=None, settle_time=0.2)
|
||||
# return status
|
||||
|
||||
|
||||
class PcoEdge5M(HelgeCameraBase):
|
||||
"""Ophyd baseclass for PCO.Edge cameras
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
@@ -310,28 +398,36 @@ class PcoEdge5M(HelgeCameraBase):
|
||||
camRate = Component(EpicsSignalRO, "CAMRATE", auto_monitor=True, kind=Kind.config)
|
||||
|
||||
# ########################################################################
|
||||
# Acquisition configuration
|
||||
# Acquisition configuration
|
||||
acqMode = Component(EpicsSignalRO, "ACQMODE", 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)
|
||||
|
||||
# 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
|
||||
pxRoiX_lo = Component(EpicsSignal, "REGIONX_START", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
pxRoiX_hi = Component(EpicsSignal, "REGIONX_END", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
pxRoiY_lo = Component(EpicsSignal, "REGIONY_START", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
pxRoiY_hi = Component(EpicsSignal, "REGIONY_END", put_complete=True, auto_monitor=True, kind=Kind.config)
|
||||
pxRoiX_lo = Component(
|
||||
EpicsSignal, "REGIONX_START", put_complete=True, auto_monitor=True, kind=Kind.config
|
||||
)
|
||||
pxRoiX_hi = Component(
|
||||
EpicsSignal, "REGIONX_END", put_complete=True, auto_monitor=True, kind=Kind.config
|
||||
)
|
||||
pxRoiY_lo = Component(
|
||||
EpicsSignal, "REGIONY_START", put_complete=True, auto_monitor=True, kind=Kind.config
|
||||
)
|
||||
pxRoiY_hi = Component(
|
||||
EpicsSignal, "REGIONY_END", put_complete=True, auto_monitor=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
|
||||
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.
|
||||
|
||||
NOTE:
|
||||
@@ -360,18 +456,18 @@ class PcoEdge5M(HelgeCameraBase):
|
||||
# Need to be smart how we set the ROI....
|
||||
# Image sensor is 2560x2160 (X and Y)
|
||||
# Values are rounded to multiples of 16
|
||||
if 'image_width' in d and d['image_width'] is not None:
|
||||
width = d['image_width']
|
||||
self.pxRoiX_lo.set(2560/2-width/2).wait()
|
||||
self.pxRoiX_hi.set(2560/2+width/2).wait()
|
||||
if 'image_height' in d and d['image_height'] is not None:
|
||||
height = d['image_height']
|
||||
self.pxRoiY_lo.set(2160/2-height/2).wait()
|
||||
self.pxRoiY_hi.set(2160/2+height/2).wait()
|
||||
if 'image_binx' in d and d['image_binx'] is not None:
|
||||
self.pxBinX.set(d['image_binx']).wait()
|
||||
if 'image_biny' in d and d['image_biny'] is not None:
|
||||
self.pxBinY.set(d['image_biny']).wait()
|
||||
if "image_width" in d and d["image_width"] is not None:
|
||||
width = d["image_width"]
|
||||
self.pxRoiX_lo.set(2560 / 2 - width / 2).wait()
|
||||
self.pxRoiX_hi.set(2560 / 2 + width / 2).wait()
|
||||
if "image_height" in d and d["image_height"] is not None:
|
||||
height = d["image_height"]
|
||||
self.pxRoiY_lo.set(2160 / 2 - height / 2).wait()
|
||||
self.pxRoiY_hi.set(2160 / 2 + height / 2).wait()
|
||||
if "image_binx" in d and d["image_binx"] is not None:
|
||||
self.pxBinX.set(d["image_binx"]).wait()
|
||||
if "image_biny" in d and d["image_biny"] is not None:
|
||||
self.pxBinY.set(d["image_biny"]).wait()
|
||||
|
||||
# Call super() to commit the changes
|
||||
super().configure(d)
|
||||
@@ -379,7 +475,7 @@ class PcoEdge5M(HelgeCameraBase):
|
||||
|
||||
# Automatically connect to test camera if directly invoked
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
# Drive data collection
|
||||
cam = PcoEdgeBase("X02DA-CCDCAM2:", name="mcpcam")
|
||||
cam.wait_for_connection()
|
||||
@@ -119,7 +119,7 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
for msg in self.parent._wsclient:
|
||||
message = json.loads(msg)
|
||||
self.parent.runstatus.put(message["status"], force=True)
|
||||
logger.info(f"[{self.parent.name}] Pushed status: {message['status']}")
|
||||
# logger.info(f"[{self.parent.name}] Pushed status: {message['status']}")
|
||||
except (ConnectionClosedError, ConnectionClosedOK, AssertionError):
|
||||
# Libraty throws theese after connection is closed
|
||||
return
|
||||
|
||||
@@ -97,7 +97,7 @@ class TomcatStepScan(ScanBase):
|
||||
yield from self._move_scan_motors_and_wait(pos)
|
||||
time.sleep(self.settling_time)
|
||||
|
||||
trigger_time = self.exp_time * self.burst_at_each_point
|
||||
trigger_time = 0.001*self.exp_time * self.burst_at_each_point
|
||||
yield from self.stubs.trigger(min_wait=trigger_time)
|
||||
# yield from self.stubs.trigger(group='trigger', point_id=self.point_id)
|
||||
# time.sleep(trigger_time)
|
||||
|
||||
@@ -46,7 +46,7 @@ class AcquireDark(Acquire):
|
||||
|
||||
def scan_core(self):
|
||||
# close the shutter
|
||||
# yield from self._move_scan_motors_and_wait(self.dark_shutter_pos)
|
||||
yield from self._move_scan_motors_and_wait(self.dark_shutter_pos)
|
||||
#yield from self.stubs.set_and_wait(device=[self.shutter], positions=[0])
|
||||
yield from super().scan_core()
|
||||
|
||||
|
||||
@@ -18,6 +18,25 @@ def dev_disable_all():
|
||||
dev[k].enabled = False
|
||||
|
||||
|
||||
def cam_select(camera: str):
|
||||
"""Select the active camera pipeline"""
|
||||
if isinstance(camera, str):
|
||||
if camera in ("gf", "gf2", "gigafrost"):
|
||||
dev.gfcam.enabled = True
|
||||
dev.gfdaq.enabled = True
|
||||
dev.daq_stream0.enabled = True
|
||||
dev.pcocam.enabled = False
|
||||
dev.pcodaq.enabled = False
|
||||
dev.pco_stream0.enabled = False
|
||||
if camera in ("pco", "pco.edge", "pcoedge"):
|
||||
dev.gfcam.enabled = False
|
||||
dev.gfdaq.enabled = False
|
||||
dev.daq_stream0.enabled = False
|
||||
dev.pcocam.enabled = True
|
||||
dev.pcodaq.enabled = True
|
||||
dev.pco_stream0.enabled = True
|
||||
|
||||
|
||||
|
||||
def anotherstepscan(
|
||||
scan_start,
|
||||
@@ -26,8 +45,9 @@ def anotherstepscan(
|
||||
exp_time=0.005,
|
||||
exp_burst=5,
|
||||
settling_time=0,
|
||||
image_width=2016,
|
||||
image_height=2016,
|
||||
camera=None,
|
||||
image_width=None,
|
||||
image_height=None,
|
||||
sync="inp1",
|
||||
**kwargs
|
||||
):
|
||||
@@ -41,31 +61,49 @@ def anotherstepscan(
|
||||
--------
|
||||
demostepscan(scan_start=-32, scan_end=148, steps=180, exp_time=0.005, exp_burst=5)
|
||||
"""
|
||||
# if not bl_check_beam():
|
||||
# raise RuntimeError("Beamline is not in ready state")
|
||||
# Check beamline status before scan
|
||||
if not bl_check_beam():
|
||||
raise RuntimeError("Beamline is not in ready state")
|
||||
# Enable the correct camera before scan
|
||||
cam_select(camera)
|
||||
# This scan uses software triggering
|
||||
if isinstance(camera, str):
|
||||
if camera in ("gf", "gf2", "gigafrost"):
|
||||
dev.gfcam.software_trigger = True
|
||||
if camera in ("pco", "pco.edge", "pcoedge"):
|
||||
dev.pcocam.software_trigger = True
|
||||
|
||||
dev_disable_all()
|
||||
# Disable aerotech controller
|
||||
# dev.es1_tasks.enabled = False
|
||||
# dev.es1_psod.enabled = False
|
||||
# dev.es1_ddaq.enabled = True
|
||||
# Enable scan motor
|
||||
dev.es1_roty.enabled = True
|
||||
#dev.es1_ddaq.enabled = True
|
||||
dev.gfcam.enabled = True
|
||||
dev.gfdaq.enabled = True
|
||||
dev.daq_stream0.enabled = True
|
||||
dev.daq_stream1.enabled = False
|
||||
|
||||
|
||||
|
||||
print("Handing over to 'scans.tomcatstepscan'")
|
||||
scans.tomcatstepscan(
|
||||
scan_start=scan_start,
|
||||
scan_end=scan_end,
|
||||
steps=steps,
|
||||
exp_time=exp_time,
|
||||
exp_burst=exp_burst,
|
||||
relative=False,
|
||||
image_width=image_width,
|
||||
image_height=image_height,
|
||||
settling_time=settling_time,
|
||||
sync=sync,
|
||||
**kwargs
|
||||
)
|
||||
try:
|
||||
scans.tomcatstepscan(
|
||||
scan_start=scan_start,
|
||||
scan_end=scan_end,
|
||||
steps=steps,
|
||||
exp_time=exp_time,
|
||||
exp_burst=exp_burst,
|
||||
relative=False,
|
||||
image_width=image_width,
|
||||
image_height=image_height,
|
||||
settling_time=settling_time,
|
||||
sync=sync,
|
||||
**kwargs
|
||||
)
|
||||
finally:
|
||||
# if isinstance(camera, str):
|
||||
# if camera in ("gf", "gf2", "gigafrost"):
|
||||
# dev.gfcam.software_trigger = False
|
||||
# if camera in ("pco", "pco.edge", "pcoedge"):
|
||||
# dev.pcocam.software_trigger = False
|
||||
pass
|
||||
|
||||
|
||||
|
||||
@@ -78,8 +116,9 @@ def anothersequencescan(
|
||||
repmode="PosNeg",
|
||||
exp_time=0.005,
|
||||
exp_burst=180,
|
||||
image_width=2016,
|
||||
image_height=2016,
|
||||
camera=None,
|
||||
image_width=None,
|
||||
image_height=None,
|
||||
sync="pso",
|
||||
**kwargs
|
||||
):
|
||||
@@ -96,16 +135,15 @@ def anothersequencescan(
|
||||
--------
|
||||
>>> anothersequencescan(33, 180, 180, exp_time=0.005, exp_frames=1800, repeats=10)
|
||||
"""
|
||||
# Check beamline status before scan
|
||||
if not bl_check_beam():
|
||||
raise RuntimeError("Beamline is not in ready state")
|
||||
|
||||
# Enable the correct camera before scan
|
||||
cam_select(camera)
|
||||
# Enabling aerotech controller
|
||||
dev.es1_tasks.enabled = True
|
||||
dev.es1_psod.enabled = False
|
||||
dev.es1_ddaq.enabled = True
|
||||
dev.gfcam.enabled = True
|
||||
dev.gfdaq.enabled = True
|
||||
dev.daq_stream0.enabled = True
|
||||
dev.daq_stream1.enabled = False
|
||||
|
||||
print("Handing over to 'scans.sequencescan'")
|
||||
scans.tomcatsimplesequencescan(
|
||||
@@ -131,8 +169,9 @@ def anothersnapnstepscan(
|
||||
steps,
|
||||
exp_time=0.005,
|
||||
exp_burst=180,
|
||||
image_width=2016,
|
||||
image_height=2016,
|
||||
camera=None,
|
||||
image_width=None,
|
||||
image_height=None,
|
||||
settling_time=0.1,
|
||||
sync="pso",
|
||||
**kwargs
|
||||
@@ -148,17 +187,15 @@ def anothersnapnstepscan(
|
||||
--------
|
||||
>>> anothersnapnstepscan(33, 180, 180, exp_time=0.005, exp_frames=1800, repeats=10)
|
||||
"""
|
||||
# Check beamline status before scan
|
||||
if not bl_check_beam():
|
||||
raise RuntimeError("Beamline is not in ready state")
|
||||
|
||||
# Enable the correct camera before scan
|
||||
cam_select(camera)
|
||||
# Enabling aerotech controller
|
||||
dev.es1_tasks.enabled = True
|
||||
dev.es1_psod.enabled = False
|
||||
dev.es1_ddaq.enabled = True
|
||||
dev.gfcam.enabled = True
|
||||
dev.gfdaq.enabled = True
|
||||
dev.daq_stream0.enabled = True
|
||||
dev.daq_stream1.enabled = False
|
||||
|
||||
|
||||
print("Handing over to 'scans.tomcatsnapnstepscan'")
|
||||
scans.tomcatsnapnstepscan(
|
||||
@@ -174,3 +211,58 @@ def anothersnapnstepscan(
|
||||
**kwargs
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def ascan(motor, scan_start, scan_end, steps, exp_time, datasource, visual=True, **kwargs):
|
||||
"""Demo step scan with plotting
|
||||
|
||||
This is a simple user-space demo step scan with automatic plorring and fitting via the BEC.
|
||||
It's mostly a standard BEC scan, just adds GUI setup and fits a hardcoded LlinearModel at
|
||||
the end (other models are more picky on the initial parameters).
|
||||
|
||||
Example:
|
||||
--------
|
||||
ascan(dev.dccm_energy, 12,13, steps=21, exp_time=0.1, datasource=dev.dccm_xbpm)
|
||||
"""
|
||||
# Dummy method to check beamline status
|
||||
if not bl_check_beam():
|
||||
raise RuntimeError("Beamline is not in ready state")
|
||||
|
||||
if visual:
|
||||
# Get or create scan specific window
|
||||
window = None
|
||||
for _, val in bec.gui.windows.items():
|
||||
if val.title == "CurrentScan":
|
||||
window = val.widget
|
||||
window.clear_all()
|
||||
if window is None:
|
||||
window = bec.gui.new("CurrentScan")
|
||||
|
||||
# Draw a simploe plot in the window
|
||||
dock = window.add_dock(f"ScanDisplay {motor}")
|
||||
plt1 = dock.add_widget("BECWaveformWidget")
|
||||
plt1.plot(x_name=motor, y_name=datasource)
|
||||
plt1.set_x_label(motor)
|
||||
plt1.set_y_label(datasource)
|
||||
plt1.add_dap(motor, datasource, dap="LinearModel")
|
||||
window.show()
|
||||
|
||||
print("Handing over to 'scans.line_scan'")
|
||||
if "relative" in kwargs:
|
||||
del kwargs["relative"]
|
||||
s = scans.line_scan(
|
||||
motor, scan_start, scan_end, steps=steps, exp_time=exp_time, relative=False, **kwargs
|
||||
)
|
||||
|
||||
if visual:
|
||||
fit = plt1.get_dap_params()
|
||||
else:
|
||||
fit = bec.dap.LinearModel.fit(s, motor.name, motor.name, datasource.name, datasource.name)
|
||||
|
||||
# TODO: Move to fitted value... like center, peak, edge, etc...
|
||||
|
||||
return s, fit
|
||||
@@ -4,9 +4,7 @@
|
||||
|
||||
def bl_check_beam():
|
||||
"""Checks beamline status"""
|
||||
motor_enabled = bool(dev.es1_roty.motor_enable.get())
|
||||
result = motor_enabled
|
||||
return result
|
||||
return True
|
||||
|
||||
|
||||
def demostepscan(
|
||||
|
||||
Reference in new issue
Block a user