From 2fbaa6679f8df6e6e5ac01b984582e155b2ebcfc Mon Sep 17 00:00:00 2001 From: x01dc Date: Wed, 2 Sep 2026 13:32:19 +0200 Subject: [PATCH] feat(omny): drop redundant tomo auth code, cap type-3 step at 2.5 deg 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 Claude-Session: https://claude.ai/code/session_01QLrD7sVYGLAzsQjLVJpCgt --- .../plugins/omny/AI_docs/OPEN_ISSUES.md | 40 +++++- .../bec_ipython_client/plugins/omny/omny.py | 122 ++++++++---------- .../test_omny_tomo_angles.py | 47 ++++++- 3 files changed, 130 insertions(+), 79 deletions(-) diff --git a/csaxs_bec/bec_ipython_client/plugins/omny/AI_docs/OPEN_ISSUES.md b/csaxs_bec/bec_ipython_client/plugins/omny/AI_docs/OPEN_ISSUES.md index 78b7fd81..6f22ce7c 100644 --- a/csaxs_bec/bec_ipython_client/plugins/omny/AI_docs/OPEN_ISSUES.md +++ b/csaxs_bec/bec_ipython_client/plugins/omny/AI_docs/OPEN_ISSUES.md @@ -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- diff --git a/csaxs_bec/bec_ipython_client/plugins/omny/omny.py b/csaxs_bec/bec_ipython_client/plugins/omny/omny.py index 35368497..cc395088 100644 --- a/csaxs_bec/bec_ipython_client/plugins/omny/omny.py +++ b/csaxs_bec/bec_ipython_client/plugins/omny/omny.py @@ -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): diff --git a/tests/tests_bec_ipython_client/test_omny_tomo_angles.py b/tests/tests_bec_ipython_client/test_omny_tomo_angles.py index f71e5184..8d28e579 100644 --- a/tests/tests_bec_ipython_client/test_omny_tomo_angles.py +++ b/tests/tests_bec_ipython_client/test_omny_tomo_angles.py @@ -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)