Commit Graph
135 Commits
Author SHA1 Message Date
x01dcandClaude Sonnet 5 89c5ed1c9e fix(LamNI): don't auto-retry lsamrot moves aborted by stop()
CI for csaxs_bec / test (pull_request) Successful in 1m50s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m57s
move_and_finish()'s spurious-trip retry (added in 1f85106) couldn't tell a
genuine Galil safety-thread trip apart from a user/scan-initiated stop()
(e.g. Ctrl+C): an aborted move looked like an incomplete move either way,
so the abort got silently undone by resetting the error latches and
re-issuing the original move to the same target.

Track stop() requests on a per-motor threading.Event, cleared at the start
of each move() and checked (twice, since reset_axis_errors() itself takes
a few round-trips) before the retry branch fires, so an aborted move is
now reported as a failure instead of being retried.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 11:37:23 +02:00
x01dc 285691b629 feat(devices): add live-mode FPS measurement to AlliedVisionAravisCamera
CI for csaxs_bec / test (push) Failing after 1m42s
CI for csaxs_bec / test (pull_request) Failing after 1m38s
Port get_live_fps()/live_mode_poll_interval_s from IDSCamera (added on
main): a rolling deque of the last 10 live-mode push timestamps, exposed
as a measured frame rate rather than assuming the nominal poll interval
is actually achieved -- verified against real hardware, where the
Alvium G1-507m's actual acquisition/readout time brings the achievable
rate to ~3 fps despite the 0.2s (5 Hz) nominal poll interval.

Mirrors the same three unit tests added for IDSCamera's version.
2026-07-28 10:26:40 +02:00
x01dc 13326c0985 Merge remote-tracking branch 'origin/main' into feat/allied_vision
# Conflicts:
#	csaxs_bec/device_configs/bl_detectors.yaml
2026-07-28 10:17:04 +02:00
x01dc 13e9cb21e4 fix(LamNI): update tests for prompts/kwargs added earlier this branch
CI for csaxs_bec / test (pull_request) Successful in 1m45s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m59s
Several tomo_scan()/tomo_alignment_scan()/find_rotation_center() tests
started failing (CI) after recent commits on this branch introduced new
input()-backed confirmation/sample-name prompts, an
alignment_scan_progress GUI proxy, and tomo_queue_execute()'s
interactive=False kwarg -- none of which the existing test fixtures or
assertions were updated for. Mock/bypass the new prompts and GUI calls,
add the missing _alignment_scan_progress_proxy to the bare-object test
fixture, and update two assertions (tomo_queue interactive kwarg,
account-less sample registration) to match the intended new behavior.
2026-07-28 09:53:59 +02:00
1f8510636c feat(LamNI): auto-retry lsamrot moves after a Galil safety-thread trip
A safety thread on the LAMNI Galil controller supervises air pressure and
capacitive-sensor feedback for the rotation stage; a spurious trip drops
allaxref and sets allaxrer/caperr, failing every subsequent lsamrot move
until manually cleared with allaxrer=1/caperr=0/allaxref=1.

LamniGalilMotor.move() now resets those latches and retries once, scoped
to lsamrot only via LamniGalilController.reset_axis_errors() -- other
axes on the shared controller keep today's behavior unchanged, and a
persisting fault still surfaces as a failure after the single retry.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 12:35:35 +02:00
x01dcandClaude Sonnet 5 e299775c44 feat(LamNI): measure camera live-mode FPS and smear-sweep capture FPS
CI for csaxs_bec / test (pull_request) Successful in 3m42s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 2m31s
Neither rate was previously measured anywhere, making it hard to tell a
real acquisition bottleneck from a display/overhead one.

- IDSCamera.get_live_fps() (new USER_ACCESS entry): rolling-buffer
  measurement of the live-mode push rate in _live_mode_loop(), readable as
  dev.cam_xeye.get_live_fps(). SimIDSCamera inherits it unchanged, so it
  works against the simulated deployment too.
- XrayEyeAlign._smear_sweep() now tracks timestamps of the first and most
  recent distinct frame it captures, storing the computed rate as
  self.last_smear_fps and printing it in the sweep-done summary line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-27 10:35:38 +02:00
x01dcandClaude Sonnet 5 83f9a6bb59 fix(LamNI): show cached composite on manual channel switch, align toggle grid, archive smear runs
CI for csaxs_bec / test (pull_request) Successful in 2m42s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 1m45s
Three follow-ups from real-hardware testing of the smear aid:

1. Switching the "Smear preview channel" toggle off then back on showed
   the live camera feed, not the previous composite. Root cause:
   Image.image()'s connect_slot subscription only delivers stream
   entries published *after* connecting (bec_lib's default
   register(from_start=False, newest_only=False) bookmarks the
   then-current last entry and only forwards newer ones) -- so nothing
   re-renders on reconnect until the next sweep pushes a new frame.
   Added a permanent, independent subscription (newest_only=True) that
   keeps the last smear_preview payload cached regardless of which
   channel is currently displayed; set_live_view_signal() now force-
   renders that cache via main_image.set_data() when switching back to
   "smear_preview", instead of waiting for a push that may never come.
   Naturally "resets" once a new sweep starts pushing fresh composites.

2. The new switch row wasn't column-aligned with the existing
   shutter/camera-running row (each used an independent QHBoxLayout
   sized by its own labels' widths). Merged both rows into one
   QGridLayout with right-aligned label columns, so all four toggles
   line up regardless of label text length. Shutter now pairs with
   Smear integrating, Camera running with Smear preview channel.

3. find_rotation_center_smear_experimental() now archives each run to
   a timestamped HDF5 file (~/data/raw/logs/xrayeye_smear_calibration/),
   mirroring align()'s existing _save_alignment_data() pattern: the FZP
   reference, one composite image + one clicked centre per iteration,
   and the final result -- collecting data towards eventually
   automating center detection from the composite offline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:08:01 +02:00
x01dcandClaude Sonnet 5 39e81130af fix(LamNI): reset stale live-view channel, add GUI status/toggle switches
A Ctrl-C interrupting an in-flight RPC call (e.g. mid
set_live_view_signal) could leave the XRayEye widget stuck showing the
smear-preview channel afterward -- and since lamnigui_show_xeyealign()
reuses the existing xeyegui widget across calls rather than recreating
it, that stale state would silently carry into the *next* alignment
run too (including production align()/find_rotation_center(), since
on_live_view_enabled() now respects whichever channel is selected).

Fix: lamnigui_show_xeyealign() unconditionally resets both
set_live_view_signal("image") and set_smear_active(False) every time
it's called -- both are cheap no-ops if already in the default state,
and this is the single shared entry point for every alignment
procedure, so one fix covers all of them.

Also add two GUI-facing controls to XRayEye, in a new row below the
shutter/camera-running toggles:
- "Smear preview channel" -- an interactive toggle mirroring
  set_live_view_signal(), so the operator has a GUI-only recovery path
  independent of the ipython client (e.g. if it's hung/disconnected).
- "Smear integrating" -- a read-only status indicator (disabled
  ToggleSwitch) reflecting whether _smear_sweep() is currently
  accumulating a composite, driven entirely by the automation.

set_live_view_signal() keeps the interactive toggle's visual state in
sync regardless of who changed the channel (automation or operator) --
single source of truth. _smear_sweep() brackets accumulation with
set_smear_active(True)/(False), turning it off as soon as the sweep
ends (independent of the channel switch, which stays on the composite
until the operator's click is collected). Both new XRayEye methods
required regenerating csaxs_bec/bec_widgets/widgets/client.py via
bw-generate-cli --target csaxs_bec.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:08:01 +02:00
x01dcandClaude Sonnet 5 207be425dc feat(sim): randomize rotation-axis offset, drop synthetic crosshair
The simulated speckle pattern rotated exactly around image center --
the same point the FZP crosshair sits at -- so the calibration was
trivially already correct in sim, never exercising the actual
find-and-correct-a-miscalibration path.

Add an opt-in axis_offset_range_px/axis_offset_seed: when set, the
"true" rotation axis the speckle field orbits is offset from image
center by a random amount (fresh each device construction unless a
seed is given, so each sim session gets a new miscalibration to find).
Wired into LamNI's cam_xeye sim config (sim_axis_offset_range_px: 40,
no seed -- random each session); default (0.0) preserves prior
behavior exactly for any other SimIDSCamera user.

Also drop the synthetic crosshair lines baked into
make_speckle_test_pattern's pixel data -- a real camera image has no
such marking; the GUI already draws its own crosshair overlay
(TargetCrosshair) on top of the live image.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:08:01 +02:00
x01dcandClaude Sonnet 5 bcdfc12a88 fix(LamNI): decouple smear composite display from live acquisition channel
The "camera running" toggle (IDSCamera.live_mode_enabled) was flapping
on/off during the smear sweep, and on real hardware that's genuinely
slow (starting/stopping the live acquisition thread has real driver
overhead, unlike the simulator). Root cause: composite pushes shared
the same device_preview channel the live thread continuously publishes
to, so live_mode_enabled had to be toggled off around every push to
keep the composite from being instantly overwritten.

Give the composite its own channel instead:
- IDSCamera gains a smear_preview PreviewSignal (no rotation_90/
  transpose configured -- composite data is already display-oriented)
  and push_smear_preview(), mirroring push_preview_image() but fully
  decoupled from the live channel.
- XRayEye gains set_live_view_signal() to switch which camera signal
  the live Image view displays, defaulting to the normal channel for
  every fresh GUI instance; on_live_view_enabled() now uses whichever
  signal is currently selected instead of a hardcoded constant.
- _smear_sweep() switches the GUI to "smear_preview" for the sweep and
  pushes composite updates there -- live_mode_enabled is never touched
  at all (just ensured on at the start, exactly like _live_sweep()).
  The GUI is deliberately left on the composite after a successful
  sweep until the caller has collected the operator's click, then
  switched back to "image"; on KeyboardInterrupt it's switched back
  immediately.

This removes the resume_live/hold_display_s toggle-avoidance mechanism
added in def9bf4, which is no longer needed with a dedicated channel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:08:01 +02:00
x01dcandClaude Sonnet 5 81f6d0ff1e fix(LamNI): freeze composite display, undo double rotation, speckle sim pattern
Real-HW-adjacent (sim) testing surfaced three issues with the smear
calibration aid:

1. The composite reverted to the plain rotating live view right before
   the click prompt. find_rotation_center_smear_experimental() called
   update_frame() after _smear_sweep(), which grabs a fresh raw live
   frame -- overwriting the composite it just built. Also,
   _push_smear_composite always resumed live mode, so even without that
   bug the composite would only stay visible for hold_display_s.
   Fixed: _push_smear_composite gained resume_live=False for the final
   push; _smear_sweep now takes keep_shutter_open and closes the
   shutter itself at the end, so the composite (not a fresh grab) stays
   frozen on screen for the click. The stray update_frame() call is
   removed.

2. The composite appeared rotated relative to the live view.
   get_last_image() frames already have num_rotation_90/transpose
   applied (PreviewSignal.put() applies it to whatever it stores), so
   composites built from those frames got the transform applied AGAIN
   by push_preview_image()'s own self.image.put() call. Fixed:
   push_preview_image() now undoes the transform first (reverse order,
   since transpose is applied last forward) so the net effect is a
   single transform, matching a live frame.

3. Feature request: the sim camera's rotation-coupled pattern was a
   single eccentric gaussian blob -- a better analog for the isolated/
   point-particle case than the extended/textured case this feature
   actually targets. Added generate_speckle_grains/
   make_speckle_test_pattern (sim_cameras.py): a fixed, seeded field of
   small gaussian "grains" at a range of radii, rotating rigidly
   together as the live angle changes -- producing several concentric
   arcs in the composite instead of one, much closer to a real textured
   sample. New sim_speckle_count/sim_speckle_seed config, defaults
   requiring no yaml changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:08:01 +02:00
x01dcandClaude Sonnet 5 ea9f15cde1 feat(sim): rotating structured pattern for simulated X-ray-eye camera
The simulated cam_xeye (SimIDSCamera/_SimIDSBackend) served a single
static Gaussian blob, cached at construction -- rotating lsamrot in a
simulated session produced no visible motion, so the smear/composite
rotation-center calibration aid had nothing to smear into an arc.

Add an opt-in sim_rotation_coupling config (mirrors the existing
sim_coarse_coupling idiom in sim_lamni.py's measured_positions()): when
set, _SimIDSBackend regenerates a fresh frame on every get_image_data()
call with an eccentric blob (make_rotating_test_pattern) tracking the
coupled axis' live simulated Galil position instead of serving the
cached static frame. Wired into LamNI's cam_xeye sim config only
(host/port/axis matching lsamrot's own sim config); flomni's cam_xeye
and any other SimIDSCamera user are unaffected since the new params
default to None (byte-identical output to before, confirmed by test).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:08:01 +02:00
x01dcandClaude Sonnet 5 6d65a87ae4 fix(LamNI): push smear composite via new IDSCamera RPC method
dev.cam_xeye.image.put(composite) failed on real hardware --
PreviewSignal is a BECMessageSignal subclass, and BEC hardcodes
rpc_access=False for that signal_info, so the client-side device proxy
never exposes `image` as an attribute at all (confirmed: this is a
structural BEC constraint, not specific to this device -- no existing
RPC surface, device or widget, offers a raw-array setter either).

Add IDSCamera.push_preview_image(data), a small device-server-side
method (alongside the existing get_last_image()) that does
self.image.put(data) from within the device-server process, where
PreviewSignal is a normal ophyd attribute. _push_smear_composite now
calls dev.cam_xeye.push_preview_image(composite) instead of writing to
the signal directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:08:01 +02:00
x01dcandClaude Sonnet 5 d0d3602ed1 feat(LamNI): experimental continuous-rotation smear aid for rotation-center calibration
Adds an EXPERIMENTAL, operator-only alternative to the extended-sample
rotation-center click: instead of judging the center from a single
instant of live rotation, accumulate a max-projection composite while
lsamrot rotates continuously through a (tunable, default 360 deg)
sweep. Off-axis features smear into circular arcs; the common center
of curvature is the rotation axis, an easier target to click than one
live frame. No automatic circle fitting -- purely a visual aid feeding
the same _collect_click mechanism the production feature already uses.

Rotation is issued non-blocking via scans.mv() (new local wrapper,
sibling of this file's existing blocking umv()) and frames are grabbed
via get_last_image() in a single-threaded loop polling ScanReport.status
-- the same thing ScanReport.wait() does internally -- so no background
thread or device-server change is needed to grab frames while the motor
is actively moving. The composite is pushed to the GUI through the
camera's existing PreviewSignal (dev.cam_xeye.image), duty-cycling
live_mode_enabled off/on around each push so the background 5 Hz live
thread can't immediately overwrite it.

New entry point: lamni.xrayeye_rotation_center_calibration_smear_experimental().
Purely additive -- does not modify find_rotation_center()/_live_sweep()
or the two production entry points.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 16:08:01 +02:00
b7b5b9282a fix(LamNI): accumulate rotation-center shift across calibration iterations
lamni_move_to_scan_center's shift_x/shift_y are absolute offsets from the
currently-configured lsamx_center/lsamy_center (unchanged until the operator
confirms at the end of the procedure), not incremental deltas from wherever
the stage currently sits. The extended-sample iteration loop was passing
only the latest click's delta each time, so each new iteration partially
undid the previous one's correction instead of adding to it. Fixed by
accumulating the total shift across iterations and always applying the
running total.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 13:02:01 +02:00
7014760175 fix(LamNI): remove redundant rotate, simplify sweep, add center precondition
Three refinements found while testing find_rotation_center() against the
simulated LamNI:

- Drop the trailing tomo_rotate(0) at the end of find_rotation_center():
  both the isolated path (explicit rotate-back before applying the
  correction) and the extended path (verification sweep ends at 0) already
  leave lsamrot at 0 by then, so it was a pure duplicate move.

- Simplify _live_sweep() to rotate directly to each waypoint instead of
  stepping through artificial step_deg-sized sub-moves with settle pauses
  in between -- a single tomo_rotate() already produces continuous physical
  rotation, so the stepping added nothing but jerkier motion and extra
  queue overhead.

- Add _ensure_at_configured_center(), called at the start of
  find_rotation_center() before lfzp_in(). lamni_move_to_scan_center's
  interferometer-drift safety check (LamNIFermatScan.py) assumes lsamx/
  lsamy start near their configured center and silently skips the entire
  corrective move if that drift exceeds 150um -- exactly the situation
  find_rotation_center() is meant to handle, since its purpose is
  recalibrating a potentially-stale center. Confirmed this was a real gap:
  a run where the stage hadn't started at its configured center produced a
  correct absolute target (verified via the interferometer reading) but a
  confusingly small apparent motor delta.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 13:02:01 +02:00
26e7fddfc6 fix(LamNI): ensure OSA is out before requesting the FZP-centre click
Both align() and find_rotation_center() moved the FZP in and immediately
asked the operator to click its centre, but only confirmed OSA was out
afterwards (via loptics_out(), which runs after that click). If OSA was
left in from a previous scan setup, it would still be in the beam path
during the FZP reference click. Add an explicit losa_out() call right
before lfzp_in() in both procedures; it's a cheap, skip-if-already-out
no-op otherwise.

Found while testing find_rotation_center() against the simulated LamNI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 13:02:01 +02:00
bd2e87ebdd feat(LamNI): add automated rotation-center calibration via X-ray eye
lsamx_center/lsamy_center have to be re-measured by hand for nearly every
new sample (the rotation stage is tilted on top of lsamx/lsamy, so the
effective axis position shifts with sample thickness/mounting). Add
XrayEyeAlign.find_rotation_center()/lamni.rotation_center_calibration_start()
to automate this using the existing X-ray eye GUI and lamni_move_to_scan_center
interferometer-feedback move:

- "isolated" sample: click particle centre at 0 and 180 deg, use the
  midpoint (angle-tilt-independent) as the rotation axis position.
- "extended" sample: live 0->180->0 sweep for visual identification, single
  click, then a verification sweep and an accept/iterate prompt.

Both write the result to lsamx/lsamy's "center" userParameter after
confirmation. Heavily verbose logging throughout since this is fundamentally
an interactive, hardware-in-the-loop procedure that can't be fully exercised
by automated tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 13:02:01 +02:00
x01dcandClaude Sonnet 5 f4bd20e4e8 fix: include content changes for vmbpy->Aravis swap
CI for csaxs_bec / test (push) Failing after 2m21s
The prior commit only captured the file renames (a botched `git add`
dropped the actual edits). This commit carries the real diff: Aravis
GObject-introspection backend in base_integration/camera.py, renamed
class/config references, and the rewritten README.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 16:13:07 +02:00
x01dcandClaude Sonnet 5 b644cf786b refactor(devices): swap AlliedVision camera backend from vmbpy to Aravis
Replaces the vendor-SDK-based vmbpy backend with Aravis, an open-source
GenICam/GigE Vision library that implements the network protocol itself,
so no Allied Vision account/license/SDK download is needed - just
PyGObject plus the system-packaged Aravis library. Renames
AlliedVisionVmbCamera -> AlliedVisionAravisCamera (and matching
file/sim/test names) to reflect the new backend; low-level Camera
wrapper keeps the same public interface, only its internals changed.
README rewritten with Aravis install steps and camera-ID discovery via
arv-tool-0.8 / Aravis.get_device_id().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 16:12:39 +02:00
x01dcandClaude Sonnet 5 f3148c897c feat(devices): add native vmbpy integration for Allied Vision Alvium camera
CI for csaxs_bec / test (push) Successful in 1m41s
Adds AlliedVisionVmbCamera, talking to the Alvium G1-507m directly over
GigE Vision via vmbpy (Vimba X SDK) instead of the existing dead EPICS
IOC-based AlliedVisionCamera, which is left in place for now. Includes
the low-level Camera wrapper, simulation backend, device config example,
tests, and a README covering setup/testing/vmbpy install steps.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 08:06:45 +02:00
x01dcandClaude Sonnet 5 10ef93557b fix/stop lamni tests from leaking a fake bec global across the suite
CI for csaxs_bec / test (pull_request) Successful in 3m9s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 3m13s
test_lamni_account_change_reset.py and test_lamni_tomo_angles.py set
builtins.__dict__["bec"] directly with no cleanup, so a leaked fake
account ("e22222") from an earlier test file was still in place when
test_x_ray_eye_align.py later constructed a real LamNI(), tripping
_maybe_reset_params_on_account_change()'s interactive yesno() prompt
and failing under pytest's captured stdin. Use monkeypatch.setitem so
pytest reverts the global after each test, matching the pattern already
used in test_lamni_tomo_alignment_scan.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 16:57:06 +02:00
x01dcandClaude Sonnet 5 636dd0ec88 fix/stop LamNI per-sub-tomogram scilog entries
LamNI's tomo_scan() wrote a separate scilog entry ("Starting subtomo:
N...") every time the sub-tomogram number changed -- 8 extra entries
per tomo_type-1 scan, and one per sub-tomogram traversed for types 2/3
-- leftover behavior carried over from omny.py during the flomni->lamni
port. Flomni itself never does this: it only writes at scan start (PDF
report) and scan end (timing summary). Removed the three
_write_subtomo_to_scilog() call sites and the now-unused method so
LamNI's scilog behavior matches Flomni's.

Also fixed a stale test (test_tomo_queue_reacquire_rejects_when_another_job_is_incomplete)
that encoded the old, buggy tomo_queue_reacquire() invariant (rejecting
on ANY other job being incomplete/running) rather than the fixed one
from the prior commit (only an EARLIER job is a real conflict) --
split it into two tests covering both directions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 15:59:02 +02:00
x01dcandClaude Sonnet 5 d963cd6dfd feat/auto-refresh stale at_each_angle hooks on redefinition
register_at_each_angle_hook() stores a function object by reference, so
editing a hook's def and forgetting to re-register left the old version
running silently -- easy to miss mid-beamtime. Detect it via
func.__globals__ (a redefined top-level def, or a reloaded module,
mutates the same namespace dict in place) and auto-adopt the new
definition with a printed note instead of failing or staying stale.

Centralizes the shared lookup/error logic (previously duplicated in
lamni.py and flomni.py) into TomoQueueMixin._resolve_at_each_angle_hook().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 15:08:11 +02:00
x01dcandClaude Sonnet 5 12b2538333 feat/add lamni.tomo_alignment_scan() -- dedicated fine-alignment scan
Replaces the awkward documented workaround (configure a full tomo_type
1 setup with 96 projections, then launch just sub_tomo_scan(1, 0)) with
a dedicated command, ported from flomni's tomo_alignment_scan(): adjust
tomo_parameters() (FOV/step/counting time), then call
lamni.tomo_alignment_scan() directly -- no tomo_type/sub-tomogram
bookkeeping involved, matching flomni's clean two-step workflow.

Runs 12 points evenly spaced across the full 360 degrees (lamni has no
180-degree symmetry the way flomni does, so unlike flomni's 5-point/
180-degree scan, this covers the full circle -- point count matches
what the old workaround's docs defaulted to, endpoint=False since
360==0 degrees). Aborts if x-ray-eye alignment hasn't been done yet
(tomo_fit_xray_eye unset), mirroring flomni's equivalent guard.
write_alignment_scan_numbers() writes the same 4-line scan-number/
angle/offset log flomni's version does, to
~/data/raw/logs/ptychotomoalign_scannum.txt for SPEC_ptycho_align.m,
also printed at the console (flomni's own console-print equivalent is
dead/commented-out code; lamni's actually prints).

Scope note: flomni's version conditionally skips its eye-out/optics-in
transition when already in measurement condition with feedback
running, to avoid an unneeded interferometer reset -- lamni has no
equivalent helpers for that check, so this calls leye_out()
unconditionally instead. Left as a possible follow-up, not in scope
here.

Item 6 of csaxs_bec/bec_ipython_client/plugins/LamNI/AI_docs/
FLOMNI_LAMNI_FEATURE_GAPS_2026-07.md. Documented in
docs/user/ptychography/lamni.md's "Fine alignment" section.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 13:22:01 +02:00
x01dcandClaude Sonnet 5 db18929742 feat/add OSA collision-clearance warning to lamni lfzp_info()
Ports flomni's ffzp_info() OSA collision-clearance section (compares
live losaz against nominal "in" position and a collision-boundary
constant, warns if closer to collision than nominal or reports
clearance at IN) -- same formula shape and sign convention as flomni
(motion directions confirmed identical between the two setups).

lamni's collision-boundary constant has never been measured, so it's
read from loptz's device config (userParameter.collision_offset) via
a new _get_user_param_optional() helper -- unlike the existing
_get_user_param_safe(), it returns None instead of raising when the
parameter is undefined (as it currently is for every lamni device
config, including the simulated one), since absence here means
"not yet commissioned" rather than a config error. lfzp_info() prints
a clear warning and skips the numeric estimate entirely until someone
adds the real measured value to the config -- deliberately not a
CLI-settable/global-var parameter, since this is deployment-level
hardware calibration.

Item 7 of csaxs_bec/bec_ipython_client/plugins/LamNI/AI_docs/
FLOMNI_LAMNI_FEATURE_GAPS_2026-07.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 12:58:24 +02:00
x01dcandClaude Sonnet 5 7863b8fd37 feat/skip lfzp_in/losa_in/losa_out moves when already in position
Ports flomni's ffzp_in()/fosa_in() "skip the move (and, for the FZP,
the expensive feedback-disable + reset-enable cycle) if already there"
optimization to lamni's lfzp_in()/losa_in(). Also adds the same
skip-if-already-there check to losa_out(), which has no flomni
equivalent to mirror (fosa_out() always moves unconditionally there)
-- added as a lamni-side enhancement per discussion.

Checked every existing caller of lfzp_in()/loptics_in() before making
this change, since a skip changes behavior for callers that used to
get an unconditional reset. Found one real risk:
x_ray_eye_align.py's "Step 0: FZP centre" (the start of a fresh
alignment run) had its manual feedback disable/enable calls already
commented out, meaning it relied entirely on lfzp_in()'s old
unconditional reset to establish a correct interferometer zero.
flomni's equivalent call site doesn't have this problem because
flomni's alignment flow does its own explicit
feedback_enable_with_reset() independently beforehand -- lamni's
doesn't. Fixed by passing force_feedback_reset=True at that one call
site so it keeps its current always-reset behavior; no other lamni
caller needed this.

Item 4 of csaxs_bec/bec_ipython_client/plugins/LamNI/AI_docs/
FLOMNI_LAMNI_FEATURE_GAPS_2026-07.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 12:40:45 +02:00
x01dcandClaude Sonnet 5 b4c262d41b feat/offer tomo-param reset on lamni experiment-account change
Ports flomni's _maybe_reset_params_on_account_change()/
_set_default_tomo_params() to lamni: at LamNI.__init__, if the active
BEC account differs from the one defaults were last applied for
(persisted via defaults_applied_for_account), interactively offer to
reset tomo params to defaults -- preventing a new experiment silently
inheriting the previous one's tuned FOV/stitch/etc. Same account is a
silent no-op, so restarting a client mid-experiment never disturbs
tuned parameters.

_set_default_tomo_params() is a direct port adapted to lamni's own
param names (tomo_circfov/lamni_stitch_x/y/lamni_piezo_range_x/y
instead of fovx/fovy/stitch_x/y, no tomo_angle_range/
single_point_random_shift_max -- lamni has neither concept). Per-
sample alignment state is deliberately not touched, matching flomni.

Item 2 of csaxs_bec/bec_ipython_client/plugins/LamNI/AI_docs/
FLOMNI_LAMNI_FEATURE_GAPS_2026-07.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 11:37:37 +02:00
x01dcandClaude Sonnet 5 e59a42f978 feat/port timing-statistics log + scilog_last_ptycho_scans() to lamni
Ports flomni's per-projection/tomogram JSONL timing log
(~/data/raw/logs/timing_statistics/*.jsonl, feeding a future scan-time
prediction model) and the scilog_last_ptycho_scans(n) user command to
lamni. Direct port adapted only for lamni's own param names (no
fovx/fovy/stitch_x/stitch_y/single_point_instead_of_fermat_scan --
uses tomo_circfov/lamni_stitch_x/y/lamni_piezo_range_x/y instead, no
single-point concept at all).

_log_projection_timing() is only called on a clean (non-error_caught)
completion of _tomo_scan_at_angle()'s retry loop, so an
AlarmBase/TimeoutError attempt (no reliable duration measurement)
doesn't pollute the log.

Item 1 of csaxs_bec/bec_ipython_client/plugins/LamNI/AI_docs/
FLOMNI_LAMNI_FEATURE_GAPS_2026-07.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 10:50:08 +02:00
x01dcandClaude Sonnet 5 c3877ecdef feat/add zero-deg radiation-damage reference shot for lamni tomo_type 1
Ports flomni's zero_deg_reference_at_each_subtomo to lamni's "8 equally
spaced sub-tomograms" mode: an extra dedicated projection at exactly 0
degrees before every sub-tomogram where the rotation naturally passes
back through 0, plus one final shot once the tomogram completes, for
tracking radiation damage over a long acquisition. lamni's tomo_type
2/3 already had the equivalent (golden_projections_at_0_deg_for_damage_estimation,
byte-for-byte identical to flomni's) -- only tomo_type 1 was missing.

_subtomo_starts_near_zero() uses subtomo_number % 2 (every odd
sub-tomogram) rather than flomni's 360-mode subtomo_number % 4 == 1 --
confirmed against lamni's actual rotation behavior rather than derived
from the position-array math, which doesn't cleanly split odd/even.

The GUI (tomo_params.py) needed no new UI code: _build_type1_section()
already gated this control on a per-setup has_zero_deg_reference flag,
so enabling it for lamni was just flipping that flag plus adding the
param name/default, mirroring flomni's entries exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 04:18:23 +02:00
x01dcandClaude Sonnet 5 8a7a350280 feat/warn early about Fermat scans below the minimum point count
FlomniFermatScan/LamNIFermatScan already refuse to run with fewer than
20 positions (_check_min_positions(), raises ScanAbortion), but only
once the scan actually starts -- too late for an unattended tomo-queue
run, where the job just aborts mid-run and pauses the queue. Surface
the same prediction earlier instead, as a non-blocking warning:

- Extracted the position-generating methods on both scan classes
  (get_flomni_fermat_spiral_pos, and lamni's chain --
  _lamni_compute_scan_center, _lamni_compute_stitch_center,
  _compute_total_shift, _lamni_check_pos_in_fov_range_and_circ_fov,
  get_lamni_fermat_spiral_pos) into pure @staticmethods, so the exact
  same algorithm the scan server runs can also be called from
  client-side code without a live scan session. Behavior-preserving --
  verified against the existing exact-position/instruction assertion
  tests for both classes. Lifted the hardcoded "20" into a _MIN_POSITIONS
  class attribute on each, so client code references the same threshold.
- lamni.py/flomni.py: new _expected_fermat_position_count() calls the
  real scan-class algorithm with the live tomo parameters and prints a
  warning line in tomo_parameters() when below the minimum.
- tomo_params.py: a new live-updating "Estimated Fermat scan points"
  field (mirroring the existing achievable-step preview's styling),
  wired to the fov/step/stitch/piezo-range fields, flagged orange below
  the minimum -- never blocks Submit/Add-to-queue.

lamni's circular-FOV crop is angle/stitch-dependent, so the estimate is
representative (center tile, angle 0 for the CLI; the live stitch tile
for the GUI, which has those fields right there) rather than an exact
per-projection guarantee -- documented as such in both places.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 03:52:44 +02:00
x01dcandClaude Sonnet 5 abc2a3d041 feat/tomo queue reacquire from an earlier projection
Add tomo_queue_reacquire(job_index, projection_number) to the shared
TomoQueueMixin: reopens any queue job (even an already-"done" one) at
an earlier projection than its own recorded progress, and resets every
job queued after it -- tomo and command jobs alike -- to "pending", so
a following tomo_queue_resume() (a new alias for tomo_queue_execute(),
added for naming symmetry with tomo_scan_resume()) re-runs everything
from there forward in order. Reuses the existing tomo_scan_resume()
machinery by writing the requested resume point into the shared
progress global var, rather than adding new parameters to
tomo_scan()/tomo_queue_execute(). Guards against clobbering another
job's progress if one is already "incomplete"/"running" elsewhere in
the queue.

A flat projection number is used as the resume-point unit for all
three tomo_types, including type 1 (8 equally-spaced sub-tomograms),
via a new per-setup _resolve_type1_projection() that maps it to
(subtomo_start, start_angle). flomni's version reuses its existing
_subtomo_angle_plan(); lamni's inline angle math in sub_tomo_scan() was
extracted into an analogous _subtomo_angle_plan() static method first
(behavior-preserving -- verified against the existing angle-math test
suite) so both setups share the same pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 22:00:22 +02:00
x01dcandClaude Sonnet 5 6c3d60306d feat/lamni tomogram timing overview + repeat-on-interruption
Port two flomni tomo_scan() behaviors lamni was missing: an end-of-scan
timing overview (total time, time excluding gaps, time lost to gaps,
sample/hook info) printed and written to scilog, and automatic repeat
of an entire projection when the beamline interlock trips mid-scan.

The latter needed two pieces together: enabling
bec.builtin_actors.scan_interlock at tomo_scan() start (previously
never enabled for lamni), and wrapping _tomo_scan_at_angle in
@scan_repeat so the resulting ScanRestart (or any other transient
exception) redoes the whole projection instead of aborting the scan.
A new LamNIError marks the one non-retryable case (unregistered
at_each_angle_hook), replacing a plain ValueError there.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 21:13:21 +02:00
x01dcandClaude Sonnet 5 46cadc8894 fix(lamni): show progress GUI on scan start, clear busy heartbeat on exit
Mirrors two fixes already made to Flomni.tomo_scan(): LamNI.tomo_scan()
never opened the progress GUI automatically, and left a stale "busy"
heartbeat for up to 120s after a scan had already finished (or crashed),
since it was only ever reset at the start of the next scan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 14:21:45 +02:00
x01dcandClaude Sonnet 5 f3c90c5b91 fix(lamni): fix three real tomo_scan() crashes, mirroring flomni's fixes
bec.active_account.decode() crashed on every real tomo_scan() call --
active_account is a plain str, not bytes. Flomni's equivalent never had
the .decode() call and already guards an empty active_account (e.g. a
dev/sim session) by skipping sample-database registration instead of
crashing. Same bug fixed in DataDrivenLamNI.tomo_scan() (extra_tomo.py),
which had an identical inline copy.

write_pdf_report() read a nonexistent "mokev" device; the real device is
ccm_energy. Reads it for real now, wrapped in a broad try/except falling
back to "N/A" if unavailable, rather than a hardcoded placeholder.

write_pdf_report()'s logbook/scilog write crashed when scilog isn't
configured -- lamni.py already had this exact tolerance pattern one
method over, in write_to_scilog(); applied the same try/except here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 13:59:12 +02:00
x01dcandClaude Sonnet 5 bac3fa8754 feat(lamni,bec_widgets): generalize tomo-params GUI to lamni, fix two sub_tomo_scan angle bugs
Generalize TomoParamsWidget to lamni via a SETUP_PROFILES mechanism
(setup detection, per-setup field lists/order, a new Offsets section,
a setup-agnostic "Duplicate job" queue button), fixing two latent
TomoQueueDialog bugs that silently mishandled lamni jobs along the way.

While verifying the GUI's projection-count preview against the CLI,
found two real, pre-existing bugs in LamNI.sub_tomo_scan() unrelated
to the GUI itself: a duplicate closing angle every sub-tomogram
(360=0 degrees), and a phase offset computed from the raw stepsize
instead of the achievable one, breaking the equally-spaced-when-
combined guarantee for sub-tomogram pairs/quads/the full set. Both
fixed to mirror Flomni's existing, correct equivalents.

Also fills in lamni's user documentation with the queue/command-job/
at-each-angle-hook system, which it previously lacked entirely,
mirroring flomni.md's coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 13:39:08 +02:00
x01dcandClaude Sonnet 5 95d46c9d49 feat(flomni,lamni): port tomo-queue backend to lamni via shared mixin
CI for csaxs_bec / test (push) Successful in 1m29s
Extract the tomo-queue/at-each-angle-hook backend (job proxy, hook
registry, command-job dispatch) out of flomni.py into a new shared
TomoQueueMixin (OMNY_shared/tomo_queue_mixin.py) so it can be reused
instead of duplicated, avoiding the same param-name-mirror drift
already flagged for tomo_params.py. Flomni's refactor is behavior-
preserving; LamNI is the new consumer, with its own param names
(tomo_circfov, lamni_stitch_x/y, ...) and no 180-degree/single-point
concepts, since lamni doesn't have them. Also adds LamNI.tomo_scan_resume(),
required for tomo_queue_execute()'s resume-after-crash path but not
previously present.

Verified via a new unit test suite (test_lamni_tomo_queue.py) plus live
checks against the running simulated flomni and lamni deployments,
documented in LamNI/AI_docs/TOMO_QUEUE_PORT.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 14:17:51 +02:00
x01dcandClaude Sonnet 5 1eb324aad3 fix(flomni): reuse the same phase table for 360deg tomo instead of two
Supersedes the complementary even/odd-eighths tables from 900c810 with
a single shared phase_eighths table used in both 180 and 360 mode, so
subtomo N carries the same phase-tier role regardless of
tomo_angle_range. The complementary property of the low/high phase
sets now falls out as a consequence of the original table's structure
instead of needing a separately derived table.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 11:18:37 +02:00
x01dcandClaude Sonnet 5 aebfccc5f3 fix(flomni): clear busy heartbeat on scan end, fix duplicated tomo math
Two independent fixes:

- GUI "beamline busy" indicators (TomoParamsWidget's banner,
  FlomniWebpageGenerator's status page) purely wait for
  progress["heartbeat"] to go stale (120s / 90s respectively) - there was
  no explicit "scan finished" signal to react to instead, since
  tomo_scan() wrote a heartbeat at the start of every projection but
  never cleared it on exit. Wrapped the scan loop in try/finally so
  heartbeat is cleared on every exit path (normal completion, exception,
  or interrupt/abort), not just at the next scan's start - both
  busy-detectors now see this on their very next poll instead of waiting
  out the timeout. Live-verified: heartbeat is None immediately after
  tomo_scan() returns.

- Found while investigating a related report ("angular step of the final
  combined tomogram shown different between 180 and 360 mode, although
  it has to be identical" + "GUI shows projection count doubling when
  switching 180->360"): TomoParamsWidget's _compute_type1()/
  _requested_to_stepsize() (tomo_params.py) and the generated status
  webpage's calcProjections() JS (flomni_webpage_generator.py) are both
  independent, un-synced duplicates of the exact old N=int(angle_range/
  stepsize) formula fixed in flomni.py's own _tomo_type1_actual_grid()
  two commits ago - they were never updated when that fix landed, so the
  GUI and webpage kept reporting double the projection count for 360
  mode. Fixed both to match flomni.py: N/step/total are always computed
  against a fixed 180 degrees, independent of angle_range. Also fixed
  the "angular step of the final (combined) tomogram" CLI/wizard lines
  in flomni.py itself, which used tomo_angle_range/total_projections
  (correct for 180 mode by coincidence, wrong for 360 mode - the true
  combined resolution is always 180/total_projections, identical
  between modes). Added a regression test asserting the widget's
  formulas match flomni.py's for both modes, to catch this exact kind of
  drift if it recurs. Live-verified against the running sim: both modes
  now report identical total_projections and combined-tomogram step for
  the same stepsize.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 10:18:38 +02:00
x01dcandClaude Sonnet 5 900c810111 fix(flomni): use complementary phases for 360deg tomo, not adjacent pairs
The previous commit's 360-degree phase assignment gave the low and high
halves the SAME phase per pair (1,2)/(3,4)/(5,6)/(7,8), so each pair
concatenated into one continuous evenly-spaced range -- but that means
every low-half angle is exactly 180deg from a high-half angle, which is
exactly the redundant-measurement bug this was supposed to fix, just
reintroduced between subtomos instead of within one.

Corrected: the low half (subtomos 1,4,5,8) now uses the even eighths of
the original 8-way phase_eighths table, and the high half (2,3,6,7) uses
the odd eighths -- complementary, not shared. Folded mod 180, the two
halves interleave into exactly the same 8-way, step/8 grid that 180-mode
itself produces: every position is measured exactly once, using its full
0-360 physical range, with zero redundant measurements anywhere. Total
projection count is unchanged (still identical to 180 mode for a given
tomo_angle_stepsize).

Verified: pure-math unit tests confirm the 360-mode combined set, folded
mod 180, is an exact set match (same count, no repeated residue) against
180-mode's own set. Live-verified against the running flomni sim: real
motor motion for both modes, folding the observed 360-mode angles mod
180 exactly reproduces the observed 180-mode angles.

Also updated the two "angular step of the final combined tomogram"
CLI/parameter-wizard print statements to additionally report the
effective mod-180 reconstruction resolution for 360 mode, since it's
now finer than the raw per-half spacing they already printed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 07:28:24 +02:00
x01dcandClaude Sonnet 5 7e8e807adc fix(flomni): stop duplicate 360deg tomo angles, add sample owner, fix abort crash
Three independent fixes:

- sub_tomo_scan(): in 360-degree tomo_type-1 mode, every sub-tomogram used
  to sweep the full 0-360 range, so each one's own fine grid contained
  angle pairs exactly 180deg apart -- redundant tomographic information.
  Sub-tomograms now each cover a 180deg span, split into low/high halves
  by subtomo_number % 4 with a bit-reversal-of-4 phase table, so adjacent
  pairs (1,2)/(3,4)/(5,6)/(7,8), either quartet, and all 8 combined each
  independently form a complete, evenly-spaced 360deg tomogram at
  successively finer spacing. Total projection count for a given
  tomo_angle_stepsize is now identical between 180 and 360 mode (same N,
  no longer doubled). Updated the 4 other consumers of the old N/step
  formula (zero-deg reference gating, _tomo_type1_actual_grid, the
  parameter wizard, the PDF report) to match. 180-degree mode is
  unchanged. Live-verified against the running flomni sim: real motor
  motion traces the expected boustrophedon path with no duplicate or
  180deg-apart angles.

- Sample storage: added an owner field, packed into the same EPICS DESC
  field as the sample name ("name | owner", via new sample_desc_codec.py)
  since there's no separate PV for it. Wired through
  FlomniSampleStorage, the CLI (flomni_modify_storage_non_interactive,
  ftransfer_modify_storage), the two transfer routines that forward a
  raw DESC value across a gripper move (now unpacked/repacked so owner
  survives the move instead of being dropped or double-packed), and
  SampleStorageWidget. Scoped to flomni only this session; OMNY's
  storage/transfer mixin is unchanged.

- ConsoleButtonsWidget's ABORT button used to send SIGINT then, 500ms
  later, a stop_devices() broadcast to ALL devices with no stop_id --
  an un-suppressed error from that broadcast landing on a queue-tracked
  instruction could kill the scan worker thread outright, requiring a
  full BEC restart. Replaced with: queue.request_scan_abortion() (safe
  no-op if idle, but registers a stop_id so expected errors are
  suppressed), then SIGINT, then a direct, immediate Galil hard stop via
  new GalilController.hard_abort_and_restore_positioning_mode() -- the
  same method ftransfer_abort() now delegates to, so the CLI and GUI
  paths can't drift apart again. SIGINT is sent before the hard stop:
  live testing showed that if the hard stop's mntprgs-clearing side
  effect lands first, a same-session polling loop can mistake it for
  normal completion and fall through into ensure_gripper_up(), which
  must not happen mid-transfer -- sending SIGINT first (near-instant)
  gives that loop's own KeyboardInterrupt handler a head start before
  the hard stop's own multi-step sequence completes. The button is
  labeled per beamline (e.g. "Flomni Motion Stop") and disabled rather
  than silently inert when no hard-stop device is configured/enabled.
  Scoped to flomni only this session (OMNY/LamNI wiring deferred).
  Live-verified against the running sim, including the exact
  stop-lands-mid-queued-instruction scenario that previously crashed
  the scan worker.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-15 07:09:40 +02:00
Mirko Holler 30b5119a2f feat(flomni): add command jobs to the tomo queue
Type-tags queue jobs with a "kind" field (back-compat: missing kind
defaults to "tomo"), and adds "command" jobs that reconfigure the
beamline (currently: absolute moves on an allow-listed device, plus an
optimize_idgap stub) between tomograms instead of running a scan.

Command jobs are added via tomo_queue_add_command() against a curated
named-action registry (_TOMO_QUEUE_ACTIONS) - not arbitrary code - so
every queued action is auditable and safe to persist across a kernel
restart. Validation is two-layer: schema checks at add time, and each
action re-checks its own arguments against live hardware at execution
time, which is the actual safety boundary. Crash-resume for a command
job re-runs the whole step sequence from the top if every step is
idempotent, or prompts the operator otherwise.

flomni_webpage_generator.py is guarded against command jobs, which
have no "params" key.

See AI_docs/TOMO_QUEUE_COMMAND_JOBS_PLAN.md for the full design.
2026-07-13 09:05:29 +02:00
Mirko HollerandClaude Sonnet 5 ff50711dc9 test(flomni): add tomo-queue e2e harness against the simulated flOMNI
Sections A-C of TOMO_QUEUE_TESTING.md's checklist, run against a live sim
session: params-restored-per-job, legacy queue migration, empty/all-done
no-ops, start_index semantics, exception/SIGKILL crash-resume, resume-
before-fresh ordering, and concurrent queue edits from a second client.
All 10 tests pass together with no cross-test interference.

The flomni_sim fixture and _bootstrap.py factor out what it takes to
construct a live Flomni against the sim (builtins/reload bootstrap,
side-effect neutralization, RT-feedback/fsamx setup) for reuse by both
in-process tests and the SIGKILL-based subprocess tests.

Also adds the AI_docs/ handoff docs (testing checklist results, command-
jobs plan with the action-registry decisions) that this work updated.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-12 23:25:33 +02:00
x01dc 3dbd9313ed test(lamni): stub xeyegui instead of the read-only gui property
CI for csaxs_bec / test (pull_request) Successful in 1m31s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m32s
XrayEyeAlign.gui is a property delegating to lamni.xeyegui and has no
setter. Mock lamni.xeyegui in the update_frame tests; drop the redundant
alignment_images reset, which __init__ already handles.
2026-07-12 18:38:44 +02:00
x01dc 69b89fcd85 test(lamni): update x-ray eye align tests to post-EPICS interface
CI for csaxs_bec / test (push) Failing after 1m26s
CI for csaxs_bec / test (pull_request) Failing after 1m24s
Drop test_save_frame (method no longer exists). Rewrite test_update_frame
against dev.fsh/dev.cam_xeye instead of the removed LamNI.alignment module.
Patch the module-level umv in test_tomo_rotate rather than builtins.
2026-07-12 18:33:13 +02:00
appel_candx01dc dbad0d00b8 wip for tests 2026-07-12 08:07:50 +02:00
x01dc 680a7c5813 fix test 2026-07-12 08:07:50 +02:00
x01dcandholler 6533226924 sim next version, including lamni and omny 2026-07-12 07:52:37 +02:00
x01dcandholler bcedf794a5 fixes/version2 sim devices
s Please enter the commit message for your changes. Lines starting
2026-07-12 07:52:37 +02:00
appel_c 46a05b48e0 fix(cont-grid): add extra point in lines to include endpoint 2026-07-10 16:53:14 +02:00