.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/08_decompose/08_02_wavelet_decomposition.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. or to run this example in your browser via JupyterLite or Binder .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_08_decompose_08_02_wavelet_decomposition.py: Wavelet Decomposition ===================== This example demonstrates how to use the Decompose module for Haar wavelet decomposition (analysis & synthesis) in 2D. .. GENERATED FROM PYTHON SOURCE LINES 14-16 Imports ------- .. GENERATED FROM PYTHON SOURCE LINES 16-33 .. code-block:: Python import numpy as np import matplotlib.pyplot as plt # For downloading and handling the image import requests from io import BytesIO from PIL import Image # Wavelet classes for 2D from splineops.decompose.wavelets.haar import HaarWavelets from splineops.decompose.wavelets.splinewavelets import ( Spline1Wavelets, Spline3Wavelets, Spline5Wavelets ) .. GENERATED FROM PYTHON SOURCE LINES 34-39 Load and Normalize a 2D Image ----------------------------- Here, we load an example image from an online repository. We convert it to grayscale in [0,1]. .. GENERATED FROM PYTHON SOURCE LINES 39-131 .. code-block:: Python url = 'https://r0k.us/graphics/kodak/kodak/kodim07.png' response = requests.get(url) img = Image.open(BytesIO(response.content)) # Convert to numpy float64 image_color = np.array(img, dtype=np.float64) # Normalize to [0,1] image_color /= 255.0 # Convert to grayscale using standard weights image_gray = ( image_color[:, :, 0] * 0.2989 + image_color[:, :, 1] * 0.5870 + image_color[:, :, 2] * 0.1140 ) ny, nx = image_gray.shape print(f"Downloaded image shape = {ny} x {nx}") def imshow_matched_LL( coeffs, levels, orig_image, # full-resolution grayscale image detail_pct=95, # percentile for LH/HL/HH stretch ll_low=2, ll_high=98,# LL stretch percentiles ax=None, title=None, cmap='gray', ): """ Visualise a wavelet pyramid so that • the smallest LL block has roughly the same contrast and overall brightness as the original image; and • every detail coefficient is stretched, then mapped to 0.5 ± 0.5 so zero is mid-gray. Parameters ---------- coeffs : 2-D np.ndarray Wavelet-coefficient array in quadrant-pyramid layout. levels : int Decomposition depth (to locate the LL block). orig_image : 2-D np.ndarray Original full-resolution grayscale image in [0, 1]. detail_pct : float |coeff| percentile that maps to ±1 in the detail bands. ll_low, ll_high : float Percentiles (0–100) used for LL contrast stretch. """ if ax is None: ax = plt.gca() vis = np.empty_like(coeffs, dtype=np.float64) ny, nx = vis.shape ny_ll = ny // (2 ** levels) nx_ll = nx // (2 ** levels) # ─────────────────── 1. LL block ─────────────────── ll = coeffs[:ny_ll, :nx_ll] lo, hi = np.percentile(ll, [ll_low, ll_high]) hi = max(hi, lo + 1e-12) # avoid zero division ll_lin = np.clip((ll - lo) / (hi - lo), 0, 1) # ▸ match mean brightness to original image mean_orig = float(np.mean(orig_image)) mean_ll = float(np.mean(ll_lin)) if mean_ll < 1e-12: # degenerate (all black): avoid log(0) gamma = 1.0 else: gamma = np.log(mean_orig + 1e-12) / np.log(mean_ll + 1e-12) ll_matched = ll_lin ** gamma vis[:ny_ll, :nx_ll] = ll_matched # ─────────────────── 2. Detail bands ─────────────── detail_mask = np.ones_like(coeffs, dtype=bool) detail_mask[:ny_ll, :nx_ll] = False if detail_mask.any(): dvals = coeffs[detail_mask] d_scale = np.percentile(np.abs(dvals), detail_pct) d_scale = max(d_scale, 1e-12) d_norm = np.clip(dvals / d_scale, -1, 1) / 2 + 0.5 # [-1,1]→[0,1] vis[detail_mask] = d_norm # ─────────────────── 3. Display ──────────────────── ax.imshow(vis, cmap=cmap, vmin=0, vmax=1, interpolation='nearest') ax.axis('off') if title: ax.set_title(title, fontsize=14) .. rst-class:: sphx-glr-script-out .. code-block:: none Downloaded image shape = 512 x 768 .. GENERATED FROM PYTHON SOURCE LINES 132-137 2D Wavelet Decomposition ------------------------ We demonstrate wavelet decomposition (analysis) and reconstruction (synthesis) using 2D Haar wavelets on a grayscale image. .. GENERATED FROM PYTHON SOURCE LINES 137-183 .. code-block:: Python haar2d = HaarWavelets(scales=3) coeffs = haar2d.analysis(image_gray) recon_haar = haar2d.synthesis(coeffs) err_haar = recon_haar - image_gray max_err_haar = np.abs(err_haar).max() print("[Wavelets 2D Haar Test]") print(f"Max error after 3-scale decomposition: {max_err_haar}") # Helper function for visualization def pyramid_with_quadrant_embedding_levels(wavelet, inp, num_levels): """ Perform multi-scale wavelet analysis in-place so that at each level the new coarse approximation is stored in the quadrant corresponding to the previous level's coarse region. Parameters ---------- wavelet : AbstractWavelets instance A wavelet instance (e.g., HaarWavelets) with the desired number of scales. inp : np.ndarray Input 2D array (e.g., grayscale image). num_levels : int The number of decomposition levels to perform. Returns ------- coeffs : np.ndarray Final coefficient array (same size as inp) with the pyramid layout. """ out = np.copy(inp) ny, nx = out.shape[:2] for level in range(num_levels): # Process the current top-left subarray sub = out[:ny, :nx] sub_out = wavelet.analysis1(sub) out[:ny, :nx] = sub_out # Update region size for next level (halve each dimension) nx = max(1, nx // 2) ny = max(1, ny // 2) return out .. rst-class:: sphx-glr-script-out .. code-block:: none [Wavelets 2D Haar Test] Max error after 3-scale decomposition: 1.3322676295501878e-15 .. GENERATED FROM PYTHON SOURCE LINES 184-186 1-Level Decomposition ~~~~~~~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 186-198 .. code-block:: Python wavelet1 = HaarWavelets(scales=1) coeffs1 = pyramid_with_quadrant_embedding_levels(wavelet1, image_gray, 1) plt.figure(figsize=(8, 8)) imshow_matched_LL(coeffs1, levels=1, orig_image=image_gray, detail_pct=95, ll_low=5, ll_high=99, title="Haar 1-Level Decomposition") plt.tight_layout() plt.show() .. image-sg:: /auto_examples/08_decompose/images/sphx_glr_08_02_wavelet_decomposition_001.png :alt: Haar 1-Level Decomposition :srcset: /auto_examples/08_decompose/images/sphx_glr_08_02_wavelet_decomposition_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 199-201 2-Level Decomposition ~~~~~~~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 201-212 .. code-block:: Python wavelet2 = HaarWavelets(scales=2) coeffs2 = pyramid_with_quadrant_embedding_levels(wavelet2, image_gray, 2) plt.figure(figsize=(8, 8)) imshow_matched_LL(coeffs2, levels=2, orig_image=image_gray, detail_pct=95, ll_low=5, ll_high=99, title="Haar 2-Level Decomposition") plt.tight_layout() plt.show() .. image-sg:: /auto_examples/08_decompose/images/sphx_glr_08_02_wavelet_decomposition_002.png :alt: Haar 2-Level Decomposition :srcset: /auto_examples/08_decompose/images/sphx_glr_08_02_wavelet_decomposition_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 213-215 3-Level Decomposition ~~~~~~~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 215-225 .. code-block:: Python wavelet3 = HaarWavelets(scales=3) coeffs3 = pyramid_with_quadrant_embedding_levels(wavelet3, image_gray, 3) plt.figure(figsize=(8, 8)) imshow_matched_LL(coeffs3, levels=3, orig_image=image_gray, detail_pct=95, ll_low=5, ll_high=99, title="Haar 3-Level Decomposition") plt.tight_layout() plt.show() .. image-sg:: /auto_examples/08_decompose/images/sphx_glr_08_02_wavelet_decomposition_003.png :alt: Haar 3-Level Decomposition :srcset: /auto_examples/08_decompose/images/sphx_glr_08_02_wavelet_decomposition_003.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 3.555 seconds) .. _sphx_glr_download_auto_examples_08_decompose_08_02_wavelet_decomposition.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: binder-badge .. image:: images/binder_badge_logo.svg :target: https://mybinder.org/v2/gh/splineops/splineops.github.io/main?urlpath=lab/tree/notebooks_binder/auto_examples/08_decompose/08_02_wavelet_decomposition.ipynb :alt: Launch binder :width: 150 px .. container:: lite-badge .. image:: images/jupyterlite_badge_logo.svg :target: ../../lite/lab/index.html?path=auto_examples/08_decompose/08_02_wavelet_decomposition.ipynb :alt: Launch JupyterLite :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: 08_02_wavelet_decomposition.ipynb <08_02_wavelet_decomposition.ipynb>` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: 08_02_wavelet_decomposition.py <08_02_wavelet_decomposition.py>` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: 08_02_wavelet_decomposition.zip <08_02_wavelet_decomposition.zip>` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_