Basic Operation in Bluesky
Note
Bluesky provides a thorough tutorial. The content here should be taken as a quick reference or crib sheet.
Commands are grouped into “plans”. All plans in the Bluesky interface must be
passed to the RunEngine
, usually accessible by the variable RE
.
In [1]: RE( <plan> )
<plan output>
The RunEngine
then takes care of orchestrating the experiment, sending
signals to devices, and storing data.
Standard plans
Standard plans are stored under the variable bp
, which stands for
bluesky.plans
. Most operations should eventually be folded into customized
plans, but these will get the job done until then. Detailed documentation on
Bluesky’s pre-assembled plans can be found
here.
Examples here will utilize basic Bluesky plans, which
may be too verbose for the average user. More specialized plans will be covered
in the Plans section
Scanning detectors: bp.scan
The scan plan takes in a list of detectors to measure, a motor to scan, a start coordinate, stop coordinate, and number of measurements.
In [3]: RE(bp.scan([det], motor, -1, 1, 5))
Transient Scan ID: 1 Time: 2020-08-27 15:57:41
Persistent Unique Scan ID: '2a328312-e6e8-4127-a815-05f7020c6b2a'
New stream: 'primary'
+-----------+------------+------------+------------+
| seq_num | time | motor | det |
+-----------+------------+------------+------------+
| 1 | 15:57:41.5 | -1.000 | 0.607 |
| 2 | 15:57:41.5 | -0.500 | 0.882 |
| 3 | 15:57:41.8 | 0.000 | 1.000 |
| 4 | 15:57:41.8 | 0.500 | 0.882 |
| 5 | 15:57:41.9 | 1.000 | 0.607 |
+-----------+------------+------------+------------+
generator scan ['2a328312'] (scan num: 1)
Out[3]: ('2a328312-e6e8-4127-a815-05f7020c6b2a',)
One can also perform a relative scan with bp.rel_scan
In [4]: %mov motor 1
In [5]: RE(bp.rel_scan([det], motor, -1, 1, 5))
Transient Scan ID: 16 Time: 2020-09-17 18:40:56
Persistent Unique Scan ID: '223510b1-fe9e-4b12-b427-70e5370ed86c'
New stream: 'baseline'
New stream: 'primary'
+-----------+------------+------------+------------+
| seq_num | time | motor | det |
+-----------+------------+------------+------------+
| 1 | 18:40:56.7 | 0.000 | 1.000 |
| 2 | 18:40:56.7 | 0.500 | 0.882 |
| 3 | 18:40:56.8 | 1.000 | 0.607 |
| 4 | 18:40:56.8 | 1.500 | 0.325 |
| 5 | 18:40:56.8 | 2.000 | 0.135 |
+-----------+------------+------------+------------+
generator rel_scan ['223510b1'] (scan num: 16)
Out[5]: ('223510b1-fe9e-4b12-b427-70e5370ed86c',)
Scanning a grid of points: bp.grid_scan
Relevant to High Throughput operations at 1-5 is the grid scan plan. This plan scans multiple motors, with the option to either raster (default) or snake along axes. Snaking will be more time efficient, but the time saved depends on factors such as motor speed More detailed descriptions of these plans can be found here. Snaking explained
In [31]: RE(bp.grid_scan([det],
...: motor1, -1, 1, 3,
...: motor2, -3, 3, 7, True))
Transient Scan ID: 17 Time: 2020-09-17 18:55:11
Persistent Unique Scan ID: 'd25cc1c3-a4bb-45b4-aabe-83379c8e9e18'
New stream: 'baseline'
New stream: 'primary'
+-----------+------------+------------+------------+------------+
| seq_num | time | motor1 | motor2 | det |
+-----------+------------+------------+------------+------------+
| 1 | 18:55:11.1 | -1.000 | -3.000 | 0.607 |
| 2 | 18:55:11.2 | -1.000 | -2.000 | 0.607 |
| 3 | 18:55:11.2 | -1.000 | -1.000 | 0.607 |
| 4 | 18:55:11.3 | -1.000 | 0.000 | 0.607 |
| 5 | 18:55:11.3 | -1.000 | 1.000 | 0.607 |
| 6 | 18:55:11.3 | -1.000 | 2.000 | 0.607 |
| 7 | 18:55:11.6 | -1.000 | 3.000 | 0.607 |
| 8 | 18:55:11.6 | 0.000 | 3.000 | 0.607 |
| 9 | 18:55:11.6 | 0.000 | 2.000 | 0.607 |
| 10 | 18:55:11.6 | 0.000 | 1.000 | 0.607 |
| 11 | 18:55:11.6 | 0.000 | 0.000 | 0.607 |
| 12 | 18:55:11.6 | 0.000 | -1.000 | 0.607 |
| 13 | 18:55:11.6 | 0.000 | -2.000 | 0.607 |
| 14 | 18:55:11.6 | 0.000 | -3.000 | 0.607 |
| 15 | 18:55:11.6 | 1.000 | -3.000 | 0.607 |
| 16 | 18:55:11.7 | 1.000 | -2.000 | 0.607 |
| 17 | 18:55:11.7 | 1.000 | -1.000 | 0.607 |
| 18 | 18:55:11.7 | 1.000 | 0.000 | 0.607 |
| 19 | 18:55:11.7 | 1.000 | 1.000 | 0.607 |
| 20 | 18:55:11.7 | 1.000 | 2.000 | 0.607 |
| 21 | 18:55:11.7 | 1.000 | 3.000 | 0.607 |
+-----------+------------+------------+------------+------------+
generator grid_scan ['d25cc1c3'] (scan num: 17)
Out[31]: ('d25cc1c3-a4bb-45b4-aabe-83379c8e9e18',)
Grid scans: bp.grid_scan
Scanning over an array of samples arranged in a grid is handled by the ‘grid scan’ plan. A more complete discussion can be found on the official documentation
Move motors: bps.mv
The one exception to the rule is the “move” plan, which is a “plan_stub” used
as a component of other plans. As such it is accessed via bps.mv
.
In [1]: RE(bps.mv(motor, 2))
Out[1]: ()
Can also access via “ipython magic” shortcuts
In [2]: %mv motor 2
Convenience Functions
Motor position summary: %wa
In [1]: %wa
motors
Positioner Value Low Limit High Limit Offset
motor 0 AttributeError AttributeError AttributeError
motor1 0 AttributeError AttributeError AttributeError
motor2 0 AttributeError AttributeError AttributeError
Local variable name Ophyd name (to be recorded as metadata)
motor motor
motor1 motor1
motor2 motor2
Accessing saved data
You can access saved data through the databroker instance, which should be
accessible as db
in the bluesky sesison. Some standard patterns are
demonstrated below:
Official Documentation
In [19]: uid = RE(bp.scan([det], motor, -1, 1, 5))
Transient Scan ID: 15 Time: 2020-09-17 18:13:03
Persistent Unique Scan ID: 'e2d02570-9b8f-4254-b205-107ba990d740'
New stream: 'baseline'
New stream: 'primary'
+-----------+------------+------------+------------+
| seq_num | time | motor | det |
+-----------+------------+------------+------------+
| 1 | 18:13:03.4 | -1.000 | 0.607 |
| 2 | 18:13:03.5 | -0.500 | 0.882 |
| 3 | 18:13:03.5 | 0.000 | 1.000 |
| 4 | 18:13:03.5 | 0.500 | 0.882 |
| 5 | 18:13:03.5 | 1.000 | 0.607 |
+-----------+------------+------------+------------+
generator scan ['e2d02570'] (scan num: 15)
In [20]: db[-1].table() # -1 designates the last run. -2 would be second most recent, and so on.
Out[20]:
time det motor motor_setpoint
seq_num
1 2020-09-18 01:13:03.437488556 0.606531 -1.0 -1.0
2 2020-09-18 01:13:03.519411087 0.882497 -0.5 -0.5
3 2020-09-18 01:13:03.561402082 1.000000 0.0 0.0
4 2020-09-18 01:13:03.569704294 0.882497 0.5 0.5
5 2020-09-18 01:13:03.576864004 0.606531 1.0 1.0
In [24]: db[-1].table('baseline') # we have set up bluesky to take baseline measurements before and after each run
Out[24]:
time s_stage_px ... s_stage_th s_stage_th_user_setpoint
seq_num ...
1 2020-09-18 01:13:03.422709942 12.0 ... 1.0 1.0
2 2020-09-18 01:13:03.589560270 12.0 ... 1.0 1.0
[2 rows x 13 columns]
For those familiar with python data analysis, the db[-1].table()
returns a
familiar pandas.DataFrame
object.
Searching the DataBroker:
One of the key advantages of the Bluesky data collection system is the ability to search data quickly via saved metadata. Again for a more thorough discussion, look here.
To search DataBroker based on metadata, use parentheses:
# Search by plan name.
headers = db(plan_name='scan')
# Search for runs involving a motor with the name 'eta'.
headers = db(motor='eta')
# Search for runs operated by a given user---assuming this metadata was
# recorded in the first place!
headers = db(operator='Dan')
# Search by time range. (These keywords have a special meaning.)
headers = db(since='2015-03-05', until='2015-03-10')
To search DataBroker based on relative indexing or unique id, you can use square brackets:
# Get the most recent run.
header = db[-1]
# Get the fifth most recent run.
header = db[-5]
# Get a list of all five most recent runs, using Python slicing syntax.
headers = db[-5:]
# Get a run whose unique ID ("RunStart uid") begins with 'x39do5'.
header = db['x39do5']
# Get a run whose integer scan_id is 42. Note that this might not be
# unique. In the event of duplicates, the most recent match is returned.
header = db[42]
Managing Metadata
Metadata is stored with each run, and can be set either for a single run or for all future runs. For a more thorough discussion, look here for the full documentation.
Per-run metadata
Information such as sample name or operational note can be added only to the current plan. One-time metadata can be added directly to the RE() call, and will be propogated to all runs in the plan:
In [1]: RE(plan(), sample_id='A', purpose='calibration')
Persistent metadata
For information pertaining to all runs in a beamtime, metadata can be recorded persistently.
To see the current metadata dictionary:
In [8]: show_md()
Persistent Metadata --------------------
beamline_id: SSRL 1-5 HiTp
scan_id: 6
login_id: b_spec@bluedevlx.slac.stanford.edu
pid: 28034
versions: {'bluesky': '1.6.6', 'ophyd': '1.5.3', 'databroker': '1.1.0', 'ssrltools': '0.1', 'epics': '3.4.2', 'numpy': '1.19.1', 'matplotlib': '3.3.1', 'pymongo': '3.11.0'}
proposal_id: testing
----------------------------------------
Setting items in this metadata dictionary follows standard python dictionary syntax. This metadata will be retained across ipython session restarts:
In [10]: RE.md['operator']='roberttk'
In [11]: show_md()
Persistent Metadata --------------------
beamline_id: SSRL 1-5 HiTp
scan_id: 6
login_id: b_spec@bluedevlx.slac.stanford.edu
pid: 28034
operator: roberttk
versions: {'bluesky': '1.6.6', 'ophyd': '1.5.3', 'databroker': '1.1.0', 'ssrltools': '0.1', 'epics': '3.4.2', 'numpy': '1.19.1', 'matplotlib': '3.3.1', 'pymongo': '3.11.0'}
proposal_id: testing
----------------------------------------
Viewing metadata
Most of the useful metadata can be accessed from the ‘run_start’ document:
In [4]: header = db[-1] # grab header for the last/most recent run
In [5]: header.start
Out[5]:
{'uid': '882fe013-33c4-4fc8-b345-48a4eacb8c87',
'time': 1600276751.726932,
'versions': {'ophyd': '1.5.3', 'bluesky': '1.6.6'},
'scan_id': 1,
'plan_type': 'generator',
'plan_name': 'scan',
'detectors': ['det'],
'motors': ['motor'],
'num_points': 3,
'num_intervals': 2,
'plan_args': {'detectors': ["SynGauss(prefix='', name='det', read_attrs=['val'], configuration_attrs=['Imax', 'center', 'sigma', 'noise', 'noise_multiplier'])"],
'num': 3,
'args': ["SynAxis(prefix='', name='motor', read_attrs=['readback', 'setpoint'], configuration_attrs=['velocity', 'acceleration'])",
-1,
1],
'per_step': 'None'},
'hints': {'dimensions': [[['motor'], 'primary']]},
'plan_pattern': 'inner_product',
'plan_pattern_module': 'bluesky.plan_patterns',
'plan_pattern_args': {'num': 3,
'args': ["SynAxis(prefix='', name='motor', read_attrs=['readback', 'setpoint'], configuration_attrs=['velocity', 'acceleration'])",
-1,
1]}}