.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/02_resize/04_antialiasing.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_04_antialiasing.py: Antialiasing ============ Interpolate 2D images with an **antialiased** down-sampling step and compare the result to standard interpolation. We shrink the image with either: - plain cubic interpolation (no explicit low-pass), - cubic antialiasing (oblique projection low-pass) via ``"cubic-antialiasing"``, then up-sample both back to the original size using standard cubic interpolation. SNR and MSE are computed only on a central region to exclude boundary artifacts. .. GENERATED FROM PYTHON SOURCE LINES 23-25 Imports ------- .. GENERATED FROM PYTHON SOURCE LINES 25-48 .. code-block:: Python import numpy as np import time from urllib.request import urlopen from PIL import Image import matplotlib.pyplot as plt from scipy.ndimage import zoom as _scipy_zoom # only if you want extra comparisons from splineops.resize import resize, resize_degrees 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_two_method_comparisons def fmt_ms(seconds: float) -> str: """Format seconds as a short 'X.X ms' string.""" return f"{seconds * 1000.0:.1f} ms" # Use float32 for storage / IO (resize still computes internally in float64) DTYPE = np.float32 .. GENERATED FROM PYTHON SOURCE LINES 50-52 Pipeline Diagram ---------------- .. GENERATED FROM PYTHON SOURCE LINES 52-62 .. code-block:: Python _ = draw_two_method_comparisons( "Standard Interpolation", "Antialiasing", include_downsample_labels=True, include_upsample_labels=True, scale_factor=4, width=12.0, ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_001.png :alt: 04 antialiasing :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 63-65 Load and Normalize an Image --------------------------- .. GENERATED FROM PYTHON SOURCE LINES 65-112 .. code-block:: Python url = "https://r0k.us/graphics/kodak/kodak/kodim14.png" with urlopen(url, timeout=10) as resp: img = Image.open(resp) data = np.array(img, dtype=np.float64) # Convert to [0..1] + grayscale input_image_normalized = data / 255.0 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 spline backend in float32 for performance # (it still computes internally in float64). input_image_normalized = input_image_normalized.astype(DTYPE, copy=False) h_img, w_img = input_image_normalized.shape # Shared parameters zoom = np.e / 9 # ≈ 0.3020313142732272 zoom_factors_2d = (zoom, zoom) border_fraction = 0.3 # central crop for SNR/MSE ROI_SIZE_PX = 64 # Face-centered 64×64 ROI (for visual comparisons) FACE_ROW, FACE_COL = 400, 600 # (row, col) approx center of the detail # 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_rect = (row_top, col_left, ROI_SIZE_PX, ROI_SIZE_PX) # (r, c, h, w) 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 ) # Mapping for resized-space ROI (used by both resized displays) zoom_r, zoom_c = zoom_factors_2d center_r_res = int(round(FACE_ROW * zoom_r)) center_c_res = int(round(FACE_COL * zoom_c)) roi_h_res = max(1, int(round(ROI_SIZE_PX * zoom_r))) roi_w_res = max(1, int(round(ROI_SIZE_PX * zoom_c))) .. GENERATED FROM PYTHON SOURCE LINES 113-115 Standard Interpolation ---------------------- .. GENERATED FROM PYTHON SOURCE LINES 115-141 .. code-block:: Python t0 = time.perf_counter() resized_2d_std = resize( input_image_normalized, zoom_factors=zoom_factors_2d, method="cubic", ) t1 = time.perf_counter() recovered_2d_std = resize( resized_2d_std, output_size=input_image_normalized.shape, method="cubic", ) t2 = time.perf_counter() time_2d_std_fwd = t1 - t0 # forward resize (down/up) time_2d_std_back = t2 - t1 # backward resize (return to original size) time_2d_std = t2 - t0 # total pipeline time # SNR/MSE on central region (no ROI cropping here) snr_2d_std, mse_2d_std = compute_snr_and_mse_region( input_image_normalized, recovered_2d_std, border_fraction=border_fraction, ) .. GENERATED FROM PYTHON SOURCE LINES 142-144 Antialiasing ------------ .. GENERATED FROM PYTHON SOURCE LINES 144-169 .. code-block:: Python t0 = time.perf_counter() resized_2d_aa = resize( input_image_normalized, zoom_factors=zoom_factors_2d, method="cubic-antialiasing", # antialiased shrink ) t1 = time.perf_counter() recovered_2d_aa = resize( resized_2d_aa, output_size=input_image_normalized.shape, method="cubic-antialiasing", # (you could also use "cubic" here) ) t2 = time.perf_counter() time_2d_aa_fwd = t1 - t0 time_2d_aa_back = t2 - t1 time_2d_aa = t2 - t0 snr_2d_aa, mse_2d_aa = compute_snr_and_mse_region( input_image_normalized, recovered_2d_aa, border_fraction=border_fraction, ) .. GENERATED FROM PYTHON SOURCE LINES 170-175 ROI Comparison -------------- Build a quick ROI triptych (nearest-neighbour magnification) from the recovered images for visual comparison. .. GENERATED FROM PYTHON SOURCE LINES 175-211 .. code-block:: Python def _nearest_big(roi: np.ndarray, target_h: int) -> np.ndarray: h, w = roi.shape mag = max(1, int(round(target_h / h))) return np.repeat(np.repeat(roi, mag, axis=0), mag, axis=1) roi_orig = input_image_normalized[row_top:row_top+ROI_SIZE_PX, col_left:col_left+ROI_SIZE_PX] roi_std = recovered_2d_std[row_top:row_top+ROI_SIZE_PX, col_left:col_left+ROI_SIZE_PX] roi_aa = recovered_2d_aa[row_top:row_top+ROI_SIZE_PX, col_left:col_left+ROI_SIZE_PX] DISPLAY_H = 256 roi_big_orig = _nearest_big(roi_orig, DISPLAY_H) roi_big_std = _nearest_big(roi_std, DISPLAY_H) roi_big_aa = _nearest_big(roi_aa, DISPLAY_H) fig, axes = plt.subplots(1, 3, figsize=(12.5, 4.6)) titles = [ "Original ROI", f"Recovered (Standard, {fmt_ms(time_2d_std_back)})", f"Recovered (Antialiasing, {fmt_ms(time_2d_aa_back)})", ] for ax, im, title in zip( axes, [roi_big_orig, roi_big_std, roi_big_aa], titles, ): ax.imshow(im, cmap="gray", interpolation="nearest") ax.set_title(title) ax.axis("off") ax.set_aspect("equal") fig.tight_layout() plt.show() .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_002.png :alt: Original ROI, Recovered (Standard, 1.5 ms), Recovered (Antialiasing, 3.0 ms) :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 212-214 Original with ROI ----------------- .. GENERATED FROM PYTHON SOURCE LINES 214-221 .. code-block:: Python _ = show_roi_zoom( input_image_normalized, ax_titles=("Original Image", None), **roi_kwargs ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_003.png :alt: Original Image :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 222-224 Resized Images -------------- .. GENERATED FROM PYTHON SOURCE LINES 226-228 Antialiasing ~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 228-252 .. code-block:: Python h_res_aa, w_res_aa = resized_2d_aa.shape row_top_res_aa = int(np.clip(center_r_res - roi_h_res // 2, 0, h_res_aa - roi_h_res)) col_left_res_aa = int(np.clip(center_c_res - roi_w_res // 2, 0, w_res_aa - roi_w_res)) canvas_aa = np.ones((h_img, w_img), dtype=resized_2d_aa.dtype) # white background in [0,1] canvas_aa[:h_res_aa, :w_res_aa] = resized_2d_aa roi_kwargs_on_canvas_aa = dict( roi_height_frac=roi_h_res / h_img, grayscale=True, roi_xy=(row_top_res_aa, col_left_res_aa), ) _ = show_roi_zoom( canvas_aa, ax_titles=( f"Resized Image (antialiasing, {fmt_ms(time_2d_aa_fwd)})", None, ), **roi_kwargs_on_canvas_aa ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_004.png :alt: Resized Image (antialiasing, 2.8 ms) :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 253-255 Standard Interpolation ~~~~~~~~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 255-279 .. code-block:: Python h_res_std, w_res_std = resized_2d_std.shape row_top_res_std = int(np.clip(center_r_res - roi_h_res // 2, 0, h_res_std - roi_h_res)) col_left_res_std = int(np.clip(center_c_res - roi_w_res // 2, 0, w_res_std - roi_w_res)) canvas_std = np.ones((h_img, w_img), dtype=resized_2d_std.dtype) canvas_std[:h_res_std, :w_res_std] = resized_2d_std roi_kwargs_on_canvas_std = dict( roi_height_frac=roi_h_res / h_img, grayscale=True, roi_xy=(row_top_res_std, col_left_res_std), ) _ = show_roi_zoom( canvas_std, ax_titles=( f"Resized Image (standard, {fmt_ms(time_2d_std_fwd)})", None, ), **roi_kwargs_on_canvas_std ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_005.png :alt: Resized Image (standard, 1.8 ms) :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_005.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 280-282 Recovered Images ---------------- .. GENERATED FROM PYTHON SOURCE LINES 284-286 Antialiasing Pipeline ~~~~~~~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 286-296 .. code-block:: Python _ = show_roi_zoom( recovered_2d_aa, ax_titles=( f"Recovered Image (antialiased, {fmt_ms(time_2d_aa_back)})", None, ), **roi_kwargs ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_006.png :alt: Recovered Image (antialiased, 3.0 ms) :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_006.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 297-299 Standard Interpolation ~~~~~~~~~~~~~~~~~~~~~~ .. GENERATED FROM PYTHON SOURCE LINES 299-309 .. code-block:: Python _ = show_roi_zoom( recovered_2d_std, ax_titles=( f"Recovered Image (standard interpolation, {fmt_ms(time_2d_std_back)})", None, ), **roi_kwargs ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_007.png :alt: Recovered Image (standard interpolation, 1.5 ms) :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_007.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 310-312 Difference Images ----------------- .. GENERATED FROM PYTHON SOURCE LINES 314-319 Antialiasing ~~~~~~~~~~~~ Difference with original image on ROI (SNR/MSE numbers are from the central-region metrics computed earlier). .. GENERATED FROM PYTHON SOURCE LINES 319-329 .. code-block:: Python plot_difference_image( original=input_image_normalized, recovered=recovered_2d_aa, snr=snr_2d_aa, mse=mse_2d_aa, roi=roi_rect, title_prefix="Difference (antialiasing)", ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_008.png :alt: Difference (antialiasing) (ROI) SNR: 17.47 dB, MSE: 4.03e-03 :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_008.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 330-334 Standard Interpolation ~~~~~~~~~~~~~~~~~~~~~~ Difference with original image on ROI. .. GENERATED FROM PYTHON SOURCE LINES 334-344 .. code-block:: Python plot_difference_image( original=input_image_normalized, recovered=recovered_2d_std, snr=snr_2d_std, mse=mse_2d_std, roi=roi_rect, title_prefix="Difference (standard)", ) .. image-sg:: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_009.png :alt: Difference (standard) (ROI) SNR: 15.51 dB, MSE: 6.34e-03 :srcset: /auto_examples/02_resize/images/sphx_glr_04_antialiasing_009.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 345-355 Performance Comparison ---------------------- As a compact summary, we print a table with: - SNR / MSE on the central region (via border_fraction), - total (forward + backward) timing of the interpolation pipeline. This lets you see the cost/benefit trade-off between standard interpolation and antialiased shrink/expand. .. GENERATED FROM PYTHON SOURCE LINES 355-373 .. code-block:: Python methods = [ ("Standard Interpolation (cubic)", snr_2d_std, mse_2d_std, time_2d_std), ("Antialiasing (cubic shrink, cubic up)", snr_2d_aa, mse_2d_aa, time_2d_aa), ] header_line = f"{'Method':<40} {'SNR (dB)':>10} {'MSE':>16} {'Time (s)':>12}" print(header_line) print("-" * len(header_line)) for name, snr_val, mse_val, t in methods: print( f"{name:<40} " f"{snr_val:>10.2f} " f"{mse_val:>16.2e} " f"{t:>12.4f}" ) .. rst-class:: sphx-glr-script-out .. code-block:: none Method SNR (dB) MSE Time (s) --------------------------------------------------------------------------------- Standard Interpolation (cubic) 15.51 6.34e-03 0.0033 Antialiasing (cubic shrink, cubic up) 17.47 4.03e-03 0.0058 .. GENERATED FROM PYTHON SOURCE LINES 374-389 Least-Squares vs Antialiasing ----------------------------- We can also compare the antialiasing pipeline against a full **Least-Squares** projection of degree 3 using the low-level :func:`resize_degrees` API. We don't change any of the figures above; we only print SNR / MSE / time numbers here. In this particular example, the Least-Squares variant often achieves very good metrics (SNR/MSE) and can even look slightly “cleaner” numerically. However, in practice we generally recommend the **Antialiasing** preset: - it is extremely stable and robust across a wide range of zooms and images, - it is faster than full Least-Squares, - and the visual quality is usually very close. .. GENERATED FROM PYTHON SOURCE LINES 389-437 .. code-block:: Python t0 = time.perf_counter() resized_2d_ls = resize_degrees( input_image_normalized, zoom_factors=zoom_factors_2d, interp_degree=3, analy_degree=3, synthe_degree=3, inversable=False, ) t1 = time.perf_counter() recovered_2d_ls = resize_degrees( resized_2d_ls, output_size=input_image_normalized.shape, interp_degree=3, analy_degree=3, synthe_degree=3, inversable=False, ) t2 = time.perf_counter() time_2d_ls_fwd = t1 - t0 time_2d_ls_back = t2 - t1 time_2d_ls = t2 - t0 snr_2d_ls, mse_2d_ls = compute_snr_and_mse_region( input_image_normalized, recovered_2d_ls, border_fraction=border_fraction, ) methods_ls_vs_aa = [ ("Antialiasing (cubic shrink, cubic up)", snr_2d_aa, mse_2d_aa, time_2d_aa), ("Least-Squares (cubic) shrink+up", snr_2d_ls, mse_2d_ls, time_2d_ls), ] header_line_ls = f"{'Method':<40} {'SNR (dB)':>10} {'MSE':>16} {'Time (s)':>12}" print() print(header_line_ls) print("-" * len(header_line_ls)) for name, snr_val, mse_val, t in methods_ls_vs_aa: print( f"{name:<40} " f"{snr_val:>10.2f} " f"{mse_val:>16.2e} " f"{t:>12.4f}" ) .. rst-class:: sphx-glr-script-out .. code-block:: none Method SNR (dB) MSE Time (s) --------------------------------------------------------------------------------- Antialiasing (cubic shrink, cubic up) 17.47 4.03e-03 0.0058 Least-Squares (cubic) shrink+up 17.53 3.98e-03 0.0080 .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 2.573 seconds) .. _sphx_glr_download_auto_examples_02_resize_04_antialiasing.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/04_antialiasing.ipynb :alt: Launch binder :width: 150 px .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: 04_antialiasing.ipynb <04_antialiasing.ipynb>` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: 04_antialiasing.py <04_antialiasing.py>` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: 04_antialiasing.zip <04_antialiasing.zip>` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_