fourmodal-pathology-bind / experiments /39_external_check.py
Carpediem123's picture
Migrate release files 181-210
9a5439c verified
Raw History Blame Contribute Delete
3.78 kB
"""Sanity checks of the external results (38_external_eval.py; embeddings cache/external/emb_<set>_fold<k>.npz).
Galleries in 38_ mix spots of several samples (IDC 4 samples, DLPFC 12 sections). A model that only recognises the
SAMPLE (batch / stain / platform effects) and is random within it already beats chance there. Per pair and set:
mixed G : the 38_ metric (G = 100 random spots from all samples), both directions
sample oracle : R@1 of a model that knows the sample exactly and is random within it = mean 1/n_same_sample
top-1 same samp : fraction of top-1 hits from the query's own sample (base rate = share of same-sample items)
within-sample G : galleries drawn from ONE sample only (IDC G = 100; DLPFC G = 35 since sections have 35-48 spots,
with the mixed metric at G = 35 for comparison) -> spot-level signal beyond sample identity
shuffled null : the mixed metric after permuting one side (checks the metric code; ~1/G)
Output results/external_check.csv
"""
import os
import numpy as np
import pandas as pd
from common import CACHE, ROOT
E = os.path.join(CACHE, "external")
PAIRS = [("he", "rna"), ("rna", "mif"), ("he", "mif"), ("rna", "text"), ("he", "text"), ("mif", "text")]
NAME = {"he": "H&E", "rna": "RNA", "mif": "mIF(MIPHEI)", "text": "text(synthetic)"}
DRAWS = 200
def r1(a, b, s):
sim = a[s] @ b[s].T; d = np.diag(sim)[:, None]
return ((sim > d).sum(1) == 0).mean() / 2 + ((sim > d.T).sum(0) == 0).mean() / 2, sim
def run(a, b, smp, G, within, rng):
out = {"R@1": [], "oracle": [], "top1_same": [], "base_same": []}
uniq = [u for u in np.unique(smp) if (smp == u).sum() >= G]
for _ in range(DRAWS):
if within:
idx = np.flatnonzero(smp == uniq[rng.integers(len(uniq))])
s = rng.choice(idx, G, replace=False)
else:
s = rng.choice(len(a), G, replace=False)
v, sim = r1(a, b, s)
g = smp[s]; same = g[:, None] == g[None, :]
out["R@1"].append(v)
out["oracle"].append((1 / same.sum(1)).mean())
out["top1_same"].append((same[np.arange(G), sim.argmax(1)].mean() + same[sim.argmax(0), np.arange(G)].mean()) / 2)
out["base_same"].append(same.mean())
return {k: float(np.mean(v)) for k, v in out.items()}
rows = []
for st, Gw in [("dlpfc", 35)]: # IDC dropped from the report 2026-10-18
smp = pd.read_csv(os.path.join(E, f"{st}_meta.csv"))["sample"].astype(str).values
for k in range(5):
Z = np.load(os.path.join(E, f"emb_{st}_fold{k}.npz"))
for x, y in PAIRS:
if x not in Z or y not in Z:
continue
a, b = Z[x], Z[y]
rng = np.random.default_rng(k)
base = {"set": st, "fold": k, "pair": f"{NAME[x]}<->{NAME[y]}"}
m = run(a, b, smp, 100, False, rng)
rows.append({**base, "gallery": "mixed G=100", "chance": 0.01, **m})
rows.append({**base, "gallery": "shuffled null G=100", "chance": 0.01,
**run(a, b[rng.permutation(len(b))], smp, 100, False, rng)})
if Gw != 100:
rows.append({**base, "gallery": f"mixed G={Gw}", "chance": 1 / Gw, **run(a, b, smp, Gw, False, rng)})
rows.append({**base, "gallery": f"within-sample G={Gw}", "chance": 1 / Gw, **run(a, b, smp, Gw, True, rng)})
R = pd.DataFrame(rows)
R.to_csv(os.path.join(ROOT, "results", "external_check.csv"), index=False)
S = R.groupby(["set", "pair", "gallery"], sort=False).agg(
R1=("R@1", "mean"), R1_sd=("R@1", "std"), chance=("chance", "first"), oracle=("oracle", "mean"),
top1_same=("top1_same", "mean"), base_same=("base_same", "mean"))
S["x_chance"] = S.R1 / S.chance
pd.set_option("display.width", 250)
print(S.round(3).to_string())