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:
@@ -1291,6 +1291,10 @@ class SrasViewerWindow(QMainWindow):
|
|||||||
# need to re-read the CH4 channel from disk.
|
# need to re-read the CH4 channel from disk.
|
||||||
dc4_mv=self._dc_cache.get((angle_idx, CH4_IDX)),
|
dc4_mv=self._dc_cache.get((angle_idx, CH4_IDX)),
|
||||||
is_fft_mode=is_fft,
|
is_fft_mode=is_fft,
|
||||||
|
# Mirror _stored_fft_image: a stored row-averaged cache must stay
|
||||||
|
# eligible even when this fallback compute is the one that ends
|
||||||
|
# up serving it (e.g. before DC precompute reaches this angle).
|
||||||
|
row_avg_n=self._sras.precomputed_row_avg_n,
|
||||||
)
|
)
|
||||||
if not self._run_worker(
|
if not self._run_worker(
|
||||||
Jobs.COMPUTE, worker,
|
Jobs.COMPUTE, worker,
|
||||||
|
|||||||
+6
-3
@@ -119,7 +119,8 @@ class ComputeWorker(CancellableWorker):
|
|||||||
apply_bg_sub: bool = True, n_fft: int | None = None,
|
apply_bg_sub: bool = True, n_fft: int | None = None,
|
||||||
dc_threshold_mv: float = 0.0,
|
dc_threshold_mv: float = 0.0,
|
||||||
dc4_mv: np.ndarray | None = None,
|
dc4_mv: np.ndarray | None = None,
|
||||||
is_fft_mode: bool = False):
|
is_fft_mode: bool = False,
|
||||||
|
row_avg_n: int = 0):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._sras = sras
|
self._sras = sras
|
||||||
self._angle = angle_idx
|
self._angle = angle_idx
|
||||||
@@ -129,6 +130,7 @@ class ComputeWorker(CancellableWorker):
|
|||||||
self._dc_threshold = dc_threshold_mv
|
self._dc_threshold = dc_threshold_mv
|
||||||
self._dc4_mv = dc4_mv
|
self._dc4_mv = dc4_mv
|
||||||
self._is_fft_mode = is_fft_mode
|
self._is_fft_mode = is_fft_mode
|
||||||
|
self._row_avg_n = row_avg_n
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
@@ -136,7 +138,8 @@ class ComputeWorker(CancellableWorker):
|
|||||||
img = compute_rf_image(
|
img = compute_rf_image(
|
||||||
self._sras, self._angle, dc_threshold_mv=self._dc_threshold,
|
self._sras, self._angle, dc_threshold_mv=self._dc_threshold,
|
||||||
apply_bg_sub=self._apply_bg_sub, n_fft=self._n_fft,
|
apply_bg_sub=self._apply_bg_sub, n_fft=self._n_fft,
|
||||||
dc4_mv=self._dc4_mv, should_stop=self._stopped)
|
dc4_mv=self._dc4_mv, should_stop=self._stopped,
|
||||||
|
row_avg_n=self._row_avg_n)
|
||||||
else:
|
else:
|
||||||
img = dc_image_mv(self._sras, self._angle, self._ch,
|
img = dc_image_mv(self._sras, self._angle, self._ch,
|
||||||
should_stop=self._stopped)
|
should_stop=self._stopped)
|
||||||
@@ -412,7 +415,7 @@ class BatchExportWorker(CancellableWorker):
|
|||||||
freq_mhz = compute_rf_image(
|
freq_mhz = compute_rf_image(
|
||||||
s, angle_idx, dc_threshold_mv=self._dc_threshold_mv,
|
s, angle_idx, dc_threshold_mv=self._dc_threshold_mv,
|
||||||
apply_bg_sub=self._apply_bg_sub, n_fft=self._n_fft,
|
apply_bg_sub=self._apply_bg_sub, n_fft=self._n_fft,
|
||||||
should_stop=self._stopped)
|
should_stop=self._stopped, row_avg_n=s.precomputed_row_avg_n)
|
||||||
|
|
||||||
for channel in self._channels:
|
for channel in self._channels:
|
||||||
if self._stop:
|
if self._stop:
|
||||||
|
|||||||
@@ -593,3 +593,67 @@ def test_viewer_batch_row_average_dispatch(tmp_path, monkeypatch, no_fft):
|
|||||||
finally:
|
finally:
|
||||||
win.close()
|
win.close()
|
||||||
pump(300)
|
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user