Preset¶
Presets are used to customize a scan by performing extra actions
(eg. opening/closing a shutter) at special events like prepare()
, start()
and stop()
.
BLISS standard presets are: ScanPreset
, ChainPreset
and
ChainIterationPreset
.
They typically allow to control:
- opening/closing of a shutter
- detector cover removing/replacing
- multiplexer
- pause during a scan
- equipment protection (via data channels hook, see below)
- …
Note
In the general case, DO NOT use a Preset
to modify the acquisition
parameters (eg. acquisition time) of a device.
To modify the default acquisition parameters used by a device in
standard scans, use DEFAULT_CHAIN.set_settings
, see Default
chain.
Quick overview¶
A preset is a hook introduced in the acquisition
chain of a scan thanks to the
add_preset()
method.
A preset has three methods (prepare()
, start()
, stop()
) that user can
customize.
Bliss provides three kind of presets that act at different levels of the scan
chain: ScanPreset
, ChainPreset
and ChainIterationPreset
.
ScanPreset: (hook a whole scan)
.prepare()
: called at scan prepare.start()
: called at scan start.stop()
: called at scan stop
Example:
DEMO [1]: s = loopscan(2, 0.1, diode, run=False)
DEMO [2]: p = MyScanPreset() # a ScanPreset object
DEMO [3]: s.add_preset(p) # using the scan object method
DEMO [4]: s.run()
ChainPreset: (hook a top-master)
.prepare
: called at TopMaster.prepare.start
: called at TopMaster.start.stop
: called at TopMaster.stop
ChainPreset is similar to ScanPreset
if the chain has only one top-master
(i.e. single branch acq_chain)
DEMO [1]: s = loopscan(2, 0.1, diode, run=False)
DEMO [2]: p = MyChainPreset() # a ChainPreset object
DEMO [3]: s.acq_chain.add_preset(p) # using the chain object method
DEMO [4]: s.run()
ChainIterationPreset: (hook each iteration of a top-master)
.prepare
: called at prepare of step i (iteration i of a top-master).start
: called at start of step i (iteration i of a top-master).stop
: called at stop of step i (iteration i of a top-master)
DEMO [1]: s = loopscan(2, 0.1, diode, run=False)
DEMO [2]: p = MyIteratingChainPreset() # a ChainPreset object with
# a get_iterator method
DEMO [3]: s.acq_chain.add_preset(p) # using the chain object method
DEMO [4]: s.run()
Note
Most of the time the acquisition chain has only one top-master (only
one acquisition chain branch) and in that case using a ScanPreset
or a
ChainPreset
will produce the same result.
Add presets to the default chain¶
It is possible to add a preset to all standard scans of Bliss (i.e: ct
,
loopscan
, ascan
, etc.).
DEMO [1]: p = MyChainPreset()
DEMO [2]: DEFAULT_CHAIN.add_preset(p) # method of the default chain object
DEMO [3]: loopscan(2, 0.1, diode) # run scans...
...
DEMO [9]: DEFAULT_CHAIN.remove_preset(p) # remove preset
Multiple top-masters case¶
Keep in mind that the acquisition chain can have multiple branches (one per top-master).
The ChainPreset
and ChainIterationPreset
are associated to one top-master
(i.e: one acquisition chain branch).
That is why the add_preset()
method of the acquisition chain takes an optional
argument master
.
def add_preset(self, preset, master=None):
"""
Add a preset on a top-master.
Args:
preset: a ChainPreset object
master: if None, take the first top-master of the chain
"""
ScanPreset¶
This is the simplest one. It has 3 callback methods:
prepare()
: called before all devices preparationstart()
: called before all devices startingstop()
: called after all devices are stopped
Example of custom scan preset:
from bliss.scanning.scan import ScanPreset
class CustomPreset(ScanPreset):
def prepare(self,scan):
print(f"Preparing scan {scan.name}\n")
def start(self,scan):
print(f"Starting scan {scan.name}")
print(f"Opening the shutter")
def stop(self,scan):
print(f"{scan.name} scan is stopped")
print(f"Closing the shutter")
and it’s usage:
DEMO [3]: p = CustomPreset()
DEMO [4]: s = loopscan(2, 0.1, diode, run=False)
DEMO [5]: s.add_preset(p)
DEMO [6]: s.run()
Scan 12 Wed Mar 13 11:06:11 2019 /tmp/scans/demo/data.h5 demo user = seb
loopscan 2 0.1
# dt[s] diode
Preparing scan loopscan
Starting scan loopscan
Opening the shutter
0 0 -40.2222
1 0.104891 -9.11111
Took 0:00:09.830967
loopscan scan is stopped
Closing the shutter
Data channels hook¶
The ScanPreset
has a connect_data_channels()
method to execute a callback
when data is emitted from channels.
It is useful to protect some equipments. For example, if the value measured by a diode exceeds some threshold, the scan can be stopped or some attenuators can be activated.
The basic usage is to call the .connect_data_channels()
method from the
.prepare()
of a ScanPreset
.
The callback will receive the arguments: counter
, channel_name
and data
.
class ScanPreset:
...
def connect_data_channels(self, counters_list, callback):
"""
Associate a callback to the data emission by the channels
of a list of counters.
Args:
* counters_list: the list of counters to connect data channels to
* callback: a callback function
"""
...
Example:
from bliss.scanning.scan import ScanPreset
class MyScanPreset(ScanPreset):
def __init__(self, diode):
super().__init__()
self.diode = diode
def prepare(self, scan):
self.connect_data_channels([self.diode, ...], self.protect_my_detector)
def protect_my_detector(self, counter, channel_name, data):
if counter == diode:
# assuming the counter has only 1 channel, no need to
# check for channel name
if data > threshold:
# protect the detector...
If an exception is raised in the callback function, the scan will stop.
ChainPreset¶
ChainPreset hook is linked to a top-master of the acquisition chain. So the
callback method will be called during prepare
, start
and stop
phases of
this top-master. It has exactly the same behaviour than the ScanPreset
if the
chain has only one top-master.
Example: In a loopscan, the only top master is a timer. So here it is the same example as shown above with the ScanPreset, where a shutter is opened at the beginning of the scan and closed at the end.
The only difference is that the add_preset()
method is called on the
acquisition chain object instead of the scan object
(s.acq_chain.add_preset()
instead of s.add_preset()
).
In the multiple top-masters case, the acq_chain.add_preset
method takes an
optional master
argument to specify the top-master that should be associated
to this preset (see Multiple top-masters
case)
from bliss.scanning.chain import ChainPreset
class MyPreset(ChainPreset):
def prepare(self,acq_chain):
print("Preparing")
def start(self,acq_chain):
print("Starting, Opening the shutter")
def stop(self,acq_chain):
print("Stopped, closing the shutter")
DEMO [1]: s = loopscan(2, 0.1, diode, run=False)
DEMO [1]: p = MyPreset()
DEMO [1]: s.acq_chain.add_preset(p)
DEMO [1]: s.run()
Scan 13 Wed Mar 13 11:54:08 2019 /tmp/scans/demo/data.h5 demo user = seb
loopscan 2 0.1
# dt[s] diode
Preparing
Starting, Opening the shutter
0 0 24.1111
1 0.105099 1.22222
Stopped, closing the shutter
Took 0:00:36.189189
ChainIterationPreset¶
ChainIterationPreset is used to set a hook on each iteration of a
top-master. ChainIterationPreset
is yield from ChainPreset
instance by
the get_iterator()
method.
Example to open and close a shutter at each iteration of a scan:
from bliss.scanning.chain import ChainPreset, ChainIterationPreset
class ShutterPreset(ChainPreset):
class Iterator(ChainIterationPreset):
def __init__(self, iteration_nb):
self.iteration = iteration_nb
def prepare(self):
print(f"Preparing iteration {self.iteration}")
def start(self):
print(f"Starting, Opening the shutter iter {self.iteration}")
def stop(self):
print(f"Stopped, closing the shutter, iter {self.iteration}")
def get_iterator(self, acq_chain):
iteration_nb = 0
while True:
yield ShutterPreset.Iterator(iteration_nb)
iteration_nb += 1
Usage:
DEMO [2]: p = ShutterPreset()
DEMO [3]: s = loopscan(2, 0.1, diode, run=False)
DEMO [4]: s.acq_chain.add_preset(p)
DEMO [5]: s.run()
Scan 18 Wed Mar 13 14:15:40 2019 /tmp/scans/demo/data.h5 demo user = seb
loopscan 2 0.1
# dt[s] diode
Preparing iteration 0
Starting, Opening the shutter iter 0
Stopped, closing the shutter, iter 0
Preparing iteration 1
Starting, Opening the shutter iter 1
0 0 22.4444
1 0.10728 13.5556
Stopped, closing the shutter, iter 1
Took 0:00:16.677241
Warning
In this example, data display and the chain iteration are executed by two separated greenlets which are not synchronised. This is not a problem but can be confusing for the user.
In the multiple top-masters case, the acq_chain.add_preset
method takes an
optional master
argument to specify the top-master that should be associated
to this preset (see Multiple top-masters
case)
To pause a scan¶
As Preset
callbacks are executed synchronously, they can easily pause a scan,
just by not returning imediatly from prepare()
, start()
or stop()
callback
methods.
For example, to pause a scan in case of beam loss, the condition has to be checked in a loop.
Example to delay starting a scan until the beam is present.
from bliss.scanning.scan import ScanPreset
class BeamCheckPreset(ScanPreset):
def __init__(self, diode, beam_trigger_value):
self._diode = diode
self._beam_trigger_value
def prepare(self, scan):
beam_value = self._diode.read()
while beam_value < self._beam_trigger_value:
print("Waiting for beam")
print(f"read {beam_value} expect {self._beam_trigger_value}",end='\r')
gevent.sleep(1)
beam_value = self._diode.read()