Implement FFT pad-factor caching and the stored-cache display fast path
tests/test_stored_cache.py exercised two features that were never built, so six of its tests had been failing on main. Both are now implemented. Pad-factor caching. A padded view could not use a stored FFT cache at all: the store was pad 1 by definition and cached_rf_image rejected any n_fft outright, so a user working at a pad factor got nothing from batch-computing a file. CACH tail v3 records the pad the images were resolved at, cache_file computes at a requested pad, and the batch actions pass the viewer's own pad down — while still refusing a store resolved at a different pad, since a padded FFT interpolates between the natural bins and so resolves genuinely different peak frequencies. v1/v2 tails read as pad 1 and keep working. Stored-cache dispatch. _refresh_display only ever consulted this window's in-session dicts, so after a batch every angle change still queued a worker and a progress popup for an image already on disk — the exact cost the batch was run to avoid. It now checks the file's own DC/FFT blocks first, asking with allow_dc_recompute=False so the GUI thread never touches I/O. When the stored cache genuinely cannot serve the view, the scan info panel says why rather than leaving the silent recompute a mystery. Two bugs surfaced on the way: - The angle spinbox was wired on editingFinished, which QAbstractSpinBox emits only on Return or focus-out — never on a step. Clicking its arrows, the ordinary way to walk a scan, moved the number and left the image behind. Now valueChanged with keyboard tracking off, which fires on a step and once on commit, but not per keystroke mid-typing. Every existing GUI test called _on_view_changed() by hand and so could not have caught this; two new tests pin it and fail against the old wiring. - A plain "fft" batch over a file previously cached with fft_rowavg carried the old row_avg_n forward, labelling raw images as row-averaged. It now writes row_avg_n=0 explicitly. Verified: 111 passed (was 103 passed / 6 failed), and tools/check_equivalence.py is byte-identical to the pre-change baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,71 @@ def test_angle_switching_from_cache(ctx):
|
||||
"no compute job needed for cached DC angles"
|
||||
|
||||
|
||||
def test_stepping_the_angle_spinbox_redraws(ctx):
|
||||
"""Clicking the angle spinbox's arrows (or pressing Up/Down in it) must
|
||||
move the display, not just the number.
|
||||
|
||||
This is the ordinary way to walk a scan, and it used to do nothing: the
|
||||
spinbox was wired on editingFinished, which QAbstractSpinBox emits only
|
||||
on Return or focus-out — never on a step. Every other test in this module
|
||||
called _on_view_changed() by hand and so could not have caught it.
|
||||
"""
|
||||
win, s = ctx.win, ctx.s
|
||||
assert s.n_angles >= 3, "need room to step in both directions"
|
||||
|
||||
win.spin_angle.setValue(0)
|
||||
assert wait_until(lambda: win._current_angle == 0), "settled on angle 0"
|
||||
|
||||
for expected in range(1, s.n_angles):
|
||||
win.spin_angle.stepUp()
|
||||
assert wait_until(lambda e=expected: win._current_angle == e), \
|
||||
f"stepping up to angle {expected} redrew the display"
|
||||
|
||||
win.spin_angle.stepDown()
|
||||
assert wait_until(lambda: win._current_angle == s.n_angles - 2), \
|
||||
"stepping down redraws too"
|
||||
|
||||
# Keyboard stepping goes through the same signal, so it must work as well.
|
||||
QTest.keyClick(win.spin_angle, Qt.Key.Key_Down)
|
||||
assert wait_until(lambda: win._current_angle == s.n_angles - 3), \
|
||||
"Key_Down redraws"
|
||||
|
||||
win.spin_angle.setValue(0)
|
||||
assert wait_until(lambda: win._current_angle == 0), "back to angle 0"
|
||||
|
||||
|
||||
def test_typing_an_angle_does_not_compute_intermediate_angles(ctx):
|
||||
"""Keyboard tracking must stay off: with it on, valueChanged fires per
|
||||
keystroke, so typing "12" would dispatch a compute for angle 1 first —
|
||||
on a real scan, a whole wasted FFT for an angle the user never asked for.
|
||||
"""
|
||||
win, s = ctx.win, ctx.s
|
||||
assert not win.spin_angle.keyboardTracking(), \
|
||||
"keyboard tracking off is what makes valueChanged safe to connect"
|
||||
|
||||
target = s.n_angles - 1
|
||||
assert target >= 2, "need a multi-digit-ish range to make the point"
|
||||
win.spin_angle.setValue(0)
|
||||
wait_until(lambda: win._current_angle == 0)
|
||||
|
||||
seen = []
|
||||
win.spin_angle.valueChanged.connect(seen.append)
|
||||
try:
|
||||
win.spin_angle.lineEdit().selectAll()
|
||||
QTest.keyClicks(win.spin_angle, str(target))
|
||||
pump(60)
|
||||
assert seen == [], f"no signal while typing, got {seen}"
|
||||
QTest.keyClick(win.spin_angle, Qt.Key.Key_Return)
|
||||
pump(60)
|
||||
assert seen == [target], f"one signal on commit, got {seen}"
|
||||
finally:
|
||||
win.spin_angle.valueChanged.disconnect(seen.append)
|
||||
assert wait_until(lambda: win._current_angle == target), "committed angle shown"
|
||||
|
||||
win.spin_angle.setValue(0)
|
||||
assert wait_until(lambda: win._current_angle == 0), "back to angle 0"
|
||||
|
||||
|
||||
def test_channel_switching(ctx):
|
||||
win = ctx.win
|
||||
win.spin_angle.setValue(0)
|
||||
|
||||
Reference in New Issue
Block a user