129 lines
3.4 KiB
Python
129 lines
3.4 KiB
Python
#
|
|
# Image to graph utility
|
|
#
|
|
# For the given data we have:
|
|
# dip_hw_3.mat["d1a"] # [MN x MN] affinity matrix
|
|
# dip_hw_3.mat["d2a"] # [M x N x 3] RGB image
|
|
# dip_hw_3.mat["d2b"] # [M x N x 3] RGB image
|
|
#
|
|
#
|
|
# author: Christos Choutouridis <cchoutou@ece.auth.gr>
|
|
# date: 05/07/2025
|
|
#
|
|
|
|
try:
|
|
import numpy as np
|
|
from numpy._typing import NDArray
|
|
from sklearn.metrics import pairwise_distances
|
|
|
|
# Testing requirements
|
|
import matplotlib.pyplot
|
|
from scipy.io import loadmat
|
|
except ImportError as e:
|
|
print("Missing package: ", e)
|
|
print("Run: pip install -r requirements.txt to install.")
|
|
exit(1)
|
|
|
|
def image_to_graph(
|
|
img_array: NDArray[np.floating]
|
|
) -> NDArray[np.float64]:
|
|
"""
|
|
Converts an input image into a fully connected graph represented
|
|
by an affinity matrix.
|
|
|
|
Parameters:
|
|
----------
|
|
img_array : np.ndarray of shape (M, N, C), dtype=float
|
|
The input image with C channels (e.g., 3 for RGB),
|
|
with values normalized in [0, 1].
|
|
|
|
Returns:
|
|
-------
|
|
affinity_mat : np.ndarray of shape (M*N, M*N), dtype=float
|
|
Symmetric affinity matrix representing the fully connected graph.
|
|
A(i, j) = 1 / ||pixel_i - pixel_j||_2
|
|
"""
|
|
if not np.issubdtype(img_array.dtype, np.floating):
|
|
raise ValueError("img_array must be of float type with values in [0, 1].")
|
|
|
|
M, N, C = img_array.shape
|
|
pixels = img_array.reshape(-1, C) # shape (M*N, C)
|
|
|
|
# Compute Euclidean distances between all pixel vectors
|
|
distances = pairwise_distances(pixels, metric='euclidean') # shape (MN, MN)
|
|
|
|
# Avoid division by zero on the diagonal
|
|
np.fill_diagonal(distances, 1e-10)
|
|
|
|
# Affinity = 1 / e^d(i,j)
|
|
affinity_mat = 1.0 / np.exp(distances)
|
|
|
|
return affinity_mat
|
|
|
|
|
|
|
|
def _test_1(plot : bool = False):
|
|
"""
|
|
Test image_to_graph() with a small 4x4 RGB random value array
|
|
"""
|
|
print(f" === Test 1 === ")
|
|
print(f"")
|
|
# Small 4x4 RGB with random values at [0, 1]
|
|
img_array = np.random.rand(4, 4, 3).astype(np.float32)
|
|
|
|
# affinity matrix calculation
|
|
A = image_to_graph(img_array)
|
|
|
|
# Print specs
|
|
print("Shape of affinity matrix:", A.shape) # (16, 16)
|
|
print("Is symmetric:", np.allclose(A, A.T)) # True
|
|
print("Max value:", np.max(A))
|
|
print("Min value:", np.min(A))
|
|
|
|
if plot:
|
|
matplotlib.use("TkAgg")
|
|
matplotlib.pyplot.imshow(A, cmap='hot')
|
|
matplotlib.pyplot.colorbar()
|
|
matplotlib.pyplot.title("Affinity Matrix Heatmap")
|
|
matplotlib.pyplot.show()
|
|
|
|
|
|
def _test_2(plot : bool = False):
|
|
"""
|
|
Test image_to_graph() with d2b matrix
|
|
"""
|
|
print(f" === Test 2 === ")
|
|
print(f"")
|
|
|
|
data = loadmat("dip_hw_3.mat")
|
|
img_array = data["d2b"] # shape (M, N, 3), dtype float, values in [0, 1]
|
|
|
|
# Check shape and type
|
|
print("Input image shape:", img_array.shape)
|
|
print("dtype:", img_array.dtype)
|
|
|
|
# affinity matrix calculation
|
|
A = image_to_graph(img_array)
|
|
|
|
# Print specs
|
|
print("Affinity matrix shape:", A.shape)
|
|
print("Is symmetric:", np.allclose(A, A.T))
|
|
print("Max value:", np.max(A))
|
|
print("Min value:", np.min(A))
|
|
|
|
if plot:
|
|
matplotlib.use("TkAgg")
|
|
matplotlib.pyplot.imshow(A, cmap='hot')
|
|
matplotlib.pyplot.title("Affinity Matrix Heatmap")
|
|
matplotlib.pyplot.colorbar()
|
|
matplotlib.pyplot.show()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
# If you have TkAgg you can pass True, otherwise pass False
|
|
_test_1(False)
|
|
_test_2(False)
|
|
|
|
|
|
|