Fixes/lamni hw commissioning #266

Merged
holler merged 36 commits from fixes/lamni_hw_commissioning into main 2026-07-21 17:00:59 +02:00
Member
No description provided.
holler added 36 commits 2026-07-21 16:57:42 +02:00
fix/increase delay after NEWPAR in FES
CI for csaxs_bec / test (push) Successful in 1m38s
3c46555df4
fix/delays after newpar. lamni needs more time
CI for csaxs_bec / test (push) Successful in 1m35s
cdc4ae02b5
feat/smaract_show_all
CI for csaxs_bec / test (push) Successful in 1m35s
c0fdba0e6c
fix/improve doc
CI for csaxs_bec / test (push) Successful in 1m34s
55f6f944f7
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>
Hovering a queue entry showed every snapshotted param unconditionally,
including golden-ratio settings on a type-1 (non-golden) job -- and
vice versa, type-1-only fields (angle range, zero-deg reference) on a
golden-ratio job. Reuses _update_type_visibility()'s existing
per-tomo_type section/field gating (already used for the live edit
form) to filter _job_tooltip()'s params list the same way, for both
flomni and lamni jobs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Correction 1, correction 2, and the x-ray-eye correction were already
printed for every projection, but the manual/constant shift
(manual_shift_x/manual_shift_y) applied in the same offset math wasn't
-- added on both setups, right after the existing correction calls in
lamni's tomo_scan_projection() and flomni's tomo_scan_projection() /
tomo_acquire_at_angle().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
flomni's tomo_parameters() CLI printout already showed the active
at_each_angle_hook; lamni's didn't, making it easy to have a custom
per-projection hook active without noticing from the parameter dump.
Added the identical guarded print, reusing the shared
TomoQueueMixin._describe_active_hook() helper both classes already
inherit. Also switched lamni's tomo_scan() end-of-scan summary to use
that same helper instead of the raw attribute, matching flomni's
equivalent block and correctly flagging a stale/unregistered hook name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Add both to the tomo-queue command reference in flomni.md and
lamni.md: tomo_queue_resume() as an alias for tomo_queue_execute(),
and tomo_queue_reacquire(job_index, projection_number) for reopening
a job (including an already-"done" one) at an earlier projection and
cascading later jobs to pending, mirroring the code added in the
previous commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Brief AI_docs note covering commits 6c3d603..8a7a350: what changed and
what to test (automated + manual/simulated-session) to confirm the
branch is in good shape before relying on it at the beamline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Extend commit range to c3877ec, add item 7 (zero_deg_reference_at_each_subtomo
for lamni tomo_type 1), update expected test count to 423, and add a
manual-check line for the new feature.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up sweep after this session's ports (timing overview, scan
interruption handling, tooltip filtering, constant shift, hook
display, zero-deg reference). Found and verified 9 items: a missing
per-projection/tomogram timing log + scilog_last_ptycho_scans()
command, no reset-tomo-params-on-account-change offer, no
collect_empty_frames() flat-field acquisition, no skip-if-already-in
optimization on lfzp_in(), no hard-stop button wired into lamni's
GUI, tomo_alignment_scan() with no clear lamni equivalent (lamni has
a different lookup-table correction scheme instead -- needs
discussion, not necessarily a gap), no OSA-collision-clearance
warning in lfzp_info(), a large confirmed-hardware-specific block
(sample-transfer/gripper/laser-tracker methods, not portable), and a
webpage-display opportunity that's symmetric on both setups rather
than a one-sided gap. No code changes -- findings only, for review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
The tool is called BEC_ptycho_align now, not SPEC_ptycho_align.m as
the previous docs/comments said -- fixes the references introduced in
the previous commit (12b2538) in lamni.py's docstrings/print message
and the user docs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add items 8-12 (timing log, account-change reset, optics
skip-if-in-position, OSA collision warning, tomo_alignment_scan) plus
the BEC_ptycho_align naming fix, updated test count (458), and
corresponding manual-check lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
The single-resumable-job invariant check scanned all other jobs, but
jobs after job_index get force-reset to pending anyway -- so a later
incomplete/running job was never a real conflict, only an earlier one
is (since earlier jobs are left untouched).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_format_projections() computed type 3's count with type 1's fixed
"N*8 sub-tomograms" formula, ignoring golden_max_number_of_projections
-- the value that actually bounds a type-3 run (lamni.py/flomni.py's
type-3 scan loop has no sub-tomogram cap, it stops only once
ii > golden_max_number_of_projections, same as type 2). This produced
an ~8x-inflated "Projections" column (e.g. 240 instead of 30) for any
type-3 job. Merged type 3 into type 2's branch since both share the
same uncapped/golden_max_number_of_projections model.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
write_pdf_report() didn't mention the active at_each_angle_hook at all,
unlike Flomni's equivalent which already appends the hook's description
and source to both the PDF and its scilog entry. Ported that pattern
over using the same _describe_active_hook()/_active_hook_source() shared
helpers lamni already uses elsewhere (end-of-scan summary,
tomo_parameters() printout) -- no new logic needed, just the missing
call site.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Flomni's ring-progress center label already showed "Est. finish" and
the active at-each-angle hook name; LamNI's equivalent
(_lamnigui_update_progress()) was missing both. Ported the same
formatting/display logic over so the two setups' progress displays
match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Flomni.add_sample_database() and LamNI.add_sample_database() were
byte-for-byte identical -- both just forward to
self.tomo_id_manager.register(), a TomoIDManager from
omny/omny_general_tools.py that each setup already constructs
independently in its own __init__. Moved the method into
TomoQueueMixin (OMNY_shared), the base class both already inherit,
same "moved code, not changed logic" precedent as the tomo-queue
backend and webpage generator. omny.py's own separate, older
add_sample_database() (different URL, no e-account fallback) is
untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Confirmed this script no longer works and needs a rewrite. Commented
out the subprocess.run() call in write_pdf_report(), mirroring Flomni
which already has the identical call disabled the same way. omny.py's
own copy is left untouched (out of scope for this migration).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/add file-loaded at_each_angle hook example (exposure + step size)
CI for csaxs_bec / test (push) Successful in 2m23s
CI for csaxs_bec / test (pull_request) Failing after 2m3s
9198638f9b
Adds a worked example to both lamni.md and flomni.md showing a hook
loaded from a file that temporarily bumps tomo_countingtime and
tomo_shellstep every 5th projection within sub-tomogram 2 (tomo_type 1),
restoring both in a finally block -- demonstrating that a hook can
change any scan parameter, not just move devices like the existing
polarizer example. Notes that progress["subtomo"]/["subtomo_projection"]
work the same way for tomo_types 2/3, just with an open-ended
sub-tomogram count there instead of type 1's fixed 8.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
10ef93557b
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>
holler merged commit 10ef93557b into main 2026-07-21 17:00:59 +02:00
holler deleted branch fixes/lamni_hw_commissioning 2026-07-21 17:00:59 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: bec/csaxs_bec#266