From 9373fe307a45fe2e0bc671858798599ffbd45974 Mon Sep 17 00:00:00 2001 From: x01dc Date: Wed, 30 Sep 2026 14:57:11 +0200 Subject: [PATCH] fix(flomni): no 0-deg damage-estimation shots for tomo_type 4 Sub-tomogram numbers in a user angle file are just labels, so inserting extra 0-deg projections at odd sub-tomogram transitions is surprising. tomo_type 4 now ignores golden_projections_at_0_deg_for_damage_estimation; the CLI no longer prompts for it and the widget hides it for type 4. The unconditional 0-deg reference shot at the start of a new tomogram is unchanged; further 0-deg shots can be put in the file directly. Co-Authored-By: Claude Opus 5.5 --- .../plugins/flomni/flomni.py | 28 +++---------------- .../widgets/tomo_params/tomo_params.py | 23 +++++---------- .../test_flomni_tomo_angles.py | 19 ++++--------- .../test_tomo_params_angle_file.py | 2 +- 4 files changed, 17 insertions(+), 55 deletions(-) diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py index 06e176f9..2a1b3c4c 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py @@ -3017,11 +3017,11 @@ class Flomni( break elif self.tomo_type == 4: # Angles (and sub-tomogram numbers) read from tomo_angle_file, - # acquired in file order. - previous_subtomo_number = -1 + # acquired in file order. No extra 0-deg damage-estimation + # shots here: sub-tomogram numbers in a user file are just + # labels, so golden_projections_at_0_deg_for_damage_estimation + # is ignored (put 0-deg lines in the file instead). start_ii = 0 if projection_number is None else projection_number - # Same first-transition suppression as tomo_type 2/3 above. - resuming = projection_number is not None # Per-projection position within its own sub-tomogram, and # each sub-tomogram's size, for the progress display. subtomo_sizes = {} @@ -3031,16 +3031,6 @@ class Flomni( subtomo_sizes[subtomo] = subtomo_sizes.get(subtomo, 0) + 1 for ii in range(start_ii, len(file_angles)): angle, subtomo_number = file_angles[ii] - if previous_subtomo_number != subtomo_number: - if ( - subtomo_number % 2 == 1 - and ii > 0 - and self.golden_projections_at_0_deg_for_damage_estimation == 1 - and not resuming - ): - self._tomo_scan_at_angle(0, subtomo_number) - previous_subtomo_number = subtomo_number - resuming = False self.progress["tomo_type"] = "Angles from file" self.progress["subtomo"] = subtomo_number self.progress["projection"] = ii @@ -3980,11 +3970,6 @@ class Flomni( if self.tomo_type == 4: print("\x1b[1mTomo type 4:\x1b[0m Angles read from file") self._print_angle_file_summary() - if self.golden_projections_at_0_deg_for_damage_estimation == 1: - print( - "repeating projections at 0 degrees at the beginning of every odd" - " sub-tomogram" - ) print(f"\nSample name: {self.sample_name}\n") if self.OMNYTools.yesno("Are these parameters correctly set for your scan?", "y"): @@ -4164,11 +4149,6 @@ class Flomni( " sub-tomogram number, '#' comments. Re-read at every scan start." ) self._prompt_angle_file() - self.golden_projections_at_0_deg_for_damage_estimation = self._get_val( - "Repeat projections at 0 deg at the start of every odd subtomo 1/0 ?", - self.golden_projections_at_0_deg_for_damage_estimation, - int, - ) def _print_angle_file_summary(self) -> bool: """Print path + parsed summary (+ 180-deg-pair warning) of diff --git a/csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py b/csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py index 30ee5131..3a7056b9 100644 --- a/csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py +++ b/csaxs_bec/bec_widgets/widgets/tomo_params/tomo_params.py @@ -90,15 +90,17 @@ TOMO_TYPES = { # Mirrors _update_type_visibility()'s section/field gating (_sec_type1 shown # only for type 1, golden_ratio_bunch_size shown only for type 2 within -# _sec_type23, golden_max_number_of_projections for type 2/3, the 0-deg -# damage flag for type 2/3/4, _sec_type4 only for type 4) -- reused by +# _sec_type23, the rest of _sec_type23 shown for type 2/3, _sec_type4 only +# for type 4) -- reused by # _job_tooltip() so a queued job's hover details don't show fields that are # meaningless for its own tomo_type (e.g. golden-ratio settings on a type-1 # job), on both flomni and lamni. _TYPE1_ONLY_PARAMS = {"tomo_angle_range", "zero_deg_reference_at_each_subtomo"} _TYPE2_ONLY_PARAMS = {"golden_ratio_bunch_size"} -_TYPE23_PARAMS = {"golden_max_number_of_projections"} -_TYPE234_PARAMS = {"golden_projections_at_0_deg_for_damage_estimation"} +_TYPE23_PARAMS = { + "golden_max_number_of_projections", + "golden_projections_at_0_deg_for_damage_estimation", +} _TYPE4_ONLY_PARAMS = {"tomo_angle_file"} @@ -110,8 +112,6 @@ def _irrelevant_params_for_type(tomo_type: Any) -> set[str]: irrelevant |= _TYPE1_ONLY_PARAMS if tomo_type not in (2, 3): irrelevant |= _TYPE23_PARAMS - if tomo_type not in (2, 3, 4): - irrelevant |= _TYPE234_PARAMS if tomo_type != 2: irrelevant |= _TYPE2_ONLY_PARAMS if tomo_type != 4: @@ -1038,16 +1038,7 @@ class OMNY_TomoParams(BECWidget, QWidget): def _update_type_visibility(self, tomo_type: int) -> None: self._sec_type1.setVisible(tomo_type == 1) - # type 4 reuses _sec_type23's 0-deg damage-estimation checkbox (one - # global var can only map to one widget), with the golden-ratio-only - # rows hidden - self._sec_type23.setVisible(tomo_type in (2, 3, 4)) - self._sec_type23.setTitle( - "Type 4 – damage estimation" if tomo_type == 4 else "Type 2 / 3 – golden ratio settings" - ) - self._set_form_row_visible( - self._sec_type23, "golden_max_number_of_projections", tomo_type in (2, 3) - ) + self._sec_type23.setVisible(tomo_type in (2, 3)) self._set_form_row_visible(self._sec_type23, "_numprj_type3", tomo_type == 3) self._set_form_row_visible(self._sec_type23, "golden_ratio_bunch_size", tomo_type == 2) if self._sec_type4 is not None: diff --git a/tests/tests_bec_ipython_client/test_flomni_tomo_angles.py b/tests/tests_bec_ipython_client/test_flomni_tomo_angles.py index 24303251..a9310651 100644 --- a/tests/tests_bec_ipython_client/test_flomni_tomo_angles.py +++ b/tests/tests_bec_ipython_client/test_flomni_tomo_angles.py @@ -354,26 +354,17 @@ def test_angle_file_missing_raises_before_any_acquisition(monkeypatch, tmp_path) assert calls == [] -def test_angle_file_zero_deg_shots_on_odd_subtomo_transitions(monkeypatch, tmp_path): - flomni = make_flomni_for_angle_file(monkeypatch, tmp_path, "10 1\n20 2\n30 3\n40 3\n") +def test_angle_file_ignores_golden_zero_deg_damage_flag(monkeypatch, tmp_path): + """No extra 0-deg shots at sub-tomogram transitions for type 4 -- only + the unconditional reference shot at the start of a new tomogram.""" + flomni = make_flomni_for_angle_file(monkeypatch, tmp_path, "10 1\n20 2\n30 3\n40 5\n") flomni.golden_projections_at_0_deg_for_damage_estimation = 1 recorded = [] flomni._tomo_scan_at_angle = lambda angle, subtomo: recorded.append((angle, subtomo)) flomni.tomo_scan() - assert recorded == [(0, 1), (10.0, 1), (20.0, 2), (0, 3), (30.0, 3), (40.0, 3)] - - -def test_angle_file_resume_suppresses_spurious_zero_deg_shot(monkeypatch, tmp_path): - flomni = make_flomni_for_angle_file(monkeypatch, tmp_path, "10 1\n20 2\n30 3\n40 5\n") - flomni.golden_projections_at_0_deg_for_damage_estimation = 1 - recorded = [] - flomni._tomo_scan_at_angle = lambda angle, subtomo: recorded.append((angle, subtomo)) - - flomni.tomo_scan(projection_number=2) - - assert recorded == [(30.0, 3), (0, 5), (40.0, 5)] + assert recorded == [(0, 1), (10.0, 1), (20.0, 2), (30.0, 3), (40.0, 5)] def test_max_scan_angle_is_360_only_for_type_4(): diff --git a/tests/tests_bec_widgets/test_tomo_params_angle_file.py b/tests/tests_bec_widgets/test_tomo_params_angle_file.py index 516e310e..d5b73238 100644 --- a/tests/tests_bec_widgets/test_tomo_params_angle_file.py +++ b/tests/tests_bec_widgets/test_tomo_params_angle_file.py @@ -77,7 +77,7 @@ def test_validate_rejects_type_4_on_lamni(): def test_tooltip_gating_for_type_4(): irrelevant = _irrelevant_params_for_type(4) assert "tomo_angle_file" not in irrelevant - assert "golden_projections_at_0_deg_for_damage_estimation" not in irrelevant + assert "golden_projections_at_0_deg_for_damage_estimation" in irrelevant assert "golden_max_number_of_projections" in irrelevant assert "golden_ratio_bunch_size" in irrelevant assert "tomo_angle_range" in irrelevant