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
This commit is contained in:
x01dcandClaude Sonnet 5 committed 2026-09-19 21:16:28 +02:00
1 parent a7e1b5ecf0
commit d77b5cede9
3 files changed
+79 -10

No files matched your search

@@ -2153,17 +2153,26 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
print("OK. continue.")
return
self.tomo_countingtime = self._get_val("<ctime> s", self.tomo_countingtime, float)
self.tomo_shellstep = self._get_val("<step size> um", self.tomo_shellstep, float)
self.tomo_countingtime = self._get_val(
"<ctime> s", self.tomo_countingtime, float, min_=0.001, max_=100.0
)
self.tomo_shellstep = self._get_val(
"<step size> um", self.tomo_shellstep, float, min_=0.025, max_=20.0
)
self.lamni_piezo_range_x = self._get_val(
"<piezo range X (max 80)> um", self.lamni_piezo_range_x, float
"<piezo range X (max 80)> um", self.lamni_piezo_range_x, float, min_=0.0, max_=80.0
)
self.lamni_piezo_range_y = self._get_val(
"<piezo range Y (max 80)> um", self.lamni_piezo_range_y, float
"<piezo range Y (max 80)> um", self.lamni_piezo_range_y, float, min_=0.0, max_=80.0
)
self.lamni_stitch_x = self._get_val("<stitch X>", self.lamni_stitch_x, int)
self.lamni_stitch_y = self._get_val("<stitch Y>", self.lamni_stitch_y, int)
self.tomo_circfov = self._get_val("<circular FOV> um", self.tomo_circfov, float)
self.tomo_stitch_overlap = self._get_val(
"<stitch overlap> um", self.tomo_stitch_overlap, float, min_=0.0, max_=10.0
)
self.tomo_circfov = self._get_val(
"<circular FOV> um", self.tomo_circfov, float, min_=0.0, max_=500.0
)
self.ptycho_reconstruct_foldername = self._get_val(
"Reconstruction queue", self.ptycho_reconstruct_foldername, str
)
@@ -2243,8 +2252,22 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
)
@staticmethod
def _get_val(msg: str, default_value, data_type):
return data_type(input(f"{msg} ({default_value}): ") or default_value)
def _get_val(msg: str, default_value, data_type, min_=None, max_=None):
"""Prompt for a value, casting to data_type. When min_/max_ are
given, re-prompts (rather than silently clamping) until the typed
value is in range, mirroring the Tomo Parameters GUI's spinbox
bounds (tomo_params.py) so tomo_parameters() enforces the same
limits instead of accepting anything of the right type. Mirrors
Flomni._get_val() exactly."""
while True:
val = data_type(input(f"{msg} ({default_value}): ") or default_value)
if min_ is not None and val < min_:
print(f"Value must be >= {min_}. Please try again.")
continue
if max_ is not None and val > max_:
print(f"Value must be <= {max_}. Please try again.")
continue
return val
def _confirm_sequence_override(self, warning: str, force: bool) -> bool:
"""Print *warning* and ask whether to proceed anyway.
@@ -2362,7 +2362,7 @@ SETUP_PROFILES: dict[str, dict[str, Any]] = {
"has_180_mode": False,
"has_single_point": False,
"has_zero_deg_reference": True,
"fov_fields": [("tomo_circfov", "Circular FOV (µm, 0 = disabled)", 0.0, 200.0, 2)],
"fov_fields": [("tomo_circfov", "Circular FOV (µm, 0 = disabled)", 0.0, 500.0, 2)],
"stitch_fields": [
("lamni_stitch_x", "Stitch x", 0, 50),
("lamni_stitch_y", "Stitch y", 0, 50),
@@ -2372,8 +2372,8 @@ SETUP_PROFILES: dict[str, dict[str, Any]] = {
("manual_shift_y", "Manual shift y (µm)", -1000.0, 1000.0, 3),
],
"piezo_range_fields": [
("lamni_piezo_range_x", "Piezo range x (µm)", 0.0, 200.0, 2),
("lamni_piezo_range_y", "Piezo range y (µm)", 0.0, 200.0, 2),
("lamni_piezo_range_x", "Piezo range x (µm)", 0.0, 80.0, 2),
("lamni_piezo_range_y", "Piezo range y (µm)", 0.0, 80.0, 2),
],
# tomo_fovx_offset/tomo_fovy_offset: alignment values (LamNIAlignmentMixin),
# not part of _TOMO_SCAN_PARAM_NAMES -- shown/editable here (mirroring
@@ -0,0 +1,46 @@
"""Tests for LamNI._get_val()'s min_/max_ range validation.
Mirrors test_flomni_get_val_range.py -- LamNI's tomo_parameters() CLI wizard
previously had no range validation at all (unlike the GUI's spinbox
ranges); _get_val() now optionally re-prompts until the typed value is in
range, same mechanism as Flomni's.
"""
import builtins
from csaxs_bec.bec_ipython_client.plugins.LamNI.lamni import LamNI
def _fake_inputs(monkeypatch, values):
it = iter(values)
monkeypatch.setattr(builtins, "input", lambda prompt="": next(it))
def test_get_val_without_bounds_accepts_anything(monkeypatch):
_fake_inputs(monkeypatch, ["12345"])
assert LamNI._get_val("x", 0.0, float) == 12345.0
def test_get_val_accepts_in_range_value_first_try(monkeypatch):
_fake_inputs(monkeypatch, ["10"])
assert LamNI._get_val("shellstep", 0.0, float, min_=0.025, max_=20.0) == 10.0
def test_get_val_reprompts_when_above_max(monkeypatch, capsys):
_fake_inputs(monkeypatch, ["999", "50"])
result = LamNI._get_val("circfov", 0.0, float, min_=0.0, max_=500.0)
assert result == 50.0
assert "must be <= 500.0" in capsys.readouterr().out
def test_get_val_reprompts_when_below_min(monkeypatch, capsys):
_fake_inputs(monkeypatch, ["-5", "10"])
result = LamNI._get_val("piezo range x", 20.0, float, min_=0.0, max_=80.0)
assert result == 10.0
assert "must be >= 0.0" in capsys.readouterr().out
def test_get_val_boundary_values_are_accepted(monkeypatch):
_fake_inputs(monkeypatch, ["0.025", "20.0"])
assert LamNI._get_val("a", 0.0, float, min_=0.025, max_=20.0) == 0.025
assert LamNI._get_val("b", 0.0, float, min_=0.025, max_=20.0) == 20.0