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>
This commit is contained in:
x01dc
2026-07-20 21:13:21 +02:00
co-authored by Claude Sonnet 5
parent 55f6f944f7
commit 6c3d60306d
4 changed files with 68 additions and 8 deletions
@@ -166,8 +166,14 @@ def make_lamni_for_tomo_scan(tomo_angle_stepsize: float, active_account: str) ->
obj._print_progress = lambda: None
obj._format_duration = lambda seconds: "0s"
obj.lamnigui_show_progress = lambda: None
obj.at_each_angle_hook = None
obj.OMNYTools = types.SimpleNamespace(printgreenbold=lambda msg: None)
builtins.__dict__["bec"] = types.SimpleNamespace(
active_account=active_account, queue=types.SimpleNamespace(next_scan_number=1)
active_account=active_account,
queue=types.SimpleNamespace(next_scan_number=1),
builtin_actors=types.SimpleNamespace(
scan_interlock=types.SimpleNamespace(trigger_setting=None, enabled=False)
),
)
builtins.__dict__["scans"] = _FakeScans()
return obj
@@ -11,7 +11,7 @@ import types
import csaxs_bec.bec_ipython_client.plugins.LamNI.lamni as lamni_module
import csaxs_bec.bec_ipython_client.plugins.OMNY_shared.tomo_queue_mixin as tomo_queue_mixin
from csaxs_bec.bec_ipython_client.plugins.LamNI.lamni import LamNI
from csaxs_bec.bec_ipython_client.plugins.LamNI.lamni import LamNI, LamNIError
class _FakeRtx:
@@ -200,7 +200,7 @@ def test_at_each_angle_raises_for_unregistered_hook_name():
lamni.at_each_angle_hook = "missing_hook"
try:
lamni._at_each_angle(0.0)
assert False, "expected ValueError for an unregistered hook name"
except ValueError as exc:
assert False, "expected LamNIError for an unregistered hook name"
except LamNIError as exc:
assert "missing_hook" in str(exc)
assert "not registered" in str(exc)