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>
This commit is contained in:
x01dc
2026-07-21 11:37:37 +02:00
co-authored by Claude Sonnet 5
parent e59a42f978
commit b4c262d41b
2 changed files with 194 additions and 0 deletions
@@ -0,0 +1,127 @@
"""Tests for lamni's tomo-parameter reset offer on experiment-account
change, ported from flomni (see csaxs_bec/bec_ipython_client/plugins/LamNI/
AI_docs/FLOMNI_LAMNI_FEATURE_GAPS_2026-07.md, item 2).
Uses a bare LamNI instance (bypassing __init__'s heavy side effects), same
pattern as test_lamni_tomo_queue.py/test_tomo_queue_reacquire.py.
"""
import builtins
import types
import csaxs_bec.bec_ipython_client.plugins.LamNI.lamni as lamni_module
from csaxs_bec.bec_ipython_client.plugins.LamNI.lamni import LamNI, _ProgressProxy
class FakeClient:
"""Minimal in-memory stand-in for BEC's global-var store."""
def __init__(self):
self._vars = {}
def get_global_var(self, key):
return self._vars.get(key)
def set_global_var(self, key, value):
self._vars[key] = value
class FakeOMNYTools:
def __init__(self, answer=True):
self.answer = answer
self.prompts = []
def yesno(self, prompt, default):
self.prompts.append(prompt)
return self.answer
class _FakeRtx:
user_parameter = {"large_range_scan": True}
class _FakeDev:
rtx = _FakeRtx()
def __contains__(self, name):
return hasattr(self, name)
def __getitem__(self, name):
return getattr(self, name)
def make_lamni(answer=True):
lamni_module.dev = _FakeDev()
obj = object.__new__(LamNI)
obj.client = FakeClient()
obj._progress_proxy = _ProgressProxy(obj.client)
obj.OMNYTools = FakeOMNYTools(answer=answer)
obj.reconstructor = types.SimpleNamespace(folder_name=None)
return obj
def test_skips_silently_when_no_active_account():
lamni = make_lamni()
builtins.__dict__["bec"] = type("Bec", (), {"active_account": ""})()
lamni._maybe_reset_params_on_account_change()
assert lamni.client.get_global_var("defaults_applied_for_account") is None
assert lamni.OMNYTools.prompts == []
def test_skips_silently_on_same_account():
lamni = make_lamni()
lamni.client.set_global_var("defaults_applied_for_account", "e12345")
builtins.__dict__["bec"] = type("Bec", (), {"active_account": "e12345"})()
lamni.tomo_shellstep = 99.0
lamni._maybe_reset_params_on_account_change()
assert lamni.OMNYTools.prompts == []
assert lamni.tomo_shellstep == 99.0
def test_prompts_and_resets_on_new_account_when_confirmed():
lamni = make_lamni(answer=True)
lamni.client.set_global_var("defaults_applied_for_account", "e11111")
lamni.tomo_shellstep = 99.0
lamni.tomo_circfov = 123.0
builtins.__dict__["bec"] = type("Bec", (), {"active_account": "e22222"})()
lamni._maybe_reset_params_on_account_change()
assert len(lamni.OMNYTools.prompts) == 1
assert "e22222" in lamni.OMNYTools.prompts[0]
assert "e11111" in lamni.OMNYTools.prompts[0]
assert lamni.tomo_shellstep == 1
assert lamni.tomo_circfov == 0.0
assert lamni.client.get_global_var("defaults_applied_for_account") == "e22222"
def test_records_account_without_resetting_when_declined():
lamni = make_lamni(answer=False)
lamni.client.set_global_var("defaults_applied_for_account", "e11111")
lamni.tomo_shellstep = 99.0
builtins.__dict__["bec"] = type("Bec", (), {"active_account": "e22222"})()
lamni._maybe_reset_params_on_account_change()
assert lamni.tomo_shellstep == 99.0
assert lamni.client.get_global_var("defaults_applied_for_account") == "e22222"
def test_set_default_tomo_params_matches_getter_fallbacks():
"""Every value _set_default_tomo_params() writes must match the property
getter's own None-fallback -- otherwise "reset to defaults" and "never
configured" would silently disagree."""
lamni = make_lamni()
lamni._set_default_tomo_params()
for name in LamNI._TOMO_SCAN_PARAM_NAMES:
if name == "at_each_angle_hook":
continue # deliberately not touched by a reset, mirrors flomni
fresh = object.__new__(LamNI)
fresh.client = FakeClient()
assert getattr(lamni, name) == getattr(fresh, name), name