Skip to content

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 preparation
  • start(): called before all devices starting
  • stop(): 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()