.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/02_resize/03_standard_interpolation.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 Binder. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_02_resize_03_standard_interpolation.py: Standard Interpolation ====================== Interpolate 2D images with standard interpolation. Compare them to SciPy zoom. We compute SNR and MSE on a *face ROI* (not the whole frame) to focus on detail and avoid boundary artifacts. .. GENERATED FROM PYTHON SOURCE LINES 15-17 Imports ------- .. GENERATED FROM PYTHON SOURCE LINES 17-39 .. code-block:: Python import numpy as np import time from urllib.request import urlopen from PIL import Image from scipy.ndimage import zoom as _scipy_zoom from splineops.resize import resize from splineops.utils.metrics import compute_snr_and_mse_region from splineops.utils.plotting import plot_difference_image, show_roi_zoom from splineops.utils.diagram import draw_standard_vs_scipy_pipeline def fmt_ms(seconds: float) -> str: """Format seconds as a short 'X.X ms' string.""" return f"{seconds * 1000.0:.1f} ms" # You can switch this to np.float64 if you want full double precision. DTYPE = np.float32 .. GENERATED FROM PYTHON SOURCE LINES 41-46 Pipeline Diagram ---------------- These experiments validate the standard interpolation against SciPy's by showing they produce (nearly) the same result, and where tiny differences are. .. GENERATED FROM PYTHON SOURCE LINES 46-54 .. code-block:: Python _ = draw_standard_vs_scipy_pipeline( show_separator=True, # keep dashed divider show_plus=False, # no far-right '+' include_upsample_labels=True, # show '↑ 4' inside the boxes width=12.0 # figure width in inches (height auto) ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_001.png :alt: 03 standard interpolation :srcset: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 55-60 Load and Normalize an Image --------------------------- Here, we load an example image from an online repository and convert to grayscale in [0, 1]. .. GENERATED FROM PYTHON SOURCE LINES 60-111 .. code-block:: Python url = 'https://r0k.us/graphics/kodak/kodak/kodim14.png' with urlopen(url, timeout=10) as resp: img = Image.open(resp) # Start in float64 for robust normalization, then cast once to DTYPE. data = np.array(img, dtype=np.float64) # Convert to [0..1] input_image_normalized = data / 255.0 # Convert to grayscale via simple weighting input_image_normalized = ( input_image_normalized[:, :, 0] * 0.2989 + # Red channel input_image_normalized[:, :, 1] * 0.5870 + # Green channel input_image_normalized[:, :, 2] * 0.1140 # Blue channel ) # Run the interpolation backends in DTYPE (e.g. float32 for speed). input_image_normalized = input_image_normalized.astype(DTYPE, copy=False) zoom = np.pi / 6 # ≈ 0.5235987756 zoom_factors_2d = (zoom, zoom) border_fraction = 0.3 # still available as a fallback (unused when roi=... is set) # Face-centered 64×64 ROI (focus region for metrics & diffs) ROI_SIZE_PX = 64 FACE_ROW, FACE_COL = 400, 600 # (row, col) approx center of the detail h_img, w_img = input_image_normalized.shape # Top-left of the 64×64 box, clipped to stay inside the image row_top = int(np.clip(FACE_ROW - ROI_SIZE_PX // 2, 0, h_img - ROI_SIZE_PX)) col_left = int(np.clip(FACE_COL - ROI_SIZE_PX // 2, 0, w_img - ROI_SIZE_PX)) # ROI rectangle for metrics/plots (row, col, height, width) roi_rect = (row_top, col_left, ROI_SIZE_PX, ROI_SIZE_PX) roi_kwargs = dict( roi_height_frac=ROI_SIZE_PX / h_img, # keeps height at 64 px (square ROI) grayscale=True, roi_xy=(row_top, col_left), # top-left of the ROI ) # Original (shifted ROI) _ = show_roi_zoom( input_image_normalized, ax_titles=("Original Image", None), **roi_kwargs ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_002.png :alt: Original Image :srcset: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 112-117 Standard Interpolation ---------------------- We use our standard interpolation method (cubic). SNR/MSE are computed on the face ROI. .. GENERATED FROM PYTHON SOURCE LINES 117-145 .. code-block:: Python # Forward + backward resize with splineops.resize.resize t0 = time.perf_counter() resized_2d_interp = resize( input_image_normalized, zoom_factors=zoom_factors_2d, method="cubic", ) t1 = time.perf_counter() recovered_2d_interp = resize( resized_2d_interp, output_size=input_image_normalized.shape, method="cubic", ) t2 = time.perf_counter() time_2d_interp_fwd = t1 - t0 time_2d_interp_back = t2 - t1 time_2d_interp = t2 - t0 # total pipeline time # Metrics (ROI-aware) snr_2d_interp, mse_2d_interp = compute_snr_and_mse_region( input_image_normalized, recovered_2d_interp, roi=roi_rect, border_fraction=border_fraction, ) .. GENERATED FROM PYTHON SOURCE LINES 146-150 Resized Image ~~~~~~~~~~~~~ Show the resized image pasted on a white canvas (for zoom-out), plus the ROI zoom. .. GENERATED FROM PYTHON SOURCE LINES 150-183 .. code-block:: Python # Zoomed face detail for the resized (cubic) image pasted onto original-size canvas h_res, w_res = resized_2d_interp.shape zoom_r, zoom_c = zoom_factors_2d # ROI size in the resized image (e.g., 64 -> 16 px when zoom=0.25) roi_h_res = max(1, int(round(ROI_SIZE_PX * zoom_r))) roi_w_res = max(1, int(round(ROI_SIZE_PX * zoom_c))) # ROI center mapped into the resized image center_r_res = int(round(FACE_ROW * zoom_r)) center_c_res = int(round(FACE_COL * zoom_c)) # Top-left of the ROI in the resized image, clipped to bounds row_top_res = int(np.clip(center_r_res - roi_h_res // 2, 0, h_res - roi_h_res)) col_left_res = int(np.clip(center_c_res - roi_w_res // 2, 0, w_res - roi_w_res)) # --- Build original-size white canvas and paste the small resized image at top-left (0,0) --- canvas = np.ones((h_img, w_img), dtype=resized_2d_interp.dtype) # white background in [0,1] canvas[:h_res, :w_res] = resized_2d_interp roi_kwargs_on_canvas = dict( roi_height_frac=roi_h_res / h_img, # keeps the inset square at roi_h_res pixels high grayscale=True, roi_xy=(row_top_res, col_left_res), # same coords since pasted at (0,0) ) _ = show_roi_zoom( canvas, ax_titles=(f"Resized Image (standard, {fmt_ms(time_2d_interp_fwd)})", None), **roi_kwargs_on_canvas ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_003.png :alt: Resized Image (standard, 2.4 ms) :srcset: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 184-188 Recovered Image (Standard) ~~~~~~~~~~~~~~~~~~~~~~~~~~ We plot the recovered image (after reversing the zoom) with the same ROI. .. GENERATED FROM PYTHON SOURCE LINES 188-195 .. code-block:: Python _ = show_roi_zoom( recovered_2d_interp, ax_titles=(f"Recovered Image (standard, {fmt_ms(time_2d_interp_back)})", None), **roi_kwargs ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_004.png :alt: Recovered Image (standard, 2.0 ms) :srcset: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 196-201 SciPy Interpolation ------------------- For comparison, we also use SciPy's zoom method. Metrics are computed on the ROI. SciPy preserves the input dtype, so it will also run in DTYPE here. .. GENERATED FROM PYTHON SOURCE LINES 201-227 .. code-block:: Python t0 = time.perf_counter() resized_2d_scipy = _scipy_zoom( input_image_normalized, zoom_factors_2d, order=3, ) t1 = time.perf_counter() recovered_2d_scipy = _scipy_zoom( resized_2d_scipy, 1.0 / np.asarray(zoom_factors_2d), order=3, ) t2 = time.perf_counter() time_2d_scipy_fwd = t1 - t0 time_2d_scipy_back = t2 - t1 time_2d_scipy = t2 - t0 # total pipeline time snr_2d_scipy, mse_2d_scipy = compute_snr_and_mse_region( input_image_normalized, recovered_2d_scipy, roi=roi_rect, border_fraction=border_fraction, ) .. GENERATED FROM PYTHON SOURCE LINES 228-230 Recovered Image (SciPy) ~~~~~~~~~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 230-237 .. code-block:: Python _ = show_roi_zoom( recovered_2d_scipy, ax_titles=(f"Recovered Image (SciPy, {fmt_ms(time_2d_scipy_back)})", None), **roi_kwargs ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_005.png :alt: Recovered Image (SciPy, 24.8 ms) :srcset: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_005.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 238-240 Difference Images ----------------- .. GENERATED FROM PYTHON SOURCE LINES 242-246 Standard vs SciPy ~~~~~~~~~~~~~~~~~ Compare the two recovered images on the face ROI and plot that difference. .. GENERATED FROM PYTHON SOURCE LINES 246-260 .. code-block:: Python snr_scipy_vs_interp, mse_scipy_vs_interp = compute_snr_and_mse_region( recovered_2d_scipy, recovered_2d_interp, roi=roi_rect ) plot_difference_image( original=recovered_2d_scipy, recovered=recovered_2d_interp, snr=snr_scipy_vs_interp, mse=mse_scipy_vs_interp, roi=roi_rect, title_prefix="Recovered diff (standard vs SciPy)", ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_006.png :alt: Recovered diff (standard vs SciPy) (ROI) SNR: 145.34 dB, MSE: 1.04e-15 :srcset: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_006.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 261-266 Original vs Standard ~~~~~~~~~~~~~~~~~~~~ For completeness, show the difference image (original - recovered with standard interpolation) on the ROI. .. GENERATED FROM PYTHON SOURCE LINES 266-276 .. code-block:: Python plot_difference_image( original=input_image_normalized, recovered=recovered_2d_interp, snr=snr_2d_interp, mse=mse_2d_interp, roi=roi_rect, title_prefix="Difference (original vs standard)", ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_007.png :alt: Difference (original vs standard) (ROI) SNR: 19.91 dB, MSE: 3.69e-03 :srcset: /auto_examples/02_resize/images/sphx_glr_03_standard_interpolation_007.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 277-282 Alternative using TensorSpline ------------------------------ As an alternative, we can replicate the same interpolation manually using the ``TensorSpline`` class, which underpins the `resize()` function behind the scenes. .. GENERATED FROM PYTHON SOURCE LINES 282-342 .. code-block:: Python from splineops.spline_interpolation.tensor_spline import TensorSpline # 1) Build uniform coordinate arrays that match the shape of 'input_image_normalized' height, width = input_image_normalized.shape # Use the same dtype as the image for coordinates, so everything lives in DTYPE. x_coords = np.linspace(0, height - 1, height, dtype=input_image_normalized.dtype) y_coords = np.linspace(0, width - 1, width, dtype=input_image_normalized.dtype) coordinates_2d = (x_coords, y_coords) # 2) For "cubic interpolation", pick "bspline3". # For boundary handling, we can pick "mirror", "zero", etc. ts = TensorSpline( data=input_image_normalized, coordinates=coordinates_2d, bases="bspline3", # cubic B-splines modes="mirror" # handles boundaries with mirroring ) # 3) Define new coordinate grids for the "zoomed" shape. zoomed_height = int(height * zoom_factors_2d[0]) zoomed_width = int(width * zoom_factors_2d[1]) x_coords_zoomed = np.linspace( 0, height - 1, zoomed_height, dtype=input_image_normalized.dtype ) y_coords_zoomed = np.linspace( 0, width - 1, zoomed_width, dtype=input_image_normalized.dtype ) coords_zoomed_2d = (x_coords_zoomed, y_coords_zoomed) # Evaluate (forward pass): zoom in or out resized_direct_ts = ts(coordinates=coords_zoomed_2d) # 4) Define coordinate grids for returning to the original shape x_coords_orig = np.linspace( 0, height - 1, height, dtype=input_image_normalized.dtype ) y_coords_orig = np.linspace( 0, width - 1, width, dtype=input_image_normalized.dtype ) coords_orig_2d = (x_coords_orig, y_coords_orig) # Evaluate (backward pass): from zoomed shape back to original ts_zoomed = TensorSpline( data=resized_direct_ts, coordinates=coords_zoomed_2d, bases="bspline3", modes="mirror" ) recovered_direct_ts = ts_zoomed(coordinates=coords_orig_2d) # Now, resized_direct_ts / recovered_direct_ts should be very similar # to 'resized_2d_interp' / 'recovered_2d_interp' from the high-level "resize()" approach. # Let's compute MSE to confirm: mse_forward = np.mean((resized_direct_ts - resized_2d_interp ) ** 2) mse_backward = np.mean((recovered_direct_ts - recovered_2d_interp) ** 2) print(f"MSE (TensorSpline vs. resize()) resized: {mse_forward:.6e}") print(f"MSE (TensorSpline vs. resize()) recovered: {mse_backward:.6e}") .. rst-class:: sphx-glr-script-out .. code-block:: none MSE (TensorSpline vs. resize()) resized: 7.308340e-13 MSE (TensorSpline vs. resize()) recovered: 1.133354e-12 .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 2.799 seconds) .. _sphx_glr_download_auto_examples_02_resize_03_standard_interpolation.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/02_resize/03_standard_interpolation.ipynb :alt: Launch binder :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: 03_standard_interpolation.ipynb <03_standard_interpolation.ipynb>` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: 03_standard_interpolation.py <03_standard_interpolation.py>` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: 03_standard_interpolation.zip <03_standard_interpolation.zip>` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_