Coming to Bluesky from SPEC#

This page is for users with SPEC experience. We assume you know what mv, umvr, ascan, dscan, and ct do; what wm shows; and how SPEC stores data in numbered scan files.

Bluesky is a different beast. This page is the cross-walk between SPEC concepts and their Bluesky equivalents – close enough to bridge the gap, honest about where Bluesky is more trouble than SPEC.

The single most important difference#

In SPEC, you type a command and the hardware moves:

SPEC> mv samx 5

In Bluesky, you type the description of a motion and hand it to the RunEngine to execute:

RE(bps.mv(sample_stage.xprime, 5))

The bps.mv(...) part returns a Python generator – a description of what should happen. The RE(...) part does it. If you forget the RE(...) and type just bps.mv(...), the motor does not move.

This is the source of more new-user confusion than anything else. See The RunEngine for why Bluesky is built this way.

Command cross-walk#

SPEC

Bluesky

Notes

mv samx 5

RE(bps.mv(sample_stage.xprime, 5))

Move one motor to absolute position.

mv samx 5 samy 3

RE(bps.mv(sample_stage.xprime, 5, sample_stage.base_y, 3))

Multi-motor move (parallel).

umv samx 5

RE(bps.mv(sample_stage.xprime, 5))

Bluesky always waits; no “update vs. non-update” distinction.

mvr samx 0.1

RE(bps.mvr(sample_stage.xprime, 0.1))

Relative move.

wm samx

sample_stage.xprime.position

Returns a float; no RE(...) – this is not a plan.

wa

%wa motors (IPython magic)

“where all” for motors with the motors label.

ct 1

RE(bp.count([scaler], num=1))

One count of named detector(s).

ct 1 5

RE(bp.count([scaler], num=5, delay=...))

Five counts; delay= is between them.

ascan samx 0 10 10 1

RE(bp.scan([scaler], sample_stage.xprime, 0, 10, 11))

SPEC counts intervals; Bluesky counts points. 10 intervals = 11 points.

dscan samx -1 1 10 1

RE(bp.rel_scan([scaler], sample_stage.xprime, -1, 1, 11))

Relative scan around current position.

a2scan samx 0 10 samy 0 5 10 1

RE(bp.scan([scaler], sample_stage.xprime, 0, 10, sample_stage.base_y, 0, 5, 11))

Multi-motor synchronized scan.

mesh samx 0 10 5 samy 0 5 5 1

RE(bp.grid_scan([scaler], sample_stage.xprime, 0, 10, 6, sample_stage.base_y, 0, 5, 6))

2-D mesh; again, +1 on each “intervals” count.

chg_offset samx 0

sample_stage.xprime.set_current_position(0)

Set the current readback to a new value (changes the offset).

set_lm samx -10 10

(use motor record .LLM / .HLM via caput, or write a plan)

Bluesky has no built-in limit-setter shortcut; ophyd exposes them as sample_stage.xprime.low_limit_travel and .high_limit_travel.

shopen / shclose

RE(bps.mv(shutter, "open")) / RE(bps.mv(shutter, "close"))

(or shutter.open() / shutter.close() direct, no RE – see “When NOT to use RE” below)

# data file management

cat = init_catalog(iconfig) (Tiled-backed catalog)

Bluesky writes structured runs to a server; no per-scan file.

newfile mydata

(not applicable; runs are tagged with metadata)

See md={...} in scans.

pdshow, pd write

(bec does this live; see inspect data for after)

The bec callback prints tables and opens plots automatically.

qdo overnight.mac

%run -i overnight.py

load (and execute) code from a file

Bluesky’s built-in scan plans live in bluesky.plans and are imported as bp in the session. Plan stubs (one-shot operations that compose into plans) live in bluesky.plan_stubs and are imported as bps. See Plans and stubs for the distinction.

When not to use RE(...)#

SPEC has no distinction: every command at the prompt is a command. Bluesky has two kinds of expressions:

# Plans -- use RE(...)
RE(bps.mv(motor, 5))
RE(bp.scan([detector], motor, 0, 10, 11))
RE(laser_optics.move_out())

# NOT plans -- call directly, no RE(...)
sample_stage.xprime.position             # returns a float
sample_stage.xprime.user_readback.get()  # returns a float
laser_optics.is_out                 # returns a bool
sample_stage.xprime.read()               # returns a dict
shutter.open()                      # returns immediately; the bare device method
cat[-1].primary.read()              # returns an xarray Dataset

The rule: if it returns a generator, wrap with RE; if it returns data, call directly. See The RunEngine for the full reasoning.

What Bluesky has that SPEC does not#

These are the upsides:

  • Structured metadata. Every run has a unique UID, a scan_id, a start time, an end time, and a customizable metadata dict (md=). No more “what was scan 47 of yesterday?”.

  • Pause / resume. Two Ctrl-C’s pause the RunEngine between steps. Inspect, fix, then RE.resume().

  • Suspenders. “Pause on beam dump, resume when it comes back” is a generic Bluesky mechanism, not bespoke per-beamline code.

  • Document streams. Every run emits a stream of structured documents (start, descriptor, event, stop). Subscribers consume the stream live: BEC for plots, TiledWriter for storage, SPEC-format writer for SPEC-style files (if enabled).

  • Live plotting. The BestEffortCallback (bec) opens a plot for any 1-D scan automatically.

What SPEC has that Bluesky does not#

These are real downsides; pretending otherwise wastes everyone’s time:

  • Compactness. ascan samx 0 10 10 1 is much shorter than RE(bp.scan([scaler], sample_stage.xprime, 0, 10, 11)). You can alias common scans to save typing, but the bare commands are longer.

  • Inline command editing. SPEC lets you edit and re-run scans by recalling the command line. Bluesky has IPython history, which is close but not the same.

  • Macros. SPEC’s overnight.mac is sometimes faster to write than authoring a Python plan, especially for one-off ideas. Bluesky plans are more reusable but more verbose to write. Both can be nested, Bluesky plans are easier to understand side-effects when nesting.

  • One command, one file. SPEC files are human-readable text with per-scan headers. Bluesky data lives in a Tiled catalog; reading it requires a client (Python, or a Tiled web browser).

  • Decades of stability. SPEC’s command set has not changed in ages. Bluesky is younger and the ecosystem is still moving.

Data files#

In SPEC, you set a data file with newfile, every scan appends to it, and you read with pdshow / external tools.

In Bluesky at 3-ID-C:

  • Runs are sent to a Tiled server by the TiledWriter callback. Runs land under the /raw tree on that server.

  • Area-detector image files (Eiger HDF5) are written by the IOC into a beamline-specific directory and linked into the Bluesky master HDF5 file via HDF5 external links.

  • The catalog client (cat in your session) is the way to look at runs after the fact: run = cat[-1]; run.primary.read().

See How to inspect data and How to visualize HDF5 for the practical workflows.

See also#