feat(omny): drop redundant tomo auth code, cap type-3 step at 2.5 deg
CI for csaxs_bec / test (push) Successful in 1m45s

The "Enter authorization code" (x12sa) prompt gating advanced tomo modes
(types 2-5) predates the account-based _SUPERUSER_ACCOUNTS gate added
earlier this branch, and is now fully redundant with it -- Mirko asked to
remove it. Each mode still prints the existing "significant wear" warning
unconditionally instead of only after entering the code.

Also added a hard 2.5 degree ceiling on tomo type 3's angular step (golden
ratio starting angle mode), replacing its old minimum-100-projections floor
with an explicit wear-motivated cap; if the requested projection count
implies a larger step, it's clamped down to 2.5 degrees. Type 2's
golden_ratio_bunch_size minimum stays at 100 per Mirko's call -- wear
avoidance there is handled by the warning message, not a lower floor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLrD7sVYGLAzsQjLVJpCgt
This commit is contained in:
x01dc
2026-09-02 13:32:19 +02:00
co-authored by Claude Sonnet 5
parent bd129b6313
commit 2fbaa6679f
3 changed files with 130 additions and 79 deletions
@@ -16,11 +16,15 @@ full trace of each. GUI flows (`omny.omnygui_show_progress()`, `omnygui_show_xey
`omnygui_show_omnycam_parking()`/`omnygui_show_omnycam_samplestage()`) also work now, ported onto
flomni's current `gui_tools.py` API.
**Not yet started**: testing the advanced tomo modes (golden ratio, 4/8 sub-tomogram) added
earlier this branch — this is the next thing being tested live. `holler` (Mirko's account) was
added to `_SUPERUSER_ACCOUNTS` (`omny.py`) alongside `gac-x01dc` so `tomo_parameters()` shows the
advanced options when testing from his own machine, where `bec.active_account` is unset and the
superuser check falls back to the OS login.
**Now being tested**: the advanced tomo modes (golden ratio, 4/8 sub-tomogram) added earlier this
branch. `holler` (Mirko's account) was added to `_SUPERUSER_ACCOUNTS` (`omny.py`) alongside
`gac-x01dc` so `tomo_parameters()` shows the advanced options when testing from his own machine,
where `bec.active_account` is unset and the superuser check falls back to the OS login. The old
`x12sa` authorization-code prompt gating tomo types 2-5 was removed — it predates the
`_SUPERUSER_ACCOUNTS` account check and was fully redundant with it; see "Authorization code
removed" below. Tomo type 3 also gained a hard cap of 2.5 degrees on the angular step (previously
only a minimum-100-projections floor, which happened to already satisfy this but wasn't expressed
as a wear limit).
**Still open / deferred** (unchanged from before, not touched this pass):
- `x_ray_eye_align.py` is still LamNI-derived, unadapted code beyond the two call-site renames
@@ -244,6 +248,32 @@ explicit `laser_tracker_check_and_wait_for_signalstrength()` call in `prepare_se
(pure duplication of the per-axis checks the rtx/rtz `set()` calls already trigger). Net effect:
quieter output, one less redundant tracker-check wait per scan setup, same safety behavior.
## tomo_parameters(): authorization code removed, type-3 step cap added (fixed)
Advanced tomo modes (types 2-5) used to require entering a hardcoded string (`x12sa`) at an "Enter
authorization code" prompt, on top of already being hidden from non-superuser accounts by
`_is_superuser_account()`/`_SUPERUSER_ACCOUNTS` (added earlier this branch). Once account-based
gating existed, the code prompt was pure redundancy — Mirko pointed this out directly ("we can
remove that now that it is based on the user account") — so it's been removed from all three
blocks (types 2, 3, 4/5). Each block still prints the same "This mode causes significant wear in
OMNY" wear warning as before (now unconditional, not gated behind entering the code), reworded
slightly to explicitly address the power user.
Also added: tomo type 3 (equally spaced tomography, golden ratio starting angle) now enforces a
hard ceiling of 2.5 degrees on the angular step, in addition to just deriving the step from the
requested projection count. Previously it only enforced a minimum-100-projections floor (step
≤1.8°), which happened to already satisfy a 2.5° ceiling — but expressed the constraint as a
projection-count floor rather than a wear-motivated angular ceiling. If the requested projection
count yields a step above 2.5°, the step is clamped down to 2.5° and a message is printed; the
actual number of projections used ends up higher than what was typed. Type 2's
`golden_ratio_bunch_size` minimum was left at 100 (unchanged) — the wear-avoidance goal there is
served by the warning message, not by loosening that floor.
Test coverage: `tests/tests_bec_ipython_client/test_omny_tomo_angles.py`'s
`test_tomo_parameters_allows_superuser_to_unlock_8_subtomo_mode` updated to drop the now-removed
`"x12sa"` canned answer; new
`test_tomo_parameters_clamps_type_3_step_size_to_max_2_5_degrees` covers the clamp.
## Environment note (not code, but will bite again if forgotten)
Both `csaxs_bec` and `bec_widgets` were pip-installed editable pointing at pre-repo-
@@ -1448,85 +1448,69 @@ class OMNY(
print(f"The angular step in a subtomogram it will be {self.tomo_angle_stepsize}")
if self.tomo_type == 2:
code = self._get_val(
"This mode causes significant wear in OMNY. Enter authorization code.", 0, str
print(
"This mode causes significant wear in OMNY. You are a power user -- please"
" avoid unnecessarily many rotations and be gentle to the setup."
)
self.golden_ratio_bunch_size = self._get_val(
"Number of projections sorted per bunch (minimum 100)",
self.golden_ratio_bunch_size,
int,
)
if self.golden_ratio_bunch_size < 100:
print("Minimum of 100 selected.")
self.golden_ratio_bunch_size = 100
self.golden_max_number_of_projections = self._get_val(
"Stop after number of projections (zero for endless)",
self.golden_max_number_of_projections,
int,
)
self.golden_projections_at_0_deg_for_damage_estimation = self._get_val(
"Repeat projections at 0 deg every second subtomo 1/0 ?",
self.golden_projections_at_0_deg_for_damage_estimation,
int,
)
if code == "x12sa":
self.golden_ratio_bunch_size = self._get_val(
"Number of projections sorted per bunch (minimum 100)",
self.golden_ratio_bunch_size,
int,
)
if self.golden_ratio_bunch_size < 100:
print("Minimum of 100 selected.")
self.golden_ratio_bunch_size = 100
self.golden_max_number_of_projections = self._get_val(
"Stop after number of projections (zero for endless)",
self.golden_max_number_of_projections,
int,
)
self.golden_projections_at_0_deg_for_damage_estimation = self._get_val(
"Repeat projections at 0 deg every second subtomo 1/0 ?",
self.golden_projections_at_0_deg_for_damage_estimation,
int,
)
else:
print(
"Wrong authorization code. Tomo type 1 selected: 2 sub-tomograms selected."
)
self.tomo_type = 1
if self.tomo_type == 3:
code = self._get_val(
"This mode causes significant wear in OMNY. Enter authorization code.", 0, str
print(
"This mode causes significant wear in OMNY. You are a power user -- please"
" avoid unnecessarily many rotations and be gentle to the setup."
)
if code == "x12sa":
numprj = self._get_val(
"Number of projections per sub-tomogram (minimum 100)",
int(180 / self.tomo_angle_stepsize),
int,
)
if numprj < 100:
numprj = 100
print("Minimum of 100 selected.")
self.tomo_angle_stepsize = 180 / numprj
self.golden_max_number_of_projections = self._get_val(
"Stop after number of projections (zero for endless)",
self.golden_max_number_of_projections,
int,
)
self.golden_projections_at_0_deg_for_damage_estimation = self._get_val(
"Repeat projections at 0 deg every second subtomo",
self.golden_projections_at_0_deg_for_damage_estimation,
int,
)
else:
numprj = self._get_val(
"Number of projections per sub-tomogram",
int(180 / self.tomo_angle_stepsize),
int,
)
self.tomo_angle_stepsize = 180 / numprj
if self.tomo_angle_stepsize > 2.5:
self.tomo_angle_stepsize = 2.5
print(
"Wrong authorization code. Tomo type 1 selected: 2 sub-tomograms selected."
"Angular step exceeded the maximum of 2.5 degrees; clamped to 2.5 degrees"
f" ({int(180 / 2.5)} projections per sub-tomogram)."
)
self.tomo_type = 1
self.golden_max_number_of_projections = self._get_val(
"Stop after number of projections (zero for endless)",
self.golden_max_number_of_projections,
int,
)
self.golden_projections_at_0_deg_for_damage_estimation = self._get_val(
"Repeat projections at 0 deg every second subtomo",
self.golden_projections_at_0_deg_for_damage_estimation,
int,
)
if self.tomo_type in (4, 5):
n_subtomos = {4: 4, 5: 8}[self.tomo_type]
code = self._get_val(
"This mode causes significant wear in OMNY. Enter authorization code.", 0, str
print(
"This mode causes significant wear in OMNY. You are a power user -- please"
" avoid unnecessarily many rotations and be gentle to the setup."
)
if code == "x12sa":
tomo_numberofprojections = self._get_val(
"Total number of projections",
180 / self.tomo_angle_stepsize * n_subtomos,
int,
)
print(f"The angular step will be {180/tomo_numberofprojections}")
self.tomo_angle_stepsize = 180 / tomo_numberofprojections * n_subtomos
print(
f"The angular step in a subtomogram it will be {self.tomo_angle_stepsize}"
)
else:
print(
"Wrong authorization code. Tomo type 1 selected: 2 sub-tomograms selected."
)
self.tomo_type = 1
tomo_numberofprojections = self._get_val(
"Total number of projections", 180 / self.tomo_angle_stepsize * n_subtomos, int
)
print(f"The angular step will be {180/tomo_numberofprojections}")
self.tomo_angle_stepsize = 180 / tomo_numberofprojections * n_subtomos
print(f"The angular step in a subtomogram it will be {self.tomo_angle_stepsize}")
@staticmethod
def _get_val(msg: str, default_value, data_type):
@@ -244,8 +244,9 @@ def test_tomo_parameters_forces_type_1_for_non_superuser(monkeypatch, fake_bec):
def test_tomo_parameters_allows_superuser_to_unlock_8_subtomo_mode(monkeypatch, fake_bec):
"""A superuser account can select and unlock tomo_type 5 (8 sub-tomograms)
with the correct authorization code."""
"""A superuser account can select and unlock tomo_type 5 (8 sub-tomograms).
No authorization code is required -- access is gated purely by the account
check (_is_superuser_account()), which is already exercised above."""
monkeypatch.setattr(omny_module, "_SUPERUSER_ACCOUNTS", frozenset({"e99999"}))
fake_bec.active_account = "e99999" # superuser
omny = make_omny()
@@ -265,9 +266,9 @@ def test_tomo_parameters_allows_superuser_to_unlock_8_subtomo_mode(monkeypatch,
omny.OMNYTools = types.SimpleNamespace(yesno=lambda *a, **k: False)
# canned answers for: ctime, step, fovx, fovy, stitch_x, stitch_y,
# reconstruct_foldername, tomo_type (=5, 8 sub-tomograms), authorization
# code, total number of projections
canned = iter([1.0, 0.1, 20, 20, 1, 1, "queue", 5, "x12sa", 144])
# reconstruct_foldername, tomo_type (=5, 8 sub-tomograms), total number of
# projections
canned = iter([1.0, 0.1, 20, 20, 1, 1, "queue", 5, 144])
monkeypatch.setattr(OMNY, "_get_val", staticmethod(lambda *a, **k: next(canned)))
omny.tomo_parameters()
@@ -275,3 +276,39 @@ def test_tomo_parameters_allows_superuser_to_unlock_8_subtomo_mode(monkeypatch,
assert omny.tomo_type == 5
# 144 total projections / 8 sub-tomograms -> stepsize 180/(144/8) = 10.0
assert omny.tomo_angle_stepsize == pytest.approx(10.0)
def test_tomo_parameters_clamps_type_3_step_size_to_max_2_5_degrees(monkeypatch, fake_bec):
"""tomo_type 3 (equally spaced, golden ratio starting angle) enforces a
maximum angular step of 2.5 degrees, to limit wear from too many
rotations. A superuser requesting too few projections per sub-tomogram
(i.e. too large a step) gets clamped down to 2.5 degrees."""
monkeypatch.setattr(omny_module, "_SUPERUSER_ACCOUNTS", frozenset({"e99999"}))
fake_bec.active_account = "e99999" # superuser
omny = make_omny()
omny.tomo_countingtime = 1.0
omny.tomo_shellstep = 0.1
omny.fovx = 20
omny.fovy = 20
omny.stitch_x = 1
omny.stitch_y = 1
omny.ptycho_reconstruct_foldername = "queue"
omny.tomo_angle_stepsize = 10.0
omny.tomo_type = 1
fake_dev = types.SimpleNamespace(
omny_samples=types.SimpleNamespace(get_sample_name_in_samplestage=lambda: "test_sample")
)
monkeypatch.setattr(omny_module, "dev", fake_dev, raising=False)
omny.OMNYTools = types.SimpleNamespace(yesno=lambda *a, **k: False)
# canned answers for: ctime, step, fovx, fovy, stitch_x, stitch_y,
# reconstruct_foldername, tomo_type (=3), projections per sub-tomogram
# (=10 -> would be an 18 degree step, far above the 2.5 degree cap),
# stop-after-projections, repeat-at-0-deg
canned = iter([1.0, 0.1, 20, 20, 1, 1, "queue", 3, 10, 0, 0])
monkeypatch.setattr(OMNY, "_get_val", staticmethod(lambda *a, **k: next(canned)))
omny.tomo_parameters()
assert omny.tomo_type == 3
assert omny.tomo_angle_stepsize == pytest.approx(2.5)