id3c.user.s3idc_plans.setup_june_26#
3-ID-C data-acquisition plans – S3IDC commissioning beamtime (June 2026).
These plans were developed and tested during the development cycle from 15 June to 2 July 2026 – the beamtime in which Bluesky was commissioned for the S3IDC (3-ID-C) beamline. Developed and tested by Dishant Beniwal, Barbara Lavina and Peter Jemian.
Detailed, hand-tuned plans for 3-ID-C data collection. Author these
as @plan-decorated generators and run them through the RunEngine:
from id3c.user.s3idc_plans.setup_june_26 import omega_fly
RE(omega_fly())
Conventions (see ../../AGENTS.md and docs/running_scans_at_3idc.md):
Decorate every plan / plan-stub with
@planso a forgottenRE(...)warns instead of silently doing nothing.Compose with
yield from; never call a plan bare inside another.Look devices up by name from the module-level
oregistry(imported fromapsbits.core.instrument_init, the same way the libraryflyscanplan does) – not via@with_registryand not via session globals.Pass an explicit
plan_name=to wrapped library plans (e.g.flyscan) so the run’s provenance reflects this wrapper.
GUI-parseable docstrings (IMPORTANT – keep consistent across ALL plans)#
The Bluesky Plan Runner GUI builds each plan’s parameter form directly
from its docstring + signature (no import, AST only), so every plan MUST
document its arguments in one fixed grammar. Use a standard NumPy
Parameters section with one entry per argument:
<name> : <dtype>[ [<units>]]
<short name> :: <long description, may wrap over indented lines>
<dtype>is one ofstr,int,float,bool,choice{opt1, opt2, ...}orpositions(multi-line triples).[<units>]is optional, e.g.[deg],[mm],[s],[1/deg].The body is split on the first `` :: `` – left = the short field label, right = the long help text (shown as a tooltip).
Defaults and required-ness come from the signature, never the docstring: no default => required; a
Nonedefault => the field is optional and left blank omits the argument.Arguments left OUT of the
Parameterssection (e.g.md) are hidden from the GUI. The plan summary shown in the GUI is this docstring’s first paragraph.
Functions#
|
Integrate ONE detector image while rastering the sample in X-Y. |
|
Raster the sample in X-Y while saving SEVERAL frames along the path. |
|
Continuous fly scan of |
|
Run |
|
Run an |
|
Step scan of ONE detector_stage axis, built on |
Module Contents#
- id3c.user.s3idc_plans.setup_june_26.sweep_xy_integrate_one(x_start: float, x_end: float, y_start: float, y_end: float, exposure_time: float, n_rows: int = 5, file_name: str = 'sweep1', file_path: str = '/home/sector3/s3ida/XRD/2026-2/setup/June17/', open_shutter: bool = True, file_write_mode: str = 'Capture')[source]#
Integrate ONE detector image while rastering the sample in X-Y.
omega is left fixed.
sample_stage.xprime(X) sweeps back-and-forth across[x_start, x_end]whilesample_stage.base_y(Y) steps fromy_starttoy_endovern_rowsrows (a serpentine raster) – all during a SINGLE Eiger exposure ofexposure_timeseconds. The detector integrates the diffraction from the whole X-Y area into one image, which is then saved to a single HDF5 file. Conceptually likeomega_flybut the sample is translated (not rotated) and only one collective frame is recorded.The X sweep speed is derived so the raster fills the exposure window:
v_x = n_rows * |x_end - x_start| / exposure_time
(Y-step and acceleration overhead make the real raster slightly exceed
exposure_time; if the last row looks cut off, raiseexposure_timeor lowern_rows.)- Parameters:
x_start (float [mm]) – X start :: Start of the X (sample_stage.xprime) sweep range.
x_end (float [mm]) – X end :: End of the X (sample_stage.xprime) sweep range.
y_start (float [mm]) – Y start :: Start of the Y (sample_stage.base_y) range.
y_end (float [mm]) – Y end :: End of the Y range, split into n_rows rows.
exposure_time (float [s]) – Exposure time :: Single-frame integration time; also the target raster duration.
n_rows (int) – Number of Y rows :: Rows in the serpentine raster (>= 1).
file_name (str) – File name :: Output HDF5 base file name.
file_path (str) – File path :: IOC-side directory the HDF5 file is written to.
open_shutter (bool) – Open shutter :: Open shutterc for the exposure, then close it (always, even on error). Needed or the frame integrates with no beam.
file_write_mode (choice{Capture, Single}) – HDF write mode :: HDF1 plugin file write mode. Capture arms capture and explicitly flushes the file (the mode tested on this Eiger).
Note
This plan drives the detector directly (no Bluesky run / catalog entry is opened) – it produces the HDF5 file only, like a manual capture.
Example:
RE(sweep_xy_integrate_one(-1, 1, -1, 1, exposure_time=10)) RE(sweep_xy_integrate_one(0, 2, 0, 2, exposure_time=30, n_rows=10, file_name="sampleA"))
- id3c.user.s3idc_plans.setup_june_26.sweep_xy_integrate_steps(x_start: float, x_end: float, y_start: float, y_end: float, total_time: float, frame_period: float, frame_exposure: float = None, n_rows: int = 5, file_name: str = 'sweepN', file_path: str = '/home/sector3/s3ida/XRD/2026-2/setup/June17/', open_shutter: bool = True, file_write_mode: str = 'Capture')[source]#
Raster the sample in X-Y while saving SEVERAL frames along the path.
Like
sweep_xy_integrate_one(omega fixed;sample_stage.xprime(X) sweeps serpentine across[x_start, x_end]whilesample_stage.base_y(Y) steps overn_rowsrows), except instead of integrating the whole raster into one image, the detector free-runs at a fixedframe_periodso it records a series of frames evenly spaced in time – i.e. at regular positions along the continuous raster path. All frames land in a single HDF5 (Capture-mode) file.Frame count is derived from the requested timing:
n_frames = round(total_time / frame_period) # >= 1 effective_total = n_frames * frame_period # actual sweep duration v_x = n_rows * |x_end - x_start| / effective_total
effective_total(and thus the X sweep velocity) is snapped to a whole number of frames so the motion and the acquisition span the same window – frameiis exposed during[i*frame_period, (i+1)*frame_period]and so corresponds to a known sub-segment of the serpentine path. Each frame integratesframe_exposureseconds (<=frame_period;None=>frame_period, continuous).- Parameters:
x_start (float [mm]) – X start :: Start of the X (sample_stage.xprime) sweep range.
x_end (float [mm]) – X end :: End of the X (sample_stage.xprime) sweep range.
y_start (float [mm]) – Y start :: Start of the Y (sample_stage.base_y) range.
y_end (float [mm]) – Y end :: End of the Y range, split into n_rows rows.
total_time (float [s]) – Total raster time :: Target total duration of the whole raster; snapped to a whole number of frame_period frames.
frame_period (float [s]) – Frame period :: Time between successive saved frames; sets how finely the path is sampled (n_frames = round(total_time / frame_period)).
frame_exposure (float [s]) – Frame exposure :: Per-frame integration time (<= frame period); blank uses the frame period.
n_rows (int) – Number of Y rows :: Rows in the serpentine raster (>= 1).
file_name (str) – File name :: Output HDF5 base file name.
file_path (str) – File path :: IOC-side directory the HDF5 file is written to.
open_shutter (bool) – Open shutter :: Open shutterc for the sweep, then close it (always, even on error). Needed or the frames integrate with no beam.
file_write_mode (choice{Capture, Single}) – HDF write mode :: HDF1 plugin file write mode. Capture arms capture and explicitly flushes the file (the mode tested on this Eiger).
Note
Like
sweep_xy_integrate_one, this drives the detector directly (no Bluesky run / catalog entry is opened) – it produces one HDF5 file withn_framesimages. Frame<->position mapping is implicit in the path geometry and even time spacing (this plan does NOT do the timestamp-based pairing thatflyscandoes); the first frame may carry a little extra latency before motion is fully underway.Example:
# 20 s sweep, one frame every 2 s -> 10 frames along the path RE(sweep_xy_integrate_steps(-1, 1, -1, 1, total_time=20, frame_period=2)) RE(sweep_xy_integrate_steps(0, 2, 0, 2, total_time=60, frame_period=1, n_rows=10, file_name="sampleA"))
- id3c.user.s3idc_plans.setup_june_26.omega_fly(file_name: str = 'omeFly', file_path: str = '/home/sector3/s3ida/XRD/2026-2/setup/June17/', p_start: float = -5, p_end: float = 5, exposures_per_egu: float = 2, t_period: float = 1.0, t_acquire: float = None, md: dict = None)[source]#
Continuous fly scan of
sample_stage.omegawith the Eiger2.Rotates omega from
p_starttop_endwhileeiger2acquires continuously, saving one HDF5 (Capture-mode) file infile_path.omega is interlocked against
laser_optics(it will not move unless the optics are OUT), so this plan retracts the laser optics first.Defaults reproduce: omega -45 -> +45 deg, 900 steps, ~10 s/degree.
- Parameters:
file_name (str) – File name :: Output HDF5 base file name.
file_path (str) – File path :: IOC-side directory the HDF5 file is written to.
p_start (float [deg]) – omega start :: Omega angle at which the continuous rotation begins.
p_end (float [deg]) – omega end :: Omega angle at which the rotation ends.
exposures_per_egu (float [1/deg]) – Exposures per degree :: Frames acquired per degree of omega travel.
t_period (float [s]) – Frame period :: Time between successive frames.
t_acquire (float [s]) – Exposure per frame :: Per-frame exposure (<= frame period); blank uses the frame period.
Example:: – RE(omega_fly()) RE(omega_fly(file_name=”sampleA”, p_start=0, p_end=180))
- id3c.user.s3idc_plans.setup_june_26.omega_fly_at_det_steps(positions, file_name: str = 'omeFly_det', file_path: str = '/home/sector3/s3ida/XRD/2026-2/setup/June17/', p_start: float = -5, p_end: float = 5, exposures_per_egu: float = 2, t_period: float = 1.0, t_acquire: float = None, start_index: int = 1, md: dict = None)[source]#
Run
omega_flyonce at each detector(det_x, eiger_y, eiger_z).positionsis a list of(det_x, eiger_y, eiger_z)triples – always in that fixed axis order. For each triple (in order): movedetector_stagedet_x,eiger_yandeiger_ztogether, then runomega_flywith identical scan settings.File naming uses the actual detector positions read back from the motor PVs after the move (not the requested values), each rounded to the nearest integer:
<file_name>_<X>_<Y>_<Z>_000001
where
<X>/<Y>/<Z>are the rounded integer readbacks ofdet_x/eiger_y/eiger_z, and_000001is the IOC’s trailing counter (constant, since the flyscan resets it each run).The series index plus the requested and actual positions of all three axes are recorded in each run’s metadata, and the full-precision actual positions are also written into the produced HDF5 file under
/entry/instrument/detector_stage(best-effort).- Parameters:
positions (positions [mm]) – Detector positions :: One (det_x, eiger_y, eiger_z) triple per line; omega_fly runs once at each, in order.
file_name (str) – File name :: Output HDF5 base file name (per-position suffix appended).
file_path (str) – File path :: IOC-side directory the HDF5 files are written to.
p_start (float [deg]) – omega start :: Omega angle at which each rotation begins.
p_end (float [deg]) – omega end :: Omega angle at which each rotation ends.
exposures_per_egu (float [1/deg]) – Exposures per degree :: Frames acquired per degree of omega travel.
t_period (float [s]) – Frame period :: Time between successive frames.
t_acquire (float [s]) – Exposure per frame :: Per-frame exposure (<= frame period); blank uses the frame period.
start_index (int) – Start index :: First value of the per-run series index recorded in metadata.
Example:: –
RE(omega_fly_at_det_steps([(100, 0, 50), (150, 0, 50), (200, 0, 50)])) RE(omega_fly_at_det_steps([(0, 0, 0), (5, 0, 10)], file_name=”sampleA”,
p_start=-45, p_end=45))
- id3c.user.s3idc_plans.setup_june_26.omega_fly_at_sam_steps(x_start: float, x_end: float, n_x: int, y_start: float, y_end: float, n_y: int, file_name: str = 'omeFly_sam', file_path: str = '/home/sector3/s3ida/XRD/2026-2/setup/June17/', p_start: float = -5, p_end: float = 5, exposures_per_egu: float = 2, t_period: float = 1.0, t_acquire: float = None, start_index: int = 1, md: dict = None)[source]#
Run an
omega_flyat each sample (samX, samY) grid position.Rasters the sample over a rectangular grid of
n_xxn_ypositions –samXissample_stage.xprimeandsamYissample_stage.base_y(the same axessweep_xy_integrate_one/sweep_xy_integrate_stepstranslate) – and at every grid point runs a fullomega_fly(continuous omega fly with the Eiger2, one HDF5 file per point).Grid traversal (NOT serpentine). Rows are visited from
y_starttoy_end; within every rowsamXalways runs from the negative (lower) X to the positive (higher) X. WhensamYsteps to the next row,samXresets back to the negative end – so the X sweep direction is identical for every row:samY = y_rows[0]: x_lo -> ... -> x_hi samY = y_rows[1]: x_lo -> ... -> x_hi (samX restarts at x_lo) ...
(
x_start/x_endmay be passed in either order; the samX columns are always ordered low->high so the sweep goes negative->positive.)File naming. Each point’s HDF5 base name carries that point’s requested sample coordinates:
<file_name>_<x>_<y>_000001
where
<x>/<y>are the commanded samX/samY grid values rounded to 2 decimals (decimal point written asp, e.g.-0p5_1p25) and_000001is the IOC’s trailing counter (constant per run), e.g.omeFly_sam_-1_0p5_000001.- Parameters:
x_start (float [mm]) – samX start :: samX (sample_stage.xprime) grid limit; ordered internally so the sweep is always low->high.
x_end (float [mm]) – samX end :: samX (sample_stage.xprime) grid limit; ordered internally so the sweep is always low->high.
n_x (int) – Number of samX columns :: Number of samX grid columns (>= 1).
y_start (float [mm]) – samY start :: samY (sample_stage.base_y) grid limit; rows are visited in the order given (y_start -> y_end).
y_end (float [mm]) – samY end :: samY (sample_stage.base_y) grid limit; rows are visited in the order given (y_start -> y_end).
n_y (int) – Number of samY rows :: Number of samY grid rows (>= 1).
file_name (str) – File name :: Output HDF5 base name; a _<x>_<y> position suffix is appended per point.
file_path (str) – File path :: IOC-side directory the HDF5 files are written to.
p_start (float [deg]) – omega start :: Omega angle at which each rotation begins.
p_end (float [deg]) – omega end :: Omega angle at which each rotation ends.
exposures_per_egu (float [1/deg]) – Exposures per degree :: Frames acquired per degree of omega travel.
t_period (float [s]) – Frame period :: Time between successive frames.
t_acquire (float [s]) – Exposure per frame :: Per-frame exposure (<= frame period); blank uses the frame period.
start_index (int) – Start index :: First value of the per-point series index recorded in metadata.
Note
Each
omega_flyretractslaser_optics(the omega interlock) and opens its own Bluesky run + HDF5 file, exactly as a standaloneomega_flywould. The detector is forced idle once the whole series finishes – or is aborted partway through – best-effort.Example:
# 3 samX columns x 2 samY rows = 6 omega flyscans RE(omega_fly_at_sam_steps(-1, 1, 3, 0, 0.5, 2)) RE(omega_fly_at_sam_steps(-2, 2, 5, -1, 1, 3, file_name="sampleA", p_start=-45, p_end=45))
- id3c.user.s3idc_plans.setup_june_26.fixed_exp_at_det_steps(scan_axis: str = 'det_x', start: float = -25, end: float = 100, spacing: float = 5, exposure_time: float = 2.0, det_x: float = None, eiger_y: float = None, eiger_z: float = None, file_name: str = 'fixExp_det', file_path: str = '/home/sector3/s3ida/XRD/2026-2/setup/June17/', open_shutter: bool = True, file_write_mode: str = 'Capture', md: dict = None)[source]#
Step scan of ONE detector_stage axis, built on
bp.list_scan.Uses
list_scanas the engine – it opens a Bluesky run (documents + Tiled catalog entry) over the list of positions – but a customper_stepdrives the detector at each point so every position gets its own HDF5 file named by that point’s detector geometry as<file_name>_<X>_<Y>_<Z>_NNNNNN(X=det_x, Y=eiger_y, Z=eiger_z, rounded to int;_NNNNNNis the IOC counter), e.g.fixExp_det_-25_7_30_000001. The detector is therefore NOT passed tolist_scanas a staged detector (detectors=[]); instead the per-step opens the cam + HDF plugin, exposes one frame, writes the file, and closes them – and records the three axis readbacks (and the data file name) into the run’sprimarystream, so positions are also in the catalog.Moves
detector_stage.<scan_axis>fromstarttoendinspacing-sized steps (inclusive ofendwhen divisible; never overshoots); the other two axes are held at the values you pass.- Parameters:
scan_axis (choice{det_x, eiger_y, eiger_z}) – Scan axis :: Which detector_stage axis to step.
start (float [mm]) – Start :: First position of scan_axis.
end (float [mm]) – End :: Last position of scan_axis (inclusive when divisible).
spacing (float [mm]) – Step size :: Step between successive positions (> 0).
exposure_time (float [s]) – Exposure time :: Per-point exposure; sets eiger2.cam.acquire_time.
det_x (float [mm]) – det_x fixed :: Fixed det_x for the non-scanned axis; leave blank when det_x is the scan axis.
eiger_y (float [mm]) – eiger_y fixed :: Fixed eiger_y for the non-scanned axis; leave blank when eiger_y is the scan axis.
eiger_z (float [mm]) – eiger_z fixed :: Fixed eiger_z for the non-scanned axis; leave blank when eiger_z is the scan axis.
file_name (str) – File name :: Output HDF5 base name; a _<x>_<y>_<z> position suffix is appended per point.
file_path (str) – File path :: IOC-side directory the HDF5 files are written to.
open_shutter (bool) – Open shutter :: Open shutterc for the whole scan, then close it (held open across points; always closed, even on error).
file_write_mode (choice{Capture, Single}) – HDF write mode :: Per-point HDF1 plugin file write mode.
Example:: –
- RE(fixed_exp_at_det_steps(“det_x”, -25, 100, 5, exposure_time=2,
eiger_y=7, eiger_z=30))