Keep stored row-averaged caches eligible on the fallback compute path

ComputeWorker dropped row_avg_n, so a stored row-averaged cache was
ineligible whenever the fallback compute served the image instead of the
DC precompute path. Thread the viewer's precomputed_row_avg_n through
ComputeWorker and the batch export worker's compute_rf_image call.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Ales [M S E]
2026-08-12 10:17:16 -05:00
parent ec0d322871
commit efecc154fe
3 changed files with 74 additions and 3 deletions
+64
View File
@@ -593,3 +593,67 @@ def test_viewer_batch_row_average_dispatch(tmp_path, monkeypatch, no_fft):
finally:
win.close()
pump(300)
def test_compute_worker_fallback_honors_row_avg_n(tmp_path, monkeypatch, no_fft):
"""The other half of the row-averaged recompute bug: _stored_fft_image
isn't the only path that can serve a view. When the DC4 mask isn't known
yet without I/O (e.g. background DC precompute hasn't reached this angle),
_stored_fft_image's allow_dc_recompute=False guard bails and
_refresh_display falls through to _start_compute's ComputeWorker instead.
That worker must still ask compute_rf_image for the file's own
row_avg_n -- not silently default to 0 -- so its own internal cache
fast path also serves the stored row-averaged image rather than running
a real, non-averaged FFT."""
path = tmp_path / "rowavg_fallback.sras"
gen.write(path, n_angles=2, seed=33, samples_per_frame=128)
# Only fft_rowavg -- deliberately no DC block, exactly what "Batch
# Compute Row-Averaged FFT and Store" leaves on disk by itself.
err = cache_file(str(path), "fft_rowavg", True, dc_threshold_mv=-1e9, row_avg_n=5)
assert err == "", err
app = QApplication.instance() or QApplication([]) # noqa: F841
win = SrasViewerWindow()
win.show()
# Keep _dc_cache empty so _stored_fft_image can't resolve a mask without
# I/O and _refresh_display must fall through to _start_compute.
monkeypatch.setattr(type(win), "_start_dc_precompute", lambda self: None)
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"
assert win._sras.precomputed_row_avg_n == 5
# The initial default view (CH4, DC) has nothing cached either, so
# it dispatches its own one-off DC compute for angle 0 on load --
# unrelated to this bug. Let that settle, then clear it so no DC4 is
# available for any angle, simulating "background DC precompute
# hasn't reached this angle yet".
assert wait_until(lambda: not win._job_running("compute")), "initial DC view settled"
dispatched.clear()
win._dc_cache.clear()
no_fft.clear()
win.combo_channel.setCurrentIndex(CH1_IDX)
assert wait_until(lambda: win._current_ch == CH1_IDX
and not win._job_running("compute")), "CH1 displayed"
assert dispatched == [0], \
"with no DC cache available yet, the fallback compute must run"
assert not no_fft, \
f"the fallback's own compute_rf_image call must still hit the " \
f"stored row-averaged cache internally: {no_fft}"
expected = compute.cached_rf_image(
win._sras, 0, dc_threshold_mv=win.spin_threshold_mv.value(),
apply_bg_sub=win.chk_bg_sub.isChecked(), row_avg_n=5)
assert expected is not None
assert np.allclose(win._current_image, expected, atol=1e-3), \
"the displayed image is the stored row-averaged one, not a raw recompute"
finally:
win.close()
pump(300)