Back to Smile

SMILE — Wavelet User Guide

base/WAVELET.md

6.1.017.8 KB
Original Source

SMILE — Wavelet User Guide

Table of Contents

  1. Overview
  2. Quick Start
  3. The Wavelet Family Hierarchy
  4. Discrete Wavelet Transform
  5. Wavelet Shrinkage — Signal Denoising
  6. Choosing a Wavelet
  7. Working with Non-Power-of-2 Lengths
  8. Mathematical Background
  9. Complete Examples

Overview

The smile.wavelet package provides a fast, in-place Discrete Wavelet Transform (DWT) and its inverse for 1-D and 2-D signals, together with wavelet shrinkage for automatic signal denoising.

Key properties of the implementation:

PropertyValue
Transform typeOrthogonal (energy-preserving)
AlgorithmMallat pyramid algorithm
In-placeYes — the signal array is modified in-place
ComplexityO(N) per level, O(N) total
Signal lengthPower of 2

Quick Start

java
import smile.wavelet.*;

// 1. Pick a wavelet
Wavelet wavelet = new DaubechiesWavelet(8);

// 2. Forward transform (in-place)
double[] signal = { 1.0, 2.0, 3.0, 4.0, 3.0, 2.0, 1.0, 0.0 };
wavelet.transform(signal);
// signal[] now holds the wavelet coefficients

// 3. Inverse transform (in-place) — recover the original signal
wavelet.inverse(signal);

// 4. Denoise a noisy signal in one line
double[] noisy = { /* your data */ };
WaveletShrinkage.denoise(noisy, new HaarWavelet());

The Wavelet Family Hierarchy

All wavelets extend the base Wavelet class:

Wavelet
├── HaarWavelet
├── D4Wavelet
├── DaubechiesWavelet  (n = 4, 6, 8, 10, 12, 14, 16, 18, 20)
├── CoifletWavelet     (n = 6, 12, 18, 24, 30)
├── SymletWavelet      (n = 8, 10, 12, 14, 16, 18, 20)
└── BestLocalizedWavelet (n = 14, 18, 20)

Haar Wavelet

The simplest possible wavelet — a step function. Equivalent to D2 (Daubechies with 2 coefficients). Computes pair-wise averages and differences, producing a fast transform with the lowest computational cost.

java
Wavelet w = new HaarWavelet();

Best for: piecewise-constant signals, quick prototyping, signal with abrupt discontinuities.


D4 Wavelet

Daubechies D4 (4-coefficient) wavelet using a different centering convention from DaubechiesWavelet(4). The forward and inverse steps are computed explicitly (no generic filter loop) for maximum speed.

java
Wavelet w = new D4Wavelet();

Best for: signals with linear trends. One extra vanishing moment compared to Haar.


Daubechies Wavelets

The standard Daubechies family with maximal vanishing moments for a given support width n. The number of vanishing moments equals n/2.

java
Wavelet w4  = new DaubechiesWavelet(4);   // 2 vanishing moments
Wavelet w6  = new DaubechiesWavelet(6);   // 3 vanishing moments
Wavelet w8  = new DaubechiesWavelet(8);   // 4 vanishing moments
// ... up to n=20 (10 vanishing moments)

Supported orders: 4, 6, 8, 10, 12, 14, 16, 18, 20.

Best for: general-purpose use; more vanishing moments → better polynomial approximation at the cost of a wider support.


Coiflet Wavelets

Coiflets are designed so that both the wavelet function and the scaling function have vanishing moments. This makes the scaling coefficients better approximations of signal samples, which is useful in numerical analysis.

java
Wavelet c6  = new CoifletWavelet(6);    // 2 vanishing moments each
Wavelet c12 = new CoifletWavelet(12);   // 4 vanishing moments each
// ... up to n=30

Supported orders: 6, 12, 18, 24, 30 (multiples of 6).

Best for: signals where the coarsest approximation coefficients should closely match the original sample values (e.g. numerical integration, quadrature).


Symlet Wavelets

Symlets (S8–S20) are near-symmetric modifications of the Daubechies family. They have the same number of vanishing moments for a given support, but the filter energy is more symmetrically distributed, reducing phase distortion.

java
Wavelet s8  = new SymletWavelet(8);
Wavelet s12 = new SymletWavelet(12);
// ... up to n=20

Supported orders: 8, 10, 12, 14, 16, 18, 20 (even numbers only).

Best for: audio processing and signals where linear phase response matters.


Best Localized Wavelets

Best Localized (BL) wavelets minimize time-frequency uncertainty while maintaining compact support. They are better localized in time than standard Daubechies wavelets of the same order.

java
Wavelet bl14 = new BestLocalizedWavelet(14);
Wavelet bl18 = new BestLocalizedWavelet(18);
Wavelet bl20 = new BestLocalizedWavelet(20);

Supported orders: 14, 18, 20.

Best for: transient detection in signals where both time and frequency localization matter.


Custom Wavelets

Any orthogonal filter bank coefficients can be wrapped in the base Wavelet class directly:

java
// Example: Daubechies D4 coefficients
double[] h = {
    0.4829629131445341,  0.8365163037378079,
    0.2241438680420134, -0.1294095225512604
};
Wavelet myWavelet = new Wavelet(h);

The constructor automatically derives the high-pass (wavelet) filter g from the low-pass (scaling) filter h via the quadrature mirror filter (QMF) relationship:

g[n] = (-1)^n * h[N-1-n]

Discrete Wavelet Transform

Forward Transform

wavelet.transform(double[] a) applies the full multi-level DWT in-place. Starting from the full signal length N, it repeatedly applies the one-level forward filter, halving the active length at each step, until the active length drops below ncof (the number of filter coefficients).

java
double[] a = { 1, 2, 3, 4, 5, 6, 7, 8 };  // length must be a power of 2
new DaubechiesWavelet(4).transform(a);
// a[] now contains the multi-resolution wavelet coefficients

The resulting array is ordered coarsest-to-finest:

[ s1 | d1 | d2 .... | d3 ............ ]
   ^    ^    ^--- level 2 detail        ^--- level 3 detail (finest)
   |    |
   |    +--- level 1 detail (coarsest)
   +--- level 0 scaling (global mean)

Inverse Transform

wavelet.inverse(double[] a) reconstructs the original signal from its wavelet coefficients in-place. It is the exact numerical inverse of transform():

java
double[] original = { 1, 2, 3, 4, 5, 6, 7, 8 };
double[] a = original.clone();

Wavelet w = new DaubechiesWavelet(8);
w.transform(a);
w.inverse(a);

// a[] ≈ original[] (to within floating-point rounding, ≈ 1e-14)

Input Requirements

RequirementDetail
LengthMust be a power of 2 (2, 4, 8, 16, …)
Minimum lengthMust be ≥ the number of filter coefficients ncof
In-placeThe input array is modified; clone first if the original is needed

Both transform() and inverse() throw IllegalArgumentException if the input length is not a power of 2, or is shorter than ncof.

java
// Zero-pad to the next power of 2 for non-power-of-2 signals:
int n = signal.length;
int padded = Integer.highestOneBit(n - 1) << 1;  // next power of 2
double[] a = Arrays.copyOf(signal, padded);        // right-pad with zeros
wavelet.transform(a);

Coefficient Layout

After transform(), index 0 holds the global scaling coefficient (proportional to the signal mean). This coefficient is never thresholded by WaveletShrinkage.


Wavelet Shrinkage — Signal Denoising

WaveletShrinkage is a utility interface with only static methods. No instantiation is needed.

Universal Threshold

The threshold is computed automatically using Donoho & Johnstone's universal threshold:

λ = σ · √(2 · log(N))

where:

  • N is the signal length,
  • σ is estimated from the finest-scale detail coefficients (the upper half of the transformed array) using the scaled Median Absolute Deviation (MAD):
σ̂ = MAD(finest detail coefficients) / 0.6745

This estimator is robust to signal content because the finest-scale detail coefficients are dominated by noise (not signal) for smooth signals.

Hard vs Soft Thresholding

ModeFormulaEffect
Hard (default)`c → c ifc
Soft`c → sign(c)·max(c

Hard thresholding tends to produce better MSE for sparse signals; soft thresholding gives smoother reconstructions and is preferred when the signal is expected to be continuous.

1-D Denoising

java
import smile.wavelet.*;

double[] signal = /* ... your noisy 1-D signal, length must be a power of 2 ... */;

// Hard thresholding (default)
WaveletShrinkage.denoise(signal, new DaubechiesWavelet(8));

// Explicit hard thresholding
WaveletShrinkage.denoise(signal, new DaubechiesWavelet(8), false);

// Soft thresholding
WaveletShrinkage.denoise(signal, new DaubechiesWavelet(8), true);

Note: denoise() modifies the array in-place. Clone it first if the original is needed.

The global scaling coefficient signal[0] is always preserved — it encodes the mean of the signal and is never zeroed by thresholding.

2-D Denoising

The 2-D extension applies the 1-D denoising independently to each row, then to each column (separable 2-D wavelet transform):

java
double[][] image = /* rows × cols, both dimensions must be powers of 2 */;

// Hard thresholding (default)
WaveletShrinkage.denoise2D(image, new HaarWavelet());

// Soft thresholding
WaveletShrinkage.denoise2D(image, new HaarWavelet(), true);

Choosing a Wavelet

Use caseRecommended wavelet
General denoising / prototypingDaubechiesWavelet(8)
Piecewise-constant or discontinuous signalsHaarWavelet
Signals with smooth, polynomial-like trendsDaubechiesWavelet(12) or DaubechiesWavelet(20)
Audio / minimal phase distortionSymletWavelet(8)SymletWavelet(20)
Numerical integration / quadratureCoifletWavelet(12)
Transient / event detectionBestLocalizedWavelet(14)
Speed-critical applicationsHaarWavelet or D4Wavelet

As a rule of thumb: more filter coefficients → more vanishing moments → better suppression of smooth signal content in detail coefficients → more accurate denoising for smooth signals, at the cost of a wider support and slightly higher boundary effects.


Working with Non-Power-of-2 Lengths

The DWT requires that the signal length be a power of 2. For signals of arbitrary length, zero-pad on the right:

java
import java.util.Arrays;

double[] rawSignal = /* length N, not necessarily a power of 2 */;

// Find the next power of 2
int n = rawSignal.length;
int paddedLength = n == 1 ? 1 : Integer.highestOneBit(n - 1) << 1;

double[] padded = Arrays.copyOf(rawSignal, paddedLength);  // pads with 0.0

Wavelet w = new DaubechiesWavelet(8);
WaveletShrinkage.denoise(padded, w);

// Trim back to original length
double[] denoised = Arrays.copyOf(padded, n);

Tip: Symmetric (periodic) padding often gives better results than zero padding at signal boundaries, especially when the signal does not start and end near zero. For simple use cases, zero padding is sufficient.


Mathematical Background

Mallat Pyramid Algorithm

The DWT is computed via the Mallat pyramid (also called sub-band coding). At each level, the signal of length n is decomposed by:

  1. Convolving with the low-pass scaling filter h (downsampled by 2) → approximation coefficients of length n/2.
  2. Convolving with the high-pass wavelet filter g (downsampled by 2) → detail coefficients of length n/2.

Both convolutions use circular (periodic) boundary conditions, so the output has exactly the same length as the input.

The high-pass filter is derived from the low-pass filter via the quadrature mirror relationship:

g[k] = (-1)^k · h[N−1−k]

This guarantees that the transform is orthogonal (energy-preserving, Parseval's theorem holds).

Orthogonality and Energy Preservation

For any orthogonal wavelet, the DWT preserves signal energy (Parseval's theorem):

Σ a[i]²  =  Σ w[i]²

where a is the original signal and w are the wavelet coefficients.

Universal Threshold Derivation

For a length-N signal with i.i.d. Gaussian noise of standard deviation σ, the universal threshold

λ = σ · √(2 log N)

guarantees (with high probability as N → ∞) that all pure-noise coefficients are zeroed while at least one signal coefficient survives, minimising the minimax risk over a broad class of signals (Donoho & Johnstone, 1994).


Complete Examples

Example 1 — Round-Trip Transform

java
import smile.wavelet.*;

public class RoundTripExample {
    public static void main(String[] args) {
        double[] signal = {1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1, 0};
        double[] original = signal.clone();

        Wavelet w = new DaubechiesWavelet(8);
        w.transform(signal);

        System.out.println("Wavelet coefficients:");
        for (int i = 0; i < signal.length; i++) {
            System.out.printf("  w[%2d] = %10.6f%n", i, signal[i]);
        }

        w.inverse(signal);

        System.out.println("\nReconstruction error:");
        double maxErr = 0;
        for (int i = 0; i < signal.length; i++) {
            maxErr = Math.max(maxErr, Math.abs(signal[i] - original[i]));
        }
        System.out.printf("  max |error| = %.2e%n", maxErr);  // expect ~1e-15
    }
}

Example 2 — 1-D Signal Denoising

java
import smile.wavelet.*;
import java.util.Arrays;

public class DenoiseExample {
    public static void main(String[] args) {
        int n = 128;
        double[] clean  = new double[n];
        double[] noisy  = new double[n];

        // Build a clean signal + broadband noise
        java.util.Random rng = new java.util.Random(42);
        for (int i = 0; i < n; i++) {
            clean[i] = Math.sin(2 * Math.PI * i / 32.0);
            noisy[i] = clean[i] + 0.4 * rng.nextGaussian();
        }

        // Denoise with Daubechies D8, hard thresholding
        double[] denoised = noisy.clone();
        WaveletShrinkage.denoise(denoised, new DaubechiesWavelet(8), false);

        // Report MSE improvement
        System.out.printf("Noisy  MSE = %.6f%n", mse(clean, noisy));
        System.out.printf("Denoised MSE = %.6f%n", mse(clean, denoised));
    }

    static double mse(double[] a, double[] b) {
        double sum = 0;
        for (int i = 0; i < a.length; i++) { double d = a[i]-b[i]; sum += d*d; }
        return sum / a.length;
    }
}

Example 3 — Soft Thresholding Comparison

java
import smile.wavelet.*;

public class SoftVsHardExample {
    public static void main(String[] args) {
        double[] signal = buildNoisySignal(256);

        double[] hard = signal.clone();
        WaveletShrinkage.denoise(hard, new SymletWavelet(8), false);

        double[] soft = signal.clone();
        WaveletShrinkage.denoise(soft, new SymletWavelet(8), true);

        System.out.println("Hard-thresholded signal (first 8): "
                + java.util.Arrays.toString(java.util.Arrays.copyOf(hard, 8)));
        System.out.println("Soft-thresholded signal (first 8): "
                + java.util.Arrays.toString(java.util.Arrays.copyOf(soft, 8)));
    }

    static double[] buildNoisySignal(int n) {
        double[] t = new double[n];
        java.util.Random rng = new java.util.Random(0);
        for (int i = 0; i < n; i++)
            t[i] = Math.cos(2 * Math.PI * i / 64.0) + 0.3 * rng.nextGaussian();
        return t;
    }
}

Example 4 — 2-D Image Denoising

java
import smile.wavelet.*;

public class Denoise2DExample {
    public static void main(String[] args) {
        int n = 64;  // must be a power of 2
        double[][] image = new double[n][n];

        // Synthetic image: smooth 2-D cosine + noise
        java.util.Random rng = new java.util.Random(1);
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                image[i][j] = Math.cos(2 * Math.PI * i / 16.0)
                             * Math.cos(2 * Math.PI * j / 16.0)
                             + 0.3 * rng.nextGaussian();

        WaveletShrinkage.denoise2D(image, new DaubechiesWavelet(8), false);

        System.out.println("Centre pixel after denoising: " + image[n/2][n/2]);
    }
}

Example 5 — Custom Wavelet

java
import smile.wavelet.*;

public class CustomWaveletExample {
    // Daubechies D6 coefficients
    private static final double[] D6 = {
        0.3326705529500825,  0.8068915093110924, 0.4598775021184914,
       -0.1350110200102546, -0.0854412738820267, 0.0352262918857095
    };

    public static void main(String[] args) {
        Wavelet w = new Wavelet(D6);

        double[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
                      15,14,13,12,11,10, 9, 8, 7,  6,  5,  4,  3,  2,  1,  0};
        double[] original = a.clone();

        w.transform(a);
        w.inverse(a);

        double maxErr = 0;
        for (int i = 0; i < a.length; i++)
            maxErr = Math.max(maxErr, Math.abs(a[i] - original[i]));
        System.out.printf("Custom D6 roundtrip error: %.2e%n", maxErr);
    }
}

SMILE — © 2010-2026 Haifeng Li. GNU GPL licensed.