flOMNI¶
flOMNI is an instrument for tomographic measurements via X-ray ptychography. The sample environment is at atmospheric pressure and room temperature (or higher). An early version of the setup is described here. Nano positioning is based on closed loop control to a position signal obtained from dedicated laser interferometry. For fast scanning a combined motion of the sample (slow axis) and beam defining fresnel zone plate (fast axis) is used. The method is described here. The setup is equipped with a heater, that can be used to apply hot gas streams to the sample. Samples are to be mounted on OMNY pins.
How to flOMNI¶
… a step-by-step guide for users
Sample storage and transfer¶
Print the current usage of the sample storage
flomni.ftransfer_show_all()
flOMNI has to know about the loaded positions and sample names. To modify use
flomni.ftransfer_modify_storage(<position>, 0 (=free) / 1 (=used))
positions <1> .. <20> are available slots in the tray.
Special position: 0 = sample stage, 100 = gripper.
You will be asked to enter a sample name.
To load a new sample in the sample stage, in principle only one command is needed
flomni.ftransfer_sample_change(<position new sample>)
You will be asked where the previous sample should go with a suggestion for an empty position in the tray.
To remove the current sample from the sample stage without mounting a new one, use
flomni.ftransfer_sample_change(-1)
You will be asked where to stow the removed sample, same as above.
Other commands:
ftransfer_tray_in /_out (not yet implemented).
If something goes wrong, there are additional commands to perform a manual operation of the gripper.
| command | explanation |
|---|---|
flomni.ftransfer_get_sample(<position>) |
Pick sample from <position> |
flomni.ftransfer_put_sample(<position>) |
Mount sample at <position> |
flomni.ftransfer_flomni_stage_in() |
stage to measurement position |
flomni.ftransfer_flomni_stage_out() |
stage to the sample change position |
Manual operation of the gripper (do not leave it too long in open state)
flomni.ftransfer_gripper_open()
flomni.ftransfer_gripper_close()
Manually move the gripper to a transfer position
flomni.ftransfer_gripper_move(<position>)
Alignment of samples¶
Coarse alignment¶
After the sample transfer the sample stage moved to the measurement position with your new sample. The Xray eye will automatically move in, the alignment GUI opens and shows a current snapshot. If you see your sample already at the approximately correct height:
-
flomni.xrayeye_alignment_start()start the coarse alignment of the sample by measuring (clicking in the X-ray eye software) the sample position at its height and then angles of 0, 45, 90, 135, 180 degrees. The GUI will present a fit of this data, which is automatically loaded to BEC for aligning the sample.Flat samples are edge-on at 90 degrees, so their center cannot be measured there: press Skip in the GUI instead of Submit to leave that angle out. One angle may be skipped, the fit needs at least 4 of the 5 angles; a second Skip is refused with a message. Skipping 45, 90 or 135 degrees costs little accuracy, skipping 0 or 180 degrees makes the fit noticeably noisier (a warning is printed). The FZP center and the height marking cannot be skipped. With 4 points the fit cannot flag a bad click, so check the plot. The skipped angles are stored in the alignment h5 file.
Otherwise adjust the height manually:
flomni.umvr_fsamy_tracked(0.01), attention: unit <mm>, move the sample stage relative up (positive) or down (negative) until the sample is approximately vertically centered in xray eye screenflomni.xrayeye_update_frame()will update the current image on the xray eye screen
Fine alignment¶
After the xrayeyealign, a fine alignment needs to be performed using ptychography.
To bypass the fine alignment: flomni.feye_out()
flomni.tomo_parameters()Adjust the ptychographic scan parameters for performing an alignment scan. Typically FOVX = FOVX(Xrayeye)+20 mu, shell step = beamsize/2.5, number of projections and tomo mode are ignored in the alignment scans.flomni.tomo_alignment_scan()perform the alignment scan. When the first scan is running, switch to a matlab session and runBEC_ptycho_alignagain. Click left and right. The third click can define the height of the scan, but is not needed and ignored by default. The widest horizontal field of view will be printed at the end of the matlab session.flomni.read_alignment_offset()Load alignment parameters calculated in matlab.
Tomographic Measurement¶
Now that the sample is aligned, the tomographic measurement can be performed.
flomni.tomo_parameters()opens the tomo parameter window (GUI) to adjust the scan parameters for the tomographic scan (flomni.tomo_parameters_cli()is the command line version, which prompts for each value in the terminal). This includes the parameters for ptychographic scans of projections plus the strategy for angular sampling. The vertical shift adjusts the field of view, up (positive) or down (negative). After adjusting the numbers, type againflomni.tomo_parameters()and verify that they are correct. Besides the computed angular sampling modes,tomo_type4 reads the angles from a text file, see Angles from a file for the file requirements. For the "8 sub-tomograms" mode, an optional 0-deg reference shot can be enabled at the start of every odd sub-tomogram and once more at the end, useful for tracking radiation damage over the course of a long measurement.flomni.tomo_scan_projection(angle)perform a ptychographic scan at the rotation angle <angle>. Launch the tomographic measurement byflomni.tomo_scan(). During the scan a live progress report is printed, including an estimated time of completion. Unusually long gaps (e.g. a beamline interruption) are detected automatically and excluded from the estimate, and the total time lost to such gaps is reported once the scan finishes.- Before changing sample, verify that all subtomograms were completely acquired using the tomo_reconstruction matlab script.
Queueing multiple scans¶
Several parameter sets can be queued and run one after another automatically, e.g. a fast overview scan followed by a higher resolution scan on the same sample, without having to wait around to start the next one by hand.
flomni.tomo_parameters()set up the first parameter set, thenflomni.tomo_queue_add("label")to add it to the queue.- Repeat for further parameter sets.
flomni.tomo_queue_show()review the queue before starting.flomni.tomo_queue_execute()run all queued scans in sequence.
If a queued scan is interrupted, the next call to flomni.tomo_queue_execute() resumes it automatically rather than starting over, then continues with the remaining entries.
The same queue can also hold command jobs that reconfigure the beamline between scans (e.g. change energy, then re-peak the undulator gap) instead of running a tomogram, so "scan A, reconfigure, scan B" runs unattended as one queue. A queued tomogram can also run custom code at each projection angle (e.g. moving a polarizer in and out between two exposures) via a registered at_each_angle hook. See Tomography below for the full command reference, including command jobs and hooks.
If something went wrong…¶
A single projection is to be repeated use
flomni.tomo_scan_projection(<angle>). The target angle of scans can be found in the second column of the file in
~/data/raw/logs/tomography_scannumbers.txt
To continue an interrupted tomography scan, run
flomni.tomo_scan_resume()
It picks up automatically from wherever the scan was interrupted, no need to look up the exact subtomogram/angle by hand. Do not use this command when running from the tomo queuing system. Instead use flomni.tomo_queue_execute().
Alternatively, the same kind of parameters used internally by tomo_scan_resume() can be given to flomni.tomo_scan() directly, e.g. to resume from a different point than where it actually stopped:
| tomo type | parameters and their defaults |
|---|---|
| 8 sub-tomograms | subtomo_start=1, start_angle=None |
| Golden ratio tomography (sorted in bunches) | projection_number=None |
| Equally spaced with golden starting angle | projection_number=None |
GUI tools¶
During operation the BEC GUI will show the relevant cameras or progress information. To manually switch view Tab completion on 'flomni.flomnigui_' will show all options to control the GUI. Most useful 'flomni.flomnigui_show_cameras()' will show the cameras for sample transfer and interior overview 'flomni.flomnigui_show_progress()' will show the measurement progress GUI 'flomnigui_show_xeyealign()' will show the XrayEye alignment GUI
How to setup flOMNI (software)¶
This part of the manual is intended for beamline staff and expert users
The nano-positioning is controlled by a feedback loop running on a real-time linux based computer. With all related hardware connected, this loop has to be started manually.
- Login to the computer by
ssh control@mpc2680. The password is written on the physical machine. cd OMNY/flOMNI/./startflOMNI
Once the loop has started, it is possible to start bec with the flOMNI configuration file.
Starting bec with session will load the scripts
bec --session flomni
The flOMNI scripts can be loaded manually by
from csaxs_bec.bec_ipython_client.plugins.flomni import Flomni
flomni = Flomni(bec)
Loading the flOMNI configuration (this command will load the OMNY configuration only - isolated from the beamline)
bec.config.update_session_with_file("/bec/csaxs_bec/csaxs_bec/device_configs/flomni_config.yaml")
If the realtime system is restarted, bec will lose communication. To restart:
flomni.rt_off() … then wait a few seconds
flomni.rt_on()
Initialization of the stages¶
The stages of flOMNI are referenced in respect to their endswitches. The stages have to be initialized at the beginning of a run or when the Galil motor controllers have been reset or restarted. To see the status of the stages following commands are available:
Show the status of all galil controllers (all stepper motors and the UPR rotation stage)
dev.fsamx.controller.galil_show_all()
The same holds true for the Smaract stages which control the OSA position. Their status can be checked by
dev.fosax.controller.show_all()
In case referencing of the flOMNI stages is required, run
flomni.flomni_init_stages()
This script will first verify that the stages are not in an initialized state, and then reference all stages in a safe way. The user will be warned in case of a potentially risky situation. This mainly involves a collision risk upstream with the exposure box exit window. It might be worth to check clearance prior to calling the init skript.
X-ray optics alignment¶
The positions of the optics stages are stored as stage parameters and are thus linked to the configuration file.
Example: The OSAx “in” position can be reviewed by dev.fosax.user_parameter
Update the value by (example "fosax", "in") by dev.fosax.update_user_parameter({"in":value})
Important note: if these values are changed, they are not automatically stored to the config file and will only be available in the current session.
Note: The minimal position of the motor foptz is 14, and the minimal reachable distance FZP to sample is 36 mm.
flomni.ffzp_info() shows info about the available FZPs at the current energy of the beamline. Optional parameter is the photon energy in keV.
Example: flomni.ffzp_info(6.2)
Documents about availabe optics can be accessed by
flomni.flomnigui_docs
The laser feedback will be disabled and fine alignment lost if foptx/y are moved!
Following functions exist to move the optics in and out, with self-explaining naming.
flomni.ffzp_in()flomni.foptics_in()flomni.foptics_out()flomni.fosa_in()flomni.fosa_out()
Interferometer¶
The position feedback in flOMNI is controlled in closed loop to an interferometric position measurement. To show the signal of the interferometers:
flomni.show_signal_strength_interferometer()
Typical values with proper alignment, sample stage at the measurement position and laser tracker running are in the range of
| Axis | Value |
|---|---|
| 0 | 13681.0 |
| 1 | 12383.0 |
| 2 | 10716.0 |
| 3 | 11032.0 |
Laser tracker commands¶
The horizontal interferometer is built according to the tracking interferometer. The tracker can be controlled by following commands. During commissioning of the setup it is worthy to check the status, but during general operation these commands should not be required.
flomni.laser_tracker_show_all()flomni.laser_tracker_on()flomni.laser_tracker_off()
Interferometer feedback commands¶
The closed loop control of the Piezo stages can be controlled by
-
flomni.feedback_feedback_enable_with_reset().
There is also an enable without reset, which is used during tomography scans, when using coarse stages to increase the scan range. It should not be required to use manually. -
flomni.feedback_disable() flomni.feedback_status()
Scanning in 2D and sample alignment¶
flOMNI Fermat scan¶
The basic scan function can be called by scans.flomni_fermat_scan() and offers a detailed doc string for further details (scans.flomni_fermat_scan?). A prerequisite for scanning is a running feedback system. The scan has following parameters.
| Parameters | |
|---|---|
| fovx (float) | Fov in the piezo plane (i.e. piezo range). Max 200 um |
| fovy (float) | Fov in the piezo plane (i.e. piezo range). Max 100 um |
| cenx (float) | center position in x |
| ceny (float) | center position in y |
| step (float) | stepsize |
| fermat_asymmetry (float) | ratio of x spacing to y spacing (x/y). 1 = symmetric (default). x spacing stays fixed at step; y spacing scales by this ratio — e.g. 2.0 doubles x spacing relative to y (denser sampling in y), 0.5 does the opposite. The requested FOV is preserved, so the total point count changes with the ratio. Soft-clamped to [0.1, 10]. Useful for samples/features elongated in one direction. |
| zshift (float) | shift in z |
| angle (float) | rotation angle (will rotate first) |
| corridor_size (float) | corridor size for the corridor optimization. Default 3 um (auto-estimated if not provided) |
| randomize_offset (bool) | randomize the sub-step offset of the Fermat lattice on every scan. Default True. Set False for the unshifted lattice (identical positions on every call). See below. |
| exp_time (float) | exposure time per frame |
| frames_per_trigger (int) | Number of burst frames per position |
| burst_at_each_point (int) | Number of triggers and readouts at each point |
Example:
scans.flomni_fermat_scan(fovx=10, fovy=10, cenx=0, ceny=0, step=1, fermat_asymmetry=1.0, zshift=0, angle=0, exp_time=0.1, frames_per_trigger=1, burst_at_each_point=1)
Effective spacing with fermat_asymmetry: the x spacing always stays at step;
the y spacing becomes step / fermat_asymmetry. So with step=1 um:
| fermat_asymmetry | x spacing | y spacing | points (fovx=fovy=10 um example) |
|---|---|---|---|
| 0.7 | 1.0 um | ~1.43 um (sparser) | ~70 |
| 1.0 (default) | 1.0 um | 1.0 um | ~100 |
| 1.3 | 1.0 um | ~0.77 um (denser) | ~130 |
The requested FOV stays exactly as specified either way -- only the point density (and therefore the point count and scan duration) changes.
By default the Fermat spiral's sub-step phase is randomized on every projection
(drawn fresh per scan, uniform in [0, step)) to decorrelate position-tied
reconstruction errors that would otherwise accumulate into ring artifacts across a
tomographic series. Pass randomize_offset=False to switch this off and get the
unshifted lattice, e.g. for reproducible positions when comparing scans:
scans.flomni_fermat_scan(fovx=10, fovy=10, cenx=0, ceny=0, step=1, randomize_offset=False, ...).
The tomo scans (tomo_scan, tomo_scan_projection) do not pass this parameter and
therefore always randomize.
frames_per_trigger controls burst acquisition for detectors generally.
Separately, the OMNY PandABox is configured as one of two distinct devices in
csaxs_bec/device_configs/ptycho_flomni.yaml — omny_panda (per-gate
averaged acquisition, currently kept as a commented reference) or
omny_panda_continuous (continuous, high-rate raw-sample acquisition) —
because the two modes require different PandA hardware layouts and can't run
simultaneously. See docs/developer/panda_box_free_running_setup.md for what
each mode captures and how to switch between them.
Overview of the alignment steps¶
There are several corrections applied to maintain the sample in the FOV:
- Mirror calibration
- X-ray eye alignment
- Ptychography fine alignment (improvement of the X-ray eye alignment step)
- Vertical shifts from tomography reconstruction (for very small vertical FOV)
XrayEye and sample alignment¶
The XrayEye can be moved in and out by
flomni.feye_in()
flomni.feye_out()
The in and out positions are stored as user parameters in the stage definition. Get the values by
dev.feyex.user_parameter
dev.feyey.user_parameter
Update the values by, example for feyex and in position,
dev.feyex.update_user_parameter({"in":value})
To refresh the frame of the xray eye windows software
flomni.xrayeye_update_frame()
This command can also be called to keep the shutter open and live view active
flomni.xrayeye_update_frame(keep_shutter_open=True)
To start the xray eye alignment (and clear any previous alignment)
flomni.xrayeye_alignment_start()
This command can also be called to keep the shutter open and live view active. Warning: The dose to the sample will be significantly higher.
flomni.xrayeye_update_frame(keep_shutter_open=True)
To load the fit parameters from directory dir_path computed by BEC_ptycho_align.m in Matlab run
flomni.read_alignment_offset(dir_path='')
The loading routine uses default values for the vertical alignment. This behavior can be changed (e.g. for getting new default values) by the parameter use_vertical_default_values=False.
At each projection, the angular dependent is computed by
flomni.get_alignment_offset(angle), with angle in degrees.
To manually nudge the alignment by a few microns without recording a new fit
(e.g. to compensate for a small shift introduced by moving foptz), edit the
constant offset term of the currently loaded fit directly:
fit = flomni.client.get_global_var("tomo_alignment_fit")
fit[0][2] += 5.0 # x offset, microns
fit[1][2] += 3.0 # y offset, microns
flomni.client.set_global_var("tomo_alignment_fit", fit)
tomo_alignment_fit is a 2x5 list (row 0 = x, row 1 = y); column 2 in each
row is the constant offset of the sinusoidal fit
(correction = A*sin(angle + phase) + offset [+ 3rd-order term for y]), so
changing it shifts the correction by that amount at every angle without
touching the fitted amplitude/phase. Check the result with
flomni.get_alignment_offset(angle).
The alignment can be cleared by
flomni.reset_tomo_alignment_fit()
Fine alignment¶
The alginment obtained by the X-ray eye can be refinde by recording ptychography projections at 45 deg. intervals. For this, adjust the tomo parameters by
flomni.tomo_parameters()
Next, run the alignment scan by
flomni.tomo_alignment_scan()
Reconstruct the scan and use BEC_ptycho_align.m to obtain improved fit parameters. The new parameters can be loaded by
flomni.read_alignment_offset()
For a very tight vertical field of view, a fine vertical alignment based on outputs generated from early tomography reconstructions can be used. A corresponding file can be generated by the tomography reconstruction script and can be loaded by the following two methods:
flomni.read_additional_correction_y()
flomni.read_additional_correction_y2()
One important note: The first method is by default loading a mirror correction file automatically. If the tomogram is using that data, do not overwrite it, use the secondary correction instead.
The scan offsets are computed at each projection by
flomni.compute_additional_correction_y(angle)
flomni.compute_additional_correction_y2(angle)
The additional correct can be reset by
flomni.reset_correction()
It will automatically load the default mirror correction file as primary correction! To reset and not load any correction, which might be useful to obtain a new default correction file, run
flomni.reset_correction(use_default_correction=False)
Scanning of projections¶
At any stage of the alignment process it is possible to scan a projection.
Define the scan parameters by flomni.tomo_parameters()
Run a scan at angle (in degrees) by flomni.tomo_scan_projection(angle)
Tomography¶
The tomo parameters have to be set by
flomni.tomo_parameters()
Once satisfied with the alignment, the tomography scan can be started by
flomni.tomo_scan()
During the scan, a live progress report is printed (subtomogram/projection counters and an estimated time of completion). Gaps significantly longer than a normal acquisition cycle (e.g. a beamline-down interruption, or a crash followed by a restart) are detected automatically from the time between consecutive projections and excluded from the time estimate; the total time lost to such gaps is printed once the scan finishes.
Four modes for angular sampling are implemented (selected by tomo_type 1-4) and they have different optional parameters for the tomo_scan method:
| tomography mode | tomo_type | parameters and defaults |
|---|---|---|
| 8 sub-tomograms | 1 | subtomo_start=1, start_angle=None |
| Golden ratio tomography (sorted in bunches) | 2 | projection_number=None |
| Equally spaced with golden starting angle | 3 | projection_number=None |
| Angles read from a file | 4 | projection_number=None |
See Angles from a file below for tomo_type 4.
For the "8 sub-tomograms" mode, flomni.tomo_parameters() also offers a zero_deg_reference_at_each_subtomo option: when enabled, an additional reference projection at exactly 0 degrees is acquired at the start of every odd (forward) sub-tomogram and once more after the final sub-tomogram, useful for tracking radiation damage across the full measurement.
By default odd sub-tomograms sweep 0→180° and even ones 180→0°, so the reference is taken before every second sub-tomogram. With the tomo_type1_always_forward option ("Run all sub-tomos forward", 180° range only), every sub-tomogram sweeps 0→180°; the rotation returns to 0° between sub-tomograms and a 0° reference is taken before each of the 8 (plus one at the end). The angle set of every sub-tomogram, and thus the combined angular grid, is unchanged.
flomni.tomo_parameters() also offers stitch_x/stitch_y (integers, default 0, up to 50 each) for a field of view larger than a single Fermat scan can cover: when nonzero, every projection angle is acquired as a (2*stitch_x+1) × (2*stitch_y+1) grid of separate Fermat scans instead of a single one, tiled around the normal alignment-corrected center and spaced by fovx/fovy minus tomo_stitch_overlap (µm, default 0.2) per step in x/y — stitching together an effective field of view of roughly (2*stitch_x+1) × fovx by (2*stitch_y+1) × fovy, with tomo_stitch_overlap controlling the deliberate overlap between adjacent tiles used for aligning them together during reconstruction. Stitching is forced to 0 (disabled) whenever single_point_instead_of_fermat_scan is enabled.
The parameters above can be used to restart an interrupted acquisition manually, or - more conveniently - by running
flomni.tomo_scan_resume()
which reads the last recorded progress and resumes automatically at the exact point (subtomogram/angle, or projection for the golden ratio modes) the scan was interrupted at, without needing to look up the values by hand. When running from the tomo scan queuing system use flomni.tomo_queue_execute() instead!
Angles from a file (tomo_type 4)¶
With tomo_type 4 the projection angles are not computed but read from a plain-text file you provide, e.g. for a non-uniform sampling, a missing-wedge-aware set of angles, or to repeat a previously used list. Set the file in flomni.tomo_parameters() (choose type 4, then Browse... to pick the file; the GUI shows a live preview of the file content and blocks Submit / Add to queue while the file is invalid), or in flomni.tomo_parameters_cli() which prompts for the path until it parses. Only the path is stored (flomni.tomo_angle_file), so use a path that is readable from the BEC client machine.
File requirements
- Plain text, one projection per line.
- Column 1: the angle in degrees,
0 <= angle <= 360. Angles above 180 are allowed;tomo_angle_rangeis not applied. - Column 2 (optional): the sub-tomogram number, an integer
>= 1. If omitted, it is 1. It is only a label used for progress display and the saved metadata; it does not change the acquisition. - Columns are separated by whitespace and/or commas. Everything after
#is a comment; blank lines are ignored. - Projections are acquired in file order, so the order in the file is the order of the measurement.
- Further, optional
key=valuetokens after the angle/subtomo may override acquisition parameters for that projection -- see Per-line acquisition overrides below. - At least one angle is required. Any malformed line (not a number, angle outside 0-360, sub-tomogram number below 1, an unrecognized or malformed
key=valuetoken) makes the whole file invalid and names the offending line. - A line that is
END(case-insensitive, nothing but whitespace/comment around it) marks the file as complete -- no more angles will ever be appended. A commented-out# ENDdoes not count, so the marker can be staged ahead of time and uncommented when the file is truly finished. Anything other than a comment or blank line afterENDis an error.
Example:
# angle subtomo
0 1
45 1
190.5 2
270, 2
END
Behavior
- The file is read once at the start of
flomni.tomo_scan()(and again atflomni.tomo_scan_resume()or when a queued job runs), before any sample registration or motion. A missing or invalid file fails immediately (in the queue the job is markedincompletewith the error message). - While the scan is running, the file is re-read and polled (about every 5 s) whenever the scan catches up to the current end of file. This means you can grow the file by appending lines while a scan is running -- newly appended angles are picked up automatically, with no need to call
tomo_scan_resume(). - A file without an
ENDmarker is incomplete: once the scan reaches the end of it, it waits indefinitely for more lines rather than finishing, printing a reminder each time it starts waiting. Add anENDline when you want the scan to finish on its own -- a file prepared in advance with the full list of angles should haveENDas its last line, or a scan against it will hang at the end rather than completing. Pressing Ctrl+C while waiting raises a clean, catchable error (safe to use to abort a queued job in this state) rather than a bare interrupt. - Only append new lines after the last one the scan has already acquired. Editing or removing a line that has already been acquired aborts the scan with an error on the next poll; editing a line that has been written to the file but not yet acquired is not guaranteed to be picked up, so avoid relying on it.
- At scan start the number of angles known so far, their range and the number of sub-tomograms are printed (noting if the file is still incomplete), and the parsed angle list is written to scilog. The summary is printed again, together with the 180-deg-pair warning below, once the file becomes complete.
- A warning is printed if the file contains angles that are identical or 180 deg apart (within 0.05 deg), which are redundant in parallel-beam geometry. The scan is still run.
- No extra 0-deg damage-estimation shots are inserted (
golden_projections_at_0_deg_for_damage_estimationand the sub-tomogram 0-deg reference are ignored); put 0-deg lines into the file if you want them. flomni.tomo_scan_resume()is for recovering from a crash or an explicit abort (e.g. Ctrl+C) -- it is not needed just to pick up appended lines, since a running scan already waits for and picks those up on its own. When used, it continues at the interrupted projection index of the file, using the file's current content from that index on. If the file has become shorter than the resume index, the resume is refused.- In the tomo queue the file path is snapshotted per job when it is added, and
flomni.tomo_queue_reacquire(job_index, projection_number)uses the projection index in the file (0-indexed, in file order).
Per-line acquisition overrides¶
Besides the angle and sub-tomogram number, each line may add any number of key=value tokens that override acquisition parameters otherwise taken from the live flomni.tomo_parameters() configuration, for that projection and every one after it:
step=<um>-- the Fermat scan step size (otherwiseflomni.tomo_shellstep), range0.025-20.0.exptime=<s>-- the exposure/counting time per frame (otherwiseflomni.tomo_countingtime), range0.001-100.0.frames=<n>-- the burst frame count (otherwiseflomni.frames_per_trigger), integer, range1-100.
These three are sticky: once a line sets one, it stays in effect -- for that line and every line after it -- until a later line changes it again. They are independent of each other (setting step= doesn't touch exptime=/frames=) and independent of the sub-tomogram number, which is never sticky. A field that is never set on any line simply uses whatever the live configuration is at the moment that projection is actually acquired, so a mix of overridden and non-overridden lines behaves exactly as if the non-overridden ones weren't in a tomo_type-4 file at all.
Example -- coarse-and-fast for the first half, then fine-and-slow:
0 1
45 1
90 1 step=0.3 exptime=2 frames=3
135 1
180 2
225 2 step=0.05 exptime=10
270 2
END
tomo_shellstep/tomo_countingtime/frames_per_trigger are configured; 90/135/180 use step=0.3 exptime=2 frames=3 (135 and 180 inherit it from 90); 225/270 use step=0.05 exptime=10 with frames=3 still carried forward from before (not reset, since frames= was never changed again).
A scan summary note ("N with per-line overrides") is printed whenever any entry carries an override, so a typo'd key=value token that silently parsed as something else would stand out as an unexpected count.
If a custom at_each_angle_hook or flomni_at_each_angle is active, it owns the whole per-projection acquisition and never sees these overrides -- a warning is printed once if the file has any overrides while such a hook is active.
In case of eight equally spaced sub-tomograms, an individual sub tomogram can be scanned by flomni.sub_tomo_scan(subtomo_number, start_angle). If the start angle is not specified, it will be computed depending on the subtomo_number, which is ranging from 1 to 8.
Queueing multiple scans¶
Several tomo parameter sets can be queued and run sequentially on the same sample, without having to start each one by hand.
| command | explanation |
|---|---|
flomni.tomo_queue_add(label=None) |
Snapshot the currently set tomo parameters and add them as a new job to the queue. Returns the job's index. |
flomni.tomo_queue_show() |
Print and return the current queue, with status per job. |
flomni.tomo_queue_delete(*indices) |
Delete one or more jobs by index. |
flomni.tomo_queue_clear() |
Empty the queue. |
flomni.tomo_queue_execute(start_index=0) |
Run all pending jobs in sequence, on the current sample. |
flomni.tomo_queue_resume(start_index=0) |
Alias for tomo_queue_execute() — identical behavior, just a name that matches tomo_scan_resume(). |
The queue is persisted (it survives a BEC client restart). Each job's status is one of pending, running, incomplete, or done. A job that did not run to completion (an exception was caught, or the BEC client itself crashed mid-scan) is automatically resumed - rather than restarted - the next time flomni.tomo_queue_execute() is called.
If the process running the queue dies hard enough that it never gets to write a status update at all (a kernel restart, a Ctrl+C during a blocking prompt) a job can be left stuck showing running with nothing actually running. flomni.tomo_queue_delete(index) has no status guard and works on a stuck row regardless; you can also fix the status directly with flomni._tomo_queue_proxy.update_by_id(job_id, status="incomplete") (to make it resumable) or status="done" (to skip it). The GUI's Delete/Clear normally refuse to touch a running row, but detect a stale one (no recent scan heartbeat) and offer to proceed anyway with an explicit confirmation instead of blocking forever.
In the examples, flomni.tomo_parameters_cli() is used because it waits until the values are entered before the next command runs. With the GUI window, adjust the values there and add the job with its queue button, or with flomni.tomo_queue_add().
Example:
flomni.tomo_parameters_cli() # set up parameter set #1
flomni.tomo_queue_add("fast overview")
flomni.tomo_parameters_cli() # set up parameter set #2
flomni.tomo_queue_add("hires scan")
flomni.tomo_queue_show()
flomni.tomo_queue_execute() # runs both, in order, on this sample
GUI: reusing an earlier job's settings. In the ☰ Queue control… dialog, select a single tomo job (not a command job — those have no scan parameters) and click "Load into editor" to pull its saved settings into the params panel's editor, as if you'd just clicked Edit. Nothing is written yet — review or tweak the fields, then Submit (writes live, blocked while the beamline is busy) or "Add to queue" (always allowed) as usual. If you already had an edit in progress, it asks before discarding it.
GUI: two different "Add to queue" buttons. The params panel's own "Add to queue" (visible in edit mode) queues whatever you've typed, unsubmitted. The ☰ Queue control… window's "Add current params to queue" is a different button on a different window — it always queues the live parameters, regardless of any edit open in the panel. If you have an unsaved edit open and click the queue window's button instead of the panel's, it warns and names the correct one before proceeding, since it would otherwise queue your last-submitted values, not what you just typed.
Reacquiring from an earlier projection. A resumed job normally picks up
exactly where it stopped. If a beamline problem means earlier projections need
redoing too — even in a job that's already marked done — use:
flomni.tomo_queue_reacquire(job_index, projection_number)
job_index is the job's position, same indexing as tomo_queue_show().
projection_number is a flat, 0-indexed projection count that works the same
way for all three tomography modes — for "8 sub-tomograms" it counts straight
through all 8 in order (subtomogram 1's projections first, then subtomogram
2's, ...), exactly matching the running projection counter already shown in
the live progress report / flomni.progress["projection"]; for the
golden-ratio modes it's the same projection number those already use. This
reopens the job at that point and resets every job queued after it —
tomograms and command jobs alike, regardless of their own current status — to
pending, so the next flomni.tomo_queue_resume() (or tomo_queue_execute())
call re-runs everything from there forward, in order. It does not run
anything itself.
Example — job 0 already finished, but its last few projections (and the jobs queued after it) need redoing:
flomni.tomo_queue_show() # find the job index and the projection number to restart at
flomni.tomo_queue_reacquire(0, 130) # reopen job 0 at projection 130; jobs 1+ reset to pending
flomni.tomo_queue_resume() # actually re-run, in order
Refuses (raises) if job_index points at a command job (nothing to
reacquire), or if some other job in the queue is already incomplete/
running — only one job's progress can be tracked at a time, so resolve that
one first (or pass its own index instead).
Command jobs — reconfiguring the beamline between scans¶
In addition to tomogram jobs, the same queue can hold command jobs: an ordered list of beamline reconfiguration steps (move a device, ...) that run instead of a scan. This is what lets one queue express "tomogram A, then reconfigure, then tomogram B" unattended, e.g. change energy and re-peak the undulator gap between two tomograms on the same sample.
flomni.tomo_queue_add_command(steps, label=None, idempotent=None)
-
steps: a single{"action": ..., "kwargs": {...}}dict, or a list of them run in sequence within that one job. -
label: optional name shown bytomo_queue_show(), same as fortomo_queue_add(). idempotent: normally inferred (safe to blindly re-run after a crash only if every step is); override explicitly if needed.
Only actions from a fixed, reviewed registry can be queued — not arbitrary code:
| action | does | parameters |
|---|---|---|
move |
Move device(s) to absolute position(s). Only devices on the allow-list below can be targeted. | positions: {device: target}, e.g. {"mokev": 6.2} |
optimize_idgap |
Scan the undulator gap over a range and move to the peak. Not yet implemented (no-op stub). | search_range: mm, default 0.5, range 0–2 |
Devices allowed for move: mokev (energy, keV), idgap (undulator gap, mm). A
move naming any other device is rejected, both when the job is added and again when
it actually runs.
Example — change energy, then re-peak idgap, before the next tomogram:
flomni.tomo_queue_add_command(
[{"action": "move", "kwargs": {"positions": {"mokev": 6.2}}},
{"action": "optimize_idgap", "kwargs": {"search_range": 0.5}}],
label="reconfigure to 6.2 keV",
)
tomo_queue_show() lists command jobs alongside tomogram jobs, e.g.:
[2] pending reconfigure to 6.2 keV CMD move{'positions': {'mokev': 6.2}} > optimize_idgap{'search_range': 0.5} [idem]
If a command job is interrupted by a crash, there is no per-step resume point — the whole job is either safe to redo from the top or it isn't:
-
If every step is idempotent (the usual case — an absolute move is harmless to repeat),
flomni.tomo_queue_execute()silently re-runs the whole job from the top. -
If any step is not idempotent, you are asked whether to re-run the job from the top or mark it done as-is.
Custom behavior at each projection angle¶
By default, every projection in a tomo scan is a single ptychography scan at that angle. For cases that need something more — e.g. record a projection, move a polarizer in, record again, move it back out, at every angle — write a small Python function and register it as an at_each_angle hook. Once registered, it can be selected per tomo-queue job, so the whole modulated tomogram runs unattended along with any other queued jobs.
A hook is a function func(flomni, angle), called once per projection angle instead
of the normal acquisition. Acquire the actual projection(s) inside it the same way
tomo_scan() would on its own — tomo_scan_projection(angle) for a normal
(Fermat-scan) measurement, or tomo_acquire_at_angle(angle) if tomo_parameters()
has single-point mode enabled for this job (see the warning below for why it matters
which one you call):
def polarizer_modulation(flomni, angle):
flomni.tomo_scan_projection(angle)
umv(dev.polarizer, "in")
flomni.tomo_scan_projection(angle)
umv(dev.polarizer, "out")
| command | explanation |
|---|---|
flomni.register_at_each_angle_hook("name", func) |
Load a hook function under name. |
flomni.at_each_angle_hook = "name" |
Activate it — the next tomo-queue job added will use it. |
flomni.at_each_angle_hook = None |
Deactivate it — back to normal projections for the next job. |
flomni.list_at_each_angle_hooks() |
Print the names of all currently registered hooks (the active one is marked). |
flomni.unregister_at_each_angle_hook("name") |
Remove a hook by name. |
Example — queue a polarizer-modulated tomogram, then a normal one:
flomni.register_at_each_angle_hook("polarizer_mod", polarizer_modulation)
flomni.tomo_parameters_cli() # set up the scan parameters as usual
flomni.at_each_angle_hook = "polarizer_mod" # activate the hook
flomni.tomo_queue_add("polarizer run")
flomni.at_each_angle_hook = None # back to normal -- do not skip this!
flomni.tomo_queue_add("plain follow-up scan")
flomni.tomo_queue_execute() # runs both, hook active only for the first
flomni.at_each_angle_hook = None before adding a job
that should run normally — each queued job remembers whatever at_each_angle_hook
was set to at the moment it was added (like every other tomo parameter), so a job
added right after a hook-using one without resetting it first will silently run with
that hook still active.
Registered hooks are session-only — they live in memory and do not survive a
kernel restart. If tomo_queue_execute() reaches a job whose at_each_angle_hook
isn't registered in the current session (e.g. after restarting BEC), it stops with a
clear error rather than silently running a plain scan; re-run
register_at_each_angle_hook() for that hook, then call tomo_queue_execute()
again to resume.
Editing a hook after registering it: register_at_each_angle_hook() stores
whichever function object you pass it at that moment — it is not a live link to the
function's name. If you edit the function's source and re-run the def (in a cell,
or reload the file it's defined in — see below), the change is picked up
automatically the next time the hook runs, and a note is printed telling you so.
You don't need to call register_at_each_angle_hook() again after an edit.
Recommended: define hooks in a file, not inline in the shell. For anything
beyond a quick one-off, put the hook function in a .py file — ideally together
with the tomo_queue_add()/at_each_angle_hook calls that use it, so the file is a
single, compact record of exactly what ran during the experiment. Load it with
import myhooks (or from myhooks import my_hook) and, after editing, reload with
importlib.reload(myhooks) — the automatic pick-up described above still works,
because it detects the change via the module's own namespace, not the local name
you imported it under. %run -i myhooks.py also works (runs in the current
namespace, same as a cell). Avoid plain %run myhooks.py (without -i): it
executes in a fresh, throwaway namespace each time, so an edit-and-rerun is not
picked up automatically and the old version keeps running silently.
Example — hook loaded from a file, changing exposure and step size for a subset of
projections: a hook can also read flomni.progress and temporarily change any scan
parameter (e.g. tomo_countingtime, tomo_shellstep) just for specific projections,
then restore it. This example (tomo_type 1) takes a longer exposure at a finer
real-space step every 5th projection within sub-tomogram 2, and a normal projection
everywhere else. ~/hooks/my_hooks.py:
def high_res_every_5th(flomni, angle):
if flomni.progress["subtomo"] == 2 and flomni.progress["subtomo_projection"] % 5 == 0:
orig_countingtime = flomni.tomo_countingtime
orig_shellstep = flomni.tomo_shellstep
flomni.tomo_countingtime = 0.5 # longer exposure
flomni.tomo_shellstep = 0.2 # finer real-space step
try:
flomni.tomo_scan_projection(angle)
finally:
# restore even if the scan above raises, so a retry or the next
# projection doesn't silently keep running with these settings
flomni.tomo_countingtime = orig_countingtime
flomni.tomo_shellstep = orig_shellstep
else:
flomni.tomo_scan_projection(angle)
import my_hooks
flomni.register_at_each_angle_hook("high_res_every_5th", my_hooks.high_res_every_5th)
flomni.tomo_parameters_cli() # set up the scan parameters as usual
flomni.at_each_angle_hook = "high_res_every_5th" # activate the hook
flomni.tomo_queue_add("subtomo 2 high-res every 5th")
flomni.at_each_angle_hook = None # reset the session default for whatever's queued next
flomni.tomo_queue_execute()
progress["subtomo"]/progress["subtomo_projection"] are populated the same way for
tomo_types 2/3, so the same pattern works there too — just note that unlike type 1's
fixed 8 sub-tomograms, those types have no fixed sub-tomogram count (see
Tomography above), so "sub-tomogram 2" means
something different (and open-ended) for them.
tomo_scan_projection() vs tomo_acquire_at_angle(): these are not
interchangeable. tomo_scan_projection(angle) always runs a full Fermat-scan
projection; tomo_acquire_at_angle(angle) always runs a single-point acquisition.
Use whichever matches how tomo_parameters() has this job configured
(single_point_instead_of_fermat_scan) — calling the wrong one for the job's mode
produces the wrong kind of data, and calling tomo_scan_projection() directly while
single-point mode is on prints a warning and asks for confirmation. Declining that
confirmation now fails the job (raises, rather than silently skipping that
projection) — inside a custom hook or the normal tomo flow this correctly marks the
queue job incomplete and pauses the queue, instead of quietly acquiring fewer
projections than expected with no trace, or (if you get asked at every angle and
eventually give up and hit Ctrl+C) leaving a job stuck at status running forever
with nothing actually running. If you hit this, use tomo_acquire_at_angle(angle)
in your hook instead, or fix the queue entry via flomni.tomo_queue_delete(index)
(no status guard, works even on a stuck running row) or by marking it done/
incomplete directly: flomni._tomo_queue_proxy.update_by_id(job_id, status=...).
A reminder of which one to use fires the moment you switch single-point mode on
(flomni.single_point_instead_of_fermat_scan = True), and again any time you check
flomni.tomo_parameters() while it's on; the params panel (GUI) shows the same
reminder as a visible note whenever "Single-point scan" is checked.
GUI: the tomo parameters panel has an "At-each-angle hook" dropdown, listing
whatever is currently registered from the CLI (register_at_each_angle_hook()) —
the GUI process can't register hooks itself, only select one by name for the job
you're about to submit or add to the queue. Pick "None (default)" for a normal scan.
If a job's stored hook isn't currently registered anywhere, it still shows up in the
dropdown labeled "(not registered in this session)" so it's never silently hidden.
A queued job's active hook (if any) also appears on the status webpage (queue detail
and "Current measurement"), the ☰ Queue control… table's Details column, and the
flomnigui_show_progress() ring's center label.
Unregistering does not clear at_each_angle_hook. unregister_at_each_angle_hook()
only removes the function from this session's registry — if at_each_angle_hook (or a
queued job) still names it, that reference is untouched; it just becomes an
unregistered name, flagged as such wherever it's shown (CLI, GUI dropdown, webpage,
Details column). Set flomni.at_each_angle_hook = None (or the GUI dropdown's "None
(default)") explicitly if you want to actually clear the selection, not just remove
the hook it points to.
Where the hook shows up in the permanent record: flomni.tomo_parameters(),
the end-of-scan scilog entry, and write_pdf_report() all show the active hook's
name (flagged if unregistered) — and, when it's still registered, its full source
code too (scilog entry and PDF report only, not the console print), so what a
tomogram actually did is part of the log even if the hook itself is edited or
removed later.
Sample storage and transfer¶
Heater¶
The heater can be moved up and down by flomni.move_fheater_down() flomni.move_fheater_up()
The functions are safe in the sense that no collisions should occur. E.g. the OSA will be moved back before a movement of the heater.
The heater still needs commissioning in BEC!!!