import os import torch import torch.nn.functional as F import pandas as pd import numpy as np from PIL import Image from torchvision import transforms # Copy the exact encoder structure from your scripts import torch.nn as nn class MSI_Structural_Encoder(nn.Module): def __init__(self): super().__init__() self.features = nn.Sequential( nn.Conv2d(1, 32, kernel_size=5, stride=2, padding=2, bias=False), nn.GroupNorm(4, 32), nn.ReLU(), nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1, bias=False), nn.GroupNorm(8, 64), nn.ReLU(), nn.Conv2d(64, 128, kernel_size=3, stride=2, padding=1, bias=False), nn.GroupNorm(16, 128), nn.ReLU(), nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1, bias=False), nn.GroupNorm(32, 256), nn.ReLU(), nn.AdaptiveAvgPool2d((1, 1)) ) def forward(self, x): return self.features(x).squeeze(-1).squeeze(-1) DEVICE = torch.device("cpu") IMG_DIR = "datos_para_ai" MODEL_PATH = "models/msi_encoder_trained.pth" if not os.path.exists(MODEL_PATH): print("Model not found. Please ensure you are in the correct directory.") exit() model = MSI_Structural_Encoder() model.features.load_state_dict(torch.load(MODEL_PATH, map_location=DEVICE)) model.eval() transform = transforms.Compose([transforms.Resize((256, 256)), transforms.ToTensor()]) def get_vector(mz_target): # Find the file closest to mz_target files = [f for f in os.listdir(IMG_DIR) if f.endswith('.png')] mzs = [float(f.replace("ion_", "").replace(".png", "")) for f in files] idx = np.argmin(np.abs(np.array(mzs) - mz_target)) closest_mz = mzs[idx] if abs(closest_mz - mz_target) > 0.1: return None, closest_mz img_path = os.path.join(IMG_DIR, files[idx]) img = Image.open(img_path).convert("L") tensor = transform(img).unsqueeze(0) with torch.no_grad(): feat = model(tensor) feat_norm = F.normalize(feat, p=2, dim=1) return feat_norm.numpy().flatten(), closest_mz print("--- INTERNAL VALIDATION: ISOTOPIC & STRUCTURAL COHERENCE ---") # Test 1: Isotopic Coherence (Atropine and its +1 Isotope) v1, mz1 = get_vector(290.02) v2, mz2 = get_vector(291.00) if v1 is not None and v2 is not None: sim = np.dot(v1, v2) print(f"Isotopic Match -> m/z {mz1:.2f} (Atropine) vs m/z {mz2:.2f} (+1 Isotope): Cosine Similarity = {sim:.4f}") # Test 2: Structural Coherence (Atropine and Scopolamine - both vascular) v3, mz3 = get_vector(304.00) if v1 is not None and v3 is not None: sim = np.dot(v1, v3) print(f"Biological Match -> m/z {mz1:.2f} (Atropine) vs m/z {mz3:.2f} (Scopolamine): Cosine Similarity = {sim:.4f}") # Test 3: Unrelated Coherence (Atropine vs random background or non-vascular) v4, mz4 = get_vector(142.09) # Tropine (paravascular, should be lower than scopolamine) if v1 is not None and v4 is not None: sim = np.dot(v1, v4) print(f"Divergent Match -> m/z {mz1:.2f} (Atropine) vs m/z {mz4:.2f} (Tropine): Cosine Similarity = {sim:.4f}")