fix(lamni): fully reset tomo progress when a new tomo scan starts

tomo_scan() only reset the timing/ETA fields, so after a finished scan the
progress GUI kept showing the old subtomo, angle and projection == total
(rings at 100 %) while the initial 0-degree reference shot ran, with an ETA
computed from the old projection count against the new start time.

New _reset_progress_for_new_scan() resets every field (incl. the right
totals for the tomo type) in one global-var update and refreshes the GUI.
Resumes are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
hollerandClaude Sonnet 5 authored and holler committed 2026-09-29 13:07:46 +02:00
1 parent fd4e8a2de1
commit 15c720e89e
2 files changed
+111 -8

No files matched your search

@@ -1478,6 +1478,56 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
print(f"Estimated finish time: ........... {finish_str}\x1b[0m")
self._lamnigui_update_progress()
def _reset_progress_for_new_scan(self) -> None:
"""Reset ``tomo_progress`` (and refresh the progress GUI) for a brand-new
tomogram, in one global-var round-trip.
Every field must be reset, not just the timing/ETA ones: a finished
previous scan leaves projection == total_projections (100 %), its last
subtomo and angle behind, and the new scan's very first
_print_progress() -- fired by the 0-degree reference shot, before the
grid loop has written a single field -- would otherwise show those
stale values (rings at 100 %, old subtomo/angle, and an ETA of "0 s"
computed from the old projection count against the NEW start time).
subtomo is set to 1 (not 0) so a scan interrupted during the 0-degree
shot is still resumable by tomo_scan_resume().
"""
if self.tomo_type == 1:
tomo_type_name = "Equally spaced sub-tomograms"
subtomo_total, _, total = self._tomo_type1_actual_grid()
elif self.tomo_type == 2:
tomo_type_name = "Golden ratio tomography"
subtomo_total = self.golden_ratio_bunch_size
total = self.golden_max_number_of_projections
elif self.tomo_type == 3:
tomo_type_name = "Equally spaced, golden ratio starting angles"
subtomo_total = 360 / self.tomo_angle_stepsize
total = self.golden_max_number_of_projections
else:
raise ValueError(f"Unknown tomo_type: {self.tomo_type}.")
self.progress.update(
{
"tomo_type": tomo_type_name,
"subtomo": 1,
"subtomo_projection": 0,
# totals are used as divisors by the GUI rings -- never 0
"subtomo_total_projections": subtomo_total or 1,
"projection": 0,
"total_projections": total if total and total > 0 else 1,
"angle": 0,
"tomo_start_time": datetime.datetime.now().isoformat(),
# no leftover ETA from a previous scan until this one has
# accumulated enough projections to compute a fresh one
"estimated_remaining_time": None,
"estimated_finish_time": None,
"heartbeat": None,
"accumulated_idle_time": 0.0,
}
)
self._lamnigui_update_progress()
# ------------------------------------------------------------------
# Tomo scan orchestration
# ------------------------------------------------------------------
@@ -1857,14 +1907,7 @@ class LamNI(TomoQueueMixin, LamNIAlignmentMixin, LamNIOpticsMixin, LamniGuiTools
"BEC",
)
self.write_pdf_report()
self.progress["tomo_start_time"] = datetime.datetime.now().isoformat()
# reset stale estimates from any previous scan, otherwise the GUI
# would keep showing a leftover ETA from before this scan has
# accumulated enough projections to compute a fresh one
self.progress["estimated_remaining_time"] = None
self.progress["estimated_finish_time"] = None
self.progress["accumulated_idle_time"] = 0.0
self.progress["heartbeat"] = None
self._reset_progress_for_new_scan()
# Unconditional single 0-degree reference projection at the very
# start of a new tomogram, independent of
# zero_deg_reference_at_each_subtomo /
@@ -129,6 +129,65 @@ def test_all_eight_subtomos_are_equally_spaced_when_combined(stepsize):
_assert_equally_spaced(combined, "full (all 8)")
# ── progress reset at the start of a new scan ───────────────────────────────
#
# A finished scan leaves projection == total (100 %), its last subtomo and
# angle in tomo_progress. The new scan's first _print_progress() (from the
# 0-degree reference shot) used to render those stale values in the progress
# GUI, because only the timing/ETA fields were reset.
def test_new_scan_resets_stale_progress_before_first_projection(monkeypatch):
lamni = make_lamni_for_tomo_scan(monkeypatch, 10.0, active_account="")
lamni.progress.update(
{
"tomo_type": "Golden ratio tomography",
"subtomo": 8,
"subtomo_projection": 36,
"subtomo_total_projections": 36,
"projection": 288,
"total_projections": 288,
"angle": 355.0,
"estimated_remaining_time": 0.0,
"estimated_finish_time": "2020-01-01T00:00:00",
"accumulated_idle_time": 99.0,
"heartbeat": "2020-01-01T00:00:00",
}
)
lamni.add_sample_database = lambda *a, **k: 0
seen = []
lamni._tomo_scan_at_angle = lambda angle, subtomo_number: seen.append(
(angle, subtomo_number, dict(lamni.progress.as_dict()))
)
lamni.tomo_scan()
angle, subtomo_number, prog = seen[0]
assert (angle, subtomo_number) == (0, 1)
assert prog["tomo_type"] == "Equally spaced sub-tomograms"
assert prog["subtomo"] == 1
assert prog["projection"] == 0
assert prog["subtomo_projection"] == 0
assert prog["angle"] == 0
assert prog["total_projections"] == 36 * 8
assert prog["subtomo_total_projections"] == 36
assert prog["estimated_remaining_time"] is None
assert prog["estimated_finish_time"] is None
assert prog["accumulated_idle_time"] == 0.0
assert prog["heartbeat"] is None
assert prog["tomo_start_time"] is not None
def test_resume_does_not_reset_progress(monkeypatch):
lamni = make_lamni_for_tomo_scan(monkeypatch, 10.0, active_account="")
resets = []
lamni._reset_progress_for_new_scan = lambda: resets.append(1)
lamni.tomo_scan(subtomo_start=2, start_angle=85.0)
assert resets == []
# ── resuming mid-sub-tomogram (start_angle) ─────────────────────────────────
#
# start_angle used to be treated as the ORIGIN of a fresh 360-degree sweep,
@@ -218,6 +277,7 @@ def make_lamni_for_tomo_scan(
obj._print_progress = lambda: None
obj._format_duration = lambda seconds: "0s"
obj.lamnigui_show_progress = lambda: None
obj._lamnigui_update_progress = lambda: None
obj.at_each_angle_hook = None
obj.OMNYTools = types.SimpleNamespace(printgreenbold=lambda msg: None)
# These tests exercise tomo_scan()'s account-handling/heartbeat/progress-GUI