117 lines
4.6 KiB
Python
117 lines
4.6 KiB
Python
import os
|
|
import torch
|
|
import torch.nn as nn
|
|
from torch.utils.data import DataLoader, Dataset
|
|
from torchvision import models, transforms
|
|
from PIL import Image
|
|
import numpy as np
|
|
from sklearn.cluster import KMeans
|
|
from sklearn.decomposition import PCA
|
|
from sklearn.preprocessing import StandardScaler
|
|
from sklearn.manifold import TSNE
|
|
import pandas as pd
|
|
import matplotlib.pyplot as plt
|
|
|
|
# 1. CONFIGURACIÓN
|
|
IMG_DIR = "datos_para_ai"
|
|
OUTPUT_DIR = "pruebas_clusters"
|
|
N_CLUSTERS = 10
|
|
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
|
|
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
|
|
|
# 2. CARGAR MODELO PRE-ENTRENADO (Directo de PyTorch)
|
|
def get_pretrained_extractor():
|
|
# Usamos los pesos por defecto de ImageNet
|
|
model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT)
|
|
model.classifier = nn.Identity() # Quitamos la capa de clasificación
|
|
model.to(DEVICE)
|
|
model.eval()
|
|
return model
|
|
|
|
# 3. DATASET CON NORMALIZACIÓN ESTÁNDAR
|
|
class MSIDatasetDirect(Dataset):
|
|
def __init__(self, img_dir):
|
|
self.img_dir = img_dir
|
|
self.img_names = sorted([f for f in os.listdir(img_dir) if f.endswith('.png')])
|
|
self.transform = transforms.Compose([
|
|
transforms.Resize((224, 224)),
|
|
transforms.ToTensor(),
|
|
# Normalización obligatoria para modelos pre-entrenados
|
|
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
|
])
|
|
|
|
def __len__(self):
|
|
return len(self.img_names)
|
|
|
|
def __getitem__(self, idx):
|
|
img_path = os.path.join(self.img_dir, self.img_names[idx])
|
|
image = Image.open(img_path).convert("RGB")
|
|
return self.transform(image), self.img_names[idx]
|
|
|
|
if __name__ == "__main__":
|
|
print(f"Extrayendo características con red pre-entrenada en {DEVICE}...")
|
|
model = get_pretrained_extractor()
|
|
dataset = MSIDatasetDirect(IMG_DIR)
|
|
loader = DataLoader(dataset, batch_size=1, shuffle=False)
|
|
|
|
features = []
|
|
ion_names = []
|
|
|
|
with torch.no_grad():
|
|
for img, name in loader:
|
|
img = img.to(DEVICE)
|
|
feat = model(img)
|
|
# Aplanar el vector de características (1280 dimensiones para EffNet-B0)
|
|
features.append(feat.cpu().numpy().flatten())
|
|
ion_names.append(name[0])
|
|
|
|
features = np.array(features)
|
|
print(f"Características extraídas: {features.shape}")
|
|
|
|
# 4. REDUCCIÓN DE DIMENSIONALIDAD (PCA)
|
|
# Esto ayuda a que el clustering no se pierda en dimensiones irrelevantes
|
|
print("Reduciendo ruido con PCA...")
|
|
features_scaled = StandardScaler().fit_transform(features)
|
|
pca = PCA(n_components=0.95) # Mantenemos el 95% de la varianza
|
|
features_pca = pca.fit_transform(features_scaled)
|
|
print(f"Dimensiones tras PCA: {features_pca.shape[1]}")
|
|
|
|
# 5. CLUSTERING K-MEANS
|
|
print(f"Agrupando en {N_CLUSTERS} clusters...")
|
|
km = KMeans(n_clusters=N_CLUSTERS, n_init=20, random_state=42)
|
|
labels = km.fit_predict(features_pca)
|
|
|
|
# 6. VISUALIZACIÓN (t-SNE)
|
|
print("Generando visualización t-SNE para verificar consistencia...")
|
|
tsne = TSNE(n_components=2, random_state=42)
|
|
features_tsne = tsne.fit_transform(features_pca)
|
|
|
|
plt.figure(figsize=(10, 8))
|
|
scatter = plt.scatter(features_tsne[:, 0], features_tsne[:, 1], c=labels, cmap='tab10', alpha=0.7)
|
|
plt.colorbar(scatter, ticks=range(N_CLUSTERS), label='Cluster ID')
|
|
plt.title(f"Visualización de Clusters (K={N_CLUSTERS}) - Directo")
|
|
plt.xlabel("t-SNE 1")
|
|
plt.ylabel("t-SNE 2")
|
|
plt.grid(True, linestyle='--', alpha=0.5)
|
|
plt.savefig(os.path.join(OUTPUT_DIR, "msi_clusters_direct_plot.png"))
|
|
print(f"Gráfico de consistencia guardado en '{OUTPUT_DIR}/msi_clusters_direct_plot.png'.")
|
|
|
|
# 7. GUARDAR Y REPORTAR
|
|
df = pd.DataFrame({'ion_image': ion_names, 'cluster': labels})
|
|
df['mz'] = df['ion_image'].str.extract(r'(\d+\.\d+)').astype(float)
|
|
df.to_csv(os.path.join(OUTPUT_DIR, "msi_clusters_direct_results.csv"), index=False)
|
|
|
|
# Crear un resumen de cuántos iones hay por cluster
|
|
print("\nResumen de Clusters (Consistente con el CSV y el Gráfico):")
|
|
counts = df['cluster'].value_counts().sort_index()
|
|
print(counts)
|
|
|
|
# Guardar también el resumen para evitar confusiones con otros archivos
|
|
summary_df = counts.reset_index()
|
|
summary_df.columns = ['Cluster_ID', 'Ion_Count']
|
|
summary_df.to_csv(os.path.join(OUTPUT_DIR, "msi_clusters_direct_summary.csv"), index=False)
|
|
|
|
print(f"\nResultados guardados en '{OUTPUT_DIR}/msi_clusters_direct_results.csv' y '{OUTPUT_DIR}/msi_clusters_direct_summary.csv'.")
|
|
print(f"Usa '{OUTPUT_DIR}/msi_clusters_direct_plot.png' para validar visualmente los puntos.")
|