Demo step scans working
This commit is contained in:
@@ -89,12 +89,19 @@ es1_tasks:
|
||||
|
||||
|
||||
es1_psod:
|
||||
description: 'AA1 PSO output interface'
|
||||
description: 'AA1 PSO output interface (trigger)'
|
||||
deviceClass: tomcat_bec.devices.aa1AxisPsoDistance
|
||||
deviceConfig: {prefix: 'X02DA-ES1-SMP1:ROTY:PSO:'}
|
||||
onFailure: buffer
|
||||
deviceConfig:
|
||||
prefix: 'X02DA-ES1-SMP1:ROTY:PSO:'
|
||||
deviceTags:
|
||||
- es1
|
||||
- trigger
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
es1_ddaq:
|
||||
description: 'Automation1 position recording interface'
|
||||
@@ -153,16 +160,15 @@ daq_stream0:
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
|
||||
# Unthrottled stream
|
||||
# daq_stream1:
|
||||
# description: stdDAQ preview (4 at 10 Hz)
|
||||
# deviceClass: tomcat_bec.devices.StdDaqPreviewDetector
|
||||
# deviceConfig:
|
||||
# url: 'tcp://129.129.95.111:20001'
|
||||
# deviceTags:
|
||||
# - std-daq
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readOnly: false
|
||||
# readoutPriority: monitored
|
||||
# softwareTrigger: false
|
||||
daq_stream1:
|
||||
description: stdDAQ preview (1 at 5 Hz)
|
||||
deviceClass: tomcat_bec.devices.StdDaqPreviewDetector
|
||||
deviceConfig:
|
||||
url: 'tcp://129.129.95.111:20001'
|
||||
deviceTags:
|
||||
- std-daq
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
|
||||
@@ -512,8 +512,8 @@ class aa1AxisPsoBase(Device):
|
||||
USER_ACCESS = ["configure"]
|
||||
# ########################################################################
|
||||
# General module status
|
||||
status = Component(EpicsSignalRO, "STATUS", auto_monitor=True, kind=Kind.hinted)
|
||||
output = Component(EpicsSignalRO, "OUTPUT-RBV", auto_monitor=True, kind=Kind.hinted)
|
||||
status = Component(EpicsSignalRO, "STATUS", auto_monitor=True, kind=Kind.normal)
|
||||
output = Component(EpicsSignalRO, "OUTPUT-RBV", auto_monitor=True, kind=Kind.normal)
|
||||
address = Component(EpicsSignalRO, "ARRAY-ADDR", kind=Kind.config)
|
||||
_eventSingle = Component(EpicsSignal, "EVENT:SINGLE", put_complete=True, kind=Kind.omitted)
|
||||
|
||||
@@ -561,9 +561,12 @@ class aa1AxisPsoBase(Device):
|
||||
outPin = Component(EpicsSignalRO, "PIN", auto_monitor=True, kind=Kind.config)
|
||||
outSource = Component(EpicsSignal, "SOURCE", put_complete=True, kind=Kind.config)
|
||||
|
||||
def trigger(self, settle_time=0.1):
|
||||
def trigger(self, settle_time=0.1) -> DeviceStatus:
|
||||
"""Fire a single PSO event (i.e. manual software trigger)"""
|
||||
self._eventSingle.set(1, settle_time=settle_time).wait()
|
||||
status = DeviceStatus(self)
|
||||
status.set_finished()
|
||||
return status
|
||||
|
||||
def toggle(self):
|
||||
""" Toggle waveform outup"""
|
||||
@@ -658,6 +661,7 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
|
||||
pso.kickoff().wait()
|
||||
"""
|
||||
_distance_value = None
|
||||
USER_ACCESS = ["configure", "prepare", "kickoff", "complete"]
|
||||
|
||||
# ########################################################################
|
||||
# PSO high level interface
|
||||
@@ -698,27 +702,35 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
|
||||
return (old, new)
|
||||
|
||||
# ########################################################################
|
||||
# Bluesky step scan interface
|
||||
# Bluesky step scan interface gutted
|
||||
def stage(self):
|
||||
""" Stage the device: just ignore it... """
|
||||
return super().stage()
|
||||
|
||||
# ########################################################################
|
||||
# Bluesky flyer interface
|
||||
def prepare(self):
|
||||
""" Rearms the configured array"""
|
||||
# Rearm the configured array
|
||||
if isinstance(self._distance_value, (np.ndarray, list, tuple)):
|
||||
self.dstArrayRearm.set(1).wait()
|
||||
def prepare(self) -> DeviceStatus:
|
||||
""" Arm trigger for a synchronous or asynchronous acquisition"""
|
||||
if self._distance_value is not None:
|
||||
# Rearm the configured array
|
||||
if isinstance(self._distance_value, (np.ndarray, list, tuple)):
|
||||
self.dstArrayRearm.set(1).wait()
|
||||
# Start monitoring the counters
|
||||
self.dstEventsEna.set("On").wait()
|
||||
self.dstCounterEna.set("On").wait()
|
||||
status = DeviceStatus(self)
|
||||
status.set_finished()
|
||||
return status
|
||||
|
||||
def kickoff(self) -> DeviceStatus:
|
||||
""" Kick off an asynchronous acquisition"""
|
||||
# Rearm the configured array
|
||||
if isinstance(self._distance_value, (np.ndarray, list, tuple)):
|
||||
self.dstArrayRearm.set(1).wait()
|
||||
# Start monitoring the counters
|
||||
self.dstEventsEna.set("On").wait()
|
||||
self.dstCounterEna.set("On").wait()
|
||||
""" Kick off asynchronous triggering on the controller"""
|
||||
if self._distance_value is not None:
|
||||
# Rearm the configured array
|
||||
if isinstance(self._distance_value, (np.ndarray, list, tuple)):
|
||||
self.dstArrayRearm.set(1).wait()
|
||||
# Start monitoring the counters
|
||||
self.dstEventsEna.set("On").wait()
|
||||
self.dstCounterEna.set("On").wait()
|
||||
status = DeviceStatus(self)
|
||||
status.set_finished()
|
||||
print("PSO kicked off")
|
||||
@@ -883,7 +895,7 @@ class aa1AxisDriveDataCollection(Device):
|
||||
|
||||
# ########################################################################
|
||||
# General module status
|
||||
state = Component(EpicsSignalRO, "STATE", auto_monitor=True, kind=Kind.hinted)
|
||||
state = Component(EpicsSignalRO, "STATE", auto_monitor=True, kind=Kind.normal)
|
||||
nsamples_rbv = Component(EpicsSignalRO, "SAMPLES_RBV", auto_monitor=True, kind=Kind.hinted)
|
||||
_switch = Component(EpicsSignal, "ACQUIRE", put_complete=True, kind=Kind.omitted)
|
||||
_input0 = Component(EpicsSignal, "INPUT0", put_complete=True, kind=Kind.config)
|
||||
|
||||
@@ -430,12 +430,8 @@ class GigaFrostCamera(PSIDetectorBase):
|
||||
super().trigger()
|
||||
|
||||
# There's no status readback from the camera, so we just wait
|
||||
status = DeviceStatus(self)
|
||||
sleep_time = self.cfgExposure.value*self.cfgCntNum.value*0.001+0.050
|
||||
sleep(sleep_time)
|
||||
logger.debug(f"[{self.name}] Slept for {sleep_time} seconds")
|
||||
status.set_finished()
|
||||
return status
|
||||
return DeviceStatus(self, done=True, success=True, settle_time=sleep_time)
|
||||
|
||||
def configure(self, d: dict = None):
|
||||
"""Configure the next scan with the GigaFRoST camera
|
||||
|
||||
@@ -143,9 +143,10 @@ class StdDaqPreviewDetector(PSIDetectorBase):
|
||||
url = Component(Signal, kind=Kind.config)
|
||||
throttle = Component(Signal, value=0.25, kind=Kind.config)
|
||||
status = Component(Signal, value=StdDaqPreviewState.UNKNOWN, kind=Kind.omitted)
|
||||
image = Component(Signal, kind=Kind.normal)
|
||||
frame = Component(Signal, kind=Kind.hinted)
|
||||
image_shape = Component(Signal, kind=Kind.normal)
|
||||
# FIXME: The BEC client caches the read()s from the last 50 scans
|
||||
image = Component(Signal, kind=Kind.config)
|
||||
|
||||
def __init__(
|
||||
self, *args, url: str = "tcp://129.129.95.38:20000", parent: Device = None, **kwargs
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
from .gigafrost_test import GigaFrostStepScan
|
||||
from .tomcat_scanbase import SnapAndStepScanBase, SimpleStepScanBase
|
||||
from .tomcat_scanbase import SnapAndStepScanBase, GigaStepScanBase
|
||||
|
||||
@@ -55,8 +55,6 @@ class TemplatedScanBase(AsyncFlyScanBase):
|
||||
scan_name = "aeroscript_scan_base"
|
||||
scan_report_hint = "table"
|
||||
required_kwargs = ["filename", "subs"]
|
||||
arg_input = {}
|
||||
arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None}
|
||||
|
||||
def __init__(self, *args, parameter: dict = None, **kwargs):
|
||||
"""Executes an AeroScript template as a flyer
|
||||
@@ -170,179 +168,99 @@ class TemplatedScanBase(AsyncFlyScanBase):
|
||||
|
||||
|
||||
|
||||
class GigaStepScanBase(ScanBase):
|
||||
""" Gigafrost Step scan base class
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SimpleStepScanBase(TemplatedScanBase):
|
||||
""" Simple Step scan base class
|
||||
|
||||
Example temporary base class for AeroScript-based step scans. As a temporary
|
||||
setup until the final cabling is done, the scan is in a soft-trigger mode.
|
||||
The Gigafrost is just software triggered, and the position readback is wired
|
||||
to the internal trigger.
|
||||
Example base class for AeroScript-based step scans using the low-level API.
|
||||
Until the final cabling is done, the scan is in soft-trigger mode.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> scans.simplestep(range=(0, 180), steps=1800, exp_time=20, exp_burst=5)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
range : (float, float)
|
||||
Scan range of the axis.
|
||||
steps : int, optional
|
||||
Number of scan steps to cover the range. (default = 10)
|
||||
exp_time : float, optional [0.01 ... 40]
|
||||
Exposure time for each frame in [ms]. The IOC fixes the exposure
|
||||
period to be 2x this long so it doesnt matter. (default = 20)
|
||||
exp_burst : float, optional
|
||||
Number of images to be taken for each scan point. (default=1)
|
||||
>>> scans.gigastep(scan_start=-25, scan_end=155, steps=180, exp_time=0.005, exp_burst=5)
|
||||
"""
|
||||
scan_name = "simplestep"
|
||||
scan_report_hint = "table"
|
||||
required_kwargs = ["range","steps"]
|
||||
arg_input = {}
|
||||
arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None}
|
||||
scan_name = "gigastep"
|
||||
required_kwargs = ["scan_start", "scan_end","steps"]
|
||||
gui_config = {
|
||||
"Movement parameters": ["steps"],
|
||||
"Acquisition parameters" : ["exp_time", "burst_at_each_point"]
|
||||
}
|
||||
|
||||
def __init__(self, *args, parameter: dict = None, **kwargs):
|
||||
"""Example step scan
|
||||
def __init__(
|
||||
self,
|
||||
scan_start: float,
|
||||
scan_end: float,
|
||||
steps: int,
|
||||
exp_time = 0.005,
|
||||
settling_time = 0.2,
|
||||
burst_at_each_point = 1,
|
||||
**kwargs):
|
||||
super().__init__(
|
||||
exp_time=exp_time,
|
||||
settling_time=settling_time,
|
||||
relative=False,
|
||||
burst_at_each_point=1,
|
||||
optim_trajectory=None,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
Perform a simple step scan with a motor while software triggering the
|
||||
gigafrost burst sequence at each point and recording it to the StdDAQ.
|
||||
Actually only the configuration is gigafrost specific, everything else
|
||||
is just using standard Bluesky event model.
|
||||
self.motor = "es1_roty"
|
||||
self.scan_start = scan_start
|
||||
self.scan_end = scan_end
|
||||
self.scan_steps = steps
|
||||
self.scan_stepsize = (scan_end - scan_start) / steps
|
||||
self.scan_ntotal = burst_at_each_point * (steps + 1)
|
||||
self.scan_nburst = burst_at_each_point
|
||||
|
||||
"""
|
||||
# Auto-setup configuration parameters from input
|
||||
logger.info(parameter)
|
||||
self.scan_axis = "es1_roty"
|
||||
self.scan_range = parameter['kwargs'].get("range")
|
||||
self.scan_stepnum = int(parameter['kwargs'].get("steps"))
|
||||
self.scan_exptime = float(parameter['kwargs'].get("exp_time", 20))
|
||||
self.scan_expburst = float(parameter['kwargs'].get("exp_burst", 1))
|
||||
self.scan_roix = int(parameter['kwargs'].get("roix", 2016))
|
||||
self.scan_roiy = int(parameter['kwargs'].get("roiy", 2016))
|
||||
# Override wait time with actual latency
|
||||
# NOTE : The BEC does not wait for trigger() status done
|
||||
self.exp_time = settling_time + exp_time * 2 * burst_at_each_point
|
||||
|
||||
# Synthetic values
|
||||
self.scan_startpos = self.scan_range[0]
|
||||
self.scan_stepsize = (self.scan_range[1]-self.scan_range[0])/self.scan_stepnum
|
||||
self.scan_ntotal = self.scan_stepnum * self.scan_expburst
|
||||
|
||||
# Aerotech DDC settings:
|
||||
# PsoOut = 0
|
||||
# PsoEvent = 1
|
||||
# PsoHsInp0_rise = 2
|
||||
# PsoHsInp0_fall = 3
|
||||
# PsoHsInp1_rise = 4
|
||||
# PsoHsInp1_fall = 5
|
||||
parameter['kwargs']['daqname'] = "es1_ddaq"
|
||||
parameter['kwargs']['daqcfg'] = {'ntotal': self.scan_ntotal, 'trigger': 1}
|
||||
# Gigafrost config
|
||||
parameter['kwargs']['camcfg'] = {
|
||||
# Prepare configuration dicts
|
||||
self.psocfg = {}
|
||||
self.daqcfg = {'ntotal': self.scan_ntotal, 'trigger': 1}
|
||||
self.camcfg = {
|
||||
"ntotal": self.scan_ntotal,
|
||||
"nimages": self.scan_expburst,
|
||||
"exposure": self.scan_exptime,
|
||||
"period": 2*self.scan_exptime,
|
||||
"pixel_width": self.scan_roix,
|
||||
"pixel_height": self.scan_roiy
|
||||
"nimages": burst_at_each_point,
|
||||
"exposure": 1000*exp_time,
|
||||
"period": 2000*exp_time,
|
||||
"pixel_width": 2016,
|
||||
"pixel_height": 2016
|
||||
}
|
||||
|
||||
# Parameter validation before scan
|
||||
if self.scan_stepnum <=0:
|
||||
raise RuntimeError(f"Requires at least one scan step, got {self.scan_stepnum}")
|
||||
|
||||
# Call super()
|
||||
self.filename = "foo"
|
||||
self.filetext = "bar"
|
||||
super().__init__(parameter=parameter, **kwargs)
|
||||
|
||||
def prepare_positions(self):
|
||||
def _calculate_positions(self) -> None:
|
||||
""" Pre-calculate scan positions """
|
||||
self.positions = [self.scan_startpos]
|
||||
for ii in range(self.scan_stepnum):
|
||||
self.positions.append(self.scan_startpos + ii*self.scan_stepsize)
|
||||
yield None
|
||||
|
||||
def pre_scan(self):
|
||||
# Move roughly to start position
|
||||
logger.info(f"\n\nScan start position: {self.scan_startpos} with axis {self.scan_axis}\n\n")
|
||||
yield from self.stubs.send_rpc_and_wait(self.scan_axis, "motor_enable.set", 1)
|
||||
st = yield from self.stubs.send_rpc_and_wait(self.scan_axis, "move", self.scan_startpos)
|
||||
st.wait()
|
||||
yield None
|
||||
self.positions = [self.scan_start]
|
||||
for ii in range(self.scan_steps):
|
||||
self.positions.append(self.scan_start + ii*self.scan_stepsize)
|
||||
|
||||
def stage(self):
|
||||
""" Configure and stage all devices"""
|
||||
""" First do the actual configuration then the gutted staging"""
|
||||
|
||||
print(f"TOMCAT Staging step scan with motors {self.scan_axis}")
|
||||
print(f"TOMCAT Staging step scan with motors {self.motor}")
|
||||
# Configure the camera (usually together wit the DAQ)
|
||||
yield from self.stubs.send_rpc_and_wait(self.camera, "configure", self.camcfg)
|
||||
|
||||
# Configure the camera (usually together wit the DAQ)
|
||||
if self.daqname is not None:
|
||||
yield from self.stubs.send_rpc_and_wait(self.daqname, "configure", self.daqcfg)
|
||||
|
||||
# ###################################################################################
|
||||
# Staging
|
||||
if self.camera is not None:
|
||||
yield from self.stubs.send_rpc_and_wait(self.camera, "stage")
|
||||
if self.daqname:
|
||||
yield from self.stubs.send_rpc_and_wait(self.daqname, "stage")
|
||||
if self.preview is not None:
|
||||
yield from self.stubs.send_rpc_and_wait(self.preview, "stage")
|
||||
# For God, NO!
|
||||
# yield from super().stage()
|
||||
|
||||
|
||||
def scan_core(self):
|
||||
""" Start"""
|
||||
t_start = time.time()
|
||||
|
||||
for ii in range(self.scan_stepnum):
|
||||
print(f"Point: {ii}")
|
||||
st = yield from self.stubs.send_rpc_and_wait(self.scan_axis, "move", self.positions[ii])
|
||||
st.wait()
|
||||
st = yield from self.stubs.send_rpc_and_wait("gfclient", "trigger")
|
||||
st = yield from self.stubs.send_rpc_and_wait("es1_psod", "trigger")
|
||||
time.sleep(0.1)
|
||||
|
||||
t_end = time.time()
|
||||
t_elapsed = t_end - t_start
|
||||
print(f"Elapsed scan time: {t_elapsed}")
|
||||
time.sleep(0.5)
|
||||
|
||||
# Collect
|
||||
if self.daqmode=="collect":
|
||||
positions = yield from self.stubs.send_rpc_and_wait(self.daqname, "collect")
|
||||
logger.info(f"Finished scan with collected positions: {positions}")
|
||||
|
||||
logger.info("Scan done\n\n")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
yield from self.stubs.send_rpc_and_wait("gfclient", "configure", self.camcfg)
|
||||
# Configure the position readback
|
||||
yield from self.stubs.send_rpc_and_wait("es1_ddaq", "configure", self.daqcfg)
|
||||
# Configure the manual triggers
|
||||
yield from self.stubs.send_rpc_and_wait("es1_psod", "configure", self.psocfg)
|
||||
|
||||
# Explicitly arm trigger
|
||||
yield from self.stubs.send_rpc_and_wait("es1_psod", "prepare")
|
||||
# TODO: Explicitly arm detector
|
||||
|
||||
# Explicitly arm trigger
|
||||
yield from self.stubs.send_rpc_and_wait("gfclient", "stage")
|
||||
yield from self.stubs.send_rpc_and_wait("es1_ddaq", "stage")
|
||||
yield from self.stubs.send_rpc_and_wait("es1_psod", "stage")
|
||||
yield from self.stubs.send_rpc_and_wait("daq_stream1", "stage")
|
||||
|
||||
# FIXME: Shouldn't this stage() everything?
|
||||
return super().stage()
|
||||
|
||||
def unstage(self):
|
||||
""" Wait for DAQ before unstaging"""
|
||||
time.sleep(5)
|
||||
yield from super().unstage()
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
""" Demo scans for Tomcat at the microXAS test bench
|
||||
"""
|
||||
|
||||
def demostepscan(scan_start, scan_end, steps, exp_time=0.005, burst_at_each_point=1):
|
||||
|
||||
def demostepscan(scan_start, scan_end, steps, exp_time=0.005, burst_at_each_point=1, settling_time=0):
|
||||
""" Demo step scan with GigaFrost
|
||||
|
||||
This is a small user-space demo step scan at the microXAS testbench using
|
||||
the gigafrost in software triggering mode. It tries to be as standard as
|
||||
possible, while still setting up the environment.
|
||||
|
||||
Example:
|
||||
--------
|
||||
demostepscan(scan_start=-32, scan_end=148, steps=180, exp_time=0.005, burst_at_each_point=5)
|
||||
"""
|
||||
scan_ntotal = burst_at_each_point * (steps + 1)
|
||||
print("NI")
|
||||
|
||||
# Move to start position
|
||||
cfg = {'ntotal': scan_ntotal, 'trigger': 1}
|
||||
@@ -11,17 +23,20 @@ def demostepscan(scan_start, scan_end, steps, exp_time=0.005, burst_at_each_poin
|
||||
"ntotal": scan_ntotal, "nimages": burst_at_each_point,
|
||||
"exposure": 1000*exp_time, "period": 2000*exp_time}
|
||||
dev.gfclient.configure(d=cfg)
|
||||
# Configure PSO trigger
|
||||
cfg = {}
|
||||
dev.es1_psod.configure(d=cfg)
|
||||
# Configure PSO trigger (trigger is normally disabled in fly mode)
|
||||
dev.es1_psod.configure(d={})
|
||||
dev.es1_psod.software_trigger = True
|
||||
|
||||
# Explicitly arm trigger (and detector in future)
|
||||
dev.es1_psod.prepare()
|
||||
|
||||
print("Handing over to 'scans.line_scan'")
|
||||
wait_time = 0.1 + 2*exp_time * burst_at_each_point
|
||||
scans.line_scan(
|
||||
dev.es1_roty, scan_start, scan_end,
|
||||
steps=steps, exp_time=2*exp_time, relative=False)
|
||||
steps=steps, exp_time=wait_time, relative=False, settling_time=settling_time)
|
||||
|
||||
# Move to start position
|
||||
umv(dev.es1_roty, scan_start)
|
||||
# Cleanup: disable SW trigger
|
||||
dev.es1_psod.software_trigger = False
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user