Custom bluesky plan#

APS Training for Bluesky Data Acquisition.

Objective

Build a custom plan for an up-down scan using m2 and scaler1 channels I0 and diode. Make the counting time configurable. Accept user metadata.

Preparation#

First, start the instrument package.

[1]:
import pathlib, sys
sys.path.append(str(pathlib.Path.home() / "bluesky"))
from instrument.collection import *
/home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/_iconfig.py
Activating auto-logging. Current session state plus future input saved.
Filename       : /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/.logs/ipython_console.log
Mode           : rotate
Output logging : True
Raw input log  : False
Timestamping   : True
State          : active
I Fri-15:54:55 - ############################################################ startup
I Fri-15:54:55 - logging started
I Fri-15:54:55 - logging level = 10
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/session_logs.py
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/collection.py
I Fri-15:54:55 - CONDA_PREFIX = /home/prjemian/.conda/envs/bluesky_2023_2
Exception reporting mode: Minimal
I Fri-15:54:55 - xmode exception level: 'Minimal'
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/mpl/notebook.py
I Fri-15:54:55 - #### Bluesky Framework ####
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/framework/check_python.py
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/framework/check_bluesky.py
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/framework/initialize.py
I Fri-15:54:55 - RunEngine metadata saved in directory: /home/prjemian/Bluesky_RunEngine_md
I Fri-15:54:55 - using databroker catalog 'training'
I Fri-15:54:55 - using ophyd control layer: pyepics
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/framework/metadata.py
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/epics_signal_config.py
I Fri-15:54:55 - Using RunEngine metadata for scan_id
I Fri-15:54:55 - #### Devices ####
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/area_detector.py
I Fri-15:54:55 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/calculation_records.py
I Fri-15:54:58 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/fourc_diffractometer.py
I Fri-15:54:58 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/ioc_stats.py
I Fri-15:54:58 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/kohzu_monochromator.py
I Fri-15:54:58 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/motors.py
I Fri-15:54:58 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/noisy_detector.py
I Fri-15:54:58 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/scaler.py
I Fri-15:54:59 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/shutter_simulator.py
I Fri-15:54:59 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/simulated_fourc.py
I Fri-15:54:59 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/simulated_kappa.py
I Fri-15:54:59 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/slits.py
I Fri-15:54:59 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/sixc_diffractometer.py
I Fri-15:55:00 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/devices/temperature_signal.py
I Fri-15:55:00 - #### Callbacks ####
I Fri-15:55:00 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/callbacks/spec_data_file_writer.py
I Fri-15:55:00 - #### Plans ####
I Fri-15:55:00 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/plans/lup_plan.py
I Fri-15:55:00 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/plans/peak_finder_example.py
I Fri-15:55:00 - /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/instrument/utils/image_analysis.py
I Fri-15:55:00 - #### Utilities ####
I Fri-15:55:00 - writing to SPEC file: /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/bluesky/20230414-155500.dat
I Fri-15:55:00 -    >>>>   Using default SPEC file name   <<<<
I Fri-15:55:00 -    file will be created when bluesky ends its next scan
I Fri-15:55:00 -    to change SPEC file, use command:   newSpecFile('title')
I Fri-15:55:00 - #### Startup is complete. ####

Select scaler channels#

The scaler channels are selected by calling its .select_channels() method:

[2]:
scaler1.select_channels(["I0", "diode"])

Count time#

The count time is in the .preset_time attribute. The way to set the scaler with this time is to use ophyd’s staging process. Add preset_time to the .stage_sigs dictionary with the desired count time as the value.

[3]:
scaler1.stage_sigs["preset_time"] = 1
scaler1.stage_sigs
[3]:
OrderedDict([('preset_time', 1)])

Construct the plan#

Create a plan that scans up, then scans down (2 scans total). Add temperature just to get some other interesting data. We’ll add a metadata key for the scan direction.

We’ll do this in small steps starting with the innermost part.

[4]:
def up_down_once(lo, hi, num, md={}):
    dets = [scaler1, temperature]
    _md = {}
    _md.update(md)
    _md["direction"] = "down"
    yield from bp.scan(dets, m2, lo, hi, num, md=_md)
    _md["direction"] = "down"
    yield from bp.scan(dets, m2, hi, lo, num, md=_md)

Verify that this code will process through the RunEngine by testing it without running it. Use the summarize_plan() function imported from the bluesky package. This will show the steps of the measurement.

[5]:
summarize_plan(up_down_once(0, 1, 5))
=================================== Open Run ===================================
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================
=================================== Open Run ===================================
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================

Next, build a plan that runs this twice.

[6]:
def up_down_twice(lo, hi, num, md={}):
    for i in range(2):
        yield from up_down_once(lo, hi, num, md=md)
[7]:
summarize_plan(up_down_twice(0, 1, 5))
=================================== Open Run ===================================
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================
=================================== Open Run ===================================
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================
=================================== Open Run ===================================
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================
=================================== Open Run ===================================
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================

Finish the custom plan by setting the count time. Give a default of 1.0 s.

[8]:
def customScan(lo=0, hi=1, num=5, ct=1, md={}):
    scaler1.stage_sigs["preset_time"] = ct
    _md = {}
    _md.update(md)
    _md["count_time"] = ct
    _md["example"] = "customPlan"

    scaler1.select_channels(["I0", "diode"])
    yield from up_down_twice(lo, hi, num, md=_md)
    scaler1.select_channels()  # reset for all named channels
    del scaler1.stage_sigs["preset_time"]  # remove our custom staging
[9]:
summarize_plan(customScan(md=dict(demo="concise custom plan example")))
=================================== Open Run ===================================
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================
=================================== Open Run ===================================
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================
=================================== Open Run ===================================
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================
=================================== Open Run ===================================
m2 -> 1.0
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.75
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.5
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.25
  Read ['scaler1', 'temperature', 'm2']
m2 -> 0.0
  Read ['scaler1', 'temperature', 'm2']
================================== Close Run ===================================

Run#

Run the customScan() plan, using the RunEngine object: RE()

[9]:
RE(customScan(md=dict(demo="concise custom plan example")))


Transient Scan ID: 971     Time: 2023-04-14 15:55:48
Persistent Unique Scan ID: '3533adeb-2796-47c7-9c34-c174063ca1bd'
New stream: 'label_start_motor'
New stream: 'primary'
+-----------+------------+------------+-------------+------------+------------+
|   seq_num |       time |         m2 | temperature |         I0 |      diode |
+-----------+------------+------------+-------------+------------+------------+
|         1 | 15:55:50.2 |     0.0000 |    25.18394 |          4 |          4 |
|         2 | 15:55:52.0 |     0.2500 |    24.85393 |          5 |          3 |
|         3 | 15:55:53.8 |     0.5000 |    24.50531 |          5 |          6 |
|         4 | 15:55:55.7 |     0.7500 |    25.21157 |          6 |          5 |
|         5 | 15:55:57.6 |     1.0000 |    25.49052 |          5 |          5 |
+-----------+------------+------------+-------------+------------+------------+
generator scan ['3533adeb'] (scan num: 971)


Transient Scan ID: 972     Time: 2023-04-14 15:55:58
Persistent Unique Scan ID: '2f6e0c34-f250-4344-8e5d-776b4a8c12f9'
New stream: 'label_start_motor'
New stream: 'primary'
+-----------+------------+------------+-------------+------------+------------+
|   seq_num |       time |         m2 | temperature |         I0 |      diode |
+-----------+------------+------------+-------------+------------+------------+
|         1 | 15:55:59.5 |     1.0000 |    25.49052 |          5 |          5 |
|         2 | 15:56:01.3 |     0.7500 |    25.38635 |          6 |          4 |
|         3 | 15:56:03.1 |     0.5000 |    24.53601 |          5 |          5 |
|         4 | 15:56:05.0 |     0.2500 |    25.03256 |          4 |          5 |
|         5 | 15:56:06.9 |     0.0000 |    24.64768 |          3 |          5 |
+-----------+------------+------------+-------------+------------+------------+
generator scan ['2f6e0c34'] (scan num: 972)


Transient Scan ID: 973     Time: 2023-04-14 15:56:07
Persistent Unique Scan ID: '539e7792-04da-4d3c-bb41-937b1779e40f'
New stream: 'label_start_motor'
New stream: 'primary'
+-----------+------------+------------+-------------+------------+------------+
|   seq_num |       time |         m2 | temperature |         I0 |      diode |
+-----------+------------+------------+-------------+------------+------------+
|         1 | 15:56:09.0 |     0.0000 |    24.72637 |          4 |          3 |
|         2 | 15:56:10.8 |     0.2500 |    24.81574 |          5 |          7 |
|         3 | 15:56:12.7 |     0.5000 |    24.84084 |          5 |          6 |
|         4 | 15:56:14.6 |     0.7500 |    25.38981 |          5 |          4 |
|         5 | 15:56:16.5 |     1.0000 |    25.42090 |          6 |          5 |
+-----------+------------+------------+-------------+------------+------------+
generator scan ['539e7792'] (scan num: 973)


Transient Scan ID: 974     Time: 2023-04-14 15:56:17
Persistent Unique Scan ID: 'a697d94a-c398-4285-aeb6-d54d369e051b'
New stream: 'label_start_motor'
New stream: 'primary'
+-----------+------------+------------+-------------+------------+------------+
|   seq_num |       time |         m2 | temperature |         I0 |      diode |
+-----------+------------+------------+-------------+------------+------------+
|         1 | 15:56:18.5 |     1.0000 |    25.45387 |          4 |          4 |
|         2 | 15:56:20.5 |     0.7500 |    25.05831 |          5 |          4 |
|         3 | 15:56:22.5 |     0.5000 |    25.20127 |          5 |          5 |
|         4 | 15:56:24.5 |     0.2500 |    24.76677 |          6 |          5 |
|         5 | 15:56:26.5 |     0.0000 |    25.30977 |          6 |          7 |
+-----------+------------+------------+-------------+------------+------------+
generator scan ['a697d94a'] (scan num: 974)
[9]:
('3533adeb-2796-47c7-9c34-c174063ca1bd',
 '2f6e0c34-f250-4344-8e5d-776b4a8c12f9',
 '539e7792-04da-4d3c-bb41-937b1779e40f',
 'a697d94a-c398-4285-aeb6-d54d369e051b')
../_images/howto__custom_plan_17_2.png

Challenges#

Make these additions or improvements to the plans:

  1. Let the caller specify how many up-down iterations.

  2. Wait between up and down scans for some time (caller-specified). (hint: yield from bps.sleep(seconds))

  3. Use logger to report at various places.

    Hint: logger.info("text: %s  value: %g", s1, v2, ...)

    Consider examples as used in instrument/plans/peak_finder_example.py. See the Python logging tutorial for more information.

  4. Control the shutter (hint: yield from bps.mv(shutter, "open") or close)

  5. Change the temperature before up scan and then before down scan. (hint: yield from bps.mv(temperature, value))