PCO step scanning works

This commit is contained in:
gac-x05la
2025-02-13 18:16:42 +01:00
parent aea898e60f
commit 163ef3c7a5
7 changed files with 358 additions and 171 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

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

View File

@@ -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()

View File

@@ -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

View File

@@ -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(