The instrument package for Bluesky Data Acquisition#

From APS Python Training for Bluesky Data Acquisition.

Objective

In this notebook, we describe the instrument package for a simulated X-ray instrument at a user facility such as the Advanced Photon Source. The goal is to use EPICS as much as possible to provide the control system features and to use Bluesky (providing the data acquisition framework) as a thin layer on top of EPICS.

Overview

This notebook uses two EPICS servers (IOCs), one, with prefix "gp:", providing (simulated) general beamline equipment such as motors, slits, shutter, counters, … The other IOC, with prefix "ad:", provides a simulated EPICS area detector. This notebook will acquaint you with some of the features provided by our Python instrument package which uses these IOCs.

IOC details

This simulated instrument is provided using docker images for EPICS base, the synApps xxx module, and EPICS area detector ADSimDetector. The images are based on these software versions:

  • Debian GNU/Linux 11 (bullseye)

  • EPICS base 7.0.5

  • synApps 6.2.1

  • area detector 3.11

Two EPICS IOCs are provided:

prefix

description

docker image

documentation

ad:

area detector IOC

prjemian/custom-synapps-6.2-ad-3.10

prjemian/epics-docker

gp:

general purpose IOC

prjemian/prjemian/custom-synapps-6.2

prjemian/epics-docker

Start the instrument package#

To start the instrument, use this Python startup snippet:

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

Description#

Might be a good idea to know now what this instrument package provides.

Notably, the table includes:

ophyd name(s)

Description

label(s)

adsimdet

simulated EPICS area detector

area_detector

calcs & calcouts

calculation support

gp_stats

details about the general purpose IOC

I0, diode, …

named scaler channels

counter

iconfig

instrument configuration parameters

m1 .. m16

16 simulated EPICS motors

motor

noisy

simulated diffraction peak

scaler1

simulated 16-channel EPICS scaler

scalers

shutter

simulated shutter

temperature

simulated temperature controller

iconfig#

iconfig, a Python dictionary, contains the instrument’s configuration details defined in one place for the user to control. These details, defined in instrument/iconfig.yml, are used in various places in the instrument package. Any changes to the iconfig.yml file will take effect the next time the instrument is started.

adsimdet#

The EPICS Area Detector ADSimDetector is included with this instrument as adsimdet. The detector is a monochrome 1k x 1k frame. The image is written to an HDF5 file and made available to the databroker via a shared volume from the docker container. adsimdet can be used as a detector.

The image is a simulated diffraction spot with center randomly-chosen between 100..900 on both axes. The width is also random as is the peak intensity and noise level. Furthermore, to simulate realistic conditions, the center position of both axes is adjusted by a few pixels using a pair of swait records (gp:userCalc9 & gp:userCalc10) updating from random numbers.

[2]:
adsimdet.summary()
data keys (* hints)
-------------------

read attrs
----------
hdf1                 MyHDF5Plugin        ('adsimdet_hdf1')

config keys
-----------
adsimdet_cam_acquire_period
adsimdet_cam_acquire_time
adsimdet_cam_image_mode
adsimdet_cam_manufacturer
adsimdet_cam_model
adsimdet_cam_num_exposures
adsimdet_cam_num_images
adsimdet_cam_trigger_mode

configuration attrs
-------------------
cam                  SimDetectorCam_V34  ('adsimdet_cam')
cam.acquire_period   EpicsSignalWithRBV  ('adsimdet_cam_acquire_period')
cam.acquire_time     EpicsSignalWithRBV  ('adsimdet_cam_acquire_time')
cam.image_mode       EpicsSignalWithRBV  ('adsimdet_cam_image_mode')
cam.manufacturer     EpicsSignalRO       ('adsimdet_cam_manufacturer')
cam.model            EpicsSignalRO       ('adsimdet_cam_model')
cam.num_exposures    EpicsSignalWithRBV  ('adsimdet_cam_num_exposures')
cam.num_images       EpicsSignalWithRBV  ('adsimdet_cam_num_images')
cam.trigger_mode     EpicsSignalWithRBV  ('adsimdet_cam_trigger_mode')
hdf1                 MyHDF5Plugin        ('adsimdet_hdf1')

unused attrs
------------
configuration_names  ArrayAttributeSignal('adsimdet_configuration_names')
image                ImagePlugin_V34     ('adsimdet_image')
pva                  PvaPlugin_V34       ('adsimdet_pva')

scaler1#

A simulated 16-channel scaler is configured with several channels of pulse counters, typical of a synchrotron beamline. scaler1 can be used as a detector.

[3]:
scaler1.summary()
data keys (* hints)
-------------------
*I0
*I00
*I000
*roi1
 scaler1_time
*scint
*timebase

read attrs
----------
channels             Channels            ('scaler1_channels')
channels.chan01      ScalerChannel       ('scaler1_channels_chan01')
channels.chan01.s    EpicsSignalRO       ('timebase')
channels.chan02      ScalerChannel       ('scaler1_channels_chan02')
channels.chan02.s    EpicsSignalRO       ('I0')
channels.chan04      ScalerChannel       ('scaler1_channels_chan04')
channels.chan04.s    EpicsSignalRO       ('scint')
channels.chan05      ScalerChannel       ('scaler1_channels_chan05')
channels.chan05.s    EpicsSignalRO       ('I000')
channels.chan06      ScalerChannel       ('scaler1_channels_chan06')
channels.chan06.s    EpicsSignalRO       ('I00')
channels.chan07      ScalerChannel       ('scaler1_channels_chan07')
channels.chan07.s    EpicsSignalRO       ('roi1')
time                 EpicsSignal         ('scaler1_time')

config keys
-----------
scaler1_auto_count_delay
scaler1_auto_count_time
scaler1_channels_chan01_chname
scaler1_channels_chan01_gate
scaler1_channels_chan01_preset
scaler1_channels_chan02_chname
scaler1_channels_chan02_gate
scaler1_channels_chan02_preset
scaler1_channels_chan04_chname
scaler1_channels_chan04_gate
scaler1_channels_chan04_preset
scaler1_channels_chan05_chname
scaler1_channels_chan05_gate
scaler1_channels_chan05_preset
scaler1_channels_chan06_chname
scaler1_channels_chan06_gate
scaler1_channels_chan06_preset
scaler1_channels_chan07_chname
scaler1_channels_chan07_gate
scaler1_channels_chan07_preset
scaler1_count_mode
scaler1_delay
scaler1_egu
scaler1_freq
scaler1_preset_time

configuration attrs
-------------------
channels             Channels            ('scaler1_channels')
channels.chan01      ScalerChannel       ('scaler1_channels_chan01')
channels.chan01.chname EpicsSignal         ('scaler1_channels_chan01_chname')
channels.chan01.preset EpicsSignal         ('scaler1_channels_chan01_preset')
channels.chan01.gate EpicsSignal         ('scaler1_channels_chan01_gate')
channels.chan02      ScalerChannel       ('scaler1_channels_chan02')
channels.chan02.chname EpicsSignal         ('scaler1_channels_chan02_chname')
channels.chan02.preset EpicsSignal         ('scaler1_channels_chan02_preset')
channels.chan02.gate EpicsSignal         ('scaler1_channels_chan02_gate')
channels.chan04      ScalerChannel       ('scaler1_channels_chan04')
channels.chan04.chname EpicsSignal         ('scaler1_channels_chan04_chname')
channels.chan04.preset EpicsSignal         ('scaler1_channels_chan04_preset')
channels.chan04.gate EpicsSignal         ('scaler1_channels_chan04_gate')
channels.chan05      ScalerChannel       ('scaler1_channels_chan05')
channels.chan05.chname EpicsSignal         ('scaler1_channels_chan05_chname')
channels.chan05.preset EpicsSignal         ('scaler1_channels_chan05_preset')
channels.chan05.gate EpicsSignal         ('scaler1_channels_chan05_gate')
channels.chan06      ScalerChannel       ('scaler1_channels_chan06')
channels.chan06.chname EpicsSignal         ('scaler1_channels_chan06_chname')
channels.chan06.preset EpicsSignal         ('scaler1_channels_chan06_preset')
channels.chan06.gate EpicsSignal         ('scaler1_channels_chan06_gate')
channels.chan07      ScalerChannel       ('scaler1_channels_chan07')
channels.chan07.chname EpicsSignal         ('scaler1_channels_chan07_chname')
channels.chan07.preset EpicsSignal         ('scaler1_channels_chan07_preset')
channels.chan07.gate EpicsSignal         ('scaler1_channels_chan07_gate')
count_mode           EpicsSignal         ('scaler1_count_mode')
delay                EpicsSignal         ('scaler1_delay')
auto_count_delay     EpicsSignal         ('scaler1_auto_count_delay')
freq                 EpicsSignal         ('scaler1_freq')
preset_time          EpicsSignal         ('scaler1_preset_time')
auto_count_time      EpicsSignal         ('scaler1_auto_count_time')
egu                  EpicsSignal         ('scaler1_egu')

unused attrs
------------
count                EpicsSignal         ('scaler1_count')
update_rate          EpicsSignal         ('scaler1_update_rate')
auto_count_update_rate EpicsSignal         ('scaler1_auto_count_update_rate')

The timebase is always shown. The additional channels (using names from the scaler GUI screen) are shown output of listdevice(scaler1) below.

Note that in the instrument package, the channel names were assigned on startup. An operating beamline would not do define the names here but instead, let the instrument team name these channels in the GUI screen at the time the cables from the pulse detectors are connected physically to the scaler.

Count the scaler from the command line:

[4]:
%ct scalers
[This data will not be saved. Use the RunEngine to collect data.]
timebase                       16000000.0
I0                             8.0
scint                          7.0
I000                           6.0
I00                            8.0
roi1                           6.0
scaler1_time                   1.6

NOTE: the actual counts are random numbers. Also, the EPICS soft scaler seems to add 0.1s to the counting time. Might be a bug with the EPICS scaler code.

[5]:
listdevice(scaler1)
[5]:
============================== ========== ==========================
data name                      value      timestamp
============================== ========== ==========================
scaler1_channels_chan01_chname timebase   2023-04-14 10:58:36.645292
timebase                       16000000.0 2023-04-14 11:16:00.339417
scaler1_channels_chan01_preset 15000000.0 2023-04-14 10:58:36.645292
scaler1_channels_chan01_gate   Y          2023-04-14 10:58:36.645292
scaler1_channels_chan02_chname I0         2023-04-14 10:58:36.645292
I0                             8.0        2023-04-14 11:16:00.339417
scaler1_channels_chan02_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan02_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan03_chname scint      2023-04-14 10:58:36.645292
scint                          7.0        2023-04-14 10:58:36.645292
scaler1_channels_chan03_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan03_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan04_chname scint      2023-04-14 10:58:36.645292
scint                          7.0        2023-04-14 11:16:00.339417
scaler1_channels_chan04_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan04_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan05_chname I000       2023-04-14 10:58:36.645292
I000                           6.0        2023-04-14 11:16:00.339417
scaler1_channels_chan05_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan05_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan06_chname I00        2023-04-14 10:58:36.645292
I00                            8.0        2023-04-14 11:16:00.339417
scaler1_channels_chan06_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan06_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan07_chname roi1       2023-04-14 10:58:36.645292
roi1                           6.0        2023-04-14 11:16:00.339417
scaler1_channels_chan07_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan07_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan08_chname            2023-04-14 10:58:36.645292
                               8.0        2023-04-14 10:58:36.645292
scaler1_channels_chan08_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan08_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan09_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan09_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan09_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan10_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan10_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan10_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan11_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan11_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan11_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan12_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan12_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan12_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan13_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan13_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan13_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan14_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan14_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan14_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan15_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan15_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan15_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan16_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan16_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan16_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan17_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan17_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan17_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan18_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan18_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan18_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan19_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan19_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan19_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan20_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan20_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan20_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan21_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan21_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan21_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan22_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan22_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan22_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan23_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan23_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan23_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan24_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan24_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan24_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan25_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan25_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan25_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan26_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan26_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan26_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan27_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan27_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan27_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan28_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan28_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan28_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan29_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan29_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan29_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan30_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan30_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan30_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan31_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan31_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan31_gate   N          2023-04-14 10:58:36.645292
scaler1_channels_chan32_chname            2023-04-14 10:58:36.645292
                               0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan32_preset 0.0        2023-04-14 10:58:36.645292
scaler1_channels_chan32_gate   N          2023-04-14 10:58:36.645292
scaler1_count                  0          2023-04-14 10:58:36.645292
scaler1_count_mode             OneShot    2023-04-14 10:58:36.645292
scaler1_delay                  0.0        2023-04-14 10:58:36.645292
scaler1_auto_count_delay       0.0        2023-04-14 10:58:36.645292
scaler1_time                   1.6        2023-04-14 10:58:36.645292
scaler1_freq                   10000000.0 2023-04-14 10:58:36.645292
scaler1_preset_time            1.5        2023-04-14 10:58:36.645292
scaler1_auto_count_time        1.0        2023-04-14 10:58:36.645292
scaler1_update_rate            10.0       2023-04-14 10:58:36.645292
scaler1_auto_count_update_rate 0.0        2023-04-14 10:58:36.645292
scaler1_egu                               2023-04-14 10:58:36.645292
============================== ========== ==========================

As an added convenience, shortcut names for these channels were also assigned to local Python symbols (since the object path to the channels is easy to forget) in instrument/devices/scaler.py with these lines:

timebase = scaler1.channels.chan01.s
I0 = scaler1.channels.chan02.s
scint = scaler1.channels.chan03.s
diode = scaler1.channels.chan04.s

This command: scaler1.select_channels() configures scaler to use just the channels with non-empty names. To limit data collection to a subset of these channels, name the channels in a list argument, such as: scaler1.select_channels(['I0', 'diode'])

temperature#

The temperature simulator, implemented in gp:userCalc8 works like a temperature controller, with setpoint and readback values. temperature can be used as a detector and as a positioner (like a motor).

Additional controls for noise, update interval, maximum change per update, in-position tolerance, and done are provided, either through EPICS or in Python. Once a new setpoint is entered, the readback will progress towards it, limited by the max change and the update interval. Once at the setpoint, noise continues to be added to the temperature to provide realism.

[6]:
temperature.summary()
listdevice(temperature)
data keys (* hints)
-------------------
*temperature
 temperature_calculation
 temperature_description
 temperature_done
 temperature_max_change
 temperature_noise
 temperature_previous_value_pv
 temperature_scanning_rate
 temperature_setpoint
 temperature_tolerance

read attrs
----------
setpoint             EpicsSignal         ('temperature_setpoint')
readback             EpicsSignal         ('temperature')
done                 Signal              ('temperature_done')
calculation          EpicsSignal         ('temperature_calculation')
description          EpicsSignal         ('temperature_description')
max_change           EpicsSignal         ('temperature_max_change')
noise                EpicsSignal         ('temperature_noise')
previous_value_pv    EpicsSignal         ('temperature_previous_value_pv')
scanning_rate        EpicsSignal         ('temperature_scanning_rate')
tolerance            EpicsSignal         ('temperature_tolerance')

config keys
-----------

configuration attrs
-------------------

unused attrs
------------
report_dmov_changes  Signal              ('temperature_report_dmov_changes')

[6]:
=============================== =================================== ==========================
data name                       value                               timestamp
=============================== =================================== ==========================
temperature_setpoint            25.0                                2023-04-14 11:15:58.067527
temperature                     25.07474631876097                   2023-04-14 11:16:00.268549
temperature_done                True                                2023-04-14 11:16:00.274526
temperature_calculation         A+max(-D,min(D,(B-A)))+C*(RNDM-0.5) 2023-04-14 11:15:58.071359
temperature_description         temperature                         2023-04-14 11:15:58.065403
temperature_max_change          2.0                                 2023-04-14 11:15:58.070733
temperature_noise               1.0                                 2023-04-14 11:15:58.070030
temperature_previous_value_pv   gp:userCalc8.VAL                    2023-04-14 11:15:58.065403
temperature_scanning_rate       5                                   2023-04-14 11:15:58.071359
temperature_tolerance           1.0                                 2023-04-14 11:15:58.071359
temperature_report_dmov_changes False                               2023-04-14 11:15:58.066705
=============================== =================================== ==========================

motors#

There are 16 soft motor channels: gp:m1 .. gp:m16. Any motor can be used as a detector and as a positioner.

The first motor, gp:m1, is used to compute a simulated 1-D diffraction peak (noisy) using gp:userCalc1.

To make it easier to change the motor step size, a custom MyEpicsMotor class was made, subclassing from ophyd.EpicsMotor and adding a component to access the motor’s .SREV (steps/revolution) field. The default motor step size is 0.01 (with SREV=200). For only m1, the step size has been changed to 0.001 by setting SREV=2000.

NOTE: Changing SREV (in this simulator* seems to affect the soft motor’s simulated hardware limit. Be careful if you adjust SREV to higher numbers (smaller step sizes) that the expected range of motion remains sufficient for your measurements. Again, another thing to change in the EPICS support.

[7]:
m1.summary()
listdevice(m1)
data keys (* hints)
-------------------
*m1
 m1_user_setpoint

read attrs
----------
user_readback        EpicsSignalRO       ('m1')
user_setpoint        EpicsSignal         ('m1_user_setpoint')

config keys
-----------
m1_acceleration
m1_motor_egu
m1_user_offset
m1_user_offset_dir
m1_velocity

configuration attrs
-------------------
user_offset          EpicsSignal         ('m1_user_offset')
user_offset_dir      EpicsSignal         ('m1_user_offset_dir')
velocity             EpicsSignal         ('m1_velocity')
acceleration         EpicsSignal         ('m1_acceleration')
motor_egu            EpicsSignal         ('m1_motor_egu')

unused attrs
------------
offset_freeze_switch EpicsSignal         ('m1_offset_freeze_switch')
set_use_switch       EpicsSignal         ('m1_set_use_switch')
motor_is_moving      EpicsSignalRO       ('m1_motor_is_moving')
motor_done_move      EpicsSignalRO       ('m1_motor_done_move')
high_limit_switch    EpicsSignalRO       ('m1_high_limit_switch')
low_limit_switch     EpicsSignalRO       ('m1_low_limit_switch')
high_limit_travel    EpicsSignal         ('m1_high_limit_travel')
low_limit_travel     EpicsSignal         ('m1_low_limit_travel')
direction_of_travel  EpicsSignal         ('m1_direction_of_travel')
motor_stop           EpicsSignal         ('m1_motor_stop')
home_forward         EpicsSignal         ('m1_home_forward')
home_reverse         EpicsSignal         ('m1_home_reverse')
steps_per_revolution EpicsSignal         ('m1_steps_per_revolution')

[7]:
======================= ======= ==========================
data name               value   timestamp
======================= ======= ==========================
m1                      0.0     2023-04-14 10:58:33.041083
m1_user_setpoint        0.0     2023-04-14 10:58:33.041083
m1_user_offset          0.0     2023-04-14 10:58:33.041083
m1_user_offset_dir      0       2023-04-14 10:58:33.041083
m1_offset_freeze_switch 0       2023-04-14 10:58:33.041083
m1_set_use_switch       0       2023-04-14 10:58:33.041083
m1_velocity             1.0     2023-04-14 10:58:33.041083
m1_acceleration         0.2     2023-04-14 10:58:33.041083
m1_motor_egu            degrees 2023-04-14 10:58:33.041083
m1_motor_is_moving      0       2023-04-14 10:58:33.041083
m1_motor_done_move      1       2023-04-14 10:58:33.041083
m1_high_limit_switch    0       2023-04-14 10:58:33.041083
m1_low_limit_switch     0       2023-04-14 10:58:33.041083
m1_high_limit_travel    1000.0  2023-04-14 10:58:33.041083
m1_low_limit_travel     -1000.0 2023-04-14 10:58:33.041083
m1_direction_of_travel  0       2023-04-14 10:58:33.041083
m1_motor_stop           0       2023-04-14 10:58:33.041083
m1_home_forward         0       2023-04-14 10:58:33.041083
m1_home_reverse         0       2023-04-14 10:58:33.041083
m1_steps_per_revolution 2000    2023-04-14 10:58:33.041083
======================= ======= ==========================

noisy#

A simulated diffraction peak is computed in 1-D using a Lorentzian function in EPICS PV gp:userCalc1. The simulation uses the position of motor gp:m1 and random choices for center, width, noise, and peak intensity (scale factor). noisy can be used as a detector.

NOTE: noisy is an ophyd.EpicsSignal (variant), thus it lacks the .summary() method that ophyd.Device objects have.

[8]:
listdevice(noisy)
[8]:
========= ================= ==========================
data name value             timestamp
========= ================= ==========================
noisy     685.3703376927396 2023-04-14 11:15:56.876793
========= ================= ==========================

plans#

Several examples of user plan are provided in file instrument/plans/peak_finder_example.py. These plans are described and used in the notebook Lineup a 1-D peak.

Log files#

For diagnostic and general support, log files are created to record activity. In the working directory, the log files are written to a ./.logs subdirectory.

There are two kinds of file, one that records user commands and the python result, the other records items sent to the Python logging package.

In the IPython session, use the ! to run a linux command:

[9]:
!ls -lAFgh .logs
total 7.5M
-rw-rw-r-- 1 prjemian  15K Apr 14 11:16 ipython_console.log
-rw-rw-r-- 1 prjemian  15K Apr 14 10:58 ipython_console.log.001~
-rw-rw-r-- 1 prjemian  16K Apr 14 10:57 ipython_console.log.002~
-rw-rw-r-- 1 prjemian 5.8K Apr 14 11:15 ipython_logger.log
-rw-rw-r-- 1 prjemian 455K Apr 14 11:16 ophyd.control_layer.log
-rw-rw-r-- 1 prjemian 1.0M Apr 14 11:15 ophyd.control_layer.log.1
-rw-rw-r-- 1 prjemian 1.0M Apr 14 11:12 ophyd.control_layer.log.2
-rw-rw-r-- 1 prjemian 1.0M Apr 14 11:06 ophyd.control_layer.log.3
-rw-rw-r-- 1 prjemian 1.0M Apr 14 11:01 ophyd.control_layer.log.4
-rw-rw-r-- 1 prjemian 1.0M Apr 14 10:58 ophyd.control_layer.log.5
-rw-rw-r-- 1 prjemian 1.0M Apr 14 10:55 ophyd.control_layer.log.6
-rw-rw-r-- 1 prjemian 1.0M Apr 14 10:49 ophyd.control_layer.log.7

Let’s take a look at a few lines of each type of file, to get a feel for the information logged.

The ipython_console.log file is created for every session (every time IPython is started or every time the Jupyter kernel is started). It records the commands that were entered and the output, if any, from each command. It does not record anything that was sent to the console by print() statements. The older log files are numbered, higher number is older (each new session shifts these numbers by 1).

[10]:
!head .logs/ipython_console.log
# IPython log file

# Fri, 14 Apr 2023 11:15:58
adsimdet.summary()
# Fri, 14 Apr 2023 11:15:58
scaler1.summary()
# Fri, 14 Apr 2023 11:15:58
get_ipython().run_line_magic('ct', 'scalers')
# Fri, 14 Apr 2023 11:16:00
listdevice(scaler1)

The ipython_logger.log file(s) contain the output from calls to the logger. The files are appended with new logger reports until the file reaches ca. 1 MB. (Larger files are slow to append.) Then the file is given a number and a new logger file is created. Higher numbers are older. At most, 9 numbered files are retained (to avoid filling disk storage with unnecessary diagnostics).

A single logger file may contain reports from several sessions. The third piece of information is the pid (process identifier) of the session. The PID is assigned by the operating system when the session is started.

The logger file contains a full report while the report’s representation is more terse. (Users do not always want the full details. Just remember they are available in the logger file.) Here’s the last few logger lines as shown on the console:

I Wed-00:24:52 - /home/mintadmin/Documents/projects/BCDA-APS/bluesky_instrument_training/instrument/devices/temperature_signal.py
I Wed-00:24:52 - /home/mintadmin/Documents/projects/BCDA-APS/bluesky_instrument_training/instrument/plans/peak_finder_example.py
I Wed-00:24:52 - Startup is complete.

Those same lines appear in the logger file as:

|2021-02-24 00:24:52.091|INFO|25986|bluesky-session|temperature_signal|11|MainThread| - /home/mintadmin/Documents/projects/BCDA-APS/bluesky_instrument_training/instrument/devices/temperature_signal.py
|2021-02-24 00:24:52.165|INFO|25986|bluesky-session|peak_finder_example|15|MainThread| - /home/mintadmin/Documents/projects/BCDA-APS/bluesky_instrument_training/instrument/plans/peak_finder_example.py
|2021-02-24 00:24:52.179|INFO|25986|bluesky-session|collection|23|MainThread| - Startup is complete.
[11]:
!tail .logs/ipython_logger.log
|2023-04-14 11:15:53.916|INFO|3701168|bluesky-session|collection|29|MainThread| - #### Bluesky Framework ####
|2023-04-14 11:15:54.025|INFO|3701168|bluesky-session|collection|32|MainThread| - #### Devices ####
|2023-04-14 11:15:58.072|INFO|3701168|bluesky-session|collection|35|MainThread| - #### Callbacks ####
|2023-04-14 11:15:58.079|INFO|3701168|bluesky-session|collection|38|MainThread| - #### Plans ####
|2023-04-14 11:15:58.130|INFO|3701168|bluesky-session|collection|41|MainThread| - #### Utilities ####
|2023-04-14 11:15:58.131|INFO|3701168|bluesky-session|collection|49|MainThread| - writing to SPEC file: /home/prjemian/Documents/projects/BCDA-APS/bluesky_training/docs/source/instrument/20230414-111558.dat
|2023-04-14 11:15:58.133|INFO|3701168|bluesky-session|collection|50|MainThread| -    >>>>   Using default SPEC file name   <<<<
|2023-04-14 11:15:58.136|INFO|3701168|bluesky-session|collection|51|MainThread| -    file will be created when bluesky ends its next scan
|2023-04-14 11:15:58.137|INFO|3701168|bluesky-session|collection|52|MainThread| -    to change SPEC file, use command:   newSpecFile('title')
|2023-04-14 11:15:58.138|INFO|3701168|bluesky-session|collection|56|MainThread| - #### Startup is complete. ####

SPEC data files#

A SPEC data file records a copy of the scan data (area detector images are not stored in SPEC files). By default, the file is created in the present working directory using a name constructed from the date and time with a .dat file extension. Unlike SPEC, new data is written at the end of a scan. It is also possible to use the SpecWriterCallback to write data extracted from the database after the experiment is done.

The 2021-03 training does not cover the use of these files. You are free to examine them yourselves.

Example SPEC data file from a bluesky session:

#F /home/mintadmin/Documents/projects/BCDA-APS/bluesky_training/20210723-205451.dat
#E 1627091691
#D Fri Jul 23 20:54:51 2021
#C Bluesky  user = mintadmin  host = mint-vm
#O0
#o0

#S 344  scan(detectors=['noisy', 'th_tth_permit'], num=11, args='['theta', 4.5, 3.0, 'ttheta', 9, 6]', per_step='None')
#D Fri Jul 23 20:55:57 2021
#C Fri Jul 23 20:55:57 2021.  plan_type = generator
#C Fri Jul 23 20:55:57 2021.  uid = 17ebe732-8cff-458e-8ebd-f7c5207b2e46
#MD uid = 17ebe732-8cff-458e-8ebd-f7c5207b2e46
#MD beamline_id = Bluesky_training
#MD detectors = ['noisy', 'th_tth_permit']
#MD instrument_name = Bluesky Case Studies
#MD login_id = mintadmin@mint-vm
#MD motors = ('theta', 'ttheta')
#MD notebook = UB_autosave
#MD num_intervals = 10
#MD num_points = 11
#MD objective = Demonstrate UB matrix save & restore
#MD pid = 10389
#MD plan_pattern = inner_product
#MD plan_pattern_args = {'num': 11, 'args': ["EpicsMotor(prefix='gp:m1', name='theta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 4.5, 3.0, "EpicsMotor(prefix='gp:m2', name='ttheta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 9, 6]}
#MD plan_pattern_module = bluesky.plan_patterns
#MD proposal_id = training
#MD versions = {'apstools': '1.5.1', 'bluesky': '1.7.0', 'databroker': '1.2.3', 'epics': '3.5.0', 'h5py': '3.2.1', 'intake': '0.6.2', 'matplotlib': '3.3.4', 'numpy': '1.20.3', 'ophyd': '1.6.1', 'pyRestTable': '2020.0.3', 'spec2nexus': '2021.1.8'}
#P0
#N 8
#L theta  ttheta  Epoch_float  Epoch  ttheta_user_setpoint  theta_user_setpoint  noisy  th_tth_permit
4.5 9.0 9.998106479644775 10 9.0 4.5 0.0 31963.763745860513
4.3500000000000005 8.700000000000001 10.893603086471558 11 8.7 4.35 0.0 34611.37532330056
4.2 8.4 11.595271110534668 12 8.4 4.2 0.0 35180.93342784417
4.05 8.1 12.299082040786743 12 8.1 4.05 0.0 37694.92329152548
3.9 7.8 13.00736141204834 13 7.8 3.9 0.0 39442.76049515945
3.75 7.5 13.71463131904602 14 7.5 3.75 0.0 42096.21324791565
3.6 7.2 14.41884994506836 14 7.2 3.6 0.0 44406.552389513294
3.45 6.9 15.12493348121643 15 6.9 3.45 0.0 45977.709130280506
3.3000000000000003 6.6000000000000005 15.830729246139526 16 6.6 3.3 0.0 48881.20001936124
3.15 6.3 16.53253173828125 17 6.300000000000001 3.1500000000000004 0.0 51082.06178377704
3.0 6.0 17.24329447746277 17 6.0 3.0 0.0 53210.29908657747
#C Fri Jul 23 20:56:14 2021.  num_events_baseline = 2
#C Fri Jul 23 20:56:14 2021.  num_events_primary = 11
#C Fri Jul 23 20:56:14 2021.  exit_status = success

#S 345  scan(detectors=['noisy', 'th_tth_permit'], num=4, args='['theta', 3.5, 3.0, 'ttheta', 7, 6]', per_step='None')
#D Fri Jul 23 20:57:50 2021
#C Fri Jul 23 20:57:50 2021.  plan_type = generator
#C Fri Jul 23 20:57:50 2021.  uid = 5761047b-ac05-428a-8b9c-6601b0aba2c0
#MD uid = 5761047b-ac05-428a-8b9c-6601b0aba2c0
#MD beamline_id = Bluesky_training
#MD detectors = ['noisy', 'th_tth_permit']
#MD instrument_name = Bluesky Case Studies
#MD login_id = mintadmin@mint-vm
#MD motors = ('theta', 'ttheta')
#MD notebook = UB_autosave
#MD num_intervals = 3
#MD num_points = 4
#MD objective = Demonstrate UB matrix save & restore
#MD pid = 10389
#MD plan_pattern = inner_product
#MD plan_pattern_args = {'num': 4, 'args': ["MyMotor(prefix='gp:m1', name='theta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint', 'backlash', 'backlash_velocity'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 3.5, 3.0, "MyMotor(prefix='gp:m2', name='ttheta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint', 'backlash', 'backlash_velocity'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 7, 6]}
#MD plan_pattern_module = bluesky.plan_patterns
#MD proposal_id = training
#MD versions = {'apstools': '1.5.1', 'bluesky': '1.7.0', 'databroker': '1.2.3', 'epics': '3.5.0', 'h5py': '3.2.1', 'intake': '0.6.2', 'matplotlib': '3.3.4', 'numpy': '1.20.3', 'ophyd': '1.6.1', 'pyRestTable': '2020.0.3', 'spec2nexus': '2021.1.8'}
#P0
#N 12
#L theta  ttheta  Epoch_float  Epoch  theta_user_setpoint  theta_backlash  theta_backlash_velocity  ttheta_user_setpoint  ttheta_backlash  ttheta_backlash_velocity  noisy  th_tth_permit
3.5 7.0 4.44653844833374 4 3.5 0.0 1.0 7.0 0.5 0.2 0.0 46002.45836582881
3.333 6.67 8.672361612319946 9 3.3333333333333335 0.0 1.0 6.666666666666667 0.5 0.2 0.0 46875.28455439954
3.1670000000000003 6.33 12.796637535095215 13 3.1666666666666665 0.0 1.0 6.333333333333333 0.5 0.2 0.0 50480.82899166115
3.0 6.0 16.955684661865234 17 3.0 0.0 1.0 6.0 0.5 0.2 0.0 54033.337551435354
#C Fri Jul 23 20:58:08 2021.  num_events_baseline = 2
#C Fri Jul 23 20:58:08 2021.  num_events_primary = 4
#C Fri Jul 23 20:58:08 2021.  exit_status = success

#S 346  scan(detectors=['noisy', 'th_tth_permit'], num=4, args='['theta', 4.0, 3.0, 'ttheta', 8, 6]', per_step='None')
#D Fri Jul 23 20:58:28 2021
#C Fri Jul 23 20:58:28 2021.  plan_type = generator
#C Fri Jul 23 20:58:28 2021.  uid = 14e4398d-42b5-40cf-b082-e2f7e4fd07fd
#MD uid = 14e4398d-42b5-40cf-b082-e2f7e4fd07fd
#MD beamline_id = Bluesky_training
#MD detectors = ['noisy', 'th_tth_permit']
#MD instrument_name = Bluesky Case Studies
#MD login_id = mintadmin@mint-vm
#MD motors = ('theta', 'ttheta')
#MD notebook = UB_autosave
#MD num_intervals = 3
#MD num_points = 4
#MD objective = Demonstrate UB matrix save & restore
#MD pid = 10389
#MD plan_pattern = inner_product
#MD plan_pattern_args = {'num': 4, 'args': ["MyMotor(prefix='gp:m1', name='theta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint', 'backlash', 'backlash_velocity'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 4.0, 3.0, "MyMotor(prefix='gp:m2', name='ttheta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint', 'backlash', 'backlash_velocity'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 8, 6]}
#MD plan_pattern_module = bluesky.plan_patterns
#MD proposal_id = training
#MD versions = {'apstools': '1.5.1', 'bluesky': '1.7.0', 'databroker': '1.2.3', 'epics': '3.5.0', 'h5py': '3.2.1', 'intake': '0.6.2', 'matplotlib': '3.3.4', 'numpy': '1.20.3', 'ophyd': '1.6.1', 'pyRestTable': '2020.0.3', 'spec2nexus': '2021.1.8'}
#P0
#N 12
#L theta  ttheta  Epoch_float  Epoch  theta_user_setpoint  theta_backlash  theta_backlash_velocity  ttheta_user_setpoint  ttheta_backlash  ttheta_backlash_velocity  noisy  th_tth_permit
4.0 8.0 5.639213562011719 6 4.0 0.0 1.0 8.0 0.5 0.2 0.0 37946.01826904763
3.6670000000000003 7.33 10.168693780899048 10 3.6666666666666665 0.0 1.0 7.333333333333333 0.5 0.2 0.0 43240.06624621665
3.333 6.67 14.600727081298828 15 3.3333333333333335 0.0 1.0 6.666666666666667 0.5 0.2 0.0 47339.97568055583
3.0 6.0 19.046493530273438 19 3.0 0.0 1.0 6.0 0.5 0.2 0.0 52702.49931930254
#C Fri Jul 23 20:58:48 2021.  num_events_baseline = 2
#C Fri Jul 23 20:58:48 2021.  num_events_primary = 4
#C Fri Jul 23 20:58:48 2021.  exit_status = success

#S 347  scan(detectors=['noisy', 'th_tth_permit'], num=4, args='['theta', 4.0, 3.0, 'ttheta', 8, 6]', per_step='None')
#D Fri Jul 23 21:03:30 2021
#C Fri Jul 23 21:03:30 2021.  plan_type = generator
#C Fri Jul 23 21:03:30 2021.  uid = 8869043f-56fc-4b3b-ad75-bc024694e6d6
#MD uid = 8869043f-56fc-4b3b-ad75-bc024694e6d6
#MD beamline_id = Bluesky_training
#MD detectors = ['noisy', 'th_tth_permit']
#MD instrument_name = Bluesky Case Studies
#MD login_id = mintadmin@mint-vm
#MD motors = ('theta', 'ttheta')
#MD notebook = UB_autosave
#MD num_intervals = 3
#MD num_points = 4
#MD objective = Demonstrate UB matrix save & restore
#MD pid = 10389
#MD plan_pattern = inner_product
#MD plan_pattern_args = {'num': 4, 'args': ["MyMotor(prefix='gp:m1', name='theta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint', 'backlash', 'backlash_velocity'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 4.0, 3.0, "MyMotor(prefix='gp:m2', name='ttheta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint', 'backlash', 'backlash_velocity'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 8, 6]}
#MD plan_pattern_module = bluesky.plan_patterns
#MD proposal_id = training
#MD versions = {'apstools': '1.5.1', 'bluesky': '1.7.0', 'databroker': '1.2.3', 'epics': '3.5.0', 'h5py': '3.2.1', 'intake': '0.6.2', 'matplotlib': '3.3.4', 'numpy': '1.20.3', 'ophyd': '1.6.1', 'pyRestTable': '2020.0.3', 'spec2nexus': '2021.1.8'}
#P0
#N 12
#L theta  ttheta  Epoch_float  Epoch  theta_user_setpoint  theta_backlash  theta_backlash_velocity  ttheta_user_setpoint  ttheta_backlash  ttheta_backlash_velocity  noisy  th_tth_permit
4.0 8.0 5.540862798690796 6 4.0 0.0 1.0 8.0 0.5 0.2 0.0 37996.6450939875
3.6670000000000003 7.33 9.968387842178345 10 3.6666666666666665 0.0 1.0 7.333333333333333 0.5 0.2 0.0 42649.32391462264
3.333 6.67 14.498717546463013 14 3.3333333333333335 0.0 1.0 6.666666666666667 0.5 0.2 0.0 46995.88551308093
3.0 6.0 19.0245144367218 19 3.0 0.0 1.0 6.0 0.5 0.2 0.0 54150.34088276983
#C Fri Jul 23 21:03:49 2021.  num_events_baseline = 2
#C Fri Jul 23 21:03:49 2021.  num_events_primary = 4
#C Fri Jul 23 21:03:49 2021.  exit_status = success

#S 348  scan(detectors=['noisy', 'th_tth_permit'], num=4, args='['theta', 4.0, 3.0, 'ttheta', 8, 6]', per_step='None')
#D Fri Jul 23 21:04:04 2021
#C Fri Jul 23 21:04:04 2021.  plan_type = generator
#C Fri Jul 23 21:04:04 2021.  uid = e9498d10-8fb7-41c4-aaba-f5204db32761
#MD uid = e9498d10-8fb7-41c4-aaba-f5204db32761
#MD beamline_id = Bluesky_training
#MD detectors = ['noisy', 'th_tth_permit']
#MD instrument_name = Bluesky Case Studies
#MD login_id = mintadmin@mint-vm
#MD motors = ('theta', 'ttheta')
#MD notebook = UB_autosave
#MD num_intervals = 3
#MD num_points = 4
#MD objective = Demonstrate UB matrix save & restore
#MD pid = 10389
#MD plan_pattern = inner_product
#MD plan_pattern_args = {'num': 4, 'args': ["MyMotor(prefix='gp:m1', name='theta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint', 'backlash', 'backlash_velocity'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 4.0, 3.0, "MyMotor(prefix='gp:m2', name='ttheta', settle_time=0.0, timeout=None, read_attrs=['user_readback', 'user_setpoint', 'backlash', 'backlash_velocity'], configuration_attrs=['user_offset', 'user_offset_dir', 'velocity', 'acceleration', 'motor_egu'])", 8, 6]}
#MD plan_pattern_module = bluesky.plan_patterns
#MD proposal_id = training
#MD versions = {'apstools': '1.5.1', 'bluesky': '1.7.0', 'databroker': '1.2.3', 'epics': '3.5.0', 'h5py': '3.2.1', 'intake': '0.6.2', 'matplotlib': '3.3.4', 'numpy': '1.20.3', 'ophyd': '1.6.1', 'pyRestTable': '2020.0.3', 'spec2nexus': '2021.1.8'}
#P0
#N 12
#L theta  ttheta  Epoch_float  Epoch  theta_user_setpoint  theta_backlash  theta_backlash_velocity  ttheta_user_setpoint  ttheta_backlash  ttheta_backlash_velocity  noisy  th_tth_permit
4.0 8.0 5.37143349647522 5 4.0 0.0 1.0 8.0 0.5 0.2 0.0 38949.839143231766
3.6670000000000003 7.33 9.859706163406372 10 3.6666666666666665 0.0 1.0 7.333333333333333 0.5 0.2 0.0 43461.79736667226
3.333 6.67 14.294379949569702 14 3.3333333333333335 0.0 1.0 6.666666666666667 0.5 0.2 0.0 47545.930496638146
3.0 6.0 18.838080167770386 19 3.0 0.0 1.0 6.0 0.5 0.2 0.0 53464.77967307891
#C Fri Jul 23 21:04:23 2021.  num_events_baseline = 2
#C Fri Jul 23 21:04:23 2021.  num_events_primary = 4
#C Fri Jul 23 21:04:23 2021.  exit_status = success

User Code File#

A common request from instruments is to allow the user to write some python code that can be loaded into the current session. Since development of this user code is often iterative, it must be possible to reload the code without requiring the session to exit and restart.

The local file user/quick_hello.py provides an example of such a user code file. Load this file with the command:

%run -im user.quick_hello

The %run is an IPython Magic command. Magic commands are only available from the interactive IPython command line (either in a console or Jupyter notebook). You can’t use them in your Python files, functions, classes, etc.

The user/ directory must be on the import path, which we did as part of starting the instrument package (above). Also, user/ must contain a file named init.py so that user is an importable Python package.

The m (as module) command option was added so it is not necessary to give the Python extension .py.

Alternatively, we could use the command:

%run -i user/quick_hello.py

which loads a Python file from a directory.

SPEC users

For SPEC users, the %run -i directory/file syntax is the IPython (and Jupyter notebook) equivalent of SPEC’s qdo spec_macro.mac command.

This file demonstrates the quintessential Hello, World! demonstration in the context of the Bluesky framework. It defines an ophyd HelloDevice class which is used to create a Python object for control: hello_device. Finally, it creates a hello_world() (bluesky) plan which can be used with the bluesky RunEngine to run the Hello, World! demonstration.

First, load the code from this file:

[12]:
%run -im user.quick_hello
Loading 'Hello, World!' example.
[13]:
findpv("gp:userCalc8.CALC")
NameError: name 'findpv' is not defined

Observe that the same .calculation is both readable and writable from two different ophyd objects: calcs.calc8 and temperature.

databroker#

The databroker package provides a Python interface to the database with the experiment data, including references to the large file content such as area detector images. A YAML configuration file connects databroker with a specific repository in the MongoDB database server. In the example here (bluesky_class.yml), the name of the catalog entry in this file is class_2021_03. It makes two connections to a MongoDB server running on the same workstation localhost. Both connections are to the same MongoDB collection: class_2021_03-bluesky. The name of the file is not important as long as it is placed in a directory searched by databroker.catalog.

Example

# file: bluesky_class.yml
# purpose: Configuration file to connect Bluesky databroker with MongoDB
# For 2021-03 Python Training at APS

# Copy to: ~/.local/share/intake/bluesky_class.yml
# Create subdirectories as needed

sources:
  class_2021_03:
    args:
      asset_registry_db: mongodb://localhost:27017/class_2021_03-bluesky
      metadatastore_db: mongodb://localhost:27017/class_2021_03-bluesky
    driver: bluesky-mongo-normalized-catalog

When the bluesky session starts, this class_2021_03 catalog is referenced when creating the db object in the instrument package, in instrument.framework.initialize.py by these lines:

catalog_name = "class_2021_03"
db = databroker.catalog[catalog_name].v1
logger.info(f"using databroker catalog '{catalog_name}'")