Work on sequence scaning

This commit is contained in:
gac-x05la
2024-10-11 16:06:06 +02:00
parent daa68c288a
commit 22c57aacd9
4 changed files with 300 additions and 60 deletions

View File

@@ -5,10 +5,10 @@
//
enum ScanType
Pos = 0
Neg = 1
PosNeg = 2
NegPos = 3
POS = 0
NEG = 1
POSNEG = 2
NEGPOS = 3
end
@@ -18,15 +18,14 @@ program
var $fStartPosition as real = {{ scan.startpos }}
var $fScanRange as real = $fStartPosition + {{ scan.scanrange }}
var $iNumRepeat as integer = {{ scan.nrepeat }}
var $eScanType as ScanType = ScanType.{{ scan.scandir or 'Pos' }}
var $eScanType as ScanType = ScanType.{{ scan.scandir or 'POS' }}
var $iNumDdcRead as integer = {{ scan.npoints }}
var $fVelJog as real = {{ scan.jogvel or 200 }}
var $fVelScan as real = {{ scan.scanvel }}
var $fAcceleration = {{ scan.scanacc or 500 }}
var $fSafeDist = 10.0
var $fAcceleration = {{ scan.scanacc }}
var $fAccDistance as real = {{ scan.accdist }}
//////////////////////////////////////////////////////////////////////////
// Internal parameters - dont use
var $axis as axis = ROTY
@@ -36,21 +35,20 @@ program
// Set acceleration
SetupAxisRampType($axis, RampType.Linear)
SetupAxisRampValue($axis,0,$fAcceleration)
var $fAccDistance as real = 0.5 * $fVelScan * $fVelScan / $fAcceleration + $fSafeDist
// set the actual scan range
var $fPosStart as real
var $fPosEnd as real
if $eScanType == ScanType.Pos
if $eScanType == ScanType.POS
$fPosStart = $fStartPosition - $fAccDistance
$fPosEnd = $fStartPosition + $fScanRange + $fAccDistance
elseif $eScanType == ScanType.Neg
elseif $eScanType == ScanType.NEG
$fPosStart = $fStartPosition + $fAccDistance
$fPosEnd = $fStartPosition - $fScanRange - $fAccDistance
elseif $eScanType == ScanType.PosNeg
elseif $eScanType == ScanType.POSNEG
$fPosStart = $fStartPosition - $fAccDistance
$fPosEnd = $fStartPosition + $fScanRange + $fAccDistance
elseif $eScanType == ScanType.NegPos
elseif $eScanType == ScanType.NEGPOS
$fPosStart = $fStartPosition + $fAccDistance
$fPosEnd = $fStartPosition - $fScanRange - $fAccDistance
end
@@ -67,9 +65,8 @@ program
PsoOutputConfigureSource($axis, PsoOutputSource.Waveform)
var $iPsoArrayAddr as integer = 0
// Simple PSO trigger pattern
var $iPsoArrayPos[] as real = [{% for psoDist in scan.psoBoundsPos[:-1] %} UnitsToCounts($axis, {{ psoDist }}), {% endfor %} UnitsToCounts($axis, {{ scan.psoBoundsPos[-1] }})]
// var $iPsoArrayPos[] as real = [UnitsToCounts($axis, $fAccDistance), {% for psoDist in scan.psoBoundsPos[:-1] %} UnitsToCounts($axis, psoDist), {% endfor %}]
// var $iPsoArrayNeg[] as real = [UnitsToCounts($axis, $fAccDistance), {% for psoDist in scan.psoBoundsNeg[:-1] %} UnitsToCounts($axis, psoDist), {% endfor %}]
var $iPsoArrayPos[] as real = [{% for psoDist in scan.psoBoundsPos[:-1] %} UnitsToCounts($axis, psoDist), {% endfor %}]
var $iPsoArrayNeg[] as real = [{% for psoDist in scan.psoBoundsNeg[:-1] %} UnitsToCounts($axis, psoDist), {% endfor %}]
DriveArrayWrite($axis, $iPsoArrayPos, $iPsoArrayAddr, length($iPsoArrayPos), DriveArrayType.PsoDistanceEventDistances)
PsoDistanceConfigureArrayDistances($axis, $iPsoArrayAddr, length($iPsoArrayPos), 0)
@@ -102,17 +99,17 @@ program
for $ii = 0 to ($iNumRepeat-1)
PsoDistanceConfigureArrayDistances($axis, $iPsoArrayAddr, length($iPsoArrayPos), 0)
if $eScanType == ScanType.Pos
if $eScanType == ScanType.POS
MoveAbsolute($axis, $fPosEnd, $fVelScan)
WaitForInPosition($axis)
MoveAbsolute($axis, $fPosStart, $fVelScan)
WaitForInPosition($axis)
elseif $eScanType == ScanType.Neg
elseif $eScanType == ScanType.NEG
MoveAbsolute($axis, $fPosEnd, $fVelScan)
WaitForInPosition($axis)
MoveAbsolute($axis, $fPosStart, $fVelScan)
WaitForInPosition($axis)
elseif $eScanType == ScanType.PosNeg
elseif $eScanType == ScanType.POSNEG
if ($ii % 2) == 0
MoveAbsolute($axis, $fPosEnd, $fVelScan)
WaitForInPosition($axis)
@@ -120,7 +117,7 @@ program
MoveAbsolute($axis, $fPosStart, $fVelScan)
WaitForInPosition($axis)
end
elseif $eScanType == ScanType.NegPos
elseif $eScanType == ScanType.NEGPOS
if ($ii % 2) == 0
MoveAbsolute($axis, $fPosEnd, $fVelScan)
WaitForInPosition($axis)

View File

@@ -1,2 +1,2 @@
from .gigafrost_test import GigaFrostStepScan
from .tomcat_scanbase import SnapAndStepScanBase, GigaStepScanBase
from .tomcat_scanbase import SnapAndStepScanBase, GigaStepScanBase, SequenceScanBase, SimpleFlyer

View File

@@ -62,8 +62,6 @@ class TemplatedScanBase(AsyncFlyScanBase):
drvdaq="es1_ddaq", daqcfg=None, daqmode="collect",
preview="daq_stream1",
**kwargs):
"""Executes an AeroScript template as a flyer
"""
super().__init__(**kwargs)
self.axis = []
self.num_pos = 0
@@ -171,14 +169,62 @@ class TemplatedScanBase(AsyncFlyScanBase):
class SimpleFlyer(AsyncFlyScanBase):
""" Simple fly scan base class
Very simple flyer class (something like the bluesky flyer).
It expects every device to be configured before the scan.
Example
-------
>>> scans.simpleflyer(sync_devices=['daq_stream1'], flyer_devices=['es1_tasks', 'es1_psod', 'es1_ddaq'])
"""
scan_name = "simpleflyer"
scan_report_hint = "table"
required_kwargs = ["sync_devices", "flyer_devices"]
def __init__(self, sync_devices=[], async_devices=[], **kwargs):
# Auto-setup configuration parameters from input
self.sync_devices = sync_devices
self.async_devices = async_devices
# Call super()
super().__init__(**kwargs)
def stage(self):
for dev in self.sync_devices:
yield from self.stubs.send_rpc_and_wait(dev, "stage")
for dev in self.async_devices:
yield from self.stubs.send_rpc_and_wait(dev, "stage")
yield from super().stage()
def scan_core(self):
for dev in self.async_devices:
yield from self.stubs.send_rpc_and_wait(dev, "kickoff")
for dev in self.async_devices:
yield from self.stubs.send_rpc_and_wait(dev, "complete")
def unstage(self):
""" Wait for DAQ before unstaging"""
time.sleep(5)
yield from super().unstage()
class GigaStepScanBase(ScanBase):
""" Gigafrost Step scan base class
""" Gigafrost software step scan base class
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 class for simple BEC-based step scans using the low-level API.
Until the final cabling is done, the scan can be tested in soft-trigger
mode.
Example
-------
@@ -254,7 +300,7 @@ class GigaStepScanBase(ScanBase):
yield from self.stubs.send_rpc_and_wait("es1_psod", "prepare")
# TODO: Explicitly arm detector
# Explicitly arm trigger
# Stage everything...
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")
@@ -270,14 +316,6 @@ class GigaStepScanBase(ScanBase):
class SnapAndStepScanBase(TemplatedScanBase):
""" Snap'n Step scan base class
@@ -295,25 +333,15 @@ class SnapAndStepScanBase(TemplatedScanBase):
"""
scan_name = "snapnstep"
scan_report_hint = "table"
required_kwargs = ["range","steps"]
required_kwargs = ["scan_start", "scan_end","steps"]
required_kwargs = ["scan_start", "scan_end", "steps"]
gui_config = {
"Movement parameters": ["steps"],
"Acquisition parameters" : ["exp_time", "burst_at_each_point", "roix", "roiy"]
"Movement parameters": ["scan_start", "scan_end", "steps"],
"Acquisition parameters" : ["exp_time", "burst_at_each_point", "roix", "roiy", "sync"]
}
def __init__(self, scan_start, scan_end, steps,
exp_time=0.005, burst_at_each_point=1,
roix=2016, roiy=2016, **kwargs):
"""Example step scan
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.
"""
roix=2016, roiy=2016, sync="event", **kwargs):
# Auto-setup configuration parameters from input
self.scan_start = scan_start
self.scan_end = scan_end
@@ -334,8 +362,14 @@ class SnapAndStepScanBase(TemplatedScanBase):
}
# Aerotech DDC settings: Internal event trigger: PsoEvent = 1
daqname = "es1_ddaq"
daqcfg = {'ntotal': self.scan_ntotal, 'trigger': 1}
if isinstance(sync, str):
t_modes = {'event': 1, 'pso': 2, 'inp0': 3, 'inp1': 1}
daq_trigger = t_modes[sync]
else:
daq_trigger = int(sync)
daqname = "es1_ddaq"
daqcfg = {'ntotal': self.scan_ntotal, 'trigger': daq_trigger}
# Gigafrost config
camname = "gfclient"
camcfg = {
@@ -360,6 +394,146 @@ class SnapAndStepScanBase(TemplatedScanBase):
preview="daq_stream1",
**kwargs)
# def scan_core(self):
# """ Don't do the scan during testing"""
# # raise RuntimeError("You shall NOT PASSS!!!")
def unstage(self):
""" Wait for DAQ before unstaging"""
time.sleep(5)
yield from super().unstage()
class SequenceScanBase(TemplatedScanBase):
""" Sequence scan base class
Example base class for AeroScript-based sequence scans. This function
loads a pre-written AeroScript file on the Automation1 controller for
low latency gated scans.
NOTE: 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
-------
>>> scans.sequencescan(33, 180, 180, exp_time=0.005, exp_frames=1800, repeats=10)
"""
scan_name = "sequencescan"
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 __init__(self, scan_start, gate_high, gate_low, repeats=1, repmode="PosNeg",
exp_time=0.005, exp_frames=180,
roix=2016, roiy=2016, sync="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
self.exp_time = exp_time
self.exp_frames = exp_frames
self.acc_dist = 42
# 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 repmode.lower() in ("pos", "neg"):
self.scan_range = gate_high + gate_low
elif repmode.lower() in ("posneg", "negpos"):
self.scan_range = repeats *(gate_high + gate_low)
filename = "AerotechSimpleSequenceTemplate.ascript"
filesubs = {
'startpos': self.scan_start,
'scanrange': self.scan_range,
'scanacc': self.scan_acceleration,
'accdist': self.scan_accdistance,
'npoints' : self.scan_ntotal,
'scandir' : self.scan_repmode.upper()
}
# Aerotech DDC settings: Internal event trigger: PsoEvent = 1
if isinstance(sync, str):
t_modes = {'event': 1, 'pso': 2, 'inp0': 3, 'inp1': 1}
daq_trigger = t_modes[sync]
else:
daq_trigger = int(sync)
daqname = "es1_ddaq"
daqcfg = {'ntotal': self.scan_ntotal, 'trigger': daq_trigger}
# Gigafrost config
camname = "gfclient"
camcfg = {
"ntotal": self.scan_ntotal,
"nimages": self.exp_frames,
"exposure": 1000 * exp_time,
"period": 2000*exp_time,
"pixel_width": roix,
"pixel_height": roiy
}
# Parameter validation before scan
if self.exp_frames <=0:
raise RuntimeError(f"Requires at least one frame per cycle, got {self.exp_frames}")
# Call super()
super().__init__(
filename=filename, filesubs=filesubs,
controller="es1_tasks", taskindex=4,
camera=camname, camcfg=camcfg,
drvdaq=daqname, daqcfg=daqcfg, daqmode="collect",
preview="daq_stream1",
**kwargs)
def prepare_positions(self):
# Fill PSO vectors according to scan direction
self.psoBoundsPos = []
self.psoBoundsNeg = []
if self.scan_repmode in ("pos", "Pos"):
self.psoBoundsPos.append(self.acc_dist)
for ii in range(self.scan_repnum):
self.psoBoundsPos += [self.gate_high, self.gate_low]
self.psoBoundsPos[-1] = 10* (self.acc_dist+self.gate_low)
if self.scan_repmode in ("neg", "Neg"):
self.psoBoundsNeg.append(self.acc_dist)
for ii in range(self.scan_repnum):
self.psoBoundsNeg += [self.gate_high, self.gate_low]
self.psoBoundsNeg[-1] = 10* (self.acc_dist+self.gate_low)
if self.scan_repmode in ("posneg", "PosNeg"):
self.psoBoundsPos.append(self.acc_dist)
self.psoBoundsNeg.append(self.acc_dist + self.gate_low)
for ii in range(self.scan_repnum // 2):
self.psoBoundsPos += [self.gate_high, self.gate_low]
self.psoBoundsNeg += [self.gate_high, self.gate_low]
self.psoBoundsPos[-1] = 10* (self.acc_dist+self.gate_low)
self.psoBoundsNeg[-1] = 10* (self.acc_dist+self.gate_low)
if self.scan_repmode in ("negpos", "NegPos"):
self.psoBoundsPos.append(self.acc_dist + self.gate_low)
self.psoBoundsNeg.append(self.acc_dist)
for ii in range(self.scan_repnum // 2):
self.psoBoundsPos += [self.gate_high, self.gate_low]
self.psoBoundsNeg += [self.gate_high, self.gate_low]
self.psoBoundsPos[-1] = 10* (self.acc_dist+self.gate_low)
self.psoBoundsNeg[-1] = 10* (self.acc_dist+self.gate_low)
self.filesubs['psoBoundsPos'] = self.psoBoundsPos
self.filesubs['psoBoundsNeg'] = self.psoBoundsNeg
# Call super() to do the substitutions
yield from super().prepare_positions()
def unstage(self):
""" Wait for DAQ before unstaging"""
time.sleep(5)
yield from super().unstage()

View File

@@ -1,22 +1,31 @@
""" Demo scans for Tomcat at the microXAS test bench
"""
def bl_check_beam():
""" Checks beamline status"""
return True
def demostepscan(scan_start, scan_end, steps, exp_time=0.005, burst_at_each_point=1, settling_time=0):
def demostepscan(scan_start, scan_end, steps, exp_time=0.005, burst_at_each_point=1, settling_time=0, sync='event'):
""" 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.
This is a small BEC user-space demo step scan at the microXAS testbench
using the gigafrost in software triggering mode. It tries to be a
standard BEC scan, 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)
"""
if not bl_check_beam():
raise RuntimeError("Beamline is not in ready state")
scan_ntotal = burst_at_each_point * (steps + 1)
# Move to start position
cfg = {'ntotal': scan_ntotal, 'trigger': 1}
t_modes = {'event': 1, 'pso': 2, 'inp0': 3, 'inp1': 1}
cfg = {'ntotal': scan_ntotal, 'trigger': t_modes[sync]}
dev.es1_ddaq.configure(d=cfg)
# Configure gigafrost
cfg = {
@@ -40,3 +49,63 @@ def demostepscan(scan_start, scan_end, steps, exp_time=0.005, burst_at_each_poin
dev.es1_psod.software_trigger = False
def demosequencescan(scan_start, gate_high, gate_low, repeats=1, repmode="PosNeg",
exp_time=0.005, exp_frames=180, sync='pso'):
""" Demo sequence scan with GigaFrost
This is a small BEC user-space sequence scan at the microXAS testbench
triggering the gigafrost in gate-enable mode. It tries to do everything
through BEC, so while still setting up the environment.
NOTE: It's not exactlythe same as the AeroScript template version.
Example:
--------
demostepscan(scan_start=-32, scan_end=148, steps=180, exp_time=0.005, burst_at_each_point=5)
"""
if not bl_check_beam():
raise RuntimeError("Beamline is not in ready state")
# Synthetic values
scan_velocity = gate_high / (exp_time * exp_frames)
scan_acceleration = 500
scan_safedistance = 10
scan_accdistance = scan_safedistance + 0.5* scan_velocity * scan_velocity / scan_acceleration
scan_ntotal = exp_frames * repeats
# Move to start position
t_modes = {'event': 1, 'pso': 2, 'inp0': 3, 'inp1': 1}
cfg = {'ntotal': scan_ntotal, 'trigger': t_modes[sync]}
dev.es1_ddaq.configure(d=cfg)
# Configure gigafrost
cfg = {
"ntotal": scan_ntotal, "nimages": exp_frames,
"exposure": 1000*exp_time, "period": 2000*exp_time}
dev.gfclient.configure(d=cfg)
# Fill PSO vectors according to scan direction
psoBounds = []
if repmode in ("pos", "Pos"):
psoBounds.append(scan_accdistance)
for ii in range(repeats):
psoBounds += [gate_high, gate_low]
psoBounds[-1] = 10* (scan_accdistance+gate_low)
dev.es1_psod.configure(d={'distance': psoBounds, 'wmode': 'toggle'})
# 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=wait_time, relative=False, settling_time=settling_time)
# Cleanup: disable SW trigger
dev.es1_psod.software_trigger = False