feat/add zero-deg radiation-damage reference shot for lamni tomo_type 1

Ports flomni's zero_deg_reference_at_each_subtomo to lamni's "8 equally
spaced sub-tomograms" mode: an extra dedicated projection at exactly 0
degrees before every sub-tomogram where the rotation naturally passes
back through 0, plus one final shot once the tomogram completes, for
tracking radiation damage over a long acquisition. lamni's tomo_type
2/3 already had the equivalent (golden_projections_at_0_deg_for_damage_estimation,
byte-for-byte identical to flomni's) -- only tomo_type 1 was missing.

_subtomo_starts_near_zero() uses subtomo_number % 2 (every odd
sub-tomogram) rather than flomni's 360-mode subtomo_number % 4 == 1 --
confirmed against lamni's actual rotation behavior rather than derived
from the position-array math, which doesn't cleanly split odd/even.

The GUI (tomo_params.py) needed no new UI code: _build_type1_section()
already gated this control on a per-setup has_zero_deg_reference flag,
so enabling it for lamni was just flipping that flag plus adding the
param name/default, mirroring flomni's entries exactly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
x01dc
2026-07-21 04:18:23 +02:00
co-authored by Claude Sonnet 5
parent 5ee00c997a
commit c3877ecdef
3 changed files with 137 additions and 4 deletions
@@ -268,3 +268,68 @@ def test_tomo_scan_clears_heartbeat_even_if_scan_raises():
lamni.tomo_scan()
assert lamni.progress["heartbeat"] is None
@pytest.mark.parametrize(
"subtomo_number,expected",
[(1, True), (2, False), (3, True), (4, False), (5, True), (6, False), (7, True), (8, False)],
)
def test_subtomo_starts_near_zero(subtomo_number, expected):
"""Every odd sub-tomogram is where lamni's rotation naturally passes
back through 0 degrees (confirmed against lamni's own hardware
behavior -- not flomni's 360-mode subtomo_number % 4 == 1, which is
specific to flomni's split into two 180-degree halves)."""
lamni = make_lamni(10.0)
assert lamni._subtomo_starts_near_zero(subtomo_number) is expected
def test_zero_deg_reference_disabled_by_default():
"""zero_deg_reference_at_each_subtomo defaults to False -- a fresh
tomo_scan() must not fire any extra 0-deg shots."""
lamni = make_lamni_for_tomo_scan(45.0, active_account="")
lamni.add_sample_database = lambda *a, **k: 0
recorded = []
lamni._tomo_scan_at_angle = lambda angle, subtomo_number: recorded.append(
(angle, subtomo_number)
)
lamni.sub_tomo_scan = lambda subtomo_number, start_angle=None: None
lamni.tomo_scan()
assert recorded == []
def test_zero_deg_reference_fires_for_odd_subtomos_and_final_shot():
"""With the flag on, a fresh tomo_scan() must fire an extra angle-0 shot
before each odd sub-tomogram (1, 3, 5, 7) plus one final shot after
sub-tomogram 8 completes -- mirrors Flomni.tomo_scan()'s equivalent."""
lamni = make_lamni_for_tomo_scan(45.0, active_account="")
lamni.add_sample_database = lambda *a, **k: 0
lamni.zero_deg_reference_at_each_subtomo = True
recorded = []
lamni._tomo_scan_at_angle = lambda angle, subtomo_number: recorded.append(
(angle, subtomo_number)
)
lamni.sub_tomo_scan = lambda subtomo_number, start_angle=None: None
lamni.tomo_scan()
assert recorded == [(0, 1), (0, 3), (0, 5), (0, 7), (0, 8)]
def test_zero_deg_reference_skipped_when_resuming_mid_subtomo():
"""A resume (start_angle given explicitly for the first sub-tomogram of
this call) must skip the extra shot for that first sub-tomogram --
we're not actually passing through 0 deg at that moment -- but must
still fire normally for later sub-tomograms in the same call."""
lamni = make_lamni_for_tomo_scan(45.0, active_account="")
lamni.zero_deg_reference_at_each_subtomo = True
recorded = []
lamni._tomo_scan_at_angle = lambda angle, subtomo_number: recorded.append(
(angle, subtomo_number)
)
lamni.sub_tomo_scan = lambda subtomo_number, start_angle=None: None
lamni.tomo_scan(subtomo_start=3, start_angle=45.0)
assert recorded == [(0, 5), (0, 7), (0, 8)]