Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.6 KiB
Plan: "Skip" button on the X-ray-eye alignment widget (flomni: leave out one angle, e.g. 90 deg)
Status: implemented 2026-09-21 on a BEC test server (tests: tests_bec_ipython_client/test_flomni_xray_eye_skip.py,
tests_bec_widgets/test_x_ray_eye_skip_button.py); tested live in the flomni simulator (works, ok'd by Ana); not yet tried on real hardware (see Verification).
Written after a read-only stability study of the DAP sine fit with 4 instead of 5 angles.
Deviations from the plan below: the widget couples the Skip button to the Submit button
(enable_skip_button(True) only takes effect while Submit is enabled), so the client calls it
just once per run phase instead of next to every enable_submit_button. A refused skip re-sends
the pending step prompt. The h5 alignment_values dataset got a step_k attr and
alignment_fit a skipped_angles_deg attr. Skipping 0/180 is warned about, not forbidden.
Context
Flat samples are edge-on at 90 deg, so their centre cannot be submitted there. The flomni X-ray-eye
alignment (XrayEyeAlign._align_impl in
csaxs_bec/bec_ipython_client/plugins/flomni/x_ray_eye_align.py) submits 5 angles
0/45/90/135/180. The GUI fit tab (OMNY_XRayEye in
csaxs_bec/bec_widgets/widgets/xray_eye/x_ray_eye.py) fits
A*sin(f*x + shift) + intercept through the DAP server (LmfitService1D, model
SineModel + LinearModel, f fixed to 1 deg, start values SINE_FIT_DAP_PARAMETERS), i.e. 3 free
parameters. With 4 points there is 1 degree of freedom (today 2).
Wanted: a Skip button on the widget. One position may be skipped as long as at least 4 values remain for the fit; otherwise error.
Stability study (does the fit still work with 4 points?)
Yes. Method: (1) linear noise propagation for several angle sets, (2) Monte Carlo through the
real LmfitService1D (configure(data_x=, data_y=, parameters=SINE_FIT_DAP_PARAMETERS, oversample=5) then process(), in bec_venv) with the widget's exact start values, compared with
the global least-squares optimum. Truth A*sin(rad(angle)+phase)+C plus Gaussian noise. There is no
real alignment data on disk, so the click noise sigma is unknown (pixel = 0.44 um, hand-placed ROI
centre, probably 1-3 um); results are in units of sigma.
-
~49000 fits, 0 failed fits.
-
RMS error of the fitted correction curve over 0-180 / 0-360 deg (units of sigma):
angles used cond. number 0-180 0-360 0,45,90,135,180 (today) 3.11 0.71 1.12 skip 90: 0,45,135,180 3.23 0.88 1.29 skip 45 or 135 2.87 0.82 1.17 skip 0 or 180 5.36 1.02 1.91 Monte Carlo at sigma = 1 um agrees (skip 90: median 0.72 sigma vs 0.61 sigma, p95 1.6 vs 1.2). So skipping 90/45/135 is cheap (+15..25% noise); skipping an end point (0/180) is clearly worse but still usable.
-
Optimizer misses versus the global optimum only occur for a large offset |C| >~ 45 um (start intercept C = 0 is far away): 15/4000 in that regime for both 4 and 5 points, 0/4000 for |C| <= 20 um. A property of the start values, not of the point count. Optional hardening: seed the intercept from the data mean (the static widget parameters cannot do that themselves).
-
What is really lost with 4 points is redundancy: 1 dof, the residual has one fixed pattern (0.71, -1, 1, -0.71 for 0,45,135,180), so chi-square / the plot cannot tell which click was bad, and a mis-click at 0/180 (leverage 0.83) is mostly absorbed into phase/offset.
-
Other 4-point layouts (0,60,120,180: 0.80 sigma; 0,30,150,180: 1.17 sigma). Extending beyond 180 deg is the most robust option if the flat-sample geometry and fsamroy travel allow it: 0,45,135,225 gives 0.84 / 0.93 sigma (0-180 / 0-360).
Decision: what does Skip send to the client script?
Reuse the existing submit signal with a sentinel: omny_xray_gui.submit.set(-1)
(0 = idle, 1 = submit, -1 = skip). The widget writes no xval_x_k / yval_y_k / width_*_k for a skip.
- The client already polls exactly this one signal (
submit.get() == 1), so there is no new polling path and a skip is atomic (no race between "values written" and "flag set"). - No device change. A new
skipSignalonOMNYXRayAlignGUI(csaxs_bec/devices/omny/xray_epics_gui.py) would need a device-server reload;submitexists. - No NaN/stale-data trick.
submit = 1with NaN inxval_x_kwould make every consumer (alignment_values,update_fov,roi_pixel_data) responsible for noticing it, and the stale values of an earlier run would be read if one forgets. With -1 the client never reads that step.
Rejected: separate skip signal (cleaner, but needs device reload), NaN payload (implicit,
error-prone).
Implementation plan
Widget (x_ray_eye.py; shared with LamNI and omny, so it must be inert for them):
self.skip_button = QPushButton("Skip", ...)under the Submit button, disabled by default. Its slot doesself.dev.omny_xray_gui.submit.set(-1)(no ROI needed, unlikesubmit()).- New RPC
enable_skip_button(enable: bool): add toUSER_ACCESSnext toenable_submit_buttonand regeneratecsaxs_bec/bec_widgets/widgets/client.py(auto-generated bygenerate_cli.py). LamNI/omny never call it, so the button stays disabled there.
Client (flomni/x_ray_eye_align.py):
- Loop:
submit_val = dev.omny_xray_gui.submit.get(); act on1or-1; reset to 0 as today. MIN_FIT_POINTS = 4; trackself.skipped_steps. A skip is legal only for real fit points (steps 1-5 after_height_centered; not k = 0 FZP centre, not the height-centering submission at k == 1) and only while5 - n_skipped - 1 >= MIN_FIT_POINTS. Otherwise do nothing to the run,send_message("Cannot skip: at least 4 positions are needed for the fit")and keep waiting, so the error is immediate and not after the whole rotation series. Callenable_skip_button(False)once the budget is used andTruewhereverenable_submit_button(True)is called for steps 1-5.- Skip branch: no
alignment_values[k], store a NaN / "skipped" row inroi_pixel_data, noupdate_fov(k)(would read stale widths), then advance like a submit: for1 <= k < 5rotate to the next angle (tomo_rotate(k * 45),update_frame, next step message); atk == 5finish (hide_crosshair,break) withoutupdate_fov. write_output(): build angles/offsets only from non-skipped steps (e.g.[0,45,135,180]); final guardif len(angles) < MIN_FIT_POINTS: raise.submit_fit_arrayand the fit tab are already length-agnostic. Record the skipped angles in the h5 (alignment_fitattrs).- Messages: with 4 points "1 dof: the fit cannot flag a bad click - check the plot"; extra warning if the skipped angle is 0 or 180 (noisier fit). Step numbering "k/5" stays.
Verification
- Mock/
test_wo_movements=Truerun of the client loop with a fakesubmitsequence: (1,1,-1,1,1) ends with 4 fit points; a second -1 is refused with a message; -1 at k == 0 or at the height-centering submission is refused; -1 at the last step finishes cleanly. write_output()gives the 4-pointangles/offsets; the fit tab plots 4 points; DAP parameters arrive inomny_xray_gui.fit_params_x;flomni.read_alignment_offset(get_data_from_gui=True)loads sanetomo_alignment_fit[0].- Regression: the default 5-submit run is unchanged; LamNI/omny show Skip disabled.
- Hardware: dry run at the beamline with a flat sample, skipping 90 deg.
Open questions
- Real click noise sigma (no saved alignment data on the analysis machine).
- Should skipping 0/180 be forbidden instead of only warned about?
- Is an angle beyond 180 deg possible for flat samples (better 4-point layout)?