Skip to content

Fix output amplitude scaling in iterative Wiener filter (#420) - #447

Open
deekshaNVIDIA wants to merge 1 commit into
LCAV:masterfrom
deekshaNVIDIA:fix/iterative-wiener-output-scaling
Open

deekshaNVIDIA wants to merge 1 commit into
LCAV:masterfrom
deekshaNVIDIA:fix/iterative-wiener-output-scaling

Conversation

@deekshaNVIDIA

Copy link
Copy Markdown

Summary

Fixes #420.

IterativeWiener.compute_filtered_output divided the frame DFT by sqrt(frame_len) before applying the Wiener gain and returning the spectrum:

frame_dft /= np.sqrt(self.frame_len)
...
return self.wiener_filt * frame_dft

The spectrum is then handed to STFT.synthesis, which uses an unnormalized rfft/irfft pair (pyroomacoustics/transform/dft.py). So the returned spectrum must be on the same scale as stft.X. The extra 1 / sqrt(frame_len) factor therefore scaled the enhanced signal down by sqrt(nfft) (and, as a side effect, mutated the caller's stft.X in place).

This matches the report: rescaling the output by sqrt(nfft) recovered the input amplitude for nfft = 512, 1024, 2048.

Root cause / why removing the line is correct

The divided frame_dft was only used in two places:

  1. The returned spectrum self.wiener_filt * frame_dft — this is the bug.
  2. The intermediate frame s_i = irfft(self.wiener_filt * frame_dft), which feeds lpc(s_i, ...).

LPC coefficients are scale invariant (they depend on the normalized autocorrelation), and the gain g^2 is computed separately from current_frame (not from s_i). So the Wiener gain values are unchanged whether or not the division is applied — only the output amplitude changes. Removing the division fixes the amplitude and also removes the in-place mutation of stft.X.

Verification

Before (installed 0.10.1), output/input RMS ratio for a near-passthrough harmonic signal:

nfft ratio ratio × √nfft
512 0.0442 1.000
1024 0.0312 1.000
2048 0.0221 1.000

After the fix the ratio is ~1.0 for all sizes, and the relative denoising behaviour is unchanged.

Changes

  • Remove the erroneous frame_dft /= np.sqrt(self.frame_len) scaling in compute_filtered_output.
  • Add test_iterative_wiener_output_scaling, a regression test asserting output RMS ≈ input RMS across several FFT sizes (fails on the old code with ratio ≈ 0.044, passes now).
  • Add a CHANGELOG entry.

Test plan

  • pytest tests/denoise/test_iterative_wiener.py passes (2 passed).
  • New test fails on the previous (buggy) implementation.
  • black --check and isort --check-only pass on the changed files.

compute_filtered_output divided the frame DFT by sqrt(frame_len) before
applying the Wiener gain and returning it to STFT.synthesis, which uses an
unnormalized rfft/irfft pair. This scaled the enhanced signal down by
sqrt(nfft) (and mutated the caller's stft.X in place). The division is
unnecessary: the LPC coefficients derived from the intermediate time-domain
frame are scale invariant, so removing it leaves the denoising behaviour
unchanged while restoring the correct output amplitude.

Add a regression test asserting the output RMS matches the input RMS for a
near-passthrough signal across several FFT sizes.

Fixes LCAV#420

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Normalisation wiener filtering

1 participant