How to run a scan#
This page covers the common Bluesky scan plans and how to invoke them in an interactive session.
For the conceptual background, see The RunEngine and Plans and stubs.
For coming-from-SPEC users, the cross-walk table has the short version of everything below.
The invocation pattern#
All scan plans live in bluesky.plans, imported as bp in the
session. All take a list of detectors as the first argument:
RE(bp.count([detector], num=1))
RE(bp.scan([detector], motor, start, stop, num_points))
RE(bp.rel_scan([detector], motor, -dx, +dx, num_points))
RE(bp.grid_scan([detector], motor1, s1, e1, n1, motor2, s2, e2, n2))
RE(bp.list_scan([detector], motor, [0, 1.5, 3.0, 5.0]))
RE(bp.spiral([detector], xmotor, ymotor, x_start, y_start, x_range, y_range, dr, nth))
Replace [detector] with [scaler, eiger2] for multiple detectors.
Count#
RE(bp.count([scaler])) # one count
RE(bp.count([scaler], num=10)) # ten counts in a row
RE(bp.count([scaler], num=10, delay=0.5)) # ten counts, 0.5 s apart
Absolute and relative scans#
# Absolute -- start and stop are the actual positions:
RE(bp.scan([scaler], sample_stage.xprime, 0, 10, 11)) # 0, 1, 2, ..., 10
# Relative -- start and stop are offsets from the current position:
RE(bp.rel_scan([scaler], sample_stage.xprime, -1, 1, 11))
Counting note for SPEC users: Bluesky’s num is the number of
points, not the number of intervals. An ascan covering 0 to 10 in
SPEC’s ascan 0 10 10 is bp.scan(..., 0, 10, 11) in Bluesky.
Multi-motor synchronized scan#
bp.scan takes pairs of motor, start, stop – all motors move
synchronously:
RE(bp.scan(
[scaler],
sample_stage.xprime, 0, 10,
sample_stage.base_y, 0, 5,
num=11,
))
This is SPEC’s a2scan/a3scan.
Mesh#
bp.grid_scan takes motor, start, stop, num triples; motors are
nested innermost-last:
RE(bp.grid_scan(
[scaler],
sample_stage.xprime, 0, 10, 11, # outer; varies slowest
sample_stage.base_y, 0, 5, 6, # inner; varies fastest
))
Add snake_axes=True to alternate row directions and save the
extra traverse:
RE(bp.grid_scan(
[scaler],
sample_stage.xprime, 0, 10, 11,
sample_stage.base_y, 0, 5, 6,
snake_axes=True,
))
Attaching metadata#
Every scan accepts md={...}, a dictionary of arbitrary metadata
that travels with the run:
RE(bp.scan(
[scaler], sample_stage.xprime, 0, 10, 11,
md={"sample": "Si(111)", "experimenter": "ECP", "purpose": "alignment"},
))
You can later filter the catalog by these keys.
Live plots and tables#
When you call RE(...), two callbacks are already subscribed:
bec(BestEffortCallback) – opens a live plot for any 1-D scan against ahinted-kind detector, and prints a table.The Tiled writer – sends documents to the Tiled server (sn.xray.aps.anl.gov:8000).
You do not have to do anything special; both fire automatically for
every RE(...) invocation.
To peek at recent runs in code, see How to inspect data.
Pausing and resuming#
Press Ctrl-C twice during a scan to pause the RunEngine. At the
pause prompt you can:
RE.resume()– continue the plan from where it paused.RE.stop()– end the run cleanly (a “stop” document withexit_status='success'is emitted).RE.abort()– end the run withexit_status='abort'.RE.halt()– emergency stop without any document emission.
This works for any plan, including custom ones; the RunEngine pauses between messages, so any in-flight motion completes before the pause takes effect.
Stopping a misbehaving plan#
If a plan is doing something you do not like and Ctrl-C is not working (e.g. the plan is wedged in an external call), the path is:
Ctrl-Conce – requests a deferred pause.Ctrl-Cagain – requests an immediate pause (interrupts the current await).If the prompt does not return: in a separate terminal, find the IPython PID and
kill -INT <pid>.
The RunEngine catches KeyboardInterrupt cleanly; the IOC keeps
running whatever was commanded last.
Custom plans#
If you find yourself typing the same multi-line sequence repeatedly, turn it into a plan. See How to add a plan.
See also#
Plans and stubs – the difference between
bp.*(full plans) andbps.*(stubs).Inspect data – after the scan finishes.
Cheat sheet – one-page summary.