Useful Functions#

A reference of the session-level helpers — the functions and singletons you call interactively to configure per-experiment state, restore a session after a restart, manage devices, and annotate data. Most of the setup helpers in the first section auto-snapshot their values into RE.md["session_state"], so a later restart can re-apply everything via restore_session_state().

Scan plans (lup, ascan, grid_scan, cen, com, maxi, mini, …) live in Scan Plans. Device classes are in the Device Reference and Devices Guide.


Per-session setup helpers#

Calling each of these once per experiment configures a session-level knob and auto-saves the resulting state into RE.md["session_state"]. After a bluesky restart, restore_session_state() re-applies them.

temperature_setup(label) — active temperature controller#

Resolves the requested controller from id4_common.utils.temperature_setup.TEMPERATURE_CONTROLLERS and injects three names into the session: tc (setpoint, movable), ts (sample readback), and TEMPERATURE_CONTROLLER (the active label). Adds ts to the baseline so the sample temperature lands in every scan.

temperature_setup("g")        # 4IDG LakeShore 336 (default loop)
temperature_setup("g-340")    # 4IDG LakeShore 340
temperature_setup("h-9T")     # 4IDH 9 T magnet VTI
temperature_setup()           # interactive prompt — shows known labels

mv tc 295                     # set the setpoint to 295 K
te(295)                       # equivalent shortcut
ts.get()                      # current readback

To add a new controller, edit TEMPERATURE_CONTROLLERS (a single label (device_name, setpoint_attr_path, readback_attr_path) tuple). No class changes, no devices.yml edits.

pr_setup() — phase retarders for XMCD#

Interactive setup for the phase-retarder stack used in helicity switching: which PR to track, which PR to oscillate, the offset positioner and value, and the PZT center. Call with no arguments to walk through every PR in the registry:

pr_setup()

Or set the three exposed attributes directly when you already know what you want:

pr_setup.positioner    = oregistry.find("pr2_pzt_localdc")
pr_setup.offset        = oregistry.find("pr2_pzt_offset_microns")
pr_setup.oscillate_pzt = True

Each assignment auto-saves into RE.md["session_state"]. Print the current configuration with print(pr_setup).

qxscan_setup() — XAFS-style energy scans#

Interactive wizard that configures the pre-edge / edge / post-edge regions for the qxscan plan. Energies are stored relative to the absorption edge.

qxscan_setup()                              # interactive
qxscan_setup.save_params_json("my.json")    # snapshot to disk
qxscan_setup.load_params_json("my.json")    # restore from disk
qxscan_setup.load_from_scan(scan_id)        # restore from a previous run

print(qxscan_setup)                         # show current setup

Both qxscan_setup() and load_params_json() auto-save into RE.md["session_state"]. See the qxscan section in plans.md for the scan plan itself.

undulator_setup(...) — undulator offsets and harmonics#

Sets the upstream (us) / downstream (ds) undulator energy offset relative to the monochromator and picks the harmonic. Pass nothing for an interactive prompt; pass keyword args for a non-interactive setup:

undulator_setup()                                          # prompts
undulator_setup(ds_off=-0.063)                             # ds offset only
undulator_setup(ds_off=-0.063, ds_harm=3, us_off=-0.063)   # both sides

Auto-saves into RE.md["session_state"]. Tracking on/off is managed separately via energy.tracking_setup (below).

energy.tracking_setup([names]) — which devices follow the mono#

Picks the energy-trackable devices that should move when the monochromator energy changes (typically the undulators and one phase retarder).

energy.tracking_setup()                              # interactive table
energy.tracking_setup(["undulators_ds", "pr2"])      # explicit list
energy.tracking_setup([])                            # disable all
energy.tracking                                      # print current state

Auto-saves into RE.md["session_state"].

counters.plotselect(...) — active detectors and monitor#

Configures which scaler channels every scan plan plots, monitors, and reads. Accepts indices, single device objects, or lists of either.

counters.plotselect()                  # interactive prompt
counters.plotselect(0, 1)              # detector index 0, monitor index 1
counters.plotselect(dets=0, mon=1)     # equivalent named-arg form
counters.plotselect(
    dets=[0, 2], mon=1, extra_read=[3]
)                                      # multi-detector + extra readout
print(counters)                        # show current selection

Auto-saves into RE.md["session_state"].

crl_setup(hutch) and crl_size(focal_size) — CRL focusing#

crl_setup("g")           # apply the 4IDG sample-position offset
crl_setup("h")           # apply the 4IDH offset
crl_setup()              # interactive prompt

crl_size(50)             # focal size in µm; < 5 triggers crl.minimize_button
mov crl.beamsize 10      # write 10 µm directly to focalSize
                         # (returns immediately; no setpoint <-> readback wait)

crl.beamsize is a microns handle (PR #61) — the underlying meter PVs are still reachable as crl.beamsize.setpoint / crl.beamsize.readback.


Session restore#

The recommended path after a bluesky restart inside the same experiment is the prebuilt one-liner:

import id4_common.macros.startup_common  # noqa: F401

That import runs experiment_resume() followed by restore_session_state() and prints a per-knob status summary.

restore_session_state(state=None) — re-apply auto-saved knobs#

Reads RE.md["session_state"] (or the state dict you pass) and re-applies every saved sub-key. Returns a {knob_group: status} dict; restore never raises — a missing device or single failed .put() is logged and the rest of the restore continues.

status = restore_session_state()
for knob, msg in status.items():
    print(f"  {knob:18}  {msg}")
# "applied" / "skipped: <reason>" / "failed: <Exception>"

restore_session_state is imported into the interactive namespace by _common_startup.py, so no extra from import is needed.

save_session_state() — manual snapshot (escape hatch)#

You normally don’t need this — every supported setup helper auto-saves when you call it. save_session_state() is the explicit API if you want to force a snapshot without re-running the helpers:

save_session_state()

Same return shape as restore_session_state().


Experiment lifecycle#

experiment_setup(...) — start or re-configure an experiment#

Collects the metadata that organizes data into the right folders and associates it with an APS proposal and ESAF. Run interactively (no args) or non-interactively (all kwargs):

experiment_setup()                          # prompts
experiment_setup(
    esaf_id=281924,
    proposal_id="1014446",
    base_name="scan",
    sample="EuAl4",
    server="dserv",
    experiment_name="Frontini_26-1",
    reset_scan_id=0,
)

Writes a .polar_experiment.yml snapshot to the experiment directory. See General Examples for the full parameter description and the reset_scan_id table.

experiment_resume(path=None) — restore from the YAML snapshot#

experiment_resume()              # auto-discover (cwd or cat[-1])
experiment_resume("/path/to/dir")  # explicit base path or .yml file

Does not contact DM; works even when DM is down.

experiment_load_from_scan(scan_id=-1) — restore from a Bluesky run#

Re-derives the metadata from a specific run document; restores RE.md["scan_id"] so numbering continues from the loaded run.

experiment_load_from_scan()       # last scan (cat[-1])
experiment_load_from_scan(1234)   # specific scan id

experiment_change_sample(sample_name=...) — rotate sample mid-experiment#

experiment_change_sample(sample_name="Fe3O4", base_name="scan")

Rotates the SPEC file and writes a fresh snapshot to disk.


Device loading#

These helpers operate on the runtime device registry (oregistry). See Devices Guide for the full pattern and the deferred-EPICS-connection rule.

find_loadable_devices()                      # all entries from devices.yml
find_loadable_devices(label="4idg")          # filter by label
find_loadable_devices(name="kb")             # substring match on name
find_loadable_devices(name="crl", exact_name=True)

load_device("crl")                           # connect (or reconnect) one device
remove_device("crl")                         # disconnect + drop from baseline
reload_all_devices()                         # everything in devices.yml
reload_all_devices(stations=["core", "4idh"])  # one beamline only

load_vortex(electronics, ...) — pick the vortex electronics at runtime#

Vortex electronics (xspress3 vs dante, 1-element vs 4/7-element) are chosen per session rather than hardcoded in devices.yml. After loading, the device is added to __main__ regardless of whether the EPICS connection succeeded so the user can still call configuration methods.

load_vortex("xspress4")             # 4-element xspress3
load_vortex("xspress7")             # 7-element xspress3
load_vortex("dante1")               # 1-element dante
load_vortex("dante4")               # 4-element dante

SPEC files and annotations#

newSpecFile("my_experiment")        # creates 04_09_my_experiment.dat
                                    # (mm_dd_<title>.dat; resets scan_id
                                    # to last in file if file already exists)

spec_comment("Sample aligned, beam 50x50 µm")
                                    # writes a #C line to the active SPEC file

specwriter (the callback instance) is also available in the session if you need to introspect or rotate it manually.


Quick shortcuts (shorts.py)#

A handful of one-liners for very common operations.

te(295)              # set active temperature controller setpoint to 295
                     # (equivalent to: mv tc 295)

opt()                # move positioner of last scan to its center-of-mass
opt("max")           # … or to its maximum

crl_setup("g")      # see "CRL focusing" above
crl_size(50)        # see "CRL focusing" above

IPython magics (local_magics.py)#

Available without parentheses at the prompt:

%mov motor position             # absolute move (waits for completion)
%mov motor1 pos1 motor2 pos2    # multi-motor parallel move
%movr motor delta               # relative move
%wm motor [motor2 ...]          # show motor positions
%wa                             # show all positioners

Listing what’s available#

list_functions()                 # print every callable in __main__
list_functions("scan")           # filter by substring
%whos                            # IPython: list all variables with types

ascan?                           # show ascan() docstring
ascan??                          # show ascan() source
counters?                        # show CountersClass docstring