fix(flomni): no 0-deg damage-estimation shots for tomo_type 4
CI for csaxs_bec / test (push) Successful in 3m4s

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 <noreply@anthropic.com>
This commit is contained in:
x01dcandClaude Opus 5.5 committed 2026-09-30 14:57:11 +02:00
1 parent 35ac94cfd5
commit 9373fe307a
4 files changed
+17 -55

No files matched your search

@@ -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
@@ -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:
@@ -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():
@@ -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