fix(flomni): reset tomo progress before pre-flight gates, not after
CI for csaxs_bec / test (push) Successful in 3m18s
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:
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
|
||||
Reference in new issue
Block a user