Defaults BLISS scans

BLISS step-by-step scan functions

BLISS provides functions to perform step-by-step scans. The acquisition chain for those scans is built using the DefaultChain class.

scans_hierarchy ls loopscan ts timescan ls->ts ct ct ct->ts bssS bliss.scanning.scan.Scan ts->bssS lu lineup ds dscan lu->ds as ascan ds->as as->bssS ps pointscan ps->bssS dm dmesh am amesh dm->am am->bssS ans anscan lus lookupscan ans->lus a2s a2scan a2s->bssS a35s a{3..5}scan a35s->ans dns dnscan dns->ans d2s d2scan d2s->a2s d35s d{3..5}scan d35s->dns lus->bssS

Parameters (common to all scans)

Counters parameters

counter_args (counter-providing objects): each parameter provides counters to be integrated in the scan. if no counter parameters are provided, use the active measurement group.

Optional parameters

Common keyword arguments that can be used in all scans:

  • name (str): scan name in data nodes tree and directories [default: ‘scan’]
  • title (str): scan title [default: ‘a2scan ‘]
  • save (bool): save scan data to file [default: True]
  • save_images (bool or None): save image files [default: None, means it follows the save argument]
  • sleep_time (float): sleep time between 2 points [default: None]
  • run (bool): if True (default), run the scan. False means to just create scan object and acquisition chain.
  • return_scan (bool): True by default ???

Scan example

ascan(<mot>, <start>, <stop>, <intervals>, <acq_time>, <title>)
ascan( sy,    1,       2,      4,           0.5, title="Jadarite_LiNaSiB3O7(OH)")

This command performs a scan of five 500ms-counts of current measurement group counters at <sy> motor positions: 1, 1.25, 1.5, 1.75 and 2.

ascan dscan

ascan(motor, start, stop, intervals, count_time, *counter_args, **kwargs)

Absolute scan of one motor, as specified by <motor>. The motor starts at the position given by <start> and ends at the position given by <stop>.

The step size is: (<stop>-<start>)/<intervals>

The number of points will be <intervals> + 1.

Count time is given by <count_time> (seconds).

At the end of the scan, the motor will stay at stopping position (<stop> position in case of success).

Idem for dscan but using relative positions:

dscan(motor, rel_start, rel_stop, intervals, count_time, *counter_args, **kwargs)

Scans one motor, as specified by <motor>. If the motor is at position X before the scan begins, the scan will run from X+rel_start to X+rel_stop. The step size is: (<rel_stop>-<rel_start>)/<intervals>. The number of points will be <intervals>+1. Count time is given by <count_time> (in seconds).

At the end of a dscan (even in case of error or scan abortion, on a ctrl-c for example) the motor will return to its initial position.

a2scan

a2scan( motor1, start1, stop1,
        motor2, start2, stop2,
        intervals, count_time, *counter_args, **kwargs)

Absolute 2 motors scan.

Scans two motors, as specified by <motor1> and <motor2>. The motors start at the positions given by <start1> and <start2> and end at the positions given by <stop1> and <stop2>. The step size for each motor is given by (<stopN>-<startN>)/<intervals>. The number of points will be <intervals>+1. Count time is given by <count_time> (in seconds).

d2scan

Relative 2 motors scan.

Scans two motors, as specified by <motor1> and <motor2>. Each motor moves the same number of points. If a motor is at position X before the scan begins, the scan will run from X+<start> to X+<end>. The step size of a motor is (<stopN>-<startN>)/<intervals>. The number of points will be <intervals>+1. Count time is given by <count_time> (in seconds).

At the end of the scan (even in case of error) the motors will return to their initial positions.

a3scan a4scan a5scan

Similary to a2scan, aNscan functions are provided fo N in {3,4,5}.

example for 9 intervals:

DEMO [2]: a5scan(m1,1,2, m2,3,4, m3,5,6, m4,7,8, m5,8,9, 9, 0.1)

Scan 2 Fri Oct 26 16:07:08 2018 /tmp/scans/demo/
a5scan m1 1 2 m2 3 4 m3 5 6 m4 7 8 m5 8 9 10 0.1

     #      dt[s]      m1      m2       m3       m4       m5      simct1
     0          0       1       3        5        7        8    0.038648
     1   0.432173    1.11    3.11     5.11     7.11     8.11    0.022345
     2   0.850866    1.22    3.22     5.22     7.22     8.22    0.119345
     3    1.25996    1.33    3.33     5.33     7.33     8.33     1.06995
     4    1.74734    1.44    3.44     5.44     7.44     8.44     3.45354
     5    2.16594    1.56    3.56     5.56     7.56     8.56     3.47793
     6    2.57817    1.67    3.67     5.67     7.67     8.67     1.01595
     7    2.98574    1.78    3.78     5.78     7.78     8.78    0.128783
     8     3.4303    1.89    3.89     5.89     7.89     8.89    0.073870
     9    3.84454       2       4        6        8        9    0.019919

Took 0:00:05.226827
 Out [2]: Scan(name=a5scan_2, run_number=2, path=/tmp/scans/demo/)

aNscan dNscan

In case a scan for more than 5 motors is needed, anscan and dnscan functions can be used with a slightly different list of parameters:

anscan(<counting_time>, <intervals>, (<mot>, <start>, <stop>)*)

(<mot>, <start>, <stop>) can be repeated as much as needed.

idem for dnscan with relative start and stop positions:

anscan(<counting_time>, <intervals>, (<mot>, <rel_start>, <rel_stop>)*)

amesh

amesh( motor1, start1, stop1, intervals1,
       motor2, start2, stop2, intervals2,
       count_time, *counter_args, **kwargs)

Mesh scan.

The amesh scan traces out a grid using motor <motor1> and motor <motor2>. The first motor scans from position <start1> to <end1> using the specified number of intervals + 1 as points number. The second motor similarly scans from <start2> to <end2>. Each point is counted for for <count_time> seconds (or monitor counts).

The scan of <motor1> is done at each point scanned by <motor2>. That is, the first motor scan is nested within the second motor scan. (<motor1> is the “fast” axis, <motor2> the “slow” axis)

Special parameter:

  • backnforth: if True, do back and forth on the first motor.

dmesh

Relative amesh.

lineup

Relative scan.

lineup(motor, start, stop, intervals, count_time, *counter_args, **kwargs)

lineup performs a dscan and then goes to the maximum value of first counter.

timescan

Scan without movement.

timescan(count_time, *counter_args, **kwargs)

Performs <npoints> counts for <count_time>. If <npoints> is 0, it counts forever.

Special parameters:

  • <output_mode> (str): valid are ‘tail’ (append each line to output) or ‘monitor’ (refresh output in single line) [default: ‘tail’]
  • <npoints> (int): number of points [default: 0, meaning infinite number of points]

loopscan

loopscan(npoints, count_time, *counter_args, **kwargs)

Similar to timescan but <npoints> is mandatory.

ct

ct(count_time, *counter_args, **kwargs)

Counts for a specified time.

pointscan

Performs a scan over many positions given as a list.

pointscan(motor, positions_list, count_time, *counter_args, **kwargs)

Scans one motor, as specified by <motor>. The motor starts at the position given by the first value in <positions_list> and ends at the position given by last value <positions_list>. Count time is given by <count_time> (in seconds).

Special parameter:

  • <positions_list>: List of positions to scan for <motor> motor.

lookupscan

Previous multi-motors scans (aNscan, dNscan) are based on the generic lookupscan. It can take a variable number of motors associated to a list of positions to use for the scan.

usage:

lookupscan(counting_time, (<mot>, <positions_list>)*, <counter>*)

example:

import numpy as np
lookupscan(0.1, m0, np.arange(0, 2, 0.5), m1, np.linspace(1, 3, 4), diode2)

Scans behaviour

  • create acquisition device
  • prepare()
  • start()
  • trigger() called <nbpoints> times.
  • stop()
Scan nbpoints start/stop type
def. N list
timescan 0 []
loopscan N []
pointscan N float
ct 1 []

Default chain

All standard scans (step scans) are build the same way using the DefaultAcquisitionChain object accessible via the global variable DEFAULT_CHAIN, if you are in a session. It’s the entry point to parameterise detectors in a step scan procedure. It is easy to set acquisition parameters and saving parameters for any detector, be it for step scans or to change it’s master.

Note

Acquisition parameters are all the parameters that define the number of triggers and points, trigger type, exposure time and so on. Other detector parameters should not be part of the DefaultAcquisitionChain configuration.
For example Image configuration of a Lima device like binning, flip, rotation and so on, should be excluded from this configuration and set before any scan.

Saving parameters are parameters on some devices (like: Lima) that configure the saving.
This means that for instance on Lima devices saving_mode, saving_format and so on, may be part of the DEFAULT_CHAIN configuration.

Example: Two basler cameras with a hardware trigger provided by your counter card. Most of the time the configuration comes from Beacon and the yaml file may look like this:

- name: default_acq_chain
  plugin: default
  chain_config:
  - device: $basler_1
    acquisition_settings:
      acq_trigger_mode: EXTERNAL_TRIGGER_MULTI
      saving_format: "HDF5"
    master: $p201_0
  - device: $basler_2
    acquisition_settings:
      acq_trigger_mode: EXTERNAL_TRIGGER_MULTI
      saving_format: "HDF5"
    master: $p201_0

In this example you notice that both cameras are triggered externally (EXTERNAL_TRIGGER_MULTI) and their saving format HDF5. Their master will be the p201 counter card. To activate this setting for all steps scan of your session do as follows in the session setup file :

    DEFAULT_CHAIN.set_settings(default_acq_chain['chain_config'])

ChainPreset can also be added to the DEFAULT_CHAIN. Usually this is also done in the session setup like this:

    DEFAULT_CHAIN.add_preset(my_preset)

To go further…

Steps scans

Most unusual step scans can be defined using one of the existing standard scans.

n-regions scan example

In this example you want to define a scan with several regions. The regions have to be defined as a list of tuples like: [(start1,stop1,npoints1),(start2,stop2,npoints2),…]

import numpy
from bliss.common.scans import pointscan
def n_region_scan(motor, regions, count_time, *counter_args, **kwargs):
    positions = list()
    for start,stop,npoints in regions:
        positions.extend(numpy.linspace(start,stop,npoints))

    # change to new defined scan
    kwargs.setdefault('type', f'{len(regions)}_region_scan')

    # Build a **meaning** title
    kwargs.setdefault('title',f'{kwargs.get("type")} on {motor.name}')
    return pointscan(motor,positions,count_time,*counter_args,**kwargs)

Execute :

DEMO [1]: s = n_region_scan(roby,[(0,2,3),(10,15,11)],0.1,diode,save=False)

Scan 9 Tue Apr 02 14:58:33 2019 <no saving> demo user = seb
2_region_scan on roby

           #         dt[s]          roby         diode
           0             0             0       23.8889
           1      0.232985             1       23.5556
           2       0.46471             2      -2.11111
           3      0.823396            10       16.5556
           4       1.01696          10.5      -8.88889
           5       1.20862            11      -17.3333
           6       1.39964          11.5       20.6667
           7       1.59078            12      0.444444
           8       1.78076          12.5       17.7778
           9       1.97064            13     -0.111111
          10       2.16226          13.5            26
          11       2.35261            14       28.2222
          12       2.54606          14.5      -1.55556
          13        2.7383            15       59.2222

Took 0:00:03.366149

ascans, which take step size rather than the number of intervals

import numpy
from bliss.common.scans import ascan
def step_scan(motor, start, stop, step_size, count_time, *counter_args, **kwargs):
  intervals = int(numpy.ceil(abs(start-stop)/step_size)) - 1
  return ascan(motor, start, stop, intervals, count_time, *counter_args, **kwargs)

Execute :

TEST_SESSION [42]: s = step_scan(roby, 0, 1, 0.2, 0.1, diode)

Scan 17 Tue Apr 02 16:04:31 2019 /tmp/..../data.h5 test_session user = seb
ascan roby 0 1 5 0.1

           #         dt[s]          roby         diode
           0             0             0      -9.33333
           1      0.193154          0.25      -38.5556
           2      0.385237           0.5      -6.55556
           3      0.575978          0.75      -13.5556
           4      0.765097             1      -9.55556

Took 0:00:01.229621

Using ‘presets’ to customize a scan

In this example, the scan will pump a certain amount of liquid using a syringe before each point. To do this we will use ChainPreset.

from bliss.scanning.chain import ChainPreset,ChainIterationPreset
from bliss.common.scans import ascan

class Syringe:
      def __init__(self, available_liquid):
          self._available_liquid = available_liquid

      def pump(self,amount):
          if self._available_liquid < amount:
              raise RuntimeError("No more liquid to pump")
          self._available_liquid -= amount

my_syringe = Syringe(10) # liquid volume == 10

def syringe_ascan(syringe, liquid_amount,
                  motor, start, stop, intervals, count_time, *counter_args, **kwargs):
    class Preset(ChainPreset):
        class Point(ChainIterationPreset):
            def prepare(self):
                syringe.pump(liquid_amount)
        def get_iterator(self,acq_chain):
            while True:
                yield Preset.Point()
    kwargs.setdefault('run',False)
    s = ascan(motor, start, stop, intervals, count_time, *counter_args, **kwargs)
    preset = Preset()
    s.acq_chain.add_preset(preset)
    s.run()
    return s

Execute :

TEST_SESSION [16]: syringe_ascan(my_syringe, 1, roby, 0, 1, 15, 0.1, diode)

Scan 22 Tue Apr 02 16:37:24 2019 /tmp/..../data.h5 test_session user = seb
ascan roby 0 1 15 0.1

           #         dt[s]          roby         diode
           0             0             0       12.6667
           1       0.19153        0.0714      -12.2222
           2      0.381468        0.1429             9
           3      0.570563        0.2143       11.6667
           4      0.761761        0.2857      -12.1111
           5      0.953436        0.3571       10.8889
           6       1.14317        0.4286      -1.44444
           7       1.33038           0.5      -21.3333
           8       1.51747        0.5714       51.3333
           9        1.7079        0.6429       9.77778
!!! === RuntimeError: No more liquid to pump === !!!

Took 0:00:02.008699
!!! === RuntimeError: No more liquid to pump === !!!

In this example, before each point preparation the syringe will pump one unit of a volume and raises an error when the syringe is empty.

Note

Any exception in Preset method stop the scan.

More complex scans

For more complex scans, you may need to use a lower level api. see: Scan engine.