fix(lamni): drop redundant 45deg detour in smear verification sweep, widen GUI retry budget
CI for csaxs_bec / test (push) Successful in 1m51s

find_rotation_center_smear_experimental() rotated 45->0 for its post-shift
verification sweep, needlessly re-treading ground the preceding full smear
sweep already covered and adding a long extra blocking move that could
starve the GUI heartbeat past _gui_call_with_retry's budget, surfacing as
RuntimeError: GUI is not alive. Sweep straight back to 0 instead, and widen
the retry budget (8x1.5s -> 25x2.0s) so update_frame() can ride out long
blocking moves on slower hardware.

Also tidy a stray comment-block artifact in ptycho_lamni.yaml and update
lamni.md docs for the current xrayeye_rotation_center_calibration_* API.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
x01dc
2026-07-31 11:54:07 +02:00
co-authored by Claude Sonnet 5
parent fa30e11b3c
commit 1db9c18e0a
3 changed files with 23 additions and 16 deletions
@@ -153,16 +153,20 @@ class XrayEyeAlign:
def _enable_rt_feedback(self):
self.device_manager.devices.rtx.controller.feedback_enable_with_reset()
def _gui_call_with_retry(self, func, *args, retries: int = 8, retry_delay: float = 1.5, **kwargs):
"""Call a bec_widgets GUI RPC method, retrying briefly on "GUI is not alive".
def _gui_call_with_retry(self, func, *args, retries: int = 25, retry_delay: float = 2.0, **kwargs):
"""Call a bec_widgets GUI RPC method, retrying on "GUI is not alive".
The GUI's liveness check is a Redis heartbeat with a 10 s TTL, refreshed
every 200 ms from the same Qt event loop that renders live-view frames.
A long blocking device move (e.g. interferometer feedback reset, a live
rotation sweep) with live view left on can starve that heartbeat past
its TTL even though the GUI process is still alive -- it catches up and
resumes the heartbeat shortly after the block ends, so a short retry
loop recovers without needing any change to bec_widgets itself.
rotation sweep covering up to ~360 deg) with live view left on can
starve that heartbeat past its TTL even though the GUI process is
still alive -- it catches up and resumes the heartbeat shortly after
the block ends, so a retry loop recovers without needing any change to
bec_widgets itself. The default budget (25 retries * 2.0 s = 48 s
total wait) is sized to comfortably outlast a full-range rotation
sweep on slower hardware, where such a move has been observed to take
30+ s.
"""
for attempt in range(retries):
try:
@@ -949,7 +953,13 @@ class XrayEyeAlign:
self._apply_rotation_center_shift(cumulative_shift_x, cumulative_shift_y)
self.send_message("Verifying alignment...")
self._live_sweep([45, 0])
# No 45 deg detour here (unlike the non-smear path below): the
# _smear_sweep above already rotated continuously through the
# full range with the operator watching, so a stop at 45 deg
# would just re-tread ground already covered -- go straight back
# to 0 deg (still a full ~360 deg move since lsamrot doesn't
# wrap) with live view running.
self._live_sweep([0])
self.update_frame(keep_shutter_open)
answer = (
input("Alignment acceptable -- stop here? [Y/n] (n = run another iteration): ")
@@ -307,8 +307,6 @@ cam_xeye:
readoutPriority: async
userParameter:
pixel_calibration: 0.0009174311926605505 # mm/pixel (= 0.2 / 218)
############################################################
deviceTags:
- ptycho_lamni
#################### GUI Signals ###########################
+6 -7
View File
@@ -20,22 +20,20 @@ Mount the new sample. The X-ray eye is already in, but the X-ray optics needs to
The effective position of the axis of rotation shifts with sample thickness or mounting position of the sample along the axis of rotation. The position of the axis of rotation is controlled by user parameters **center** of the **lsamx** and **lsamy** stages.
The recommended way to (re-)measure `center` for a new sample is the GUI-driven procedure below. It replaces the fully manual jog-by-hand steps that used to live here; those are kept further down as a fallback for when the automated procedure or the GUI is unavailable.
The recommended way to (re-)measure center for a new sample is the GUI-driven procedure below. It replaces the fully manual jog-by-hand steps that used to live here; those are kept further down as a fallback for when the automated procedure or the GUI is unavailable.
**Automated rotation-center calibration**
This opens the X-ray eye widget and walks through the calibration interactively. Pick the function based on what's mounted:
- `lamni.xrayeye_rotation_center_calibration_isolated()` — for a sparse/isolated particle. You submit its centre position once at 0° and once at 180°; the midpoint of the two is the rotation axis' projected position (this works regardless of the axis tilt). No further confirmation step.
- `lamni.xrayeye_rotation_center_calibration_extended()` — for a non-isolated/textured sample where the rotation centre can be identified visually. The sample sweeps 0° → 180° → 0° with the live view left open so you can watch for the point that doesn't move, then you submit a single click at 0°. The correction is applied, a short verification sweep (0° → 45° → 0°) runs, and you're asked *"Alignment acceptable — stop here?"* — answer `n` to refine further.
- `lamni.xrayeye_rotation_center_calibration_isolated()` — for a sparse/isolated particle. You submit its centre position once at 0° and once at 180°; the midpoint of the two is the rotation axis' projected position. Pass `keep_shutter_open=True` if it's hard to relocate the sample between steps, matching `xrayeye_alignment_start()` below.
- `lamni.xrayeye_rotation_center_calibration_extended()` — for a non-isolated/textured sample where the rotation centre can be identified visually. The sample sweeps 0° → 180° → 0° with the live view left open so you can watch for the point that doesn't move, then you submit the rotation centre. The correction is applied, a short verification sweep (0° → 45° → 0°) runs, and you're asked *"Alignment acceptable — stop here?"* — answer `n` to run again.
At the end you're shown the computed `lsamx_center`/`lsamy_center` and asked to confirm before they're written via `dev.lsamx.update_user_parameter(...)` / `dev.lsamy.update_user_parameter(...)`.
Pass `keep_shutter_open=True` if it's hard to relocate the sample between steps, matching `xrayeye_alignment_start()` below. Interrupt with Ctrl-C to abort; the crosshair and DAP forwarding are cleaned up automatically.
**Experimental: continuous-rotation "smear" aid**
`lamni.xrayeye_rotation_center_calibration_smear_experimental(sweep_deg=360.0, keep_shutter_open=False)` is an **experimental** alternative to `..._extended()` for the same non-isolated-sample case. Instead of judging the rotation centre from a single instant of live rotation, it rotates `lsamrot` continuously through `sweep_deg` (default a full circle) while accumulating a max-projection ("star-trail") composite from the camera: off-axis features smear into circular arcs, and the common center of curvature of those arcs is the rotation axis — usually an easier target to click than one live frame. The composite builds up live on screen as the sweep progresses (pushed to its own preview channel, so the "camera running" indicator stays steady throughout — see {ref}`the developer notes <developer.lamni_smear_architecture>` for why that matters) and stays frozen once the sweep ends, until you submit your click. `sweep_deg` can be reduced below 360 (even below 180) if a shorter arc already shows enough curvature. There is no automatic circle fitting — you still submit the centre by eye, same click mechanism as `..._extended()`, and the same iterate/verify/apply flow follows. Not yet merged into the production branch; try it from `experimental/rotation_center_smear`.
`lamni.xrayeye_rotation_center_calibration_smear_experimental(sweep_deg=360.0)` is an **experimental** alternative to `..._extended()` for the same non-isolated-sample case. Instead of judging the rotation centre from a single instant of live rotation, it rotates `lsamrot` continuously through `sweep_deg` while accumulating a max-projection ("star-trail") composite from the camera: off-axis features smear into circular arcs, and the common center of curvature of those arcs is the rotation axis — usually an easier target to click than one live frame. The composite builds up live on screen as the sweep progresses and stays frozen once the sweep ends, until you submit your click. `sweep_deg` can be reduced below 360 if a shorter arc already shows enough curvature. There is no automatic circle fitting — you still submit the centre by eye, same click mechanism as `..._extended()`.
**Manual fallback**
@@ -53,7 +51,7 @@ To observe the axis of rotation obtain the position of the Fresnel zone plate on
#### X-ray eye alignment
The BEC GUI is used to obtain a coarse sample alignment. Start the alignment process (which clears any previous alignment) by
The BEC GUI is used to obtain a coarse sample alignment. Start the alignment process by
`lamni.xrayeye_alignment_start()`
This opens the X-ray eye widget automatically. The procedure collects the sample centre position at 8 angles (0°–315° in 45° steps, full 360° rotation). At each angle the user clicks the sample centre in the image and presses **Submit**. After all 8 angles the data is sent to the **Fit** tab of the GUI where a sinusoidal fit runs automatically in both x and y directions. The fit parameters are loaded automatically at the end of the procedure.
@@ -77,6 +75,7 @@ or with the shutter left open: `lamni.xrayeye_update_frame(keep_shutter_open=Tru
The sample fine alignment can be obtained using ptychography. For this a short laminogram has to be recorded.
* `lamni.leye_out()` if not done yet.
* `lamni.tomo_parameters()` adjust the ptychographic scan parameters for the alignment scan (FOV/step size/counting time) — `tomo_type` and number of projections are ignored, since the alignment scan always runs its own fixed 12 points spread evenly across the full 360 degrees, independent of the main tomogram's settings.
* `lamni.tomo_alignment_scan()` perform the alignment scan. Requires x-ray-eye alignment to have already been done — it will abort with a message otherwise. Scan numbers, angles and the existing x-ray-eye-fit offset at each angle are written to `~/data/raw/logs/ptychotomoalign_scannum.txt` and also printed at the end.
* Run `BEC_ptycho_align` (in Matlab, **force ptycho=1**, and **correct scan numbers**) using the printed/logged scan numbers.