More BEC like tomcat scans
This commit is contained in:
@@ -99,7 +99,7 @@ es1_psod:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
softwareTrigger: true
|
||||
|
||||
|
||||
es1_ddaq:
|
||||
|
||||
@@ -75,6 +75,7 @@ class AerotechPsoDistanceMixin(CustomDeviceMixin):
|
||||
def on_trigger(self) -> None | DeviceStatus:
|
||||
"""Fire a single PSO event (i.e. manual software trigger)"""
|
||||
# Only trigger if distance was set to invalid
|
||||
logger.warning(f"[{self.parent.name}] Triggerin...")
|
||||
if self.parent.dstDistanceVal.get() == 0:
|
||||
status = self.parent._eventSingle.set(1, settle_time=0.1)
|
||||
return status
|
||||
|
||||
@@ -55,7 +55,7 @@ class AerotechTasksMixin(CustomDeviceMixin):
|
||||
settle_time = 0.2
|
||||
if self.parent._is_configured:
|
||||
if self.parent._text_to_execute is not None:
|
||||
status = self.parent._execute.set(self._text_to_execute, settle_time=settle_time)
|
||||
status = self.parent._execute.set(self.parent._text_to_execute, settle_time=settle_time)
|
||||
else:
|
||||
status = self.parent.switch.set("Run", settle_time=settle_time)
|
||||
else:
|
||||
|
||||
@@ -36,7 +36,7 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
# Fish out our configuration from scaninfo (via explicit or generic addressing)
|
||||
scanparam = self.parent.scaninfo.scan_msg.info
|
||||
alias = self.parent.parent.name if self.parent.parent is not None else self.parent.name
|
||||
logger.warning(f"[{alias}] Scan parameters:\n{scanparam}")
|
||||
# logger.warning(f"[{alias}] Scan parameters:\n{scanparam}")
|
||||
d = {}
|
||||
if 'kwargs' in scanparam:
|
||||
scanargs = scanparam['kwargs']
|
||||
@@ -52,6 +52,10 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
|
||||
# Perform bluesky-style configuration
|
||||
if len(d) > 0:
|
||||
# Stop if current status is not idle
|
||||
if self.parent.state() != "idle":
|
||||
self.parent.safestop()
|
||||
# Configure new run (will restart the stdDAQ)
|
||||
logger.warning(f"[{self.parent.name}] Configuring with:\n{d}")
|
||||
self.parent.configure(d=d)
|
||||
|
||||
@@ -64,19 +68,9 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
file_path = self.parent.file_path.get()
|
||||
num_images = self.parent.num_images.get()
|
||||
message = {"command": "start", "path": file_path, "n_image": num_images}
|
||||
|
||||
# FIXME: This should be simplified once we don't crash the DAQ with stop
|
||||
ii = 0
|
||||
self.parent.connect()
|
||||
while True:
|
||||
try:
|
||||
reply = self.parent.message(message)
|
||||
except ConnectionClosedOK:
|
||||
# # NOTE: Sometimes the stdDAQ rejects both start and stop commands, needs nuking
|
||||
# self.parent.restart()
|
||||
# sleep(0.5)
|
||||
self.parent.connect()
|
||||
reply = self.parent.message(message)
|
||||
reply = self.parent.message(message)
|
||||
|
||||
if reply is not None:
|
||||
reply = json.loads(reply)
|
||||
@@ -106,14 +100,8 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
def on_unstage(self):
|
||||
""" Stop a running acquisition and close connection
|
||||
"""
|
||||
if self.parent._wsclient is None:
|
||||
self.parent.connect()
|
||||
|
||||
try:
|
||||
self.parent.message({"command": "stop"}, wait_reply=False)
|
||||
except (ConnectionClosedOK, ConnectionClosedError):
|
||||
self.parent.connect()
|
||||
self.parent.message({"command": "stop"}, wait_reply=False)
|
||||
self.parent.message({"command": "stop_all"}, wait_reply=False)
|
||||
except (RuntimeError, TypeError):
|
||||
# The poller thread locks recv raising a RuntimeError
|
||||
pass
|
||||
@@ -136,12 +124,12 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
for msg in self.parent._wsclient:
|
||||
message = json.loads(msg)
|
||||
self.parent.status.put(message["status"], force=True)
|
||||
logger.warning(f"[{self.parent.name}] {message['status']}")
|
||||
logger.info(f"[{self.parent.name}] Pushed status: {message['status']}")
|
||||
except (ConnectionClosedError, ConnectionClosedOK, AssertionError):
|
||||
# Libraty throws theese after connection is closed
|
||||
return
|
||||
except Exception as ex:
|
||||
logger.warning(f"[{self.parent.name}] {ex}")
|
||||
logger.warning(f"[{self.parent.name}] Exception in polling: {ex}")
|
||||
return
|
||||
finally:
|
||||
self._mon = None
|
||||
@@ -164,7 +152,7 @@ class StdDaqClient(PSIDeviceBase):
|
||||
"""
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
custom_prepare_cls = StdDaqMixin
|
||||
USER_ACCESS = ["set_daq_config", "get_daq_config", "safestop", "restart", "connect"]
|
||||
USER_ACCESS = ["set_daq_config", "get_daq_config", "safestop", "restart", "connect", "message", "state"]
|
||||
_wsclient = None
|
||||
|
||||
# Status attributes
|
||||
@@ -236,19 +224,23 @@ class StdDaqClient(PSIDeviceBase):
|
||||
"The stdDAQ websocket interface refused connection 5 times.")
|
||||
logger.debug(f"[{self.name}] Connected to DAQ after {num_retry} tries")
|
||||
|
||||
def message(self, message: dict, timeout=1, wait_reply=True):
|
||||
def message(self, message: dict, timeout=1, wait_reply=True) -> None | str:
|
||||
"""Send a message to the StdDAQ and receive a reply
|
||||
|
||||
Note: finishing acquisition means StdDAQ will close connection, so
|
||||
there's no idle state polling.
|
||||
"""
|
||||
# Connect if client was destroyed
|
||||
if self._wsclient is None:
|
||||
self.connect()
|
||||
|
||||
# Send message (reopen connection if needed)
|
||||
msg = json.dumps(message) if isinstance(message, dict) else str(message)
|
||||
try:
|
||||
msg = json.dumps(message) if isinstance(message, dict) else str(message)
|
||||
self._wsclient.send(msg)
|
||||
except (ConnectionClosedError, ConnectionClosedOK) as ex:
|
||||
print(ex)
|
||||
# self.connect()
|
||||
except (ConnectionClosedError, ConnectionClosedOK, AttributeError) as ex:
|
||||
# Re-connect if the connection was closed
|
||||
self.connect()
|
||||
self._wsclient.send(msg)
|
||||
|
||||
# Wait for reply
|
||||
@@ -257,8 +249,8 @@ class StdDaqClient(PSIDeviceBase):
|
||||
try:
|
||||
reply = self._wsclient.recv(timeout)
|
||||
return reply
|
||||
except (ConnectionClosedError, ConnectionClosedOK, TimeoutError) as ex:
|
||||
print(ex)
|
||||
except (ConnectionClosedError, ConnectionClosedOK, TimeoutError, RuntimeError) as ex:
|
||||
logger.error(f"[{self.name}] Error in receiving ws reply: {ex}")
|
||||
return reply
|
||||
|
||||
def configure(self, d: dict = None):
|
||||
@@ -308,6 +300,10 @@ class StdDaqClient(PSIDeviceBase):
|
||||
# Restart the DAQ if resolution changed
|
||||
cfg = self.get_daq_config()
|
||||
if cfg['image_pixel_height'] != self.cfg_pixel_height.get() or cfg['image_pixel_width'] != self.cfg_pixel_width.get():
|
||||
# Stop if current status is not idle
|
||||
if self.state() != "idle":
|
||||
self.safestop()
|
||||
|
||||
# Stop running acquisition
|
||||
self.unstage()
|
||||
# Update retrieved config
|
||||
@@ -359,24 +355,44 @@ class StdDaqClient(PSIDeviceBase):
|
||||
cfg = self.get_daq_config()
|
||||
self.set_daq_config(cfg)
|
||||
|
||||
def safestop(self):
|
||||
"""
|
||||
The current stdDAQ refuses connection if another session is running. This is safety so
|
||||
we don't accidentally kill a running exposure. But this also means that we have to wait
|
||||
until a dead session dies of timeout.
|
||||
def state(self) -> str | None:
|
||||
""" Querry the current system state"""
|
||||
r = self.message({'command': 'status'}, wait_reply=True)
|
||||
if r is None:
|
||||
return None
|
||||
else:
|
||||
r = json.loads(r)
|
||||
return r['status']
|
||||
|
||||
NOTE: REST reconfiguration restarts with systemd and can corrupt currently written files.
|
||||
def safestop(self, timeout=5):
|
||||
""" Stops a running acquisition
|
||||
|
||||
REST reconfiguration restarts with systemd and can corrupt currently written files.
|
||||
"""
|
||||
try:
|
||||
if self._wsclient is not None:
|
||||
self._wsclient.close()
|
||||
self._wsclient = connect(self.ws_url.get())
|
||||
msg = json.dumps({"command": "stop"})
|
||||
self._wsclient.send(msg)
|
||||
except (ConnectionClosedError, ConnectionClosedOK, ConnectionRefusedError):
|
||||
pass
|
||||
self._staged = Staged.no
|
||||
sleep(1)
|
||||
# Retries to steal connection from poller
|
||||
state = self.state()
|
||||
for rr in range(5):
|
||||
try:
|
||||
r = self.message({"command": "stop_all"})
|
||||
if r is not None:
|
||||
r = json.loads(r)
|
||||
logger.warning(f"[{self.name}] Stop-all command {r['status']}")
|
||||
else:
|
||||
# NOTE: stdDAQ just closes connection if idle
|
||||
logger.warning(f"[{self.name}] Stop-all command unknown")
|
||||
finally:
|
||||
sleep(0.2)
|
||||
|
||||
# Wait until status is back to idle
|
||||
if timeout > 0.2:
|
||||
wait_time = 0
|
||||
while wait_time < timeout/5:
|
||||
state = self.state()
|
||||
if state is not None:
|
||||
if state in ['idle', 'stoped']:
|
||||
return
|
||||
sleep(0.2)
|
||||
wait_time += 0.2
|
||||
|
||||
|
||||
# Automatically connect to microXAS testbench if directly invoked
|
||||
|
||||
@@ -68,7 +68,8 @@ class EpicsMotorMR(EpicsMotor):
|
||||
except UnknownStatusFailure:
|
||||
status = DeviceStatus(self)
|
||||
status.set_finished()
|
||||
return status
|
||||
# return status
|
||||
raise
|
||||
|
||||
|
||||
class EpicsMotorEC(EpicsMotorMR):
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
from .tutorial_fly_scan import AcquireDark, AcquireFlat, TutorialFlyScanContLine
|
||||
from .tomcat_scanbase import SnapAndStepScanBase, GigaStepScanBase, SequenceScanBase, SimpleFlyer
|
||||
from .tomcat_scans import TomcatStepScan, TomcatSnapNStep, TomcatSimpleSequence
|
||||
|
||||
@@ -0,0 +1,415 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Tomcat scan base class examples
|
||||
|
||||
A collection of example scan base classes using Automation1 rotation stage,
|
||||
GigaFrost camera and the StandardDAQ pipeline.
|
||||
|
||||
Created on Mon Sep 16 16:45:11 2024
|
||||
|
||||
@author: mohacsi_i
|
||||
"""
|
||||
import jinja2
|
||||
import os
|
||||
import time
|
||||
|
||||
from bec_lib import bec_logger
|
||||
from bec_server.scan_server.scans import AsyncFlyScanBase, ScanBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
|
||||
class TomcatStepScan(ScanBase):
|
||||
"""Simple software step scan forTomcat
|
||||
|
||||
Example class for simple BEC-based step scans using the low-level API. All it does is
|
||||
translate conventional kwargs to tomcat specific naming scheme.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> scans.gigastep(scan_start=-25, scan_end=155, steps=180, exp_time=0.005, exp_burst=5)
|
||||
"""
|
||||
|
||||
scan_name = "tomcatstepscan"
|
||||
required_kwargs = ["scan_start", "scan_end", "steps"]
|
||||
gui_config = {
|
||||
"Movement parameters": ["steps"],
|
||||
"Acquisition parameters": ["exp_time", "burst_at_each_point", "roix", "roiy"],
|
||||
}
|
||||
|
||||
def _get_scan_motors(self):
|
||||
self.scan_motors = ["es1_roty"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
scan_start: float,
|
||||
scan_end: float,
|
||||
steps: int,
|
||||
exp_time=0.005,
|
||||
settling_time=0.2,
|
||||
burst_at_each_point=1,
|
||||
roix=2016,
|
||||
roiy=2016,
|
||||
sync="event",
|
||||
**kwargs,
|
||||
):
|
||||
# Converting generic kwargs to tomcat device configuration parameters
|
||||
# Used by gigafrost
|
||||
kwargs['parameter']['kwargs']["exposure_time_ms"] = 1000 * exp_time
|
||||
kwargs['parameter']['kwargs']["exposure_period_ms"] = 2 * 1000 * exp_time
|
||||
kwargs['parameter']['kwargs']["exposure_num_burst"] = burst_at_each_point
|
||||
kwargs['parameter']['kwargs']["image_width"] = roix
|
||||
kwargs['parameter']['kwargs']["image_height"] = roiy
|
||||
# Used by stdDAQ and DDC
|
||||
kwargs['parameter']['kwargs']["num_points_total"] = burst_at_each_point * (steps + 1)
|
||||
t_modes = {"pso": 0, "event": 1, "inp0": 2, "inp1": 4}
|
||||
ddc_trigger = t_modes[sync]
|
||||
kwargs['parameter']['kwargs']["ddc_trigger"] = ddc_trigger
|
||||
# Use PSO trigger
|
||||
kwargs['parameter']['kwargs']["pso_wavemode"] = "pulsed"
|
||||
|
||||
super().__init__(
|
||||
exp_time=exp_time,
|
||||
settling_time=settling_time,
|
||||
relative=False,
|
||||
burst_at_each_point=1,
|
||||
optim_trajectory=None,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# For position calculation
|
||||
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
|
||||
|
||||
|
||||
def _calculate_positions(self) -> None:
|
||||
"""Pre-calculate scan positions"""
|
||||
for ii in range(self.scan_steps + 1):
|
||||
self.positions.append(self.scan_start + ii * self.scan_stepsize)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TomcatSnapNStep(AsyncFlyScanBase):
|
||||
"""Simple software step scan forTomcat
|
||||
|
||||
Example class for simple BEC-based step scans using the low-level API. All it does is
|
||||
translate conventional kwargs to tomcat specific naming scheme.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> scans.tomcatsnapnstepscan(scan_start=-25, scan_end=155, steps=180, exp_time=0.005, exp_burst=5)
|
||||
"""
|
||||
|
||||
scan_name = "tomcatsnapnstepscan"
|
||||
required_kwargs = ["scan_start", "scan_end", "steps"]
|
||||
gui_config = {
|
||||
"Movement parameters": ["steps"],
|
||||
"Acquisition parameters": ["exp_time", "burst_at_each_point", "roix", "roiy"],
|
||||
}
|
||||
|
||||
def _get_scan_motors(self):
|
||||
self.scan_motors = ["es1_roty"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
scan_start: float,
|
||||
scan_end: float,
|
||||
steps: int,
|
||||
exp_time=0.005,
|
||||
settling_time=0.2,
|
||||
burst_at_each_point=1,
|
||||
roix=2016,
|
||||
roiy=2016,
|
||||
sync="event",
|
||||
**kwargs,
|
||||
):
|
||||
# Converting generic kwargs to tomcat device configuration parameters
|
||||
# Used by gigafrost
|
||||
kwargs['parameter']['kwargs']["exposure_time_ms"] = 1000 * exp_time
|
||||
kwargs['parameter']['kwargs']["exposure_period_ms"] = 2 * 1000 * exp_time
|
||||
kwargs['parameter']['kwargs']["exposure_num_burst"] = burst_at_each_point
|
||||
kwargs['parameter']['kwargs']["image_width"] = roix
|
||||
kwargs['parameter']['kwargs']["image_height"] = roiy
|
||||
# Used by stdDAQ and DDC
|
||||
kwargs['parameter']['kwargs']["num_points_total"] = burst_at_each_point * (steps + 1)
|
||||
t_modes = {"pso": 0, "event": 1, "inp0": 2, "inp1": 4}
|
||||
ddc_trigger = t_modes[sync]
|
||||
kwargs['parameter']['kwargs']["ddc_trigger"] = ddc_trigger
|
||||
# Use PSO trigger (disable triggering)
|
||||
kwargs['parameter']['kwargs']["pso_distance"] = 0
|
||||
|
||||
# For position calculation
|
||||
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)
|
||||
|
||||
# Aerotech DDC settings: Internal event trigger: PsoEvent = 1
|
||||
p_modes = {"pso": "PsoOutput", "event": "PsoEvent", "inp0": "HighSpeedInput0RisingEdge", "inp1": "HighSpeedInput1RisingEdge",}
|
||||
filename = "AerotechSnapAndStepTemplate.ascript"
|
||||
filesubs = {
|
||||
"startpos": self.scan_start,
|
||||
"stepsize": self.scan_stepsize,
|
||||
"numsteps": self.scan_steps,
|
||||
"exptime": 2 * exp_time * burst_at_each_point,
|
||||
"settling": settling_time,
|
||||
"psotrigger": p_modes[sync],
|
||||
"npoints": self.scan_ntotal,
|
||||
}
|
||||
filetext = self.render_file(filename, filesubs)
|
||||
|
||||
# Task inteface
|
||||
kwargs['parameter']['kwargs']["script_text"] = filetext
|
||||
kwargs['parameter']['kwargs']["script_file"] = "bec.ascript"
|
||||
|
||||
|
||||
super().__init__(
|
||||
exp_time=exp_time,
|
||||
settling_time=settling_time,
|
||||
relative=False,
|
||||
burst_at_each_point=1,
|
||||
optim_trajectory=None,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def render_file(self, filename, filesubs):
|
||||
"""Prepare action: render AeroScript file"""
|
||||
# Load the test file
|
||||
if filename is not None:
|
||||
filename = os.path.join(
|
||||
os.path.dirname(__file__), "../devices/aerotech/" + filename
|
||||
)
|
||||
logger.info(f"Attempting to load file {filename}")
|
||||
with open(filename) as f:
|
||||
templatetext = f.read()
|
||||
|
||||
# Substitute jinja template
|
||||
tm = jinja2.Template(templatetext)
|
||||
filetext = tm.render(scan=filesubs)
|
||||
return filetext
|
||||
|
||||
def scan_core(self):
|
||||
"""The actual scan routine"""
|
||||
print("TOMCAT Running scripted scan (via Jinjad AeroScript)")
|
||||
t_start = time.time()
|
||||
|
||||
# Complete
|
||||
# FIXME: this will swallow errors
|
||||
# yield from self.stubs.complete(device="es1_tasks")
|
||||
st = yield from self.stubs.send_rpc_and_wait("es1_tasks", "complete")
|
||||
st.wait()
|
||||
task_states = yield from self.stubs.send_rpc_and_wait("es1_tasks", "taskStates.get")
|
||||
if task_states[4] == 8:
|
||||
raise RuntimeError(f"Task {4} finished in ERROR state")
|
||||
|
||||
t_end = time.time()
|
||||
t_elapsed = t_end - t_start
|
||||
print(f"Elapsed scan time: {t_elapsed}")
|
||||
time.sleep(0.5)
|
||||
|
||||
# Collect
|
||||
# if self.daqname is not None and self.daqmode=="collect":
|
||||
# print("TOMCAT Collecting scripted scan results (from EPICS)")
|
||||
# positions = yield from self.stubs.send_rpc_and_wait(self.daqname, "collect")
|
||||
# logger.info(f"Finished scan with collected positions: {positions}")
|
||||
|
||||
|
||||
def cleanup(self):
|
||||
"""Set scan progress to 1 to finish the scan"""
|
||||
self.num_pos = 1
|
||||
return super().cleanup()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TomcatSimpleSequence(AsyncFlyScanBase):
|
||||
"""Simple software step scan forTomcat
|
||||
|
||||
Example class for simple BEC-based step scans using the low-level API. All it does is
|
||||
translate conventional kwargs to tomcat specific naming scheme.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> scans.tomcatsimplesequencescan(33, 180, 180, exp_time=0.005, exp_frames=1800, repeats=10)
|
||||
"""
|
||||
|
||||
scan_name = "tomcatsimplesequencescan"
|
||||
scan_report_hint = "table"
|
||||
required_kwargs = ["scan_start", "gate_high", "gate_low"]
|
||||
gui_config = {
|
||||
"Movement parameters": ["scan_start", "repeats", "repmode"],
|
||||
"Acquisition parameters": [
|
||||
"gate_high",
|
||||
"gate_low",
|
||||
"exp_time",
|
||||
"exp_frames",
|
||||
"roix",
|
||||
"roiy",
|
||||
"sync",
|
||||
],
|
||||
}
|
||||
|
||||
def _get_scan_motors(self):
|
||||
self.scan_motors = ["es1_roty"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
scan_start: float,
|
||||
gate_high: float,
|
||||
gate_low: float,
|
||||
repeats: int=1,
|
||||
repmode: str="PosNeg",
|
||||
exp_time: float=0.005,
|
||||
exp_frames: float=180,
|
||||
roix: int=2016,
|
||||
roiy: int=2016,
|
||||
sync: str="pso",
|
||||
**kwargs,
|
||||
):
|
||||
# Auto-setup configuration parameters from input
|
||||
self.scan_start = scan_start
|
||||
self.gate_high = gate_high
|
||||
self.gate_low = gate_low
|
||||
self.scan_repnum = repeats
|
||||
self.scan_repmode = repmode.upper()
|
||||
self.exp_time = exp_time
|
||||
self.exp_frames = exp_frames
|
||||
|
||||
# Synthetic values
|
||||
self.scan_velocity = gate_high / (exp_time * exp_frames)
|
||||
self.scan_acceleration = 500
|
||||
self.scan_safedistance = 10
|
||||
self.scan_accdistance = (
|
||||
self.scan_safedistance
|
||||
+ 0.5 * self.scan_velocity * self.scan_velocity / self.scan_acceleration
|
||||
)
|
||||
self.scan_ntotal = exp_frames * repeats
|
||||
|
||||
if self.scan_repmode in ("POS", "NEG"):
|
||||
self.scan_range = repeats * (gate_high + gate_low)
|
||||
elif self.scan_repmode in ("POSNEG", "NEGPOS"):
|
||||
self.scan_range = gate_high + gate_low
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported repetition mode: {repmode}")
|
||||
|
||||
|
||||
|
||||
# Converting generic kwargs to tomcat device configuration parameters
|
||||
# Used by gigafrost
|
||||
kwargs['parameter']['kwargs']["exposure_time_ms"] = 1000 * exp_time
|
||||
kwargs['parameter']['kwargs']["exposure_period_ms"] = 2 * 1000 * exp_time
|
||||
kwargs['parameter']['kwargs']["exposure_num_burst"] = exp_frames
|
||||
kwargs['parameter']['kwargs']["image_width"] = roix
|
||||
kwargs['parameter']['kwargs']["image_height"] = roiy
|
||||
# Used by stdDAQ and DDC
|
||||
kwargs['parameter']['kwargs']["num_points_total"] = exp_frames * (self.scan_repnum + 1)
|
||||
t_modes = {"pso": 0, "event": 1, "inp0": 2, "inp1": 4}
|
||||
ddc_trigger = t_modes[sync]
|
||||
kwargs['parameter']['kwargs']["ddc_trigger"] = ddc_trigger
|
||||
# Use PSO trigger (disable triggering)
|
||||
kwargs['parameter']['kwargs']["pso_distance"] = 0
|
||||
|
||||
# Aerotech DDC settings: Internal event trigger: PsoEvent = 1
|
||||
p_modes = {"pso": "PsoOutput", "event": "PsoEvent", "inp0": "HighSpeedInput0RisingEdge", "inp1": "HighSpeedInput1RisingEdge",}
|
||||
filename = "AerotechSimpleSequenceTemplate.ascript"
|
||||
filesubs = {
|
||||
"startpos": self.scan_start,
|
||||
"scanrange": self.scan_range,
|
||||
"nrepeat": self.scan_repnum,
|
||||
"scanvel": self.scan_velocity,
|
||||
"scanacc": self.scan_acceleration,
|
||||
"accdist": self.scan_accdistance,
|
||||
"npoints": self.scan_ntotal,
|
||||
"scandir": self.scan_repmode.upper(),
|
||||
"psotrigger": p_modes[sync],
|
||||
}
|
||||
filesubs = self.get_positions(filesubs)
|
||||
filetext = self.render_file(filename, filesubs)
|
||||
|
||||
# Task inteface
|
||||
kwargs['parameter']['kwargs']["script_text"] = filetext
|
||||
kwargs['parameter']['kwargs']["script_file"] = "bec.ascript"
|
||||
|
||||
|
||||
super().__init__(
|
||||
exp_time=exp_time,
|
||||
settling_time=0.5,
|
||||
relative=False,
|
||||
burst_at_each_point=1,
|
||||
optim_trajectory=None,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def render_file(self, filename, filesubs):
|
||||
"""Prepare action: render AeroScript file"""
|
||||
# Load the test file
|
||||
if filename is not None:
|
||||
filename = os.path.join(
|
||||
os.path.dirname(__file__), "../devices/aerotech/" + filename
|
||||
)
|
||||
logger.info(f"Attempting to load file {filename}")
|
||||
with open(filename) as f:
|
||||
templatetext = f.read()
|
||||
|
||||
# Substitute jinja template
|
||||
tm = jinja2.Template(templatetext)
|
||||
filetext = tm.render(scan=filesubs)
|
||||
return filetext
|
||||
|
||||
def scan_core(self):
|
||||
"""The actual scan routine"""
|
||||
print("TOMCAT Running scripted scan (via Jinjad AeroScript)")
|
||||
t_start = time.time()
|
||||
|
||||
# Complete
|
||||
# FIXME: this will swallow errors
|
||||
# yield from self.stubs.complete(device="es1_tasks")
|
||||
st = yield from self.stubs.send_rpc_and_wait("es1_tasks", "complete")
|
||||
st.wait()
|
||||
task_states = yield from self.stubs.send_rpc_and_wait("es1_tasks", "taskStates.get")
|
||||
if task_states[4] == 8:
|
||||
raise RuntimeError(f"Task {4} finished in ERROR state")
|
||||
|
||||
t_end = time.time()
|
||||
t_elapsed = t_end - t_start
|
||||
print(f"Elapsed scan time: {t_elapsed}")
|
||||
time.sleep(0.5)
|
||||
|
||||
# Collect
|
||||
# if self.daqname is not None and self.daqmode=="collect":
|
||||
# print("TOMCAT Collecting scripted scan results (from EPICS)")
|
||||
# positions = yield from self.stubs.send_rpc_and_wait(self.daqname, "collect")
|
||||
# logger.info(f"Finished scan with collected positions: {positions}")
|
||||
|
||||
def get_positions(self, filesubs: dict={}):
|
||||
# Fill PSO vectors according to scan direction
|
||||
# NOTE: Distance counter is bidirectional
|
||||
pso_bounds_pos = []
|
||||
pso_bounds_neg = []
|
||||
if self.scan_repmode in ("pos", "Pos", "POS", "neg", "Neg", "NEG"):
|
||||
pso_bounds_pos.append(self.scan_accdistance)
|
||||
for ii in range(self.scan_repnum):
|
||||
pso_bounds_pos += [self.gate_high, self.gate_low]
|
||||
pso_bounds_pos = pso_bounds_pos[:-1]
|
||||
if self.scan_repmode in ("posneg", "PosNeg", "POSNEG", "negpos", "NegPos", "NEGPOS"):
|
||||
pso_bounds_pos = [self.scan_accdistance, self.gate_high]
|
||||
pso_bounds_neg = [self.scan_accdistance + self.gate_low, self.gate_high]
|
||||
|
||||
filesubs["psoBoundsPos"] = pso_bounds_pos
|
||||
filesubs["psoBoundsNeg"] = pso_bounds_neg
|
||||
filesubs["psoBoundsPosSize"] = len(pso_bounds_pos)
|
||||
filesubs["psoBoundsNegSize"] = len(pso_bounds_neg)
|
||||
return filesubs
|
||||
|
||||
def cleanup(self):
|
||||
"""Set scan progress to 1 to finish the scan"""
|
||||
self.num_pos = 1
|
||||
return super().cleanup()
|
||||
Reference in New Issue
Block a user