PET / scripts /bootstrap_clinical_all.py
DesonDai's picture
Add files using upload-large-folder tool
e51b27b verified
Raw
History Blame Contribute Delete
6.07 kB
"""Bootstrap 95% CI for clinical probing (AUROC) across ALL 6 encoders.
Runs clinical-only bootstrap (no Stage-1) for each encoder:
1. Load checkpoint, extract embeddings from train/val/test clinical splits
2. Train logistic probe on train/val (with C sweep)
3. Bootstrap 1000 resamples over test set for 3-way and AD-vs-CN AUROC
Usage:
cd /data/Albus/Brain
CUDA_VISIBLE_DEVICES=4 python scripts/bootstrap_clinical_all.py
"""
from __future__ import annotations
import gc
import sys
import time
from pathlib import Path
import numpy as np
import torch
sys.path.insert(0, str(Path(__file__).resolve().parent))
from bootstrap_ci import (
extract_embeddings,
_subset_cls,
train_probe,
bootstrap_clinical_auroc,
)
from train_pet_foundation import PETSUVRFoundationModel, build_encoder
from pet_vlm_dataset import PETSUVRDataset
ENCODERS = [
("ReMAP-PET", "runs/foundation/medicalnet_layer4_regalign_best.pt"),
("MedicalNet frozen", "runs/foundation/medicalnet_frozen_mlp.pt"),
("BrainIAC frozen", "runs/foundation/brainiac_frozen_mlp.pt"),
("BrainFM frozen", "runs/foundation/brainfm_frozen_mlp_b4_best.pt"),
("SAM-Med3D frozen", "runs/foundation/sam_med3d_frozen_mlp_best.pt"),
("SwinUNETR frozen", "runs/foundation/swinunetr_frozen_mlp_best.pt"),
]
TRAIN_CSV = Path("data/metadata/splits/train_clinical_server.csv")
VAL_CSV = Path("data/metadata/splits/val_clinical_server.csv")
TEST_CSV = Path("data/metadata/splits/test_clinical_server.csv")
TEST_MANIFEST = Path("metadata/splits/test.csv")
B = 1000
SEED = 42
BATCH_SIZE = 4
NUM_WORKERS = 2
def load_model(ckpt_path: Path, device: torch.device):
ckpt = torch.load(ckpt_path, map_location="cpu", weights_only=False)
saved = ckpt.get("args", {})
class _A:
pass
a = _A()
a.backbone = saved.get("backbone", "medicalnet")
a.medicalnet_weights = Path(saved.get("medicalnet_weights",
"pretrained/medicalnet/resnet_50_23dataset.pth"))
a.brainiac_weights = Path(saved.get("brainiac_weights",
"pretrained/brainiac/backbone.safetensors"))
a.brainfm_weights = Path("pretrained/brainfm/assets/brainfm_pretrained.pth")
a.brainfm_code_root = Path("pretrained/brainfm")
a.swinunetr_weights = Path("pretrained/swinunetr/model_swinvit.pt")
a.sam_med3d_weights = Path("pretrained/sam-med3d/sam_med3d_turbo.pth")
a.output_size = tuple(saved.get("output_size", (96, 96, 96)))
embed_dim = saved.get("embed_dim", 256)
freeze = bool(saved.get("freeze_encoder", False))
ds_tmp = PETSUVRDataset(TEST_MANIFEST, output_size=a.output_size)
n_regions = int(ds_tmp[0]["suvr"].numel())
encoder = build_encoder(a)
model = PETSUVRFoundationModel(encoder, n_regions, embed_dim, freeze).to(device)
model.load_state_dict(ckpt["model"], strict=True)
model.eval()
return model, a.output_size
def run_one(name: str, ckpt_path: str, device: torch.device):
print(f"\n{'='*60}", flush=True)
print(f" {name} ({ckpt_path})", flush=True)
print(f"{'='*60}", flush=True)
t0 = time.time()
model, output_size = load_model(Path(ckpt_path), device)
# extract embeddings
train_df, x_train = extract_embeddings(
model, TRAIN_CSV, output_size, BATCH_SIZE, NUM_WORKERS, device)
val_df, x_val = extract_embeddings(
model, VAL_CSV, output_size, BATCH_SIZE, NUM_WORKERS, device)
test_df, x_test = extract_embeddings(
model, TEST_CSV, output_size, BATCH_SIZE, NUM_WORKERS, device)
results = {}
# --- 3-way ---
x_tr3, y_tr3 = _subset_cls(train_df, x_train, "clinical_label", ["CN", "MCI", "AD"])
x_v3, y_v3 = _subset_cls(val_df, x_val, "clinical_label", ["CN", "MCI", "AD"])
x_te3, y_te3 = _subset_cls(test_df, x_test, "clinical_label", ["CN", "MCI", "AD"])
print(f" 3-way: train={len(y_tr3)} val={len(y_v3)} test={len(y_te3)}", flush=True)
probe3, enc3 = train_probe(x_tr3, y_tr3, x_v3, y_v3)
pt3, lo3, hi3 = bootstrap_clinical_auroc(
probe3, enc3, x_tr3, y_tr3, x_v3, y_v3, x_te3, y_te3, B=B, seed=SEED)
results["3way"] = (pt3, lo3, hi3)
print(f" 3-way AUROC: {pt3:.4f} 95% CI [{lo3:.4f}, {hi3:.4f}]", flush=True)
# --- AD vs CN ---
x_tr2, y_tr2 = _subset_cls(train_df, x_train, "clinical_label", ["CN", "AD"])
x_v2, y_v2 = _subset_cls(val_df, x_val, "clinical_label", ["CN", "AD"])
x_te2, y_te2 = _subset_cls(test_df, x_test, "clinical_label", ["CN", "AD"])
print(f" AD/CN: train={len(y_tr2)} val={len(y_v2)} test={len(y_te2)}", flush=True)
probe2, enc2 = train_probe(x_tr2, y_tr2, x_v2, y_v2)
pt2, lo2, hi2 = bootstrap_clinical_auroc(
probe2, enc2, x_tr2, y_tr2, x_v2, y_v2, x_te2, y_te2, B=B, seed=SEED)
results["adcn"] = (pt2, lo2, hi2)
print(f" AD/CN AUROC: {pt2:.4f} 95% CI [{lo2:.4f}, {hi2:.4f}]", flush=True)
elapsed = time.time() - t0
print(f" (elapsed {elapsed:.0f}s)", flush=True)
# free GPU memory
del model
gc.collect()
torch.cuda.empty_cache()
return results
def main():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Device: {device}", flush=True)
all_results = {}
for name, ckpt in ENCODERS:
all_results[name] = run_one(name, ckpt, device)
# --- summary table ---
print(f"\n\n{'='*80}", flush=True)
print(" SUMMARY: Clinical Probing Bootstrap 95% CI (B=1000)", flush=True)
print(f"{'='*80}", flush=True)
print(f"{'Model':<22s} {'3-way AUROC':>22s} {'AD/CN AUROC':>22s}", flush=True)
print("-" * 72, flush=True)
for name, res in all_results.items():
pt3, lo3, hi3 = res["3way"]
pt2, lo2, hi2 = res["adcn"]
hw3 = (hi3 - lo3) / 2
hw2 = (hi2 - lo2) / 2
print(f"{name:<22s} {pt3:.4f} +/- {hw3:.4f} {pt2:.4f} +/- {hw2:.4f}", flush=True)
print(f"{'='*80}", flush=True)
print("Done.", flush=True)
if __name__ == "__main__":
main()