Differentiate Module#

In this example, we demonstrate how to use the differentiate module to compute various differential operations on an image.

You can download this example at the tab at right (Python script or Jupyter notebook.

Imports#

Import the necessary libraries and modules.

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import requests
from io import BytesIO
from PIL import Image

# Import the Differentials class from your module (adjust import path as needed)
from splineops.differentiate.differentials import differentials

Data Preparation#

We retrieve an example color image, convert it to grayscale, and normalize its intensities to the [0,1] range.

url = 'https://r0k.us/graphics/kodak/kodak/kodim15.png'
response = requests.get(url)
img = Image.open(BytesIO(response.content))
image = np.array(img, dtype=np.float64)

# Convert to [0,1]
image_normalized = image / 255.0

# Convert to grayscale via simple weighting
image_gray = (
    image_normalized[:, :, 0] * 0.2989 +
    image_normalized[:, :, 1] * 0.5870 +
    image_normalized[:, :, 2] * 0.1140
)

Helper Visualization Functions#

def show_result_with_colorbar(title, result, units="Value", percentile_range=(5, 95)):
    """
    Displays a 2D result with a colorbar scaled using the given percentile range.

    Parameters
    ----------
    title : str
        Title for the plot.
    result : ndarray
        2D array representing the image or field to display.
    units : str
        Label for the colorbar (e.g., 'Intensity', 'Radians', etc.).
    percentile_range : tuple or None
        Percentiles to use for scaling the colormap. If None, use the min and max of the data.
    """
    h, w = result.shape
    aspect_ratio = h / float(w)
    fig_width = 6.0
    fig_height = fig_width * aspect_ratio
    fig, ax = plt.subplots(figsize=(fig_width, fig_height))

    # Determine vmin and vmax based on percentiles if provided
    if percentile_range is not None:
        pmin, pmax = np.percentile(result, percentile_range)
        im = ax.imshow(result, cmap='gray', aspect='equal', vmin=pmin, vmax=pmax)
        cbar_label = f"{units} range [{pmin:.3f}, {pmax:.3f}]"
    else:
        vmin, vmax = result.min(), result.max()
        im = ax.imshow(result, cmap='gray', aspect='equal', vmin=vmin, vmax=vmax)
        cbar_label = f"{units} range [{vmin:.3f}, {vmax:.3f}]"

    ax.set_title(title)
    ax.axis('off')

    # Create a colorbar with matching height using make_axes_locatable
    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", size="5%", pad=0.05)
    cbar = plt.colorbar(im, cax=cax)
    cbar.set_label(cbar_label)

    plt.tight_layout()
    plt.show()


def show_angle_result(title, angle_data, vmin, vmax, units="Radians"):
    """
    Displays angle data in a cyclical color map (hsv), with vmin and vmax specifying
    the circular range.

    Parameters
    ----------
    title : str
        Title for the plot.
    angle_data : ndarray
        2D array of angles (in radians).
    vmin : float
        Minimum of angle range (e.g., 0).
    vmax : float
        Maximum of angle range (e.g., 2*pi).
    units : str
        Label for the colorbar (e.g., 'Direction (radians)', etc.).
    """
    h, w = angle_data.shape
    aspect_ratio = h / float(w)
    fig_width = 6.0
    fig_height = fig_width * aspect_ratio
    fig, ax = plt.subplots(figsize=(fig_width, fig_height))

    # Plot with an HSV cyclical colormap
    im = ax.imshow(angle_data, cmap='hsv', aspect='equal', vmin=vmin, vmax=vmax)
    ax.set_title(title)
    ax.axis('off')

    # Add colorbar
    divider = make_axes_locatable(ax)
    cax = divider.append_axes("right", size="5%", pad=0.05)
    cbar = plt.colorbar(im, cax=cax, ticks=[vmin, (vmin+vmax)/2, vmax])
    cbar.set_label(f"{units} range [{vmin:.2f}, {vmax:.2f}]")

    plt.tight_layout()
    plt.show()

Show the original grayscale image with a colorbar

show_result_with_colorbar("Original Image", image_gray, units="Intensity")
Original Image

Gradient Magnitude#

diff = differentials(image_gray.copy())
diff.run(differentials.GRADIENT_MAGNITUDE)
grad_magnitude_result = diff.image

show_result_with_colorbar("Gradient Magnitude", grad_magnitude_result, units="Value")
Gradient Magnitude
Completed in 1.21 seconds

Gradient Direction#

diff = differentials(image_gray.copy())
diff.run(differentials.GRADIENT_DIRECTION)
grad_direction_result = diff.image

# Shift from [-π, π] to [0, 2π]
grad_direction_result_0_2pi = (grad_direction_result + 2.0*np.pi) % (2.0*np.pi)

# Visualize with HSV colormap, removing percentile clipping
show_angle_result(
    "Gradient Direction",
    grad_direction_result_0_2pi,
    vmin=0.0, vmax=2.0*np.pi,
    units="Direction (radians)"
)
Gradient Direction
Completed in 1.23 seconds

Laplacian#

diff = differentials(image_gray.copy())
diff.run(differentials.LAPLACIAN)
laplacian_result = diff.image

show_result_with_colorbar("Laplacian", laplacian_result, units="Value")
Laplacian
Completed in 1.44 seconds

Largest Hessian#

diff = differentials(image_gray.copy())
diff.run(differentials.LARGEST_HESSIAN)
largest_hessian_result = diff.image

show_result_with_colorbar("Largest Hessian Eigenvalue", largest_hessian_result, units="Value")
Largest Hessian Eigenvalue
Completed in 2.65 seconds

Smallest Hessian#

diff = differentials(image_gray.copy())
diff.run(differentials.SMALLEST_HESSIAN)
smallest_hessian_result = diff.image

show_result_with_colorbar("Smallest Hessian Eigenvalue", smallest_hessian_result, units="Value")
Smallest Hessian Eigenvalue
Completed in 2.65 seconds

Hessian Orientation#

diff = differentials(image_gray.copy())
diff.run(differentials.HESSIAN_ORIENTATION)
hessian_orientation_result = diff.image

show_angle_result(
    "Hessian Orientation",
    hessian_orientation_result,
    vmin=-np.pi/2.0, vmax=np.pi/2.0,
    units="Orientation (radians)"
)
Hessian Orientation
Completed in 2.67 seconds

Total running time of the script: (0 minutes 14.357 seconds)

Gallery generated by Sphinx-Gallery