Add min peak frequency floor (CACH v4) and Batch Export View as Images
Two strands of in-progress work, committed together because they overlap in sras_workers.py and main_window.py. Min peak frequency floor: - CACH tail bumped to version 4, adding u32 min_freq_khz provenance in fixed-point kHz (a float32 20.1 reads back as 20.10000038 and would report a spurious mismatch forever). v1-v3 tails read as no floor. - Stored FFT caches are accepted when the reader's floor is at or above the stored one, since a higher floor is re-applicable by masking. - Floor plumbed through compute_rf_image, BatchCacheWorker and the viewer. Batch Export View as Images: - New sras_render.py holds draw_view_image, shared by the Qt canvas and the headless exporter so a PNG cannot drift from what the GUI shows. Deliberately Qt-free so it is importable in a pool subprocess. - BatchExportImagesWorker renders the current view settings across many files, process-pooled with an inline fallback, reporting per-file output names so the caller can flag same-stem collisions. - _axes_extent extracted into sras_format for both render paths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+18
-2
@@ -157,6 +157,18 @@ by `cached_rf_image` before it hands the image back. Getting this wrong is
|
||||
not a slow display, it is a *wrong* display, which is why the check is a
|
||||
single predicate in one place rather than spread across callers.
|
||||
|
||||
The min peak frequency floor is the fourth recorded setting, and it sits
|
||||
between the DC threshold (fully re-applicable at display time) and the
|
||||
baked-in three: it is *tighten-only* re-applicable. A floor at or above the
|
||||
stored one is served by masking stored pixels below it to the 0.0 "no valid
|
||||
peak" sentinel — invalidated, not re-resolved; only a real recompute can
|
||||
find their true above-floor peak. A floor *below* the stored one is a
|
||||
genuine mismatch: the stored search never looked at those bins. Because a
|
||||
served-then-masked image is a lossy stand-in for a real floored compute,
|
||||
`cache_file` passes `use_stored=False` so a batch recompute always runs the
|
||||
real FFT — otherwise re-batching a cached file would silently bake the
|
||||
masked copy of its own old image back into the store.
|
||||
|
||||
Padding is the subtlest of the three, because a padded FFT looks like it
|
||||
should be a refinement of the unpadded one. It isn't: zero-padding
|
||||
interpolates between the natural bins, so it resolves a different peak
|
||||
@@ -183,8 +195,12 @@ recompute. The *display* path (`_stored_fft_image`) never asks
|
||||
bg-sub/pad/row-averaging controls — it asks whether the image matches its
|
||||
*own* recorded settings (`sras.precomputed_bg_sub`/`precomputed_pad_factor`/
|
||||
`precomputed_row_avg_n`), which is always true whenever a stored image
|
||||
exists. So presence alone decides whether it's shown; the live controls
|
||||
never gate it. They still matter for two things: a genuinely never-computed
|
||||
exists. Two settings are taken live instead: the DC threshold and the min
|
||||
peak frequency floor, both cheaply re-appliable as masks on serve. So for
|
||||
bg-sub/pad/row-avg, presence alone decides whether a stored image is shown;
|
||||
the floor is the one case where a live control can gate it — a live floor
|
||||
*below* the stored one can't be answered by masking, so `cached_rf_image`
|
||||
declines and the caller falls through to a real compute. They still matter for two things: a genuinely never-computed
|
||||
angle's first live compute, and an explicit batch recompute — both of which
|
||||
read the live controls and produce new stored data, at which point it's the
|
||||
new data's *own* settings that get self-matched from then on. This is what
|
||||
|
||||
Reference in New Issue
Block a user