HW03: demo3a,b,c added
59
HW03/scripts/demo3a.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#
|
||||||
|
# Demo 3a: Non-recursive normalized cuts
|
||||||
|
#
|
||||||
|
# author: Christos Choutouridis <cchoutou@ece.auth.gr>
|
||||||
|
# date: 06/07/2025
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
# Testing requirements
|
||||||
|
from scipy.io import loadmat
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Project imports
|
||||||
|
from normalized_cuts import n_cuts
|
||||||
|
from spectral_clustering import spectral_clustering
|
||||||
|
from image_to_graph import image_to_graph
|
||||||
|
except ImportError as e:
|
||||||
|
print("Missing package:", e)
|
||||||
|
print("Run: pip install -r requirements.txt")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def plot_segmentation(image, labels, k, title, fname):
|
||||||
|
M, N, _ = image.shape
|
||||||
|
segmented = labels.reshape(M, N)
|
||||||
|
plt.imshow(segmented, cmap='tab10', vmin=0, vmax=k-1)
|
||||||
|
plt.title(title)
|
||||||
|
plt.axis('off')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(fname)
|
||||||
|
print(f"Saved: {fname}")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def run_demo3a():
|
||||||
|
data = loadmat("dip_hw_3.mat")
|
||||||
|
|
||||||
|
for name in ["d2a", "d2b"]:
|
||||||
|
img = data[name]
|
||||||
|
print(f"\n=== Image {name} ===")
|
||||||
|
|
||||||
|
affinity = image_to_graph(img)
|
||||||
|
|
||||||
|
for k in [2, 3, 4]:
|
||||||
|
print(f" k = {k}")
|
||||||
|
|
||||||
|
labels_nc = n_cuts(affinity, k=k)
|
||||||
|
labels_sc = spectral_clustering(affinity, k=k, normalized=False)
|
||||||
|
labels_sc_nrm = spectral_clustering(affinity, k=k, normalized=True)
|
||||||
|
|
||||||
|
plot_segmentation(img, labels_nc, k, f"{name} - n_cuts (k={k})", f"plots/demo3a_{name}_ncuts_k{k}.png")
|
||||||
|
plot_segmentation(img, labels_sc, k, f"{name} - spectral (k={k})", f"plots/demo3a_{name}_spectral_k{k}.png")
|
||||||
|
plot_segmentation(img, labels_sc_nrm, k, f"{name} - spectral-Lnorm (k={k})", f"plots/demo3a_{name}_spectral_k{k}_norm.png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_demo3a()
|
49
HW03/scripts/demo3b.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#
|
||||||
|
# Demo 3b: One-step recursive normalized cuts with Ncut metric
|
||||||
|
#
|
||||||
|
# author: Christos Choutouridis <cchoutou@ece.auth.gr>
|
||||||
|
# date: 06/07/2025
|
||||||
|
#
|
||||||
|
try:
|
||||||
|
from scipy.io import loadmat
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
#Project requirements
|
||||||
|
from image_to_graph import image_to_graph
|
||||||
|
from normalized_cuts import n_cuts, calculate_n_cut_value
|
||||||
|
except ImportError as e:
|
||||||
|
print("Missing package:", e)
|
||||||
|
print("Run: pip install -r requirements.txt")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_split(image, labels, title, fname):
|
||||||
|
M, N, _ = image.shape
|
||||||
|
segmented = labels.reshape(M, N)
|
||||||
|
plt.imshow(segmented, cmap='tab10', vmin=0, vmax=1)
|
||||||
|
plt.title(title)
|
||||||
|
plt.axis('off')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(fname)
|
||||||
|
print(f"Saved: {fname}")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def run_demo3b():
|
||||||
|
data = loadmat("dip_hw_3.mat")
|
||||||
|
|
||||||
|
for name in ["d2a", "d2b"]:
|
||||||
|
img = data[name]
|
||||||
|
print(f"\n=== Image {name} ===")
|
||||||
|
|
||||||
|
affinity = image_to_graph(img)
|
||||||
|
|
||||||
|
labels = n_cuts(affinity, k=2)
|
||||||
|
ncut_val = calculate_n_cut_value(affinity, labels)
|
||||||
|
|
||||||
|
print(f" Ncut value: {ncut_val:.4f}")
|
||||||
|
plot_split(img, labels, f"{name} - one step n_cuts (Ncut={ncut_val:.4f})", f"plots/demo3b_{name}_ncut.png")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_demo3b()
|
57
HW03/scripts/demo3c.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#
|
||||||
|
# Demo 3c: Recursive normalized cuts (full version)
|
||||||
|
#
|
||||||
|
# author: Christos Choutouridis <cchoutou@ece.auth.gr>
|
||||||
|
# date: 06/07/2025
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
from scipy.io import loadmat
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
#Project requirements
|
||||||
|
from image_to_graph import image_to_graph
|
||||||
|
from normalized_cuts import n_cuts_recursive
|
||||||
|
except ImportError as e:
|
||||||
|
print("Missing package:", e)
|
||||||
|
print("Run: pip install -r requirements.txt")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
def plot_recursive_clusters(image, labels, title, fname):
|
||||||
|
M, N, _ = image.shape
|
||||||
|
segmented = labels.reshape(M, N)
|
||||||
|
plt.imshow(segmented, cmap='tab20') # tab20 supports up to 20 unique colors
|
||||||
|
plt.title(title)
|
||||||
|
plt.axis('off')
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(fname)
|
||||||
|
print(f"Saved: {fname}")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
|
||||||
|
def run_demo3c(T1: float, T2: float):
|
||||||
|
data = loadmat("dip_hw_3.mat")
|
||||||
|
|
||||||
|
for name in ["d2a", "d2b"]:
|
||||||
|
img = data[name]
|
||||||
|
print(f"\n=== Recursive n_cuts on {name} ===")
|
||||||
|
|
||||||
|
affinity = image_to_graph(img)
|
||||||
|
labels = n_cuts_recursive(affinity, T1=T1, T2=T2)
|
||||||
|
|
||||||
|
num_clusters = len(np.unique(labels))
|
||||||
|
print(f" Clusters found: {num_clusters}")
|
||||||
|
print(f" Labels: {np.unique(labels)}")
|
||||||
|
|
||||||
|
plot_recursive_clusters(
|
||||||
|
img,
|
||||||
|
labels,
|
||||||
|
title=f"{name} - recursive n_cuts (T1={T1}, T2={T2})",
|
||||||
|
fname=f"plots/demo3c_{name}_recursive_T1-{T1}_T2-{T2}.png"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run_demo3c(5, 0.2)
|
||||||
|
run_demo3c(5, 0.95)
|
||||||
|
run_demo3c(5, 0.975)
|
209
HW03/scripts/normalized_cuts.py
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
#
|
||||||
|
# Normalized Cuts
|
||||||
|
#
|
||||||
|
# author: Christos Choutouridis <cchoutou@ece.auth.gr>
|
||||||
|
# date: 05/07/2025
|
||||||
|
#
|
||||||
|
|
||||||
|
try:
|
||||||
|
import numpy as np
|
||||||
|
from numpy.typing import NDArray
|
||||||
|
from sklearn.cluster import KMeans
|
||||||
|
from scipy.sparse.linalg import eigs
|
||||||
|
|
||||||
|
# Testing requirements
|
||||||
|
from scipy.io import loadmat
|
||||||
|
from image_to_graph import image_to_graph
|
||||||
|
import matplotlib.pyplot
|
||||||
|
except ImportError as e:
|
||||||
|
print("Missing package:", e)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def n_cuts(
|
||||||
|
affinity_mat: NDArray[np.floating],
|
||||||
|
k: int
|
||||||
|
) -> NDArray[np.int32]:
|
||||||
|
"""
|
||||||
|
Non-recursive normalized cuts implementation using spectral embedding.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
-----------
|
||||||
|
affinity_mat : np.ndarray of shape (n, n), dtype=float
|
||||||
|
Symmetric affinity matrix representing the graph.
|
||||||
|
k : int
|
||||||
|
Number of clusters.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
--------
|
||||||
|
cluster_idx : np.ndarray of shape (n,), dtype=int
|
||||||
|
Cluster label for each node.
|
||||||
|
"""
|
||||||
|
# Degree matrix
|
||||||
|
D = np.diag(affinity_mat.sum(axis=1))
|
||||||
|
|
||||||
|
# Unnormalized Laplacian
|
||||||
|
L = D - affinity_mat
|
||||||
|
|
||||||
|
# Solve the generalized eigenvalue problem: Lx = λDx
|
||||||
|
eigvals, eigvecs = eigs(A=L, M=D, k=k, which='SR') # SR = Smallest Real part
|
||||||
|
|
||||||
|
# Each row of U is a node's representation in spectral space
|
||||||
|
# Convert complex -> real (imaginary parts should be negligible)
|
||||||
|
U = np.real(eigvecs)
|
||||||
|
|
||||||
|
# Each row is a vector to be clustered
|
||||||
|
# random_state parameter to 1, to ensure reproducibility across experiments.
|
||||||
|
kmeans = KMeans(n_clusters=k, random_state=1)
|
||||||
|
kmeans.fit(U)
|
||||||
|
|
||||||
|
return kmeans.labels_.astype(np.int32)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_n_cut_value(
|
||||||
|
affinity_mat: NDArray[np.floating],
|
||||||
|
cluster_idx: NDArray[np.int32]
|
||||||
|
) -> float:
|
||||||
|
"""
|
||||||
|
Calculates the Ncut(A, B) metric for a binary clustering.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
-----------
|
||||||
|
affinity_mat : np.ndarray of shape (n, n)
|
||||||
|
Symmetric affinity matrix.
|
||||||
|
cluster_idx : np.ndarray of shape (n,)
|
||||||
|
Cluster labels with values in {0, 1}.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
--------
|
||||||
|
n_cut_value : float
|
||||||
|
The value of the Ncut metric.
|
||||||
|
"""
|
||||||
|
A = np.where(cluster_idx == 0)[0]
|
||||||
|
B = np.where(cluster_idx == 1)[0]
|
||||||
|
|
||||||
|
assoc_AA = np.sum(affinity_mat[np.ix_(A, A)])
|
||||||
|
assoc_BB = np.sum(affinity_mat[np.ix_(B, B)])
|
||||||
|
assoc_AV = np.sum(affinity_mat[A, :])
|
||||||
|
assoc_BV = np.sum(affinity_mat[B, :])
|
||||||
|
|
||||||
|
nassoc_AB = (assoc_AA / assoc_AV) + (assoc_BB / assoc_BV)
|
||||||
|
ncut = 2 - nassoc_AB
|
||||||
|
|
||||||
|
return ncut
|
||||||
|
|
||||||
|
|
||||||
|
def n_cuts_recursive(
|
||||||
|
affinity_mat: NDArray[np.floating],
|
||||||
|
T1: int,
|
||||||
|
T2: float
|
||||||
|
) -> NDArray[np.int32]:
|
||||||
|
"""
|
||||||
|
Recursive normalized cuts clustering.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
-----------
|
||||||
|
affinity_mat : np.ndarray of shape (n, n)
|
||||||
|
Symmetric affinity matrix.
|
||||||
|
T1 : int
|
||||||
|
Minimum size for splitting a group.
|
||||||
|
T2 : float
|
||||||
|
Maximum acceptable Ncut value to allow further splitting.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
--------
|
||||||
|
cluster_idx : np.ndarray of shape (n,), dtype=int
|
||||||
|
Final cluster labels after recursive partitioning.
|
||||||
|
"""
|
||||||
|
|
||||||
|
n = affinity_mat.shape[0]
|
||||||
|
cluster_idx = np.zeros(n, dtype=np.int32)
|
||||||
|
next_label = [1] # Mutable counter to assign new cluster labels
|
||||||
|
|
||||||
|
def recursive_partition(indices: NDArray[np.int32], current_label: int):
|
||||||
|
if len(indices) <= T1:
|
||||||
|
cluster_idx[indices] = current_label
|
||||||
|
return
|
||||||
|
|
||||||
|
sub_affinity = affinity_mat[np.ix_(indices, indices)]
|
||||||
|
labels = n_cuts(sub_affinity, k=2)
|
||||||
|
|
||||||
|
ncut_value = calculate_n_cut_value(sub_affinity, labels)
|
||||||
|
A = indices[labels == 0]
|
||||||
|
B = indices[labels == 1]
|
||||||
|
|
||||||
|
if len(A) <= T1 or len(B) <= T1 or ncut_value > T2:
|
||||||
|
cluster_idx[indices] = current_label
|
||||||
|
return
|
||||||
|
|
||||||
|
# Assign recursively new labels
|
||||||
|
recursive_partition(A, current_label)
|
||||||
|
recursive_partition(B, next_label[0])
|
||||||
|
next_label[0] += 1
|
||||||
|
|
||||||
|
# Start with all indices
|
||||||
|
recursive_partition(np.arange(n), 0)
|
||||||
|
return cluster_idx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _test_n_cuts(k: int, plot: bool = False):
|
||||||
|
data = loadmat("dip_hw_3.mat")
|
||||||
|
img = data["d2a"]
|
||||||
|
M, N, _ = img.shape
|
||||||
|
|
||||||
|
print("Running non-recursive n_cuts on d2a...")
|
||||||
|
|
||||||
|
A = image_to_graph(img)
|
||||||
|
labels = n_cuts(A, k=k)
|
||||||
|
|
||||||
|
print("Unique cluster labels:", np.unique(labels))
|
||||||
|
|
||||||
|
# Visualize
|
||||||
|
if plot:
|
||||||
|
clustered = labels.reshape(M, N)
|
||||||
|
matplotlib.use("TkAgg")
|
||||||
|
matplotlib.pyplot.imshow(clustered, cmap='tab10', vmin=0, vmax=2)
|
||||||
|
matplotlib.pyplot.title("n_cuts clustering (k={k}) on d2a")
|
||||||
|
matplotlib.pyplot.axis('off')
|
||||||
|
matplotlib.pyplot.tight_layout()
|
||||||
|
matplotlib.pyplot.show()
|
||||||
|
#matplotlib.pyplot.savefig("ncuts_d2a_k3.png")
|
||||||
|
#print("Saved result to: ncuts_d2a_k3.png")
|
||||||
|
|
||||||
|
|
||||||
|
def _test_n_cuts_req(plot: bool = False):
|
||||||
|
data = loadmat("dip_hw_3.mat")
|
||||||
|
img = data["d2b"]
|
||||||
|
M, N, _ = img.shape
|
||||||
|
|
||||||
|
print("Running recursive n_cuts on d2b...")
|
||||||
|
|
||||||
|
affinity = image_to_graph(img)
|
||||||
|
|
||||||
|
# Thresholds from the demo instructions
|
||||||
|
T1 = 5
|
||||||
|
T2 = 0.95
|
||||||
|
|
||||||
|
labels = n_cuts_recursive(affinity, T1=T1, T2=T2)
|
||||||
|
|
||||||
|
print("Number of unique clusters:", len(np.unique(labels)))
|
||||||
|
print("Labels:", np.unique(labels))
|
||||||
|
|
||||||
|
if plot:
|
||||||
|
segmented = labels.reshape(M, N)
|
||||||
|
matplotlib.pyplot.imshow(segmented, cmap='tab20')
|
||||||
|
matplotlib.pyplot.title(f"Recursive n_cuts on d2b (T1={T1}, T2={T2})")
|
||||||
|
matplotlib.pyplot.axis('off')
|
||||||
|
matplotlib.pyplot.tight_layout()
|
||||||
|
matplotlib.pyplot.show()
|
||||||
|
#matplotlib.pyplot.savefig("ncuts_recursive_d2b.png")
|
||||||
|
#print("Saved result to: ncuts_recursive_d2b.png")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
for k in [2, 3, 4]:
|
||||||
|
_test_n_cuts(k, False)
|
||||||
|
|
||||||
|
_test_n_cuts_req(False)
|
||||||
|
|
||||||
|
|
BIN
HW03/scripts/plots.zip
Normal file
BIN
HW03/scripts/plots/demo3a_d2a_ncuts_k2.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
HW03/scripts/plots/demo3a_d2a_ncuts_k3.png
Normal file
After Width: | Height: | Size: 5.4 KiB |
BIN
HW03/scripts/plots/demo3a_d2a_ncuts_k4.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
HW03/scripts/plots/demo3a_d2a_spectral_k2.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
HW03/scripts/plots/demo3a_d2a_spectral_k2_norm.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
HW03/scripts/plots/demo3a_d2a_spectral_k3.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
HW03/scripts/plots/demo3a_d2a_spectral_k3_norm.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
HW03/scripts/plots/demo3a_d2a_spectral_k4.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
HW03/scripts/plots/demo3a_d2a_spectral_k4_norm.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_ncuts_k2.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_ncuts_k3.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_ncuts_k4.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_spectral_k2.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_spectral_k2_norm.png
Normal file
After Width: | Height: | Size: 7.2 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_spectral_k3.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_spectral_k3_norm.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_spectral_k4.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
HW03/scripts/plots/demo3a_d2b_spectral_k4_norm.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
HW03/scripts/plots/demo3b_d2a_ncut.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
HW03/scripts/plots/demo3b_d2b_ncut.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
HW03/scripts/plots/demo3c_d2a_recursive_T1-5_T2-0.2.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
HW03/scripts/plots/demo3c_d2a_recursive_T1-5_T2-0.95.png
Normal file
After Width: | Height: | Size: 8.0 KiB |
BIN
HW03/scripts/plots/demo3c_d2a_recursive_T1-5_T2-0.975.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
BIN
HW03/scripts/plots/demo3c_d2b_recursive_T1-5_T2-0.2.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
HW03/scripts/plots/demo3c_d2b_recursive_T1-5_T2-0.95.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
HW03/scripts/plots/demo3c_d2b_recursive_T1-5_T2-0.975.png
Normal file
After Width: | Height: | Size: 11 KiB |