Commit dc35b168 authored by Matias Guijarro's avatar Matias Guijarro

Merge branch '561-scan-presets' into 'master'

Resolve "scan presets"

Closes #561

See merge request !1156
parents e5116927 da32ca8d
Pipeline #9199 passed with stages
in 21 minutes and 44 seconds
......@@ -149,8 +149,8 @@ class DeviceIteratorWrapper(object):
class ChainPreset(object):
"""
This class interface will be called by the scan object
at the beginning and at the end of a scan.
This class interface will be called by the chain object
at the beginning and at the end of a chain iteration.
A typical usage of this class is to manage the opening/closing
by software or to control beamline multiplexer(s)
......@@ -162,19 +162,19 @@ class ChainPreset(object):
def prepare(self, chain):
"""
Called on the preparation phase of a scan.
Called on the preparation phase of the chain iteration.
"""
pass
def start(self, chain):
"""
Called on the starting phase of a scan.
Called on the starting phase of the chain iteration.
"""
pass
def stop(self, chain):
"""
Called at the end of a scan.
Called at the end of the chain iteration.
"""
pass
......@@ -791,7 +791,7 @@ class AcquisitionChain(object):
self._tree = Tree()
self._root_node = self._tree.create_node("acquisition chain", "root")
self._device_to_node = dict()
self._presets_list = list()
self._presets_master_list = weakref.WeakKeyDictionary()
self._parallel_prepare = parallel_prepare
self._stats_dict = dict()
......@@ -841,16 +841,25 @@ class AcquisitionChain(object):
self._tree.move_node(slave, master)
slave.parent = master
def add_preset(self, preset):
def add_preset(self, preset, master=None):
"""
Add a preset for the scan.
Add a preset on a top-master.
If it None mean the first in the chain
Args:
preset should be inherited for class Preset
master if None take the first top-master from the chain
"""
if not isinstance(preset, ChainPreset):
raise ValueError("Expected ChainPreset instance")
self._presets_list.append(preset)
top_masters = [x.identifier for x in self._tree.children("root")]
if master is not None and master not in top_masters:
raise ValueError(f"master {master} not in {top_masters}")
# set the preset on the chain itself if master is None
# this is to manage the case where the chain tree is still empty.
presets_list = self._presets_master_list.setdefault(master or self, list())
presets_list.append(preset)
def get_iter_list(self):
if len(self._tree) > 1:
......@@ -861,24 +870,31 @@ class AcquisitionChain(object):
del master.slaves[:]
master.slaves.extend(self._tree.get_node(master).fpointer)
sub_trees = [
self._tree.subtree(x.identifier) for x in self._tree.children("root")
]
top_masters = [x.identifier for x in self._tree.children("root")]
sub_trees = [self._tree.subtree(x) for x in top_masters]
first_top_master = top_masters.pop(0)
first_tree = sub_trees.pop(0)
# default => first top master is also store in self
presets_list = self._presets_master_list.get(self, list())
presets_list += self._presets_master_list.get(first_top_master, list())
iterators = [
AcquisitionChainIter(
self,
sub_trees.pop(0),
self._presets_list,
first_tree,
presets_list,
parallel_prepare=self._parallel_prepare,
)
]
iterators.extend(
[
AcquisitionChainIter(
self, sub_tree, list(), parallel_prepare=self._parallel_prepare
self,
sub_tree,
self._presets_master_list.get(master, list()),
parallel_prepare=self._parallel_prepare,
)
for sub_tree in sub_trees
for master, sub_tree in zip(top_masters, sub_trees)
]
)
return iterators
......
......@@ -369,6 +369,26 @@ def display_motor(func):
return f
class ScanPreset:
def prepare(self, scan):
"""
Called on the preparation phase of a scan.
"""
pass
def start(self, scan):
"""
Called on the starting phase of a scan.
"""
pass
def stop(self, scan):
"""
Called at the end of a scan.
"""
pass
class Scan(object):
IDLE_STATE, PREPARE_STATE, START_STATE, STOP_STATE = list(range(4))
......@@ -497,6 +517,7 @@ class Scan(object):
self._data_watch_callback_done = data_watch_callback_done
else:
self._data_watch_task = None
self._preset_list = list()
def __repr__(self):
return "Scan(number={}, name={}, path={})".format(
......@@ -538,6 +559,14 @@ class Scan(object):
def statistics(self):
return Statistics(self._acq_chain._stats_dict)
def add_preset(self, preset):
"""
Add a preset for this scan
"""
if not isinstance(preset, ScanPreset):
raise ValueError("Expected ScanPreset instance")
self._preset_list.append(preset)
def _get_data_axis_name(self, axis=None):
acq_chain = self._scan_info["acquisition_chain"]
master_axes = []
......@@ -743,6 +772,7 @@ class Scan(object):
self._state = self.PREPARE_STATE
with periodic_exec(0.1 if call_on_prepare else 0, set_watch_event):
self._execute_preset("prepare")
self.prepare(self.scan_info, self.acq_chain._tree)
prepare_tasks = [
gevent.spawn(i.prepare, self, self.scan_info) for i in current_iters
......@@ -752,6 +782,7 @@ class Scan(object):
finally:
gevent.killall(prepare_tasks)
self._execute_preset("start")
self._state = self.START_STATE
run_next_tasks = [
(gevent.spawn(self._run_next, i), i) for i in current_iters
......@@ -821,6 +852,8 @@ class Scan(object):
if hasattr(node, "close"):
node.close()
self._execute_preset("stop")
def _run_next(self, next_iter):
next_iter.start()
for i in next_iter:
......@@ -948,3 +981,13 @@ class Scan(object):
Activate logging trace during scan
"""
AcquisitionChain.trace(on)
def _execute_preset(self, method_name):
preset_tasks = [
gevent.spawn(getattr(preset, method_name), self)
for preset in self._preset_list
]
try:
gevent.joinall(preset_tasks, raise_error=True)
finally:
gevent.killall(preset_tasks)
......@@ -7,6 +7,7 @@
from bliss import setup_globals
from bliss.scanning.chain import AcquisitionChain, ChainPreset, ChainIterationPreset
from bliss.scanning.scan import ScanPreset
from bliss.common import scans
......@@ -81,3 +82,29 @@ def test_iteration_preset(session):
assert preset.prepare_called == 10
assert preset.start_called == 10
assert preset.stop_called == 10
def test_scan_preset(beacon):
class Preset(ScanPreset):
def __init__(self):
self.prepare_counter = 0
self.start_counter = 0
self.stop_counter = 0
def prepare(self, scan):
self.prepare_counter += 1
def start(self, scan):
self.start_counter += 1
def stop(self, scan):
self.stop_counter += 1
preset = Preset()
diode = beacon.get("diode")
s = scans.loopscan(2, 0, diode, run=False)
s.add_preset(preset)
s.run()
assert preset.prepare_counter == 1
assert preset.start_counter == 1
assert preset.stop_counter == 1
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment