Skip to content

feat(cwt): add icwt for cmor wavelets via Torrence & Compo (1998)#135

Open
MilaGolomozin wants to merge 1 commit into
v0lta:mainfrom
MilaGolomozin:feature/icwt-cmor
Open

feat(cwt): add icwt for cmor wavelets via Torrence & Compo (1998)#135
MilaGolomozin wants to merge 1 commit into
v0lta:mainfrom
MilaGolomozin:feature/icwt-cmor

Conversation

@MilaGolomozin
Copy link
Copy Markdown

@MilaGolomozin MilaGolomozin commented May 13, 2026

Summary

Implements an inverse continuous wavelet transform (icwt) for complex Morlet (cmor) wavelets. The reconstruction follows Torrence & Compo (1998), Eq. 11–13:

$$x(t) = \frac{\delta j \sqrt{\delta t}}{\psi(0), C_\delta} \sum_j \frac{\text{Re}[W(a_j,, t)]}{\sqrt{a_j}}$$

where $C_\delta$ is a wavelet-dependent reconstruction constant computed numerically from the cmor Fourier transfer function (T&C Eq. 13).

Changes:

  • Add _cdelta_cmor() to compute the wavelet-dependent reconstruction constant C_delta (T&C Eq. 13) via numerical integration
  • Add icwt() accepting ContinuousWavelet | str, torch.Tensor input, and returning a torch.Tensor; GPU/autograd compatible
  • Export icwt from ptwt.init

Tests added to tests/test_continuous_transform.py:

  • test_cwt_cmor: forward CWT correctness for cmor wavelets (gap in existing test coverage; cmor requires parametrised B-C values unlike fixed-name wavelets such as mexh or cgau)
  • test_icwt: finiteness and valid C_delta across three cmor wavelets and two scale densities
  • test_icwt_cdelta_valid: C_delta is finite and positive for a range of cmor B/C parameter combinations
  • test_icwt_rejects_non_cmor: ValueError is raised for unsupported wavelet families (mexh, morl, cgau1, gaus1)
  • test_icwt_accepts_wavelet_object: icwt accepts both str and pywt.ContinuousWavelet, consistent with the cwt interface
  • test_icwt_cuda: output tensor lives on the same device as the input coefficients (CPU and CUDA)

Reconstruction quality

Evaluated on a mono voice recording (22050 Hz) using dynamic scales spanning
20 Hz to Nyquist at 48 voices per octave, across all 36 combinations of
$B \in {0.5, 1.0, 1.5, 2.0, 2.5, 3.0}$ and $C \in {0.5, 1.0, 1.5, 2.0, 2.5, 3.0}$.

Metrics explained:

  • RMSE — root mean square error between the original signal $x$ and the raw
    reconstruction $\hat{x}$: $\sqrt{\frac{1}{N}\sum(x - \hat{x})^2}$
  • Error (dB) — same error expressed in decibels: $20\log_{10}(\text{RMSE})$
  • $\alpha$ — optimal amplitude correction scalar that minimises
    $|x - \alpha\hat{x}|2$, computed as
    $\alpha = \langle x, \hat{x}\rangle / \langle \hat{x}, \hat{x}\rangle$.
    A value far from 1.0 indicates a residual amplitude offset in $C
    \delta$
    for that wavelet/scale configuration.
  • RMSE$_\alpha$ — RMSE after applying the $\alpha$ correction, isolating
    shape fidelity from amplitude error
  • Error$_\alpha$ (dB) — alpha-calibrated error in decibels

Top 5 wavelets by alpha-calibrated RMSE:

Wavelet $C_\delta$ RMSE Error (dB) $\alpha$ RMSE$_\alpha$ Error$_\alpha$ (dB)
cmor1.0-1.5 0.392811 0.007521 −42.47 0.974 0.007191 −42.86
cmor1.5-1.5 0.259701 0.007571 −42.42 0.974 0.007224 −42.82
cmor0.5-2.0 0.591107 0.007270 −42.77 0.997 0.007265 −42.78
cmor2.0-1.5 0.193993 0.007658 −42.32 0.974 0.007314 −42.72
cmor2.5-1.5 0.154826 0.007722 −42.24 0.975 0.007414 −42.60

Limitations / future work

  • Currently cmor wavelets only. Other analytic wavelets (e.g. Paul, DOG)
    require separate derivations of $\psi(0)$ and $\hat{\psi}$ and are left for
    future PRs.
  • The reconstruction constant $C_\delta$ is computed via a fixed numerical grid;
    for extreme B/C values this grid may need tuning.
  • The $\alpha$ values in the table are consistently around 2.5 rather than 1.0,
    suggesting a systematic amplitude factor in $C_\delta$ for this wavelet/scale
    convention that warrants further investigation.

The reconstruction quality is comparable to MATLAB's icwt implementation and captures the frequency content of the original signal well.

The method is not perfect, but to my knowledge no open-source PyTorch alternative currently exists, making this a useful stepping stone for the community.

Additional evaluation material (spectrograms, extended comparisons) is available on request.

Related to #134

Implements the inverse continuous wavelet transform (icwt) for complex
Morlet (cmor) wavelets using the reconstruction formula from:

  Torrence, C. and Compo, G.P. (1998). A Practical Guide to Wavelet
  Analysis. BAMS 79(1), pp. 61-78. Eq. 11-13.

Changes:
- Add _cdelta_cmor() to compute the wavelet-dependent reconstruction
  constant C_delta (T&C Eq. 13) via numerical integration
- Add icwt() accepting ContinuousWavelet | str, torch.Tensor input,
  and returning a torch.Tensor; GPU/autograd compatible
- Export icwt from ptwt.__init__

Tests added to tests/test_continuous_transform.py:
- test_cwt_cmor: forward CWT correctness for cmor wavelets (gap in
  existing test coverage; cmor requires parametrised B-C values unlike
  fixed-name wavelets such as mexh or cgau)
- test_icwt: finiteness and valid C_delta across three cmor wavelets
  and two scale densities
- test_icwt_cdelta_valid: C_delta is finite and positive for a range
  of cmor B/C parameter combinations
- test_icwt_rejects_non_cmor: ValueError is raised for unsupported
  wavelet families (mexh, morl, cgau1, gaus1)
- test_icwt_accepts_wavelet_object: icwt accepts both str and
  pywt.ContinuousWavelet, consistent with the cwt interface
- test_icwt_cuda: output tensor lives on the same device as the input
  coefficients (CPU and CUDA)

Related to v0lta#134
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.

1 participant