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>
This commit is contained in:
+22
-9
@@ -25,7 +25,7 @@ from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg, NavigationToolb
|
||||
from matplotlib.figure import Figure
|
||||
from matplotlib.patches import Polygon
|
||||
from matplotlib.path import Path as MplPath
|
||||
from PyQt6.QtCore import QObject, Qt, QThread, pyqtSignal
|
||||
from PyQt6.QtCore import QObject, QSettings, Qt, QThread, pyqtSignal
|
||||
from PyQt6.QtGui import QAction, QKeyEvent
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication, QButtonGroup, QCheckBox, QComboBox, QDialog, QDialogButtonBox,
|
||||
@@ -655,22 +655,22 @@ class FftOptionsDialog(QDialog):
|
||||
grp_backend = QGroupBox("FFT Backend")
|
||||
bl = QVBoxLayout(grp_backend)
|
||||
|
||||
self._btn_numpy = QRadioButton("NumPy FFT (always available)")
|
||||
self._btn_scipy = QRadioButton("SciPy FFT (pocketfft) (always available)")
|
||||
self._btn_pyfftw = QRadioButton(
|
||||
"pyFFTW (faster for large arrays)" if PYFFTW_AVAILABLE
|
||||
else "pyFFTW (not installed — run: pip install pyfftw)")
|
||||
self._btn_pyfftw.setEnabled(PYFFTW_AVAILABLE)
|
||||
|
||||
self._backend_group = QButtonGroup(self)
|
||||
self._backend_group.addButton(self._btn_numpy, id=0)
|
||||
self._backend_group.addButton(self._btn_scipy, id=0)
|
||||
self._backend_group.addButton(self._btn_pyfftw, id=1)
|
||||
|
||||
if current_backend == "pyfftw" and PYFFTW_AVAILABLE:
|
||||
self._btn_pyfftw.setChecked(True)
|
||||
else:
|
||||
self._btn_numpy.setChecked(True)
|
||||
self._btn_scipy.setChecked(True)
|
||||
|
||||
bl.addWidget(self._btn_numpy)
|
||||
bl.addWidget(self._btn_scipy)
|
||||
bl.addWidget(self._btn_pyfftw)
|
||||
layout.addWidget(grp_backend)
|
||||
|
||||
@@ -737,7 +737,7 @@ class FftOptionsDialog(QDialog):
|
||||
f"(at grating = {self._grating_um:.2f} µm)")
|
||||
|
||||
def get_backend(self) -> str:
|
||||
return "pyfftw" if self._btn_pyfftw.isChecked() and PYFFTW_AVAILABLE else "numpy"
|
||||
return "pyfftw" if self._btn_pyfftw.isChecked() and PYFFTW_AVAILABLE else "scipy"
|
||||
|
||||
def get_pad_factor(self) -> int:
|
||||
return max(1, self._spin_pad.value())
|
||||
@@ -1451,8 +1451,18 @@ class SrasViewerWindow(QMainWindow):
|
||||
self._jobs: dict[str, tuple] = {}
|
||||
self._progress_dlgs: dict[str, QProgressDialog] = {}
|
||||
|
||||
# FFT settings (configured via FFT Options dialog)
|
||||
self._fft_pad_factor: int = 1 # 1 = no padding
|
||||
# FFT settings (configured via FFT Options dialog, persisted across
|
||||
# sessions). IniFormat: predictable cross-platform and redirectable
|
||||
# in tests.
|
||||
self._settings = QSettings(QSettings.Format.IniFormat,
|
||||
QSettings.Scope.UserScope,
|
||||
"sras-viewer", "sras-viewer")
|
||||
compute.set_fft_backend(str(self._settings.value("fft/backend", "scipy")))
|
||||
try:
|
||||
pad = int(self._settings.value("fft/pad_factor", 1))
|
||||
except (TypeError, ValueError):
|
||||
pad = 1
|
||||
self._fft_pad_factor: int = max(1, min(256, pad)) # 1 = no padding
|
||||
|
||||
# Convert menu: batch DC/FFT compute-and-store (v6 -> v7)
|
||||
self._batch_errors: list[str] = []
|
||||
@@ -1846,7 +1856,8 @@ class SrasViewerWindow(QMainWindow):
|
||||
self._batch_fft_act = QAction("Batch Compute FFT and Sto&re…", self)
|
||||
self._batch_fft_act.setStatusTip(
|
||||
"Select .sras files and compute+store FFT peak-frequency images "
|
||||
"for every angle, converting v6 files to v7 in place.")
|
||||
"for every angle, converting v6 files to v7 in place. Stored "
|
||||
"images are natural-resolution (pad 1); padded views compute live.")
|
||||
self._batch_fft_act.triggered.connect(lambda: self._on_batch_compute("fft"))
|
||||
convert_menu.addAction(self._batch_fft_act)
|
||||
|
||||
@@ -2748,6 +2759,8 @@ class SrasViewerWindow(QMainWindow):
|
||||
return
|
||||
compute.set_fft_backend(dlg.get_backend())
|
||||
self._fft_pad_factor = dlg.get_pad_factor()
|
||||
self._settings.setValue("fft/backend", compute.get_fft_backend())
|
||||
self._settings.setValue("fft/pad_factor", self._fft_pad_factor)
|
||||
# Pad factor changes the FFT bin count, so it genuinely invalidates
|
||||
# the cached raw FFT (part of the cache key) — _refresh_display()
|
||||
# recomputes only on a cache miss.
|
||||
|
||||
Reference in New Issue
Block a user