Commit Graph
77 Commits
Author SHA1 Message Date
x12saandClaude Sonnet 5.5 5112672368 feat(tomo): warn and confirm on manual tomo_scan_resume() when queue has an unfinished job
CI for csaxs_bec / test (push) Successful in 3m8s
A Ctrl-C abort leaves the queue job 'running'; resuming by hand bypasses the
queue and a later tomo_queue_resume() would resume it again. Manual
tomo_scan_resume() now lists the unfinished job, hints at tomo_queue_resume(),
and asks y/N. tomo_queue_execute() passes from_queue=True to skip the prompt.
OMNY port tracked in a TODO doc.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 22:41:22 +02:00
x01dcandClaude Sonnet 5.5 517b0f7e3a fix(tomo): type-4 progress GUI + tomo_params window size
CI for csaxs_bec / test (push) Successful in 3m5s
- flomni GUI progress: guard division by total_projections == 0 (type 4
  file without END, endless golden ratio); the error was swallowed and the
  progress window stayed on the reset defaults for the whole scan.
- Fill tomo type/total/subtomo for type 4 right after scan start, so the
  GUI is meaningful during empty frames and the 0-deg reference shot.
- tomo_parameters() window opens at 730x1170.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y5CiUfRUwnPyuAkq5Wyptm
2026-10-02 18:32:20 +02:00
x01dcandClaude Sonnet 5.5 28929b174c feat(lamni): tomo_type 4 (angles from file) via shared TomoAngleFileMixin
CI for csaxs_bec / test (push) Successful in 3m4s
Extract flomni's type-4 logic (tomo_angle_file property, parsing helpers,
per-line step/exptime/frames overrides, wait-for-growing-file loop) into
OMNY_shared/tomo_angle_file_mixin.py and use it from both Flomni and LamNI.

LamNI: type 4 in setter/params/queue/CLI/PDF/resume, overrides threaded
through tomo_scan_projection, progress reset before gates, no 180-deg
opposite-angle warning (laminography). Widget profile, webpage row, docs
and tests added.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y5CiUfRUwnPyuAkq5Wyptm
2026-10-02 17:01:07 +02:00
x01dc f08c3ef7b7 Merge remote-tracking branch 'origin/fixes/flomni4' into feat/flomni_tomo_from_angles_file
CI for csaxs_bec / test (push) Successful in 3m7s
# Conflicts:
#	csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py
#	csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py
2026-10-01 16:19:45 +02:00
x01dcandClaude Sonnet 5 2a91f94c06 feat(flomni): add manual_shift_x fine-adjustment parameter
CI for csaxs_bec / test (push) Successful in 2m59s
Mirrors the existing manual_shift_y: a persistent, GUI/CLI-settable x
offset applied to the scan center in tomo_scan_projection() and
tomo_acquire_at_angle(), included in tomo-queue snapshots, and reset to
0 at the start of every x-ray-eye alignment run (and on account-change
defaults) so a leftover fine adjustment never biases the next sample's
alignment tomogram. Bounded to +/-10um in the GUI and CLI, narrower
than manual_shift_y's +/-50um range.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPZ9gzN2rt3wgiMnTKyrug
2026-10-01 12:58:34 +02:00
x12saandClaude Sonnet 5.5 b3830dc3e5 feat(flomni): option to run all type-1 sub-tomos forward (0->180)
CI for csaxs_bec / test (push) Successful in 3m5s
Add tomo_type1_always_forward (180 deg range only, default off). Every
sub-tomo sweeps 0->180 with an unchanged angle set, so the 0 deg
reference (zero_deg_reference_at_each_subtomo) is taken before all 8
sub-tomos instead of every second one.

Exposed in tomo_parameters(), queue params and the tomo params widget;
resume/reacquire use the same plan. Tests and docs updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-01 12:33:49 +02:00
x01dcandClaude Sonnet 5 d119bd2ce8 fix(flomni): widen tomo_type-4 angle range to stage overtravel; fix GUI clipping
CI for csaxs_bec / test (push) Successful in 3m4s
Angle files now accept -2 to 362 degrees (the fsamroy rotation stage's
physical overtravel range) instead of the nominal 0-360 sweep. This
required widening both the parser's bounds (angle_file.py) and
_tomo_scan_at_angle()'s own gate (a new _min_scan_angle(), alongside the
existing _max_scan_angle()) -- without the latter, an angle just outside
[0, 360] would have parsed fine but then been silently skipped during
acquisition with no error, which is worse than rejecting it upfront.

Also fixes the type-4 GUI section's "Content:" label clipping its second
wrapped line: QFormLayout sizes that row from the label's sizeHint() at
construction (holding the short "---" placeholder) and doesn't reliably
recompute it when setText() later swaps in longer, word-wrapped content.
_set_angle_file_summary() now recomputes the label's minimum height from
heightForWidth() after every update.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPZ9gzN2rt3wgiMnTKyrug
2026-10-01 11:49:49 +02:00
x01dcandClaude Sonnet 5 0332f1c681 fix(flomni): reset tomo progress before pre-flight gates, not after
CI for csaxs_bec / test (push) Successful in 3m18s
tomo_scan() only reset the progress dict (projection/subtomo/angle/
tomo_start_time/heartbeat/...) inside the "new scan" block, which runs
after the eye/optics-in and filter-out-of-beam gates. If either gate
declined (e.g. the setup isn't in measurement condition, which disabling
rt feedback can trigger) or the tomo_type-4 angle-file check raised,
tomo_scan() returned/raised having never touched progress, leaving the
GUI/webpage showing projection/angle/start-time from whatever scan last
completed -- arbitrarily old, and easy to mistake for a scan still
running.

Hoists the "is this a new scan" check to the top of tomo_scan() and
calls progress.reset() there, before any gate runs; the heavier new-scan
actions (sample DB registration, PDF report, collect_empty_frames(), the
0-deg reference shot) stay exactly where they were, after the gates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPZ9gzN2rt3wgiMnTKyrug
2026-10-01 11:22:45 +02:00
x01dcandClaude Sonnet 5 67884c0c3f feat(flomni): tomo_type 4 -- live-appendable angle files, per-line overrides
CI for csaxs_bec / test (push) Successful in 2m59s
Angle files can now be grown while a scan is running: tomo_scan() polls
and re-reads the file whenever it catches up to the current end,
picking up newly appended lines automatically instead of requiring
tomo_scan_resume(). A file is "complete" only once it ends with an
explicit END sentinel line -- without one the scan waits indefinitely
at EOF rather than stopping, so a static/legacy angle file needs END
appended or a scan against it will hang at the end.

Each line may also carry optional step=/exptime=/frames= key=value
tokens overriding the Fermat step size, exposure time, and burst frame
count for that projection and (sticky) every one after it, falling
back to the live tomo_parameters() configuration whenever not set.
Threaded through as kwargs that default to None and are omitted when
empty, so every existing tomo_type 1/2/3 call site and test stub is
unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPZ9gzN2rt3wgiMnTKyrug
2026-10-01 11:01:16 +02:00
x01dcandClaude Opus 5.5 9373fe307a fix(flomni): no 0-deg damage-estimation shots for tomo_type 4
CI for csaxs_bec / test (push) Successful in 3m4s
Sub-tomogram numbers in a user angle file are just labels, so inserting
extra 0-deg projections at odd sub-tomogram transitions is surprising.
tomo_type 4 now ignores golden_projections_at_0_deg_for_damage_estimation;
the CLI no longer prompts for it and the widget hides it for type 4.
The unconditional 0-deg reference shot at the start of a new tomogram
is unchanged; further 0-deg shots can be put in the file directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:57:11 +02:00
x01dcandClaude Opus 5.5 35ac94cfd5 feat(flomni): tomo_type 4 -- angles read from a file
CI for csaxs_bec / test (push) Successful in 3m7s
New angular sampling mode: tomo_angle_file points to a plain-text file
with one angle (0-360 deg) per line, optional 2nd column = sub-tomogram
number, '#' comments. Only the path is stored; the file is re-read at
every tomo_scan() start, resume and queued-job run, so an edited file
always takes effect on the next (re)start. The parsed angle list is
logged to scilog at scan start.

- OMNY_shared/angle_file.py: shared pure parser + summary, incl.
  warning for 180-deg-opposite/duplicate angles
- flomni: tomo_type 4 branch in tomo_scan()/tomo_scan_resume(), bad
  file fails before any acquisition, angles >180 not gated by
  tomo_angle_range, CLI prompt/display, PDF report
- tomo queue: path snapshotted per job; setter does not validate so
  stale paths never break non-type-4 jobs; reacquire bounds by file
- TomoParams widget: type 4 (flomni only) with Browse... file dialog,
  live content preview, validation blocking Submit/Add to queue
- webpage: show angle file in param tables

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:46:56 +02:00
x12saandClaude Sonnet 5.5 7cb553414a fix(tests): give leye_out shutter test a csaxs and client stub
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 2m56s
leye_out() now reads self.csaxs and self.client (slit/filter handling), which
the bare LamNIOpticsMixin subclass in the test did not define.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 13:07:46 +02:00
hollerandClaude Sonnet 5 b80c6417d2 fix(lamni): go-live audit tier 2 -- wrong numbers and missing flomni ports
- tomo_scan_projection() now adds the projection to the reconstruction queue
  itself (like flomni), so custom at_each_angle hooks and direct calls are
  reconstructed; _at_each_angle()/MagLamNI no longer do it (no double entry).
  tomo_alignment_scan() opts out (_reconstruct=False) to keep its previous
  behaviour -- flomni's alignment projections are queued, LamNI's never were.
- write_pdf_report(): branch on tomo_type (a golden-ratio scan reported a stale
  type-1 count and a hardcoded '8 sub-tomograms'), e-account from
  bec.active_account, tolerate a missing detector_distance.
- tomo_parameters_cli(): range guards (stitch 0-50, frames 1-100, >= 8
  projections, type-3 >= 1, golden max 0-1e6, bunch size 20-100).
- Webpage: projection count for tomo type 1 used flomni's 180-degree
  sub-tomograms (half the real count); TOMO_TYPES gets a base_angle. Also show
  the piezo range, manual shift and 0-degree reference rows.
- Tomo-params GUI: manual shift is mm end to end (label/range said um, +-1000);
  golden bunch size floor 20 for LamNI (setter/CLI already required it, so a
  smaller GUI value broke tomo_queue_execute). flomni fields untouched.
- lfzp_info() and the queue 'move Energy' entry used the legacy 'mokev' device
  (only in device_configs/legacy); now ccm_energy.
- Special-angle UnboundLocalError fixed in flomni too (list consumed only after
  a successful projection, so retries still see the special angle).
- Tomo type 3: exact integer sub-tomogram numbering (float truncation mislabelled
  the boundary projection for ~35 values of n < 400).
- XrayEyeAlign.align(): always reset the GUI (DAP forwarding, crosshair) on any
  failure; xrayeye_update_frame() warns when beamline states are invalid.
- Stale text / debug print; gap doc updated with the current status.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 13:07:46 +02:00
hollerandClaude Sonnet 5 657e192b8c fix(lamni): go-live audit tier 1 -- lost projections, shutter, endless-scan GUI, scilog
Found by auditing LamNI against the battle-tested flomni:

- _tomo_scan_at_angle swallowed TimeoutError alarms: the projection was never
  retried, its failed scan numbers were logged as a success and it never
  reached reconstruction. Now the queue is reset and the alarm re-raised so
  @scan_repeat re-acquires it. The special-angle list is consumed only after
  success (a retry must still see it) and num_repeats is always defined.
- tomo_alignment_scan had the same swallow; it now retries (bounded) and
  rewrites ptychotomoalign_scannum.txt with the scan numbers actually acquired.
- leye_out() opened the fast shutter instead of closing it (regression from
  4967474 'removed fsh commands').
- Progress GUI: an endless golden-ratio scan (golden_max_number_of_projections
  == 0) divided by zero on the first projection, and any GUI/RPC failure
  propagated into the scan (retried 10x by @scan_repeat, then aborted). Now
  guarded and wrapped like flomni's dea43ed.
- raise_window() disabled as in flomni (it can hide the GUI window).
- write_to_scilog and the PDF report entry used the removed bec.logbook API;
  they now use bec.messaging.scilog like flomni, never raise, and include the
  at-each-angle hook source in the end-of-scan/timing entries.
- extra_tomo: num_repeats unbound for non-special angles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 13:07:46 +02:00
hollerandClaude Sonnet 5 27cdaa197c feat(lamni,flomni): tomo_parameters() opens the GUI window, CLI is tomo_parameters_cli()
- shared TomoQueueMixin.tomo_parameters() opens OMNY_TomoParams in its own
  window (lamni_tomo_params / flomni_tomo_params); repeat calls do not open
  a second one; a missing or failing GUI raises the setup's error and points
  to tomo_parameters_cli() instead of silently falling back
- rename the prompt-based versions to tomo_parameters_cli()
- docs: describe both, and use the CLI in the queue examples that rely on
  the command blocking until the values are entered
- add tests with a fake GUI

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016gUjUT2xCNpnBQkThn3CqP
2026-09-29 13:07:46 +02:00
hollerandClaude Sonnet 5 7753dfff38 fix(lamni): port flomni's tomo_reconstruct scan-list fallback and folder resync
- fall back to the last completed scan number when _current_scan_list is
  missing or stale (direct calls outside tomo_scan_projection)
- re-read ptycho_reconstruct_foldername on every write so changes from
  another client session are picked up
- add tests

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016gUjUT2xCNpnBQkThn3CqP
2026-09-29 13:07:46 +02:00
hollerandClaude Sonnet 5 15c720e89e fix(lamni): fully reset tomo progress when a new tomo scan starts
tomo_scan() only reset the timing/ETA fields, so after a finished scan the
progress GUI kept showing the old subtomo, angle and projection == total
(rings at 100 %) while the initial 0-degree reference shot ran, with an ETA
computed from the old projection count against the new start time.

New _reset_progress_for_new_scan() resets every field (incl. the right
totals for the tomo type) in one global-var update and refreshes the GUI.
Resumes are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 13:07:46 +02:00
hollerandClaude Sonnet 5 fd4e8a2de1 fix(lamni): tomo_scan_resume resumes at the interrupted angle, not a shifted sweep
_subtomo_angle_plan() used an explicit start_angle as the origin of a new
360-degree sweep. Resuming subtomo 2 at 85 deg generated 435..85; the >360
artifacts were silently skipped by _tomo_scan_at_angle's range gate, so the
resume began at 355 deg, re-measured done angles, never reached the rest and
reported wrong progress indices.

start_angle is now the literal resume point on the sub-tomogram's own grid:
the plan returns the remaining tail of the normal sweep plus a subtomo_offset
used for progress numbering (mirrors Flomni). Off-grid angles raise ValueError.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 13:07:46 +02:00
x12saandClaude Sonnet 5 bc094dd172 fix(tests): resolve flomni/lamni CLI source paths via module __file__
CI for csaxs_bec / test (pull_request) Successful in 2m57s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 1s
CI for csaxs_bec / test (push) Successful in 2m56s
test_flomni_cli_fovy_bounds_within_scan_bounds and
test_lamni_cli_piezo_range_bounds_within_scan_bounds opened the CLI
source files using a path hardcoded relative to the repo root, which
only works if pytest's cwd happens to equal the repo root. In CI the
job's cwd is one level above the checkout, so the same relative path
raised FileNotFoundError. Resolve the path from the already-imported
module's __file__ instead, which is independent of cwd.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:19:45 +02:00
x01dcandClaude Sonnet 5 0ad6c5014d feat(flomni): Skip button on the xrayeye alignment to leave out one angle
CI for csaxs_bec / test (push) Failing after 2m12s
Flat samples are edge-on at 90 deg and cannot be centered there. The
XRayEye widget gets a Skip button (disabled unless the client enables it,
so LamNI/omny are unaffected) that signals omny_xray_gui.submit = -1.

XrayEyeAlign._align_impl leaves that step out of the fit: one of the five
angles may be skipped, at least 4 are needed. Skips of the FZP center, the
height marking, or a second angle are refused with a message and the run
continues. write_output() builds angles/offsets from the remaining steps
and raises if fewer than 4 are left; the h5 records the skipped angles.

Not yet tried on real hardware.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 15:56:10 +02:00
x12saandClaude Sonnet 5 c66253709a perf(csaxs): read filter positions from cache in the fil_trans report
CI for csaxs_bec / test (push) Failing after 2m13s
_fil_trans_report is a status report, so use readback.get(cached=True) (last
published readback from redis) instead of one RPC round trip per filter axis.
A missing value still ends in the existing "readback unavailable" error. The
post-umv position check in fil_trans stays live on purpose.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017szgTwuHG65YhjoBGiK2Gj
2026-09-20 12:01:17 +02:00
x12saandClaude Sonnet 5 028e31be41 perf(csaxs): read slit positions from cache in slits_show_all()
CI for csaxs_bec / test (push) Failing after 2m10s
device.read() defaults to cached=False, i.e. one RPC round trip to the device
server per device (24 for the six slits). Use cached=True, which reads the last
published readback from redis; a missing value still shows as '---'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017szgTwuHG65YhjoBGiK2Gj
2026-09-20 11:55:26 +02:00
x01dcandClaude Sonnet 5 d01af690a9 fix(flomni,lamni): fovy/piezo-range bounds didn't match scan's strict "<"
CI for csaxs_bec / test (push) Failing after 2m12s
Real hardware failure: flomni.fovy=100.0 was accepted by the property, GUI,
and CLI (all capped at an inclusive 100.0), but FlomniFermatScan's own
ScanArgument requires fovy strictly less than 100 -- so the scan rejected it
at runtime with ScanInputValidationError, only surfacing once actually run.

- flomni.fovy property setter: now raises at >=100 (was >100), matching
  FlomniFermatScan's gt=0/lt=100. GUI/CLI max tightened 100.0 -> 99.9.
- lamni_piezo_range_x/y (become LamniFermatScan's fovx/fovy, gt=0/lt=80):
  found the same bug class at BOTH ends while writing the regression test --
  GUI/CLI min was an inclusive 0.0 (scan requires strictly >0) and max was
  an inclusive 80.0 (scan requires strictly <80). Tightened to 0.1-79.9 in
  both GUI and CLI. Property setter's large_range_scan bypass left
  untouched per earlier explicit instruction -- this fixes the GUI/CLI
  entry points, which is where the bound is actually enforced in practice.
- Added test_fov_bounds_match_scan_args.py: introspects the real
  ScanArgument gt/ge/lt/le from FlomniFermatScan/LamniFermatScan and asserts
  our configured GUI/CLI ranges are strictly inside them, so this class of
  bug (passes every check except the one that actually runs the scan)
  can't silently come back for these or future fields.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132KBoxsovfcMNRGJhS1Pbw
2026-09-19 21:30:22 +02:00
x01dcandClaude Sonnet 5 d77b5cede9 fix(lamni): tighten circfov/piezo-range GUI limits, enforce ranges in CLI
CI for csaxs_bec / test (push) Successful in 2m18s
- tomo_circfov: GUI upper bound 200.0 -> 500.0 um (floor kept at 0.0 so
  "0 = disabled" -- checked explicitly in lamni_fermat_scan.py:484 -- still
  works)
- lamni_piezo_range_x/y: GUI upper bound 200.0 -> 80.0 um, matching the
  "(max 80)" already printed in the CLI prompt. The property setter's
  large_range_scan bypass is left untouched (uncertain hardware rationale) --
  this only tightens the GUI/CLI entry points, which is where "<80" actually
  becomes enforced in practice, since both now validate before ever calling
  the (still-conditionally-permissive) property setter.
- LamNI._get_val() now supports min_/max_ (mirrors Flomni._get_val()
  exactly, added previously) -- LamNI's tomo_parameters() CLI wizard
  previously had zero range validation at all. Wired up for
  tomo_countingtime (0.001-100.0), tomo_shellstep (0.025-20.0),
  lamni_piezo_range_x/y (0.0-80.0), and tomo_circfov (0.0-500.0).
- tomo_stitch_overlap gets a brand new CLI prompt (0.0-10.0 um) -- it had no
  prompt at all before (property-only, same gap Flomni still has).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132KBoxsovfcMNRGJhS1Pbw
2026-09-19 21:16:28 +02:00
x01dcandClaude Sonnet 5 158dc800f0 fix(flomni): enforce GUI parameter ranges in tomo_parameters() CLI too
CI for csaxs_bec / test (push) Successful in 2m15s
_get_val() (the wizard's input prompt) previously did no range validation
at all -- only the Tomo Parameters GUI's spinboxes enforced limits, so the
CLI silently accepted any value of the right type (e.g. manual_shift_y=500
despite the GUI's ±50 µm range). _get_val() now takes optional min_/max_
and re-prompts until the value is in range; wired up for every
tomo_parameters() field that already has a GUI-defined range (tomo_countingtime,
tomo_shellstep, fermat_asymmetry, fovx, fovy, stitch_x/y, manual_shift_y,
frames_per_trigger, single_point_random_shift_max, total projections,
golden_ratio_bunch_size, golden_max_number_of_projections, projections/sub-tomo).
fermat_asymmetry reuses FlomniFermatScan's own MIN/MAX constants rather than
duplicating them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132KBoxsovfcMNRGJhS1Pbw
2026-09-19 20:21:20 +02:00
x01dcandClaude Sonnet 5 0a12d4892f feat(flomni,lamni): unconditional 0-deg reference projection at scan start
CI for csaxs_bec / test (push) Successful in 2m15s
Take one real 0-degree projection at the very start of every new tomogram
(subtomo_number=1, included in reconstruction like any grid projection),
independent of zero_deg_reference_at_each_subtomo /
golden_projections_at_0_deg_for_damage_estimation -- gives an immediate
radiation-damage baseline even when those flags are off. Placed right after
Flomni's existing collect_empty_frames() step; LamNI gets the same 0-deg
shot but no flat-field step, since it has no way to retract the sample out
of the beam. Omny intentionally left as a follow-up (not implemented here).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132KBoxsovfcMNRGJhS1Pbw
2026-09-19 10:06:49 +02:00
x01dcandClaude Sonnet 5 cd838e4f73 fix(eps): enforce hard byte budget on eps_alarm_history.json uploads
CI for csaxs_bec / test (push) Successful in 2m22s
The 2026-09-11 fix capped alarm-text *count* per event (_ALARM_TEXTS_MAX),
but not text length, and _load() never re-caps an already-oversized event's
texts list -- only new appends are capped. Confirmed on x12sa: the file had
grown to 1,209,271 bytes, still tripping HTTP 413 on every upload cycle
(upload.php itself has no size check -- almost certainly nginx's default
client_max_body_size=1m). HistoryTracker.save() now shrinks a copy of the
payload to a ~250 KB budget (alarm texts, then ring trace, then oldest
events, then field length as a last resort) before writing/uploading, so
the file can no longer exceed the server's limit regardless of how the
underlying data grows. In-memory history is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132KBoxsovfcMNRGJhS1Pbw
2026-09-18 13:46:12 +02:00
x01dcandClaude Sonnet 5 bf6f89636a feat(flomni,lamni): clear tomo queue and reset progress on account change
CI for csaxs_bec / test (push) Successful in 2m19s
The account-change check already offered to reset scan parameters to
defaults for a new experiment; extend it to also clear the tomo queue and
reset acquisition progress, so a new account doesn't inherit leftover
queued jobs or in-progress state from the previous account.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132KBoxsovfcMNRGJhS1Pbw
2026-09-18 09:19:41 +02:00
x01dcandClaude Sonnet 5 d237357012 fix(omny-shared): filters_out_of_beam tolerates a missing/None dev
CI for csaxs_bec / test (push) Successful in 2m31s
Surfaced live on a simulated flomni session: tomo_scan_projection()
crashed with "DeviceConfigError: Device filter_array_1_x does not
exist" -- the real beamline's four filter_array_*_x devices
(bl_endstation.yaml) aren't part of any endstation simulation config,
by design (out of scope for an isolated endstation sim, same as
frontend/optics-hutch/detector devices).

filters_out_of_beam() already tried to skip a missing device via
getattr(dev, axis_name, None), but BEC's device container raises
DeviceConfigError -- a plain Exception, not AttributeError -- for a
missing device, so getattr's default never applied. Wrap the lookup
in try/except instead, which also preserves the pre-existing (and
apparently relied-upon, per several LamNI tests) behavior of `dev`
itself being None -- the usual state of this codebase's
`dev = builtins.__dict__.get("dev")` idiom before a real session sets
it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx3KffiFyyDMKT8vvPENUW
2026-09-17 14:22:29 +02:00
x01dcandClaude Sonnet 5 8cd9235e12 feat(flomni): add fermat_asymmetry (x/y spacing ratio) to Fermat scan
CI for csaxs_bec / test (push) Successful in 2m16s
Implements docs/plans/flomni-fermat-asymmetry.md as written: generate the
spiral against an inflated fovy half-width (length_axis2 * fermat_asymmetry),
then compress the accepted y-coordinate back down by the ratio before
storing -- keeps x spacing fixed at step, scales y spacing by the ratio,
preserves the requested FOV. Composes with the already-landed sub-step
randomization: the offset is subtracted at the raw (pre-compression) scale
on both axes, before the inflated-window check, so the offset's phase-shift
stays proportional to the local spacing on both axes.

Wired through the scan class (FlomniFermatScan), Flomni's global var +
queue/CLI plumbing (_TOMO_SCAN_PARAM_NAMES, scan_kwargs, tomo_parameters()
print/prompt, _set_default_tomo_params()), and the tomo_params Qt widget
(new field with a "?" help button, following the widget's existing
offsets-section help-button pattern).

Added tests verifying the inflate/compress math directly (against an
equivalent call with an inflated FOV and fermat_asymmetry=1.0, same seed)
rather than via an approximate spacing-ratio heuristic -- a first attempt
at the latter proved too noisy on a Fermat spiral to assert reliably.
Updated the flOMNI user docs and the OMNY port TODO (out of scope here,
explicitly excludes LamNI per the plan).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx3KffiFyyDMKT8vvPENUW
2026-09-17 13:07:44 +02:00
x01dcandClaude Sonnet 5 5e26b86d03 feat(scans): randomize Fermat spiral sub-step offset (flOMNI/OMNY/LamNI)
CI for csaxs_bec / test (push) Successful in 2m14s
Restores the legacy spec behavior of drawing a fresh random offset
(uniform in [0, step)) per call and shifting the sampling lattice by
it before the FOV crop check -- decorrelates position-tied
reconstruction errors that otherwise accumulate into ring artifacts
across a ptycho-tomography series. Applied identically to all three
generators (get_flomni_fermat_spiral_pos, get_omny_fermat_spiral_pos,
get_lamni_fermat_spiral_pos), same scalar offset for x and y, matching
the historical spec. New `seed` parameter (default None = fresh
randomization) allows deterministic/reproducible calls.

The four call sites that predict Fermat point counts ahead of a real
scan (flomni.py/lamni.py's _expected_fermat_position_count(),
tomo_params.py's _compute_fermat_positions_flomni/_lamni) now pin
seed=0 so their own estimates stay reproducible across repeated calls;
their docstrings note the real scan's count can differ by a point or
two near the threshold due to the randomization. Updated the affected
tests to compare against a matching seed=0 instead of an independent
random draw.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx3KffiFyyDMKT8vvPENUW
2026-09-17 12:30:13 +02:00
x12saandClaude Sonnet 5 cc93d563bf fix(tests): accept _internal kwarg in tomo_scan_projection test doubles
CI for csaxs_bec / test (pull_request) Successful in 2m6s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 2m8s
7d64b49 added an _internal kwarg to LamNI.tomo_scan_projection() (used
by tomo_alignment_scan/_at_each_angle to skip the redundant per-angle
filter-out-of-beam check) but left three tests' fake/lambda
replacements without it, causing them to fail in CI with
"got an unexpected keyword argument '_internal'".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FFeiCHv3qUxxssmP9Qzhom
2026-09-15 08:27:47 +02:00
x12saandClaude Sonnet 5 a20be61251 fix(tests): stub measured_log in flomni tomo-angle test helper
CI for csaxs_bec / test (pull_request) Successful in 1m40s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m39s
make_flomni_for_golden_resume() builds a bare Flomni via object.__new__,
bypassing __init__, so it never got measured_log or a working
sample_get_measured_log_key -- both added by the measured-sample-tracking
commit for the P-touch label printer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 15:34:58 +02:00
x01dcandClaude Sonnet 5 67769d5ff8 fix(flomni,lamni): use .get() in tomo_queue_show()'s job-line formatter
CI for csaxs_bec / test (pull_request) Successful in 2m14s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m49s
A job's params dict only has whatever _TOMO_SCAN_PARAM_NAMES held when it
was snapshotted by tomo_queue_add(); a job persisted before a key was
added (e.g. fovx/fovy, tomo_circfov) raised KeyError on the hard p['key']
lookup, making the whole queue un-inspectable instead of just missing a
field.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 15:47:51 +02:00
x01dcandClaude Sonnet 5 abb933e10d fix(tests): patch input() where yesno() actually calls it
CI for csaxs_bec / test (pull_request) Successful in 1m52s
Read the Docs Deploy Trigger / trigger-rtd-webhook (push) Successful in 2s
CI for csaxs_bec / test (push) Successful in 1m48s
find_rotation_center()'s y/n prompts now go through OMNYTools.yesno()
(omny_general_tools.py) instead of a raw input() in x_ray_eye_align.py,
but these tests were still patching input() on the old module -- so
the real input() got called under pytest's captured stdin and raised
"reading from stdin while output is captured".

Patch input() on omny_general_tools instead, where yesno() actually
calls it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-14 10:39:12 +02:00
x01dcandClaude Sonnet 5 48b9f893ab fix(lamni,flomni): suppress spurious 0-deg damage-estimation shot on golden-ratio resume
CI for csaxs_bec / test (push) Successful in 1m48s
previous_subtomo_number resets to -1 on every tomo_scan() call in the
golden-ratio branches (tomo_type 2/3), including a resume. That makes
the first loop iteration look like a genuine sub-tomogram transition,
firing the 0-deg reference shot before the actual resume angle even
though the rotation isn't passing through 0 deg at that moment.

Mirrors tomo_type 1's existing start_angle-is-None resume guard:
suppress the shot only for the first, possibly-spurious transition
right after a resume, then let later genuine transitions in the same
call fire normally. Same fix applied to both lamni.py and flomni.py,
which share byte-for-byte identical golden-ratio logic here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 12:43:30 +02:00
x01dcandClaude Sonnet 5 7caf57cff6 feat(lamni): check measurement configuration before tomo_scan()
Flomni's tomo_scan() warns and prompts if the X-ray eye isn't out and
the FZP/OSA optics aren't in before scanning; LamNI had no equivalent,
letting a tomogram start with the eye still in the beam or the optics
retracted. Ports the same check, wired into the existing
force/interactive gating so it never blocks on input() during
unattended/queued runs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-31 12:42:49 +02:00
wakonig_k cd13e7ed9e feat: migrate to v4 scans 2026-07-29 14:48:35 +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
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 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 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
x01dcandClaude Sonnet 5 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
x01dcandClaude Sonnet 5 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
x01dcandClaude Sonnet 5 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
x01dcandClaude Sonnet 5 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 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