fix(flomni): reset tomo progress before pre-flight gates, not after
CI for csaxs_bec / test (push) Successful in 3m18s

tomo_scan() only reset the progress dict (projection/subtomo/angle/
tomo_start_time/heartbeat/...) inside the "new scan" block, which runs
after the eye/optics-in and filter-out-of-beam gates. If either gate
declined (e.g. the setup isn't in measurement condition, which disabling
rt feedback can trigger) or the tomo_type-4 angle-file check raised,
tomo_scan() returned/raised having never touched progress, leaving the
GUI/webpage showing projection/angle/start-time from whatever scan last
completed -- arbitrarily old, and easy to mistake for a scan still
running.

Hoists the "is this a new scan" check to the top of tomo_scan() and
calls progress.reset() there, before any gate runs; the heavier new-scan
actions (sample DB registration, PDF report, collect_empty_frames(), the
0-deg reference shot) stay exactly where they were, after the gates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPZ9gzN2rt3wgiMnTKyrug
This commit is contained in:
x01dcandClaude Sonnet 5 committed 2026-10-01 11:22:45 +02:00
1 parent 67884c0c3f
commit 0332f1c681
2 files changed
+88 -15

No files matched your search

@@ -2830,6 +2830,23 @@ class Flomni(
"""
dev = builtins.__dict__.get("dev")
is_new_scan = (
(self.tomo_type == 1 and subtomo_start == 1 and start_angle is None)
or (self.tomo_type == 2 and projection_number == None)
or (self.tomo_type == 3 and projection_number == None)
or (self.tomo_type == 4 and projection_number is None)
)
if is_new_scan:
# Clear any stale progress from a previous scan immediately, before
# the angle-file check and the eye/optics-in / filter-out-of-beam
# gates below -- otherwise, if any of those decline or raise (e.g.
# the setup isn't in measurement condition, which disabling rt
# feedback can easily cause), tomo_scan() returns/raises having
# never touched `progress`, and the GUI/webpage keep showing
# projection/angle/start-time from whatever scan last completed --
# arbitrarily old, and easy to mistake for a scan still running.
self.progress.reset()
# tomo_type 4: (re-)read the angle file before anything else, so a
# missing/malformed file fails immediately -- before sample DB
# registration, empty frames, or any motion (and, in the tomo queue,
@@ -2882,14 +2899,7 @@ class Flomni(
bec.builtin_actors.scan_interlock.enabled = True
self._current_special_angles = self.special_angles.copy()
# a new tomo scan was started
if (
(self.tomo_type == 1 and subtomo_start == 1 and start_angle is None)
or (self.tomo_type == 2 and projection_number == None)
or (self.tomo_type == 3 and projection_number == None)
or (self.tomo_type == 4 and projection_number is None)
):
if is_new_scan:
# pylint: disable=undefined-variable
# Always attempt registration (even for an empty/test account)
# and let add_sample_database() -> TomoIDManager.register()
@@ -2921,14 +2931,11 @@ class Flomni(
+ "\n".join(self._format_angle_entry(e) for e in file_angles.angles),
"tomoscan",
)
# estimated_remaining_time/estimated_finish_time/accumulated_idle_time/
# heartbeat/subtomo/projection/angle/... were already cleared by the
# self.progress.reset() above, before the gates -- only the actual
# start time is set here, once the scan is genuinely committed to.
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.collect_empty_frames()
# Unconditional single 0-degree reference projection at the very
# start of a new tomogram, independent of
@@ -594,3 +594,69 @@ def test_hook_warning_fires_again_only_for_newly_appended_overridden_lines(monke
# overridden line is picked up mid-scan.
assert len(warnings) == 1
assert "hook is active" in warnings[0]
# ── progress is reset before any pre-flight gate can abort a new scan ──────
def test_new_scan_resets_progress_before_eye_optics_gate_declines(monkeypatch, tmp_path):
"""A stale progress dict from a long-finished scan must not survive a
declined *new* scan attempt (e.g. the eye/optics-in prompt, which
disabling rt feedback can trigger) -- regression for a report where the
progress widget kept showing a days-old scan after a failed start."""
flomni = make_flomni_for_angle_file(monkeypatch, tmp_path, "10\nEND\n")
flomni.progress.update(
projection=196,
total_projections=288,
angle=196.2,
subtomo=6,
tomo_type="Equally spaced sub-tomograms",
tomo_start_time="2026-09-25T14:57:28",
heartbeat="2026-09-25T14:57:30",
)
flomni._check_eye_out_and_optics_in = lambda: False
flomni.OMNYTools.yesno = lambda *a, **k: False # decline "Shall I continue?"
flomni._tomo_scan_at_angle = lambda angle, subtomo, **overrides: pytest.fail(
"must not acquire -- the gate declined"
)
flomni.tomo_scan() # returns early, no exception
assert flomni.progress["projection"] == 0
assert flomni.progress["total_projections"] == 1
assert flomni.progress["angle"] == 0
assert flomni.progress["subtomo"] == 0
assert flomni.progress["tomo_type"] == 0
assert flomni.progress["tomo_start_time"] is None
assert flomni.progress["heartbeat"] is None
def test_new_scan_resets_progress_before_angle_file_error(monkeypatch, tmp_path):
"""Same as above, but the abort comes from the tomo_type-4 upfront angle-
file validation, which runs even earlier than the eye/optics gate."""
from csaxs_bec.bec_ipython_client.plugins.flomni.flomni import FlomniError
flomni = make_flomni_for_angle_file(monkeypatch, tmp_path, "10\nEND\n")
flomni.tomo_angle_file = str(tmp_path / "gone.txt")
flomni.progress.update(projection=42, tomo_start_time="2026-09-25T14:57:28")
with pytest.raises(FlomniError, match="Cannot read"):
flomni.tomo_scan()
assert flomni.progress["projection"] == 0
assert flomni.progress["tomo_start_time"] is None
def test_resume_does_not_reset_progress(monkeypatch, tmp_path):
"""A *resume* (projection_number given) must not wipe progress -- only a
genuinely new scan start does."""
flomni = make_flomni_for_angle_file(monkeypatch, tmp_path, "10\n20\n30\nEND\n")
flomni.progress.update(estimated_remaining_time=42.0)
reset_calls = []
monkeypatch.setattr(flomni._progress_proxy, "reset", lambda: reset_calls.append(1))
flomni._tomo_scan_at_angle = lambda angle, subtomo, **overrides: None
flomni.tomo_scan(projection_number=1)
assert reset_calls == []
assert flomni.progress["estimated_remaining_time"] == 42.0