Replace the plain self.progress dict with a global-var-backed
_ProgressProxy (same shape and key as FlOMNI's), so progress survives
a client restart. Adds heartbeat + idle-time-gap detection in
_tomo_scan_at_angle, ETA computation in _print_progress, and
tomo_start_time/reset handling in tomo_scan(). Adds
tomo_progress_reset() and _format_duration() for parity.
gui_tools.py's progress display already read these fields; no GUI
change needed.
feat(lamni): persist alignment lookup-table corrections
corr_pos_x/corr_pos_y/corr_angle (+ _2 iteration) become
global-var-backed properties (lamni_corr_pos_x, lamni_corr_pos_y,
lamni_corr_angle, ..._2) instead of plain instance lists that reset
on every client restart. No default correction file exists for
LamNI, so reset_correction()/reset_correction_2() just clear to
empty.
feat(lamni): add frames_per_trigger (burst mode) support
LamNIFermatScan already inherits from AsyncFlyScanBase and already
accepts/passes through frames_per_trigger -- only the client-side
plumbing was missing. Adds a validated, global-var-backed
frames_per_trigger property, threads it into
tomo_scan_projection()'s lamni_fermat_scan() call, and exposes it in
tomo_parameters(). Also folds it into the idle-time cadence estimate
alongside stitch tile count.
feat(lamni): add crosshair and structured HDF5 output to X-ray eye alignment
Set the alignment crosshair at the FZP centre (step k=0, raw pixel
coords), hidden on normal completion and on Ctrl-C abort.
update_frame() now actually captures and stores each frame
(previously not stored at all); roi_pixel_data is now collected at
every submit. write_output() additionally writes a timestamped HDF5
file (alignment_values, alignment_images, roi_pixel_data,
alignment_fit) alongside the existing plain-text archival file.
fix(lamni): stop resetting correction state on every XrayEyeAlign instantiation
A fresh XrayEyeAlign is constructed on every call to both
xrayeye_alignment_start() and xrayeye_update_frame(); resetting
corr_pos_x/y/angle and tomo_fit_xray_eye in __init__ meant the
latter -- meant as a lightweight frame check -- silently wiped valid
persisted correction data every time. Reset moved to the start of
align() itself.
fix(lamni): use dev.fsh instead of dev.omnyfsh for the alignment shutter
The shared XRayEye widget monitors and controls dev.fsh; the
alignment script was opening/closing a different device, which
would desync the GUI's shutter toggle from the actual hardware
state.
fix(lamni): correct stale self.align reference in MagLamNI.rotate_slowly
self.align no longer exists as an attribute now that
LamNIAlignmentMixin is mixed directly into LamNI. Use
self.tomo_fovx_offset/self.tomo_fovy_offset directly.
Protocol-level simulation of the flOMNI hardware injected via socket_cls:
real device/controller classes run unchanged against state machines
implementing fgalil.dmc, galil_micos_upr.dmc, the Smaract MCS protocol
and the Orchestra CommunicationServer. Cameras reuse the real device
classes with a synthetic frame source. Includes simulated_flomni device
config (boots referenced at 'in' positions) and an offline harness
covering moves, referencing, rt feedback/tracker, flyer scan readout
and the gripper transfer routine.
Replace 11 identical global-var-backed getter/setter property pairs with a
single _GlobalVarParam descriptor. Behavior is unchanged: same keys, defaults,
and None-handling; stitch_x/stitch_y retain int enforcement via the descriptor's
type_ guard (typeguard.check_type). Net -57 lines with no functional change.
xrayeye_update_frame() now checks all configured beamline states and, if any
are not valid, warns the user (the frame may be empty when the beam is down)
and asks before taking a frame. The automated alignment path is unaffected.
ftransfer_sample_change(): clarify the remove-only prompt to "sample currently
in the sample stage", and include tray slot 20 in the empty-slot scan
(range(1, 20) -> range(1, 21)) in both the remove and mount branches, matching
check_position_is_valid's 0-20 range.
When the eye is already out, optics in, and feedback running, skip the
eye-out/optics-in transition to avoid an interferometer reset. Enables
repeated alignment scans and interleaving alignment into a tomogram
without leaving the current measurement position.
feedback_disable/enable_with_reset cycle re-zeros the interferometers
and moves off the current sample position. Only run it when the FZP
actually needs to move; add force_feedback_reset kwarg and _ffzp_is_in
helper.
X-Ray Eye alignment GUI + script now share a single, device-configured
pixel calibration and the GUI shows live ROI dimensions.
GUI (x_ray_eye.py):
- Add read-only "ROI size" field below the message box, showing the active
ROI's x/y extent, updated live via each ROI's sigRegionChanged. Rectangles
report width/height; circular ROIs read raw state["size"] so an ellipse
state would be reflected (currently aspect-locked upstream).
- Add "Calibration" field showing microns/pixel, or "uncalibrated (pixels)"
when the camera parameter is absent.
- Resolve mm/pixel from dev.cam_xeye.user_parameter["pixel_calibration"];
fall back to raw pixels (scale 1.0, unit "px") if unavailable.
- Enlarge the message field from 60 to 90 px height.
Alignment script (x_ray_eye_align.py):
- Replace the hard-coded PIXEL_CALIBRATION constant with a pixel_calibration
property reading the same cam_xeye user parameter; fall back to
PIXEL_CALIBRATION_DEFAULT if unavailable.
- Fold the recurring factor of 2 into the calibration: xval_x_* come straight
from raw ROI pixels on submit, so the true scale is 0.05/113 mm/pixel.
Default is now 0.05/113 and the three /2 factors (FZP center x, height
centering, FOV) are removed. Computed physical values are unchanged.
Device config (cam_xeye_pixel_calibration.yaml):
- Add pixel_calibration userParameter (0.05/113 mm per raw ROI pixel) to
cam_xeye. Merge the userParameter block into the existing device entry.
scilog_last_scans now puts the user comment at the top of the entry and
drops the flOMNI logo attachment. number_of_scans accepts 0 to send a
comment-only entry. When fewer projection records exist than requested
(e.g. non-ptycho scans, which write no timing record), the available ones
are used and a note reports how many of the requested count were found.
At BEC session start, compare bec.active_account against the new global
var defaults_applied_for_account. On a genuine account change, interactively
offer to reset all tomo scan parameters to their defaults for the new
experiment; a client restart within the same account is a silent no-op.
Add _set_default_tomo_params() writing all 20 scan parameters via the
property setters.
process looks like a BEC client), plus a delayed (500 ms) backup
stop_devices request, defaulting to stop-all so the widget doubles as
a generic emergency stop when opened standalone from the launcher menu
The gripper routines (#GRGET/#GRPUT) run autonomously on the Galil
(thread 3); Ctrl+C previously only killed the BEC-side polling loop
while the controller kept executing, including ongoing motion.
- add FlomniSampleTransferMixin.ftransfer_abort(): dev.ftransy.stop()
(stop_devices endpoint -> device server -> XQ#STOP), wait for thread 3
to halt, then put the controller back in posmode; deliberately no
ensure_gripper_up() - recovery after an abort is manual
- wrap the mntprgs polling loops in ftransfer_get_sample/put_sample in
try/except KeyboardInterrupt -> ftransfer_abort()
- answering "No" at a confirmation point now also hard-aborts the
controller routine instead of leaving it parked in #CONFIRL
- ConsoleButtonsWidget ABORT: keep SIGINT to the client, plus a delayed
(500 ms) backup stop_devices request so the hardware stops even if
the client is hung; delay ensures the controlled client-side abort
wins the race against #STOP clearing mntprgs
Add a small ConsoleButtonsWidget (Yes/No/Abort + message label) as a
GUI alternative to console yesno() prompts during sample transfer,
docked below the live camera view.
- bec_widgets: new ConsoleButtonsWidget (csaxs_bec/bec_widgets/widgets/
console_buttons/). Yes/No write to a response property polled by the
caller; Abort sends a real SIGINT to the BEC client process (captured
via os.getppid() at construction), stopping any blocking call
(including an in-progress motor move) exactly like a console Ctrl+C.
- omny_general_tools: add OMNYTools.gui_yesno(), a GUI-backed
alternative to yesno() with the same message/default/autoconfirm
semantics, polling the widget's response instead of blocking on
input(). Not yet used as a general yesno() replacement.
- gui_tools: flomnigui_show_cameras() now also opens the console widget
docked below the two camera views (set_layout_ratios for relative
sizing, since dock_area.new() has no pixel width/height kwarg).
flomnigui_show_gui() now passes an explicit window geometry, right-
aligned on a 2560x1440 screen.
- flomni: add ftransfer_confirm_dialog(), which uses the GUI console
when available (falling back to the plain CLI yesno() otherwise) and
wire it into ftransfer_confirm()'s per-step "All OK? Continue?"
prompt during sample transfer. Other yesno() call sites (stage-in
flight-tube check, gripper-move stage-out check) intentionally left
on the CLI for now.
Known issue: requested window height in flomnigui_show_gui() is not
taking effect as expected at startup; needs follow-up.
Passing -1 stows the sample currently in the sample stage into a free
tray slot without mounting a replacement, matching the old control
system's sample_change(-1) behavior. Existing swap-path logic is
unchanged.
Add collect_empty_frames(), which acquires 10 flat-field frames at
angle 0 with fsamx shifted out of the beam by fovx/2 (opposite the
normal alignment x-offset). Called once at the start of a new
tomo_scan() (not on resume). Frames are excluded from the ptycho
reconstruction queue and logged to tomography_scannumbers.txt with
subtomo_number=0.
Expose the ConstEmitter runtime constants through the high-level flomni
interface, alongside the existing laser_tracker_* methods, and add an
interactive tweak tool for the two tracking target positions.
New methods on FlomniSampleTransferMixin (thin wrappers delegating to
dev.rtx.controller, same pattern as laser_tracker_show_all):
laser_parameters_show_all()
laser_parameters_set_targety(val)
laser_parameters_set_targetz(val)
laser_parameters_set_intensity_threshold_laser(val)
laser_parameters_set_psd_intensity_threshold_tracking_low(val)
laser_tweak(): interactive 2D raw-terminal loop for the two tracking targets,
modeled on OMNYTools.tweak_cursor.
left/right : target y -/+ (fixed 0.01 step, absolute set of current +/- step)
up/down : target z +/-
s : return both targets to their launch positions
q : quit (terminal state restored in finally)
On launch and after every change it prints the current y/z targets and the
fzp x interferometer signal (read_ssi_interferometer channel 1), so the signal
response can be watched while tweaking.
termios/tty/fcntl/sys are imported locally in laser_tweak to avoid touching
module-level imports. Additive change; no existing behaviour modified. Requires
the ConstEmitter emitter_* methods on RtFlomniController.
Replace ~44 per-slot blocking device reads per poll with a single bulk
flomni_samples.read(), and add change-detection so cells only repaint
when their state actually changes. The old per-slot accessors stalled
the GUI event loop every 2s; steady-state polling now does no UI work.
Writes invalidate the changed slot's cache so local edits still repaint
immediately.
GUI for ftransfer_show / ftransfer_modify. Displays the
sample stage (0), gripper (100) and magazine slots 1-20 in a 4x5 grid
matching the CLI layout. Right-click per slot corrects the records
(new / rename / clear), each action confirmed by its own dialog and
written immediately. Bookkeeping only: writes flomni_samples directly
(mirroring flomni_modify_storage_non_interactive), never moves hardware.
Guarded on fsamroy; OMNY shows a not-implemented placeholder.
Expose the flOMNI ConstEmitter runtime constants (served over the control-loop
TCP server, command letter 'e') to BEC users. These were previously baked into
Orchestra module parameter files; they can now be read and set live.
New RtFlomniController methods (all added to USER_ACCESS):
emitter_get() -> dict of the four values
emitter_show_all() -> PrettyTable printout
emitter_set_tracking_target_y(val) -> "ey<val>"
emitter_set_tracking_target_z(val) -> "ez<val>"
emitter_set_intensity_threshold_laser(val) -> "et<val>"
emitter_set_psd_intensity_threshold_tracking_low(val) -> "el<val>"
emitter_get issues "es" and parses the CSV reply
(targetpos_tracking_y, targetpos_tracking_z, intensity_threshold_laser,
PSD_intensity_threshold_tracking_low), same pattern as the Ts/sr readouts.
Setters use socket_put with a fixed %.4f value, same pattern as the pa
setpoint command; they are fire-and-forget (the server does not ack sets).
The rotation-loop laser threshold (intensity_threshold_rot_laser) is a startup
constant on the server side and is intentionally neither read back nor settable
here.
Named methods were chosen over a single opaque command to match the existing
laser_tracker_* / feedback_* convention and to self-document each value.
Requires the corresponding flOMNI control-loop change (ConstEmitter module +
'e' command in CommunicationServer). Additive change; no existing behaviour
modified.