FFT physics review: window-envelope compensation, odd-size soft-border centring#29
Merged
Conversation
… for odd sizes FFT physics review pass (2026-06-12), pinned by new tests in tests/test_fft_physics.py: - fourier_filter windowed the data before the transform (GUI default: Hann) but never divided the window back out after the inverse, so every radial FFT filter result carried the window envelope — vignetted toward the mean at the borders (edge std 0.03 vs centre 0.77 on unit-variance noise). The pre-existing near-identity tests used cutoff exactly 1.0/0.0, which early-return before any windowing and could not see it. fft_soft_border has always compensated its taper; fourier_filter now does the same, with the compensation gain capped at 20x so near-zero edge weights cannot amplify residual spectral leakage into spikes. The window="none" path is bit-identical to before (pinned). Note: replaying saved states that used a window now produces the corrected (envelope-free) output — same correctness-over-replay precedent as the gradient-slope and qPlus-setpoint fixes. - fft_soft_border's radial mask used index arithmetic centred at N/2.0, which is identical to the fftfreq convention for even sizes (pinned by a bit-equality test) but sat half a pixel off the fftshift DC bin for odd ones — a tiny low-pass could miss DC and shift the image mean. The mask now uses the same fftfreq-based cycles/pixel convention as fourier_filter. Verified clean in the same pass: fft_magnitude (axes, coherent gain, ROI mean handling), inverse_fft (raw unwindowed transform, conjugate-exact masks, imaginary-residual reporting), mains q-axes, the FFT viewer's q-axis construction and radial profile (per-axis physical d=, isotropic q-map), measurements/fft_points (explicit d= with honest cycles/pixel vs cycles/unit labels), and line_periodicity (rfftfreq with physical spacing, window-gain-normalised power, DC excluded). One suspicion was disproved by test: the soft-border half-axis normalisation is exactly equivalent to cycles/pixel (Δx/cx = 2f), not elliptical. Co-Authored-By: Claude Fable 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Dedicated physics review of the FFT stack (kernels + viewer axis handling).
Findings
[CRITICAL]
processing/filters.pyfourier_filter— fixedWhat: data windowed before the FFT (GUI default Hann) but never divided back out after the inverse.
Why: the window envelope stayed baked into every radial-FFT-filter result — output vignetted toward the mean at the borders (measured: edge std 0.03 vs centre 0.77 on unit-variance noise). Existing near-identity tests used cutoff exactly 1.0/0.0, which early-return before windowing.
Fix: divide the window back out post-inverse (the approach
fft_soft_borderalways used), gain capped at 20×;window="none"pinned bit-identical.[INFO]
fft_soft_border— fixedRadial mask centre was half a pixel off the fftshift DC bin for odd sizes (index arithmetic at N/2.0). Now uses the fftfreq cycles/pixel convention shared with
fourier_filter; even sizes pinned bit-identical to the old mask.Disproved by test: the soft-border "half-axis ellipse" suspicion — Δx/cx = 2f makes it exactly the cycles/pixel convention; both stripe orientations filter identically on a 64×256 image.
Verified clean:
fft_magnitude(axes/coherent gain/ROI mean),inverse_fft(raw transform, conjugate-exact masks, imag-residual reporting), mains q-axes, viewer q-axis + radial profile (per-axis physicald=),fft_points(honest unit labels),line_periodicity(physical rfftfreq, gain-normalised power).Replay note
Saved states using a window now replay with the corrected (envelope-free) output — same correctness-over-replay precedent as the gradient-slope and qPlus-setpoint fixes.
Test plan
window="none"bit-equality, odd-size DC preservation, even-size mask-rewrite bit-equality.🤖 Generated with Claude Code