Commit Graph

4 Commits

Author SHA1 Message Date
Thomas Ales 00a7afade0 Dedup format layer; public accessors replace private reach-throughs
- SrasFile: _parse_v6 now retains per-angle x_delta and the verbatim
  preamble/background byte spans, and gains public data_offset,
  y_pos_per_angle, and iter_angle_blocks() (which now owns the ragged
  block-offset walk used three separate places before).
- sras_edit_scans: the 70-line re-parse of the v6 header sections
  (_read_v6_sections/_reread_span/_v6_angle_offsets) collapses into a
  _write_v6 that consumes SrasFile directly — verified byte-identical
  round-trip on v6 int8/int16 and legacy files. Eight print-and-exit
  pairs become _die().
- tools/make_test_sras imports the struct layouts from sras_format and
  the rotation matrix from sras_compute instead of re-declaring them
  (byte assembly stays independent of the reader).
- sras_compute: block_mean_2d, pixel_pitch_mm, nominal_delta_deg made
  public (they were GUI-facing); registration_workers() and
  default_max_workers() wrap the remaining private reach-throughs from
  sras_workers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 10:40:42 -05:00
Thomas Ales 9a9a2557d6 Persist FFT settings, fix backend naming, parallelise batch DC caching
- FFT backend and pad factor persist across sessions via QSettings
  (IniFormat; tests redirect the settings path for hermeticity).
- The default backend was labelled "NumPy FFT" but always dispatched to
  scipy.fft — rename the canonical value to "scipy" ("numpy" stays as a
  legacy alias) and fix the dialog label.
- cache_file: DC caching fans out over angles via _parallel_map with
  per-angle budgets (the DcPrecomputeWorker pattern); FFT caching stays
  serial per angle because compute_rf_image now parallelises internally
  over blocks. Documented that the v7 FFT cache is natural-resolution
  (pad 1) by design.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 10:26:50 -05:00
Thomas Ales 11ff3b62e2 Rewrite the FFT peak search: block-parallel zoom refinement
At pad 40 the old path materialised a ~9 GB padded spectrum per row,
which collapsed the chunk planner to one worker and one rfft call with
workers=1 — synthesis ran single-threaded, ~1 hour per angle on real
files.

The padded spectrum is never materialised now. Each block of 512
waveforms gets a coarse rfft at next_fast_len(2*spf); every coarse bin
within 0.7 of its row's max (plus the DC-adjacent window, which coarse
DC suppression would otherwise blind) is refined onto the exact n_fft
grid by a small complex gemm. The selected bin is bit-identical to the
full padded argmax — enforced by test_zoom_identity, a 25-seed fuzz
test over adversarial spectra, and a clean golden-hash diff against the
pre-rewrite baseline across pads {1,2,4,8,40}, masked/unmasked, bg
on/off, int8/int16, and both backends.

Blocks fan out over a persistent thread pool; pyFFTW runs through
per-thread FFTW_MEASURE builder plans with wisdom persisted to
~/.cache/sras-viewer, and threadpoolctl clamps BLAS under the pool.
compute_rf_image(exact=True) (or SRAS_FFT_EXACT=1) keeps the reference
padded path for audits.

tools/bench_fft.py measures: pad 40, 16 cores, 8192x2500 synthetic —
exact serial 717 wf/s -> zoom pool 25100 wf/s (35x, pyFFTW backend;
19x scipy), every variant verified equal to the reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 10:20:14 -05:00
Thomas Ales 007089dd48 Convert test scripts to pytest; extend the equivalence harness
- pyproject.toml replaces sras_viewer_requirements.txt (same pins) and
  adds a dev extra with pytest.
- tools/test_refactor.py, test_alignment.py, test_gui.py become
  tests/test_compute.py, tests/test_alignment.py, tests/test_gui.py with
  assertions preserved verbatim. test_gui.py stays one ordered
  integration sequence over a shared module-scoped window.
- check_equivalence.py: drop the dead pre-refactor monolith shim (and the
  _compute_angle_alignment alias it consumed), extend the pad sweep to
  (1, 2, 4, 8, 40), add legacy-v4 and big-endian int16 legs (new bps=2
  option in make_test_sras) so the padded FFT path and the >i2 memmap
  path are in the baseline before the FFT rewrite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 09:45:35 -05:00