Add scripts for patient classification and visualization
- Created `classification.py` for comparing image-level and patient-level classification results using various CNN models. - Implemented `create_patient_groups.py` to extract features, generate PCA/t-SNE plots, and identify patient groups via K-means clustering. - Added `figure6.py` to generate boxplots for test accuracy across multiple seeds. - Developed `simple_patient_tsne.py` to perform t-SNE visualization of patient groups and save results in a manifest file. - Introduced `simple_patient_manifest.csv` to store patient IDs, classes, image counts, and associated images.
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
"""
|
||||
Visualization utilities for feature space exploration.
|
||||
|
||||
PCA and t-SNE plots to inspect class separability in the
|
||||
extracted feature representations.
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from sklearn.decomposition import PCA
|
||||
from sklearn.manifold import TSNE
|
||||
|
||||
|
||||
def plot_pca_tsne(X, Y, legend_names=None, perplexity=35,
|
||||
random_state=42, output_path=None, figsize=(12, 5),
|
||||
dpi=150):
|
||||
"""Generate side-by-side PCA and t-SNE plots of feature vectors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : np.ndarray of shape (n_samples, n_features)
|
||||
Feature matrix (e.g. VGG16 outputs).
|
||||
Y : np.ndarray of shape (n_samples,)
|
||||
Class labels.
|
||||
legend_names : dict, optional
|
||||
Mapping from raw label -> display label, e.g.
|
||||
{"Bengin cases": "Benign", ...}.
|
||||
perplexity : int
|
||||
t-SNE perplexity (default 35).
|
||||
random_state : int
|
||||
Seed for reproducibility.
|
||||
output_path : str, optional
|
||||
Path to save the figure. If None, saved as "pca_tsne.png".
|
||||
figsize : tuple
|
||||
Figure dimensions.
|
||||
dpi : int
|
||||
Output resolution.
|
||||
|
||||
Returns
|
||||
-------
|
||||
fig : matplotlib Figure
|
||||
"""
|
||||
if legend_names is None:
|
||||
legend_names = {}
|
||||
|
||||
n_samples, n_features = X.shape
|
||||
|
||||
# --- PCA: 50D → 2D ---
|
||||
n_pca = min(50, n_samples - 1, n_features)
|
||||
pca_pre = PCA(n_components=n_pca, random_state=random_state)
|
||||
X_pca50 = pca_pre.fit_transform(X)
|
||||
|
||||
pca_2d = PCA(n_components=2, random_state=random_state)
|
||||
X_pca2 = pca_2d.fit_transform(X_pca50)
|
||||
explained_variance = pca_2d.explained_variance_ratio_ * 100
|
||||
|
||||
# --- t-SNE: 50D → 2D ---
|
||||
tsne = TSNE(
|
||||
n_components=2,
|
||||
perplexity=perplexity,
|
||||
learning_rate="auto",
|
||||
init="pca",
|
||||
random_state=random_state
|
||||
)
|
||||
X_tsne = tsne.fit_transform(X_pca50)
|
||||
|
||||
# --- Plot ---
|
||||
fig, axes = plt.subplots(1, 2, figsize=figsize)
|
||||
unique_classes = np.unique(Y)
|
||||
|
||||
for class_name in unique_classes:
|
||||
idx = Y == class_name
|
||||
label = legend_names.get(class_name, class_name)
|
||||
|
||||
axes[0].scatter(
|
||||
X_pca2[idx, 0], X_pca2[idx, 1],
|
||||
label=label, alpha=0.7, s=8
|
||||
)
|
||||
axes[0].set_xlabel(f"PC1 ({explained_variance[0]:.1f}%)")
|
||||
axes[0].set_ylabel(f"PC2 ({explained_variance[1]:.1f}%)")
|
||||
axes[0].set_title("PCA")
|
||||
axes[0].legend(markerscale=2)
|
||||
|
||||
for class_name in unique_classes:
|
||||
idx = Y == class_name
|
||||
label = legend_names.get(class_name, class_name)
|
||||
|
||||
axes[1].scatter(
|
||||
X_tsne[idx, 0], X_tsne[idx, 1],
|
||||
label=label, alpha=0.7, s=8
|
||||
)
|
||||
axes[1].set_xlabel("t-SNE dimension 1")
|
||||
axes[1].set_ylabel("t-SNE dimension 2")
|
||||
axes[1].set_title("t-SNE")
|
||||
axes[1].legend(markerscale=2)
|
||||
|
||||
plt.tight_layout()
|
||||
|
||||
if output_path is None:
|
||||
output_path = "pca_tsne.png"
|
||||
plt.savefig(output_path, dpi=dpi)
|
||||
plt.close()
|
||||
print(f" Saved PCA/t-SNE plot to {output_path}")
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
def plot_cv_accuracy(cv_results, split_type="", output_path=None,
|
||||
figsize=(7, 5), dpi=150):
|
||||
"""Plot cross-validation accuracy vs number of selected features.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cv_results : list of dict
|
||||
Each dict has keys "nfeatures" and "best_cv_accuracy".
|
||||
split_type : str
|
||||
Label for the plot title, e.g. "Image-level split".
|
||||
output_path : str, optional
|
||||
Output file path.
|
||||
figsize : tuple
|
||||
dpi : int
|
||||
|
||||
Returns
|
||||
-------
|
||||
fig : matplotlib Figure
|
||||
"""
|
||||
fig, ax = plt.subplots(figsize=figsize)
|
||||
|
||||
nfeats = [r["nfeatures"] for r in cv_results]
|
||||
accs = [r["best_cv_accuracy"] for r in cv_results]
|
||||
|
||||
ax.plot(nfeats, accs, marker="o")
|
||||
ax.set_xscale("log")
|
||||
ax.set_xlabel("Number of selected features")
|
||||
ax.set_ylabel("Mean 5-fold validation accuracy")
|
||||
if split_type:
|
||||
ax.set_title(split_type)
|
||||
plt.tight_layout()
|
||||
|
||||
if output_path is None:
|
||||
output_path = f"{split_type.replace(' ', '_').replace('-', '_')}_cv.png"
|
||||
plt.savefig(output_path, dpi=dpi)
|
||||
plt.close()
|
||||
print(f" Saved CV accuracy plot to {output_path}")
|
||||
|
||||
return fig
|
||||
Reference in New Issue
Block a user