Stop discarding precomputed FFT data on view switches
The display path hardcoded row_avg_n=0 when checking a file's stored FFT cache, since the main window has no row-averaging control (only the batch-only RowAverageFftOptionsDialog). Once a file was batch-computed with row-averaging, every view switch saw a phantom provenance mismatch and silently launched a full raw recompute, discarding the precomputed data. The display now asks for the stored image at the settings it was actually computed under (bg-sub/pad/row-averaging), rather than the window's live controls, so a stored or already-computed image is always shown once present. Those controls now only affect a first-time compute for an uncached angle or an explicit batch recompute -- never what's already on screen. DC threshold is unaffected: it stays live, since re-masking a cached image is free. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+53
-16
@@ -256,11 +256,16 @@ def test_viewer_shows_stored_angles_without_computing(rig, no_fft, monkeypatch):
|
||||
rig.fresh[0] * win.spin_grating_um.value(), atol=1e-3)
|
||||
assert dispatched == [] and not no_fft
|
||||
|
||||
# ...but a setting the stored image cannot serve must still recompute,
|
||||
# or the fast path would be showing the wrong picture.
|
||||
# ...and a live control that no longer matches the stored image's own
|
||||
# provenance must NOT force a recompute either — the stored image is
|
||||
# shown as-is; only an explicit batch recompute changes what's shown.
|
||||
win.combo_channel.setCurrentIndex(CH1_IDX)
|
||||
pump(60)
|
||||
win.chk_bg_sub.setChecked(False)
|
||||
assert wait_until(lambda: not win._job_running("compute") and bool(no_fft)), \
|
||||
"bg-sub off falls through to a real FFT"
|
||||
pump(200)
|
||||
assert not win._job_running("compute") and not no_fft, \
|
||||
"bg-sub off still shows the stored image, no real FFT"
|
||||
assert np.allclose(win._current_image, rig.fresh[0], atol=1e-3)
|
||||
finally:
|
||||
win.close()
|
||||
pump(300)
|
||||
@@ -322,16 +327,21 @@ def test_batch_caches_at_the_viewers_pad_factor(tmp_path, no_fft, monkeypatch):
|
||||
f"no recompute at pad 10 (jobs={dispatched}, fft={no_fft})"
|
||||
assert "unusable" not in win.lbl_frame_warn.text()
|
||||
|
||||
# Change the pad and the cache legitimately stops applying — and the
|
||||
# info panel has to say so rather than leave it a mystery.
|
||||
# Change the live pad control so it no longer matches the stored
|
||||
# image's own provenance — the info panel has to say so rather than
|
||||
# leave it a mystery, but the stored pad-10 image keeps displaying;
|
||||
# only an explicit batch recompute would ever produce a pad-4 one.
|
||||
win._fft_pad_factor = 4
|
||||
win._update_scan_info_labels()
|
||||
assert "Cached FFT unusable" in win.lbl_frame_warn.text(), \
|
||||
assert "differ from the stored cache" in win.lbl_frame_warn.text(), \
|
||||
win.lbl_frame_warn.text()
|
||||
assert "pad 10x" in win.lbl_frame_warn.text()
|
||||
last_angle = win._current_angle
|
||||
win._refresh_display()
|
||||
assert wait_until(lambda: not win._job_running("compute") and bool(no_fft)), \
|
||||
"pad 4 recomputes rather than reusing the pad-10 cache"
|
||||
assert wait_until(lambda: not win._job_running("compute")), "settled"
|
||||
assert not no_fft, "no real FFT ran — the pad-10 cache still served the view"
|
||||
assert np.allclose(win._current_image, expected[last_angle], atol=1e-3), \
|
||||
"pad-10 cache still shown after the live pad control diverged"
|
||||
finally:
|
||||
win.close()
|
||||
pump(300)
|
||||
@@ -500,16 +510,17 @@ def test_cach_v1_backward_compat_defaults_row_avg_n_zero(tmp_path):
|
||||
"a v1 tail (predating this feature) can never satisfy a row-averaged request"
|
||||
|
||||
|
||||
def test_viewer_batch_row_average_dispatch(tmp_path, monkeypatch):
|
||||
def test_viewer_batch_row_average_dispatch(tmp_path, monkeypatch, no_fft):
|
||||
"""Driving the new 'Batch Compute Row-Averaged FFT and Store' action
|
||||
end-to-end through the real menu handler: dialog values reach the
|
||||
worker, the worker reaches cache_file, and the written file is
|
||||
self-describing afterward. Deliberately does not assert anything about
|
||||
whether viewing an angle afterward dispatches a compute job -- that is
|
||||
a separate, pre-existing gap in _refresh_display shared with the plain
|
||||
DC/FFT batch actions (see test_viewer_shows_stored_angles_without_computing
|
||||
/ test_viewer_shows_stored_dc_without_computing above), not something
|
||||
row-averaging introduces or is responsible for fixing."""
|
||||
self-describing afterward. Also the exact scenario the row-averaged-FFT
|
||||
recompute bug reported: before the fix, the display always asked for
|
||||
row_avg_n=0 regardless of what the file actually had stored, so viewing
|
||||
an angle after this batch action saw a phantom mismatch and launched a
|
||||
full raw recompute on every view switch. This asserts that no longer
|
||||
happens -- the stored row-averaged image is shown directly, with no
|
||||
dispatched compute job and no real FFT."""
|
||||
path = tmp_path / "rowavg_gui.sras"
|
||||
gen.write(path, n_angles=2, seed=26, samples_per_frame=128)
|
||||
|
||||
@@ -531,6 +542,12 @@ def test_viewer_batch_row_average_dispatch(tmp_path, monkeypatch):
|
||||
app = QApplication.instance() or QApplication([]) # noqa: F841
|
||||
win = SrasViewerWindow()
|
||||
win.show()
|
||||
|
||||
dispatched = []
|
||||
original_start = type(win)._start_compute
|
||||
monkeypatch.setattr(type(win), "_start_compute",
|
||||
lambda self: (dispatched.append(self.spin_angle.value()),
|
||||
original_start(self))[1])
|
||||
try:
|
||||
win._load_file(str(path))
|
||||
assert wait_until(lambda: win._sras is not None), "file loaded"
|
||||
@@ -553,6 +570,26 @@ def test_viewer_batch_row_average_dispatch(tmp_path, monkeypatch):
|
||||
apply_bg_sub=win.chk_bg_sub.isChecked(),
|
||||
row_avg_n=6)
|
||||
assert expected is not None, "the batch write left a readable row-averaged cache"
|
||||
|
||||
# The regression this batch action used to leave unfixed: viewing an
|
||||
# angle afterward must show the stored row-averaged image directly,
|
||||
# never fall through to a real (raw) recompute.
|
||||
no_fft.clear()
|
||||
dispatched.clear()
|
||||
win.combo_channel.setCurrentIndex(CH1_IDX)
|
||||
assert wait_until(lambda: win._current_ch == CH1_IDX), "CH1 displayed"
|
||||
for a in range(win._sras.n_angles):
|
||||
win.spin_angle.setValue(a)
|
||||
pump(60)
|
||||
assert np.allclose(win._current_image,
|
||||
compute.cached_rf_image(
|
||||
win._sras, a,
|
||||
dc_threshold_mv=win.spin_threshold_mv.value(),
|
||||
apply_bg_sub=win.chk_bg_sub.isChecked(),
|
||||
row_avg_n=6), atol=1e-3), \
|
||||
f"angle {a} shows the stored row-averaged image"
|
||||
assert dispatched == [] and not no_fft, \
|
||||
f"no recompute for row-averaged angles (jobs={dispatched}, fft={no_fft})"
|
||||
finally:
|
||||
win.close()
|
||||
pump(300)
|
||||
|
||||
Reference in New Issue
Block a user