Chucks90 commited on
Commit
df23831
·
verified ·
1 Parent(s): dab03bb

research programs v2 (S1-S5) + v3 (F1-F4) + cross-objective mechanism; paper drafts #2/#3; specs; figures

Browse files
gate_reports/NEGATIVE_RESULT.md CHANGED
@@ -34,6 +34,21 @@ is rare and low-rank — i.e., exactly the clinically important small lesions.
34
  All three reduce to one fact: **aggregate rank-coverage is blind to the few tokens that carry a
35
  small lesion**, even though those tokens are individually highly localizable (Gate 1, AUROC 0.87).
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  ## Why this matters beyond this paper
38
 
39
  RankMe-flavored objectives are an increasingly common, tempting choice for medical SSL
 
34
  All three reduce to one fact: **aggregate rank-coverage is blind to the few tokens that carry a
35
  small lesion**, even though those tokens are individually highly localizable (Gate 1, AUROC 0.87).
36
 
37
+ ## Closed-form law (S1, confirmed by controlled synthetic)
38
+
39
+ A controlled synthetic isolates the mechanism and yields a closed form (`research_v2/s1_crossover.json`).
40
+ Inject a signal of effective rank r across `m` tokens; select to a budget by a spanning objective
41
+ (farthest-point / effective-rank) vs a concentration objective (top-energy):
42
+
43
+ - **spanning retention(r) = min(r, m) / m**, **concentration retention = 1**,
44
+ - **gap(r) = max(0, (m − r) / m)**, **crossover r\* = m**.
45
+
46
+ A spanning objective retains a signal only in proportion to its rank; it matches concentration
47
+ only when the signal is *fully diverse* (rank = token count). Rare pathology is maximally
48
+ concentrated (r ≈ 1–3 patches, m small), so rank-based coverage is maximally mismatched there —
49
+ quantitatively reproducing the covtoken ablation (floor 0.22 vs membership 0.82 ≈ gap (m−1)/m for
50
+ the dominant 1-patch lesions). The verdict is no longer anecdotal: it is a closed-form prediction.
51
+
52
  ## Why this matters beyond this paper
53
 
54
  RankMe-flavored objectives are an increasingly common, tempting choice for medical SSL
jobs/f1a_whitening_sweep_job.py ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """F1a — does representation GEOMETRY causally control localizability? (premise for F1 pretraining)
9
+
10
+ Sweep fractional ZCA whitening strength w in [0,1] applied to frozen block-3 features:
11
+ Z'(w) = (Z - mu) @ V diag(s^(-w)) V^T (w=0: raw; w=1: fully white = flat spectrum = max
12
+ effective rank, the direction RankMe / coding-rate regularizers PUSH toward).
13
+ Refit the density subspace on the whitened bank per w; measure lesion-localizability AUROC(w) and
14
+ the bank effective-rank(w). Prediction: AUROC DROPS as w (spanning/whitening) rises => pushing a
15
+ representation toward high rank destroys rare-lesion localizability => pretraining should preserve
16
+ CONCENTRATION, not maximize rank. Emits F1A_RESULT.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json, os, sys, time
21
+ from pathlib import Path
22
+ import numpy as np, torch
23
+ from PIL import Image
24
+ from scipy import stats
25
+ from sklearn.neighbors import NearestNeighbors
26
+ from huggingface_hub import hf_hub_download
27
+
28
+ sys.path.insert(0, "/mnt/processed/covtoken_code")
29
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
30
+
31
+ BACKBONE_REPO = "ricklisz123/MedDINOv3-ViTB-16-CT-3M"
32
+ MNT = Path("/mnt"); LAYER = 2
33
+ BANK = MNT/"processed"/"covtoken"/"ct_token_bank_block2.pt"
34
+ MASK_ROOT = MNT/"processed"/"lidc_v2"; OUT = MNT/"processed"/"covtoken"
35
+ N_PATCH, CLS_OFF = 196, 5
36
+ WS = [float(x) for x in os.environ.get("WS", "0.0,0.25,0.5,0.75,1.0").split(",")]
37
+ EVAL_SLICES = int(os.environ.get("EVAL_SLICES", "800"))
38
+ REF = int(os.environ.get("REF", "60000"))
39
+ CT_MEAN=np.array([0.485,0.456,0.406],np.float32); CT_STD=np.array([0.229,0.224,0.225],np.float32)
40
+ _FEAT={}
41
+ def log(m): print(f"[f1a] {m}", flush=True)
42
+
43
+
44
+ def load_backbone(device):
45
+ ck=hf_hub_download(BACKBONE_REPO,"model.pth",token=os.environ.get("HF_TOKEN"))
46
+ m=vit_base(drop_path_rate=0.0,layerscale_init=1e-5,n_storage_tokens=4,qkv_bias=False,mask_k_bias=True)
47
+ raw=torch.load(ck,map_location="cpu"); sd=raw.get("teacher",raw)
48
+ sd={(k[9:] if k.startswith("backbone.") else k):v for k,v in sd.items()}
49
+ m.load_state_dict(sd,strict=False); m.eval().to(device)
50
+ for p in m.parameters(): p.requires_grad_(False)
51
+ def hook(_m,_i,out):
52
+ while isinstance(out,(list,tuple)): out=out[0]
53
+ _FEAT["z"]=out.detach()
54
+ m.blocks[LAYER].register_forward_hook(hook); return m
55
+
56
+
57
+ def load_img(p):
58
+ img=Image.open(p).convert("RGB").resize((224,224),Image.BILINEAR)
59
+ return torch.from_numpy(((np.asarray(img,np.float32)/255.0-CT_MEAN)/CT_STD)).permute(2,0,1)
60
+
61
+
62
+ @torch.inference_mode()
63
+ def toks(model,imgs,device):
64
+ model.forward_features(imgs.to(device,torch.float32))
65
+ return _FEAT["z"][:,CLS_OFF:CLS_OFF+N_PATCH,:].float().cpu()
66
+
67
+
68
+ def auroc(s,y):
69
+ s=np.asarray(s,float); y=np.asarray(y,int)
70
+ pos,neg=y.sum(),len(y)-y.sum()
71
+ if pos==0 or neg==0: return float("nan")
72
+ r=stats.rankdata(s); return float((r[y==1].sum()-pos*(pos+1)/2)/(pos*neg))
73
+
74
+
75
+ def effrank(S): # effective rank from singular values
76
+ p=S/ (S.sum()+1e-9); return float(np.exp(-(p*np.log(p+1e-12)).sum()))
77
+
78
+
79
+ def main():
80
+ t0=time.time(); device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
81
+ model=load_backbone(device); rng=np.random.default_rng(0)
82
+ bank=torch.load(BANK,map_location="cpu")["tokens"].float()
83
+ ref=bank[torch.from_numpy(rng.choice(bank.shape[0],min(REF,bank.shape[0]),replace=False))]
84
+ mu=ref.mean(0,keepdim=True)
85
+ # eigendecomp of covariance for fractional whitening
86
+ Xc=(ref-mu)
87
+ cov=(Xc.T@Xc)/Xc.shape[0]
88
+ evals,evecs=torch.linalg.eigh(cov.double())
89
+ evals=evals.clamp_min(1e-8); V=evecs.float(); s=evals.float()
90
+ log(f"device={device.type}; cov eig range [{s.min():.2e},{s.max():.2e}]")
91
+
92
+ # eval slices (lesion + neg) with masks
93
+ ev=[]
94
+ for cd in sorted((MASK_ROOT/"test").iterdir()):
95
+ npz=cd/"patch_masks.npz"
96
+ if cd.is_dir() and npz.exists():
97
+ pm=np.load(npz)["patch_masks"]
98
+ for idx in range(len(pm)): ev.append((cd/f"slice_{idx:04d}.png", pm[idx]))
99
+ ev=[ev[i] for i in rng.choice(len(ev),min(EVAL_SLICES,len(ev)),replace=False)]
100
+ # extract raw eval features once
101
+ Zev=[]; lab=[]
102
+ for i in range(0,len(ev),64):
103
+ chunk=ev[i:i+64]
104
+ Z=toks(model, torch.stack([load_img(p) for p,_ in chunk]), device)
105
+ Zev.append(Z.reshape(-1,768)); lab.append(np.stack([pm for _,pm in chunk]).reshape(-1))
106
+ Zev=torch.cat(Zev,0); lab=np.concatenate(lab)
107
+ log(f"eval tokens {Zev.shape[0]}, lesion rate {lab.mean():.3f}")
108
+
109
+ def whiten(Z, w):
110
+ # Z' = (Z-mu) V diag(s^{-w/2}) ; w=0 identity-ish (just rotate), w=1 full whitening
111
+ return ((Z-mu) @ V) * (s ** (-w/2.0))
112
+
113
+ res={"layer":LAYER+1,"ws":WS,"by_w":{}}
114
+ refc=(ref-mu)
115
+ for w in WS:
116
+ Zw=whiten(Zev, w).numpy()
117
+ refw=((refc) @ V * (s**(-w/2.0))).numpy()
118
+ # density membership = mean kNN distance to whitened ref bank
119
+ nn=NearestNeighbors(n_neighbors=11).fit(refw[rng.choice(len(refw),min(60000,len(refw)),replace=False)])
120
+ d,_=nn.kneighbors(Zw); memb=d[:,1:].mean(1)
121
+ a=auroc(memb,lab)
122
+ # effective rank of the whitened bank spectrum
123
+ Sw=(s**(1-w)).numpy(); er=effrank(np.sqrt(Sw))
124
+ res["by_w"][str(w)]={"density_auroc":round(a,4),"bank_effrank":round(er,1)}
125
+ log(f" w={w:.2f}: AUROC {a:.3f} bank_effrank {er:.1f}")
126
+
127
+ aur=[res["by_w"][str(w)]["density_auroc"] for w in WS]
128
+ res["auroc_drops_with_whitening"]=bool(all(aur[i]>=aur[i+1]-0.01 for i in range(len(aur)-1)))
129
+ res["auroc_w0"]=aur[0]; res["auroc_w1"]=aur[-1]; res["drop"]=round(aur[0]-aur[-1],4)
130
+ res["premise_supported"]=bool(res["auroc_drops_with_whitening"] and res["drop"]>0.05)
131
+ res["interpretation"]=("Whitening (pushing the representation toward maximal effective rank -- "
132
+ f"what RankMe/coding-rate regularizers do) drops lesion-localizability AUROC from "
133
+ f"{aur[0]:.3f} (raw) to {aur[-1]:.3f} (white). Representation GEOMETRY causally controls "
134
+ "localizability; rare-lesion signal lives in the NON-whitened, concentrated structure. "
135
+ "=> concentration-preserving pretraining (F1b) is the right prescription."
136
+ if res["premise_supported"] else "Premise not supported in this regime.")
137
+ res["elapsed_s"]=round(time.time()-t0,1)
138
+ OUT.mkdir(parents=True,exist_ok=True); (OUT/"f1a_whitening_sweep.json").write_text(json.dumps(res,indent=2))
139
+ print("F1A_RESULT "+json.dumps(res),flush=True)
140
+
141
+
142
+ if __name__=="__main__": main()
jobs/f2a_alignment_surface_job.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["torch", "numpy"]
4
+ # ///
5
+ """F2a — representation-task ALIGNMENT SURFACE (generalize S1). CPU.
6
+
7
+ S1 gave one slice (single SNR, single budget): spanning retains min(r,m)/m. F2 maps the full
8
+ alignment functional over a grid of task signal-rank r x signal-to-background SNR (amplitude),
9
+ for concentration (top-energy) vs spanning (farthest-point) selection. Outputs retention(r, amp)
10
+ for both objectives and the crossover surface r*(amp): the task-rank at which spanning catches
11
+ concentration, as a function of how clearly the signal stands out. Validates and extends the S1
12
+ closed form into a predictive map: "how much representation-quality (spanning) pressure a task of
13
+ rank r can tolerate at a given SNR." Emits F2A_RESULT.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json, os, time
18
+ import numpy as np, torch
19
+
20
+ N_TOK=196; N_LES=int(os.environ.get("N_LES","8")); BUDGET=float(os.environ.get("BUDGET","0.25"))
21
+ DIM=int(os.environ.get("DIM","256")); SUB=int(os.environ.get("SUB","64")); TRIALS=int(os.environ.get("TRIALS","150"))
22
+ RANKS=[int(x) for x in os.environ.get("RANKS","1,2,3,4,6,8,12").split(",")]
23
+ AMPS=[float(x) for x in os.environ.get("AMPS","1.0,1.5,2.0,3.0,5.0,8.0,15.0").split(",")]
24
+ def log(m): print(f"[f2a] {m}", flush=True)
25
+
26
+
27
+ def fps(Zp,k,seed):
28
+ n=Zp.shape[0]; keep=[seed]; d2=((Zp-Zp[seed])**2).sum(1)
29
+ for _ in range(k-1):
30
+ j=int(torch.argmax(d2)); keep.append(j); d2=torch.minimum(d2,((Zp-Zp[j])**2).sum(1)); d2[keep]=-1
31
+ m=np.zeros(n,bool); m[keep]=True; return m
32
+
33
+
34
+ def main():
35
+ t0=time.time(); k=max(1,int(round(BUDGET*N_TOK))); g=torch.Generator().manual_seed(0)
36
+ log(f"grid ranks={RANKS} amps={AMPS} k={k}")
37
+ surf={"ranks":RANKS,"amps":AMPS,"concentration":{}, "spanning":{}, "crossover_r_star_by_amp":{}}
38
+ for amp in AMPS:
39
+ for r in RANKS:
40
+ ce,fe=[],[]
41
+ for _ in range(TRIALS):
42
+ bg=torch.randn(N_TOK-N_LES,DIM,generator=g)
43
+ sig=0.3*torch.randn(N_LES,DIM,generator=g)
44
+ axes=torch.randperm(SUB,generator=g)[:r]; assign=torch.arange(N_LES)%r
45
+ sig[torch.arange(N_LES),axes[assign]]+=amp
46
+ Z=torch.cat([bg,sig],0); les=torch.zeros(N_TOK,dtype=torch.bool); les[-N_LES:]=True
47
+ Zp=Z[:,:SUB]; e=Zp.pow(2).sum(1)
48
+ ek=torch.zeros(N_TOK,dtype=torch.bool); ek[torch.topk(e,k).indices]=True
49
+ ce.append(float((ek&les).sum())/N_LES)
50
+ fe.append(float((torch.as_tensor(fps(Zp,k,int(torch.argmax(e))))&les).sum())/N_LES)
51
+ surf["concentration"][f"{amp}|{r}"]=round(float(np.mean(ce)),3)
52
+ surf["spanning"][f"{amp}|{r}"]=round(float(np.mean(fe)),3)
53
+ # crossover r* at this amp: smallest r where spanning >= concentration
54
+ xr=next((r for r in RANKS if surf["spanning"][f"{amp}|{r}"]>=surf["concentration"][f"{amp}|{r}"]-0.02), None)
55
+ surf["crossover_r_star_by_amp"][str(amp)]=xr
56
+ row_c=[surf["concentration"][f"{amp}|{r}"] for r in RANKS]
57
+ log(f" amp={amp:4.1f}: conc@r1={row_c[0]:.2f} crossover r*={xr}")
58
+
59
+ # the law: concentration dominates strongly at high SNR for low r; at low SNR even concentration
60
+ # weakens (signal not clearly top-energy). Quantify the high-SNR crossover.
61
+ hi_amp=str(max(AMPS))
62
+ surf["high_snr_crossover_r_star"]=surf["crossover_r_star_by_amp"][hi_amp]
63
+ surf["matches_S1_closed_form"]=bool(surf["high_snr_crossover_r_star"]==N_LES)
64
+ surf["interpretation"]=(
65
+ f"At high SNR the crossover r*={surf['high_snr_crossover_r_star']} matches the S1 closed form "
66
+ f"(r*=m={N_LES}). As SNR falls, concentration's edge shrinks (the signal is no longer clearly "
67
+ "top-energy), so the alignment functional has TWO regimes: spanning loses to concentration "
68
+ "whenever the signal is concentrated (r<m) AND distinct (high SNR). The map A(r, SNR) predicts "
69
+ "when representation-quality (spanning) regularization is safe vs harmful for a task.")
70
+ surf["elapsed_s"]=round(time.time()-t0,1)
71
+ print("F2A_RESULT "+json.dumps(surf),flush=True)
72
+
73
+
74
+ if __name__=="__main__": main()
jobs/f2b_real_alignment_job.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """F2b — validate the S1 closed-form law on REAL lesions, per-lesion, by EMPIRICAL rank.
9
+
10
+ S1 (synthetic): spanning retention = min(r, m)/m, where m = #signal tokens, r = signal rank.
11
+ For EACH real lesion we have its own (m, r): m = #lesion patches, r = effective rank of the
12
+ lesion tokens' lesion-subspace projection effrank(P_L Z_lesion). The law predicts that lesion's
13
+ SPANNING retention ~= r/m and CONCENTRATION retention ~= 1. We test this per-lesion across real
14
+ LIDC lesions (the S1a refinement: stratify by FEATURE rank, not patch count). PASS if observed
15
+ spanning retention tracks the predicted r/m (slope ~1, high correlation), confirming the
16
+ closed-form alignment law on real data. Emits F2B_RESULT.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json, os, sys, time
21
+ from pathlib import Path
22
+ import numpy as np, torch
23
+ from PIL import Image
24
+ from scipy import stats
25
+ from huggingface_hub import hf_hub_download
26
+
27
+ sys.path.insert(0, "/mnt/processed/covtoken_code")
28
+ from subspace.construction_a import DensitySubspace # noqa: E402
29
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
30
+
31
+ BACKBONE_REPO = "ricklisz123/MedDINOv3-ViTB-16-CT-3M"
32
+ MNT = Path("/mnt"); LAYER = 2
33
+ BANK = MNT/"processed"/"covtoken"/"ct_token_bank_block2.pt"
34
+ MASK_ROOT = MNT/"processed"/"lidc_v2"; OUT = MNT/"processed"/"covtoken"
35
+ N_PATCH, CLS_OFF = 196, 5; BUDGET = float(os.environ.get("BUDGET","0.25"))
36
+ CT_MEAN=np.array([0.485,0.456,0.406],np.float32); CT_STD=np.array([0.229,0.224,0.225],np.float32)
37
+ _FEAT={}
38
+ def log(m): print(f"[f2b] {m}", flush=True)
39
+
40
+
41
+ def load_backbone(device):
42
+ ck=hf_hub_download(BACKBONE_REPO,"model.pth",token=os.environ.get("HF_TOKEN"))
43
+ m=vit_base(drop_path_rate=0.0,layerscale_init=1e-5,n_storage_tokens=4,qkv_bias=False,mask_k_bias=True)
44
+ raw=torch.load(ck,map_location="cpu"); sd=raw.get("teacher",raw)
45
+ sd={(k[9:] if k.startswith("backbone.") else k):v for k,v in sd.items()}
46
+ m.load_state_dict(sd,strict=False); m.eval().to(device)
47
+ for p in m.parameters(): p.requires_grad_(False)
48
+ def hook(_m,_i,out):
49
+ while isinstance(out,(list,tuple)): out=out[0]
50
+ _FEAT["z"]=out.detach()
51
+ m.blocks[LAYER].register_forward_hook(hook); return m
52
+
53
+
54
+ def load_img(p):
55
+ img=Image.open(p).convert("RGB").resize((224,224),Image.BILINEAR)
56
+ return torch.from_numpy(((np.asarray(img,np.float32)/255.0-CT_MEAN)/CT_STD)).permute(2,0,1)
57
+
58
+
59
+ @torch.inference_mode()
60
+ def toks(model,img,device):
61
+ model.forward_features(img[None].to(device,torch.float32))
62
+ return _FEAT["z"][0,CLS_OFF:CLS_OFF+N_PATCH,:].float()
63
+
64
+
65
+ def fps(Zp,k,seed):
66
+ n=Zp.shape[0]; keep=[seed]; d2=((Zp-Zp[seed])**2).sum(1)
67
+ for _ in range(k-1):
68
+ j=int(torch.argmax(d2)); keep.append(j); d2=torch.minimum(d2,((Zp-Zp[j])**2).sum(1)); d2[keep]=-1
69
+ m=np.zeros(n,bool); m[keep]=True; return m
70
+
71
+
72
+ def effrank(M): # effective rank of a (m x d) matrix via singular values
73
+ if M.shape[0]<2: return 1.0
74
+ s=torch.linalg.svdvals(M.float()); p=s/(s.sum()+1e-9); return float(torch.exp(-(p*(p+1e-12).log()).sum()))
75
+
76
+
77
+ def main():
78
+ t0=time.time(); device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
79
+ A=DensitySubspace(rank=64,k=10,alpha=0.1,reference_size=100_000).fit(torch.load(BANK,map_location="cpu")["tokens"].float())
80
+ P_L=A.P_L_.to(device); model=load_backbone(device); k=max(1,int(round(BUDGET*N_PATCH)))
81
+ rows=[]
82
+ for cd in sorted((MASK_ROOT/"test").iterdir()):
83
+ npz=cd/"patch_masks.npz"
84
+ if cd.is_dir() and npz.exists():
85
+ pm=np.load(npz)["patch_masks"]
86
+ for idx in range(len(pm)):
87
+ if pm[idx].sum()>0: rows.append((cd.name,idx,pm[idx]))
88
+ log(f"device={device.type}; lesion slices={len(rows)}")
89
+
90
+ recs=[] # per lesion: m, r, r_over_m, span_obs, conc_obs
91
+ for i,(cid,idx,pm) in enumerate(rows):
92
+ ip=MASK_ROOT/"test"/cid/f"slice_{idx:04d}.png"
93
+ if not ip.exists(): continue
94
+ Z=toks(model,load_img(ip),device); pmb=pm.astype(bool); m=int(pmb.sum())
95
+ Zp=(Z@P_L.T)
96
+ r=effrank(Zp[torch.from_numpy(pmb)]) # empirical lesion-token rank in P_L
97
+ dens=A.membership_score_torch(Z,device=device).numpy()
98
+ conc=( (np.argsort(-dens)[:k][:,None]==np.where(pmb)[0]).any(0).sum() )/m
99
+ span=(fps(Zp,k,int(np.argmax(dens)))&pmb).sum()/m
100
+ recs.append((m, r, min(r/m,1.0), float(span), float(conc)))
101
+ if i%300==0: log(f" {i}/{len(rows)} elapsed={time.time()-t0:.0f}s")
102
+
103
+ recs=np.array(recs) # cols: m, r, r/m, span, conc
104
+ pred=recs[:,2]; span_obs=recs[:,3]; conc_obs=recs[:,4]
105
+ rho=float(stats.spearmanr(pred,span_obs).statistic)
106
+ slope=float(np.polyfit(pred,span_obs,1)[0]); inter=float(np.polyfit(pred,span_obs,1)[1])
107
+ # binned validation: predicted r/m vs observed spanning retention
108
+ bins=[(0,0.2),(0.2,0.4),(0.4,0.6),(0.6,0.8),(0.8,1.01)]
109
+ binned={}
110
+ for lo,hi in bins:
111
+ sel=(pred>=lo)&(pred<hi)
112
+ if sel.sum()>=5:
113
+ binned[f"{lo:.1f}-{hi:.1f}"]={"n":int(sel.sum()),"pred_r/m":round(float(pred[sel].mean()),3),
114
+ "obs_spanning":round(float(span_obs[sel].mean()),3),"obs_concentration":round(float(conc_obs[sel].mean()),3)}
115
+ res={"modality":"LIDC","budget":BUDGET,"n_lesions":len(recs),
116
+ "mean_m":round(float(recs[:,0].mean()),2),"mean_r":round(float(recs[:,1].mean()),2),
117
+ "mean_r_over_m":round(float(pred.mean()),3),
118
+ "law_prediction":"spanning_retention ~= r/m ; concentration ~= 1",
119
+ "spearman_pred_vs_observed_spanning":round(rho,3),
120
+ "fit_slope":round(slope,3),"fit_intercept":round(inter,3),
121
+ "mean_concentration":round(float(conc_obs.mean()),3),
122
+ "binned":binned,
123
+ "law_holds_on_real":bool(rho>=0.4 and 0.6<=slope<=1.4),
124
+ "interpretation":(f"Per real lesion, observed SPANNING retention tracks the predicted r/m "
125
+ f"(Spearman {rho:.2f}, slope {slope:.2f}), while concentration stays high ({conc_obs.mean():.2f}). "
126
+ f"Real lesions are low-rank (mean r/m {pred.mean():.2f}), so spanning loses to concentration by "
127
+ f"~{1-pred.mean():.2f} on average -- the S1 closed-form law gap=(m-r)/m, validated on REAL data."),
128
+ "elapsed_s":round(time.time()-t0,1)}
129
+ OUT.mkdir(parents=True,exist_ok=True); (OUT/"f2b_real_alignment.json").write_text(json.dumps(res,indent=2))
130
+ print("F2B_RESULT "+json.dumps(res),flush=True)
131
+
132
+
133
+ if __name__=="__main__": main()
jobs/f3_cross_objective_job.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "transformers>=4.40", "huggingface_hub>=0.34",
6
+ # ]
7
+ # ///
8
+ """F3 (decisive) — does INVARIANCE pressure CAUSE the mid-layer localizability collapse?
9
+
10
+ Cross-OBJECTIVE comparison, all natural-image-trained (domain held constant), evaluated on LIDC:
11
+ - DINOv2 (self-distillation -> high view-invariance pressure)
12
+ - MAE (masked reconstruction -> low invariance pressure)
13
+ - ViT-sup (supervised -> task-discriminative, different pressure)
14
+ Per backbone, across all 12 blocks: density-A localizability AUROC + flip view-invariance.
15
+ Prediction (invariance mechanism): the AUROC-collapse-with-depth and the invariance-rise are
16
+ COUPLED for self-distillation (DINOv2), and MAE -- low invariance -- should NOT collapse the same
17
+ way. If the collapse tracks the objective's invariance, F3a's correlation becomes causal-by-
18
+ comparison. Masks rasterized per backbone patch-grid. Emits F3X_RESULT.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ import json, os, sys, time
23
+ from pathlib import Path
24
+ import numpy as np, torch
25
+ from PIL import Image
26
+ from scipy import stats
27
+ from sklearn.neighbors import NearestNeighbors
28
+
29
+ MNT = Path("/mnt"); RAW_LIDC = MNT/"raw"/"lidc"; MASK_ROOT = MNT/"processed"/"lidc_v2"; OUT = MNT/"processed"/"covtoken"
30
+ BANK_SLICES = int(os.environ.get("BANK_SLICES","500")); EVAL_SLICES = int(os.environ.get("EVAL_SLICES","400"))
31
+ IMN_MEAN=np.array([0.485,0.456,0.406],np.float32); IMN_STD=np.array([0.229,0.224,0.225],np.float32)
32
+ # backbone -> (hf id, objective, n_special_tokens, patch_size)
33
+ BACKBONES = {
34
+ "DINOv2_selfdistill": ("facebook/dinov2-base", "self-distillation", 1, 14),
35
+ "MAE_reconstruction": ("facebook/vit-mae-base", "reconstruction", 1, 16),
36
+ "ViT_supervised": ("google/vit-base-patch16-224", "supervised", 1, 16),
37
+ }
38
+ def log(m): print(f"[f3x] {m}", flush=True)
39
+
40
+
41
+ def load_model(hf_id, device):
42
+ from transformers import AutoModel, ViTMAEModel
43
+ if "mae" in hf_id:
44
+ m = ViTMAEModel.from_pretrained(hf_id); m.config.mask_ratio = 0.0
45
+ else:
46
+ m = AutoModel.from_pretrained(hf_id)
47
+ return m.eval().to(device)
48
+
49
+
50
+ def to_img(pil):
51
+ arr=(np.asarray(pil.resize((224,224),Image.BILINEAR),np.float32)/255.0-IMN_MEAN)/IMN_STD
52
+ return torch.from_numpy(arr).permute(2,0,1)
53
+
54
+
55
+ @torch.inference_mode()
56
+ def hidden(model, imgs, device, n_special):
57
+ out = model(pixel_values=imgs.to(device,torch.float32), output_hidden_states=True)
58
+ hs = out.hidden_states # tuple, len = n_blocks+1
59
+ return [h[:, n_special:, :].float().cpu() for h in hs[1:]] # per-block patch tokens
60
+
61
+
62
+ def patch_mask(mask_path, grid):
63
+ if not mask_path.exists(): return np.zeros(grid*grid, np.uint8)
64
+ m=np.asarray(Image.open(mask_path).convert("L").resize((224,224),Image.NEAREST))>0
65
+ ps=224//grid; g=m[:grid*ps,:grid*ps].reshape(grid,ps,grid,ps).sum(axis=(1,3))
66
+ return (g>0).astype(np.uint8).reshape(-1)
67
+
68
+
69
+ def auroc(s,y):
70
+ s=np.asarray(s,float); y=np.asarray(y,int); pos,neg=y.sum(),len(y)-y.sum()
71
+ if pos==0 or neg==0: return float("nan")
72
+ r=stats.rankdata(s); return float((r[y==1].sum()-pos*(pos+1)/2)/(pos*neg))
73
+
74
+
75
+ def main():
76
+ t0=time.time(); device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
77
+ import json as _j
78
+ from huggingface_hub import hf_hub_download
79
+ scan_split=_j.load(open(hf_hub_download("Chucks90/eryon-data-pipelines","manifests/lidc/splits_v1.0.0.json",repo_type="dataset",token=os.environ.get("HF_TOKEN"))))["splits"]
80
+ rng=np.random.default_rng(0)
81
+ train=[]
82
+ for b in sorted(RAW_LIDC.glob("batch_*")):
83
+ for sd in b.iterdir():
84
+ if sd.is_dir() and scan_split.get(sd.name)=="train": train+=sorted(sd.glob("slice_*.png"))
85
+ train=[train[i] for i in rng.choice(len(train),min(BANK_SLICES,len(train)),replace=False)]
86
+ ev=[]
87
+ for cd in sorted((MASK_ROOT/"test").iterdir()):
88
+ npz=cd/"patch_masks.npz"
89
+ if cd.is_dir() and npz.exists():
90
+ n=len(np.load(npz)["patch_masks"])
91
+ for idx in range(n): ev.append((cd, idx))
92
+ ev=[ev[i] for i in rng.choice(len(ev),min(EVAL_SLICES,len(ev)),replace=False)]
93
+ log(f"device={device.type}; train={len(train)} eval={len(ev)}")
94
+
95
+ result={"modality":"LIDC","eval_domain_constant":"natural-image-trained backbones","backbones":{}}
96
+ for name,(hf_id,obj,nsp,ps) in BACKBONES.items():
97
+ grid=224//ps; n_patch=grid*grid
98
+ model=load_model(hf_id,device)
99
+ n_blocks=model.config.num_hidden_layers
100
+ log(f"{name}: {obj}, grid {grid}x{grid}={n_patch}, blocks {n_blocks}")
101
+ # per-layer banks
102
+ bankL=[[] for _ in range(n_blocks)]
103
+ for i in range(0,len(train),32):
104
+ ims=[Image.open(p).convert("RGB") for p in train[i:i+32]]
105
+ hs=hidden(model, torch.stack([to_img(im) for im in ims]), device, nsp)
106
+ for L in range(n_blocks): bankL[L].append(hs[L].reshape(-1,hs[L].shape[-1]))
107
+ nnL=[]
108
+ for L in range(n_blocks):
109
+ X=torch.cat(bankL[L],0).numpy(); X=X[rng.choice(len(X),min(40000,len(X)),replace=False)]
110
+ nnL.append(NearestNeighbors(n_neighbors=11).fit(X))
111
+ # eval: per-layer density scores + labels + flip invariance
112
+ scoresL=[[] for _ in range(n_blocks)]; labels=[]; invL=[0.0]*n_blocks; nb=0
113
+ flip_idx=(np.arange(n_patch).reshape(grid,grid)[:,::-1]).reshape(-1)
114
+ for i in range(0,len(ev),32):
115
+ chunk=ev[i:i+32]
116
+ ims=[Image.open(cd/f"slice_{idx:04d}.png").convert("RGB") for cd,idx in chunk]
117
+ hs=hidden(model, torch.stack([to_img(im) for im in ims]), device, nsp)
118
+ hsf=hidden(model, torch.stack([to_img(im.transpose(Image.FLIP_LEFT_RIGHT)) for im in ims]), device, nsp)
119
+ for L in range(n_blocks):
120
+ d,_=nnL[L].kneighbors(hs[L].reshape(-1,hs[L].shape[-1]).numpy())
121
+ scoresL[L].append(d[:,1:].mean(1).reshape(len(chunk),n_patch))
122
+ a=torch.nn.functional.normalize(hs[L],dim=2); bb=torch.nn.functional.normalize(hsf[L][:,flip_idx,:],dim=2)
123
+ invL[L]+=float((a*bb).sum(2).mean())*len(chunk)
124
+ labels.append(np.stack([patch_mask(cd/f"slice_{idx:04d}_mask.png",grid) for cd,idx in chunk]).reshape(-1))
125
+ nb+=len(chunk)
126
+ lab=np.concatenate(labels)
127
+ au=[round(auroc(np.concatenate(scoresL[L]).reshape(-1),lab),4) for L in range(n_blocks)]
128
+ inv=[round(invL[L]/nb,4) for L in range(n_blocks)]
129
+ rho_inv=float(stats.spearmanr(inv,au).statistic)
130
+ result["backbones"][name]={"objective":obj,"n_blocks":n_blocks,"auroc_by_block":au,
131
+ "flip_invariance_by_block":inv,"auroc_peak_block":int(np.nanargmax(au))+1,
132
+ "auroc_drop_peak_to_final":round(float(np.nanmax(au)-au[-1]),4),
133
+ "invariance_rise":round(inv[-1]-inv[0],4),"spearman_invariance_vs_auroc":round(rho_inv,3)}
134
+ log(f" {name}: peak block {int(np.nanargmax(au))+1}, drop_to_final {np.nanmax(au)-au[-1]:.3f}, "
135
+ f"inv_rise {inv[-1]-inv[0]:+.3f}, rho(inv,AUROC) {rho_inv:+.2f}")
136
+ del model, bankL, nnL; torch.cuda.empty_cache() if device.type=="cuda" else None
137
+
138
+ # the decisive comparison
139
+ b=result["backbones"]
140
+ coupled = {n: (b[n]["spearman_invariance_vs_auroc"], b[n]["auroc_drop_peak_to_final"], b[n]["invariance_rise"]) for n in b}
141
+ result["mechanism_test"]={
142
+ "hypothesis":"collapse magnitude and invariance-rise are largest for self-distillation (DINOv2), smallest for reconstruction (MAE)",
143
+ "by_backbone":coupled,
144
+ "self_distill_collapses_most":bool(b.get("DINOv2_selfdistill",{}).get("auroc_drop_peak_to_final",0) >= b.get("MAE_reconstruction",{}).get("auroc_drop_peak_to_final",1)),
145
+ }
146
+ result["elapsed_s"]=round(time.time()-t0,1)
147
+ OUT.mkdir(parents=True,exist_ok=True); (OUT/"f3_cross_objective.json").write_text(json.dumps(result,indent=2))
148
+ print("F3X_RESULT "+json.dumps(result),flush=True)
149
+
150
+
151
+ if __name__=="__main__": main()
jobs/f3a_ib_depth_job.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """F3a — information-bottleneck per layer: WHY does localizability peak mid-layer?
9
+
10
+ Hypothesis: global self-distillation trades SPATIAL/local information for VIEW-INVARIANT/global
11
+ information with depth; lesion localizability tracks the spatial-information curve. Per block we
12
+ measure (label-free): I(layer; spatial) via a position probe (predict patch column-bucket from
13
+ the token), and view-invariance via mean cosine between a patch and its horizontally-FLIPPED
14
+ counterpart (rises with depth as features become invariant). We then correlate both with the
15
+ localizability AUROC curve from S2. PASS if I(spatial) tracks AUROC across depth (predicts the
16
+ peak layer) and view-invariance anti-tracks it. Emits F3A_RESULT.
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import json, os, sys, time
21
+ from pathlib import Path
22
+ import numpy as np, torch
23
+ from PIL import Image
24
+ from scipy import stats
25
+ from sklearn.linear_model import LogisticRegression
26
+ from sklearn.model_selection import cross_val_score
27
+ from huggingface_hub import hf_hub_download
28
+
29
+ sys.path.insert(0, "/mnt/processed/covtoken_code")
30
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
31
+
32
+ BACKBONE_REPO = "ricklisz123/MedDINOv3-ViTB-16-CT-3M"
33
+ MNT = Path("/mnt"); MASK_ROOT = MNT/"processed"/"lidc_v2"; OUT = MNT/"processed"/"covtoken"
34
+ LAYERS = list(range(12)); N_PATCH, CLS_OFF, GRID = 196, 5, 14
35
+ N_SLICES = int(os.environ.get("N_SLICES", "400"))
36
+ CT_MEAN=np.array([0.485,0.456,0.406],np.float32); CT_STD=np.array([0.229,0.224,0.225],np.float32)
37
+ # localizability AUROC curve from S2 (block 1..12)
38
+ S2_AUROC = [0.881,0.882,0.886,0.857,0.841,0.817,0.777,0.749,0.726,0.720,0.710,0.669]
39
+ def log(m): print(f"[f3a] {m}", flush=True)
40
+
41
+
42
+ def load_backbone(device):
43
+ ck=hf_hub_download(BACKBONE_REPO,"model.pth",token=os.environ.get("HF_TOKEN"))
44
+ m=vit_base(drop_path_rate=0.0,layerscale_init=1e-5,n_storage_tokens=4,qkv_bias=False,mask_k_bias=True)
45
+ raw=torch.load(ck,map_location="cpu"); sd=raw.get("teacher",raw)
46
+ sd={(k[9:] if k.startswith("backbone.") else k):v for k,v in sd.items()}
47
+ m.load_state_dict(sd,strict=False); m.eval().to(device)
48
+ for p in m.parameters(): p.requires_grad_(False)
49
+ feats={}
50
+ for i,blk in enumerate(m.blocks):
51
+ def mk(i):
52
+ def hook(_m,_i,out):
53
+ while isinstance(out,(list,tuple)): out=out[0]
54
+ feats[i]=out.detach()
55
+ return hook
56
+ blk.register_forward_hook(mk(i))
57
+ return m,feats
58
+
59
+
60
+ def load_img(arr_img):
61
+ return torch.from_numpy(((np.asarray(arr_img,np.float32)/255.0-CT_MEAN)/CT_STD)).permute(2,0,1)
62
+
63
+
64
+ @torch.inference_mode()
65
+ def all_layers(model,feats,imgs,device):
66
+ model.forward_features(imgs.to(device,torch.float32))
67
+ return {L: feats[L][:,CLS_OFF:CLS_OFF+N_PATCH,:].float().cpu() for L in LAYERS}
68
+
69
+
70
+ def main():
71
+ t0=time.time(); device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
72
+ model,feats=load_backbone(device); rng=np.random.default_rng(0)
73
+ allp=[]
74
+ for cd in sorted((MASK_ROOT/"test").iterdir()):
75
+ if cd.is_dir():
76
+ allp+=sorted(cd.glob("slice_*.png"))
77
+ allp=[allp[i] for i in rng.choice(len(allp),min(N_SLICES,len(allp)),replace=False)]
78
+ log(f"device={device.type}; slices={len(allp)}")
79
+
80
+ col=np.tile(np.arange(GRID),GRID) # patch column index 0..13 (196,)
81
+ col_bucket=(col//4).astype(int) # 4 column buckets
82
+ Zall={L:[] for L in LAYERS}; Zflip={L:[] for L in LAYERS}; cols=[]
83
+ for i in range(0,len(allp),48):
84
+ chunk=allp[i:i+48]
85
+ ims=[Image.open(p).convert("RGB").resize((224,224),Image.BILINEAR) for p in chunk]
86
+ orig=all_layers(model,feats,torch.stack([load_img(im) for im in ims]),device)
87
+ flip=all_layers(model,feats,torch.stack([load_img(im.transpose(Image.FLIP_LEFT_RIGHT)) for im in ims]),device)
88
+ for L in LAYERS:
89
+ Zall[L].append(orig[L].reshape(-1,768)); Zflip[L].append(flip[L])
90
+ cols.append(np.tile(col_bucket,len(chunk)))
91
+ if (i//48)%4==0: log(f" {i}/{len(allp)} elapsed={time.time()-t0:.0f}s")
92
+ y=np.concatenate(cols)
93
+
94
+ by={}
95
+ for L in LAYERS:
96
+ X=torch.cat(Zall[L],0).numpy()
97
+ # I(spatial): 4-class column-bucket probe accuracy (CV)
98
+ sub=rng.choice(len(X),min(40000,len(X)),replace=False)
99
+ acc=float(np.mean(cross_val_score(LogisticRegression(max_iter=500,C=0.5),X[sub],y[sub],cv=3)))
100
+ # view-invariance: cosine(orig[i,j], flip[i, 13-j]) averaged
101
+ Zf=torch.cat(Zflip[L],0) # (n_slices,196,768)
102
+ Zo=torch.cat([z.reshape(-1,N_PATCH,768) for z in Zall[L]],0)
103
+ flip_idx=(np.arange(N_PATCH).reshape(GRID,GRID)[:,::-1]).reshape(-1)
104
+ a=torch.nn.functional.normalize(Zo,dim=2); b=torch.nn.functional.normalize(Zf[:,flip_idx,:],dim=2)
105
+ inv=float((a*b).sum(2).mean())
106
+ by[L]={"col_probe_acc":round(acc,4),"flip_invariance":round(inv,4),"auroc":S2_AUROC[L]}
107
+ log(f" block {L+1:2d}: spatial_acc {acc:.3f} flip_inv {inv:.3f} AUROC {S2_AUROC[L]:.3f}")
108
+
109
+ au=np.array(S2_AUROC)
110
+ spa=np.array([by[L]["col_probe_acc"] for L in LAYERS])
111
+ inv=np.array([by[L]["flip_invariance"] for L in LAYERS])
112
+ res={"backbone":"MedDINOv3","modality":"LIDC","by_layer":{str(L+1):by[L] for L in LAYERS},
113
+ "spearman_spatial_vs_auroc":round(float(stats.spearmanr(spa,au).statistic),3),
114
+ "spearman_invariance_vs_auroc":round(float(stats.spearmanr(inv,au).statistic),3),
115
+ "spatial_peak_block":int(np.argmax(spa))+1,"auroc_peak_block":int(np.argmax(au))+1,
116
+ "ib_hypothesis_supported":bool(stats.spearmanr(spa,au).statistic>0.5 and stats.spearmanr(inv,au).statistic<-0.3),
117
+ "elapsed_s":round(time.time()-t0,1)}
118
+ res["interpretation"]=(f"Spatial information (column-probe accuracy) correlates with localizability "
119
+ f"across depth (rho {res['spearman_spatial_vs_auroc']:+.2f}); view-invariance anti-correlates "
120
+ f"(rho {res['spearman_invariance_vs_auroc']:+.2f}). Lesion localizability tracks the LOCAL/spatial-"
121
+ "information curve and declines as features become view-invariant (global) with depth -- the "
122
+ "information-bottleneck mechanism behind the mid-layer peak."
123
+ if res["ib_hypothesis_supported"] else "IB hypothesis not clearly supported.")
124
+ OUT.mkdir(parents=True,exist_ok=True); (OUT/"f3a_ib_depth.json").write_text(json.dumps(res,indent=2))
125
+ print("F3A_RESULT "+json.dumps(res),flush=True)
126
+
127
+
128
+ if __name__=="__main__": main()
jobs/rigor_s1_multiseed_job.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["torch", "numpy", "scipy"]
4
+ # ///
5
+ """Rigor — multi-seed CI on the S1 spanning-vs-concentration law (synthetic). HF Job (CPU).
6
+
7
+ Re-runs the S1 crossover across many seeds and reports the gap(r) curve with 95% CIs and the
8
+ crossover r* distribution, so the headline law carries error bars. Emits RIGOR_S1_RESULT.
9
+ """
10
+ from __future__ import annotations
11
+ import json, os, time
12
+ import numpy as np, torch
13
+ from scipy import stats
14
+
15
+ N_TOK=196; N_LES=8; BUDGET=0.25; DIM=256; SUB=64; AMP=15.0
16
+ RANKS=[1,2,3,4,6,8,12]; SEEDS=int(os.environ.get("SEEDS","40")); TRIALS=int(os.environ.get("TRIALS","60"))
17
+ def log(m): print(f"[rigor-s1] {m}", flush=True)
18
+
19
+
20
+ def fps(Zp,k,seed):
21
+ n=Zp.shape[0]; keep=[seed]; d2=((Zp-Zp[seed])**2).sum(1)
22
+ for _ in range(k-1):
23
+ j=int(torch.argmax(d2)); keep.append(j); d2=torch.minimum(d2,((Zp-Zp[j])**2).sum(1)); d2[keep]=-1
24
+ m=np.zeros(n,bool); m[keep]=True; return m
25
+
26
+
27
+ def one_seed(seed):
28
+ g=torch.Generator().manual_seed(seed); k=max(1,int(round(BUDGET*N_TOK))); gaps={}
29
+ for r in RANKS:
30
+ ce,fe=[],[]
31
+ for _ in range(TRIALS):
32
+ bg=torch.randn(N_TOK-N_LES,DIM,generator=g); sig=0.3*torch.randn(N_LES,DIM,generator=g)
33
+ axes=torch.randperm(SUB,generator=g)[:r]; assign=torch.arange(N_LES)%r
34
+ sig[torch.arange(N_LES),axes[assign]]+=AMP
35
+ Z=torch.cat([bg,sig],0); les=torch.zeros(N_TOK,dtype=torch.bool); les[-N_LES:]=True
36
+ Zp=Z[:,:SUB]; e=Zp.pow(2).sum(1)
37
+ ek=torch.zeros(N_TOK,dtype=torch.bool); ek[torch.topk(e,k).indices]=True
38
+ ce.append(float((ek&les).sum())/N_LES)
39
+ fe.append(float((torch.as_tensor(fps(Zp,k,int(torch.argmax(e))))&les).sum())/N_LES)
40
+ gaps[r]=np.mean(ce)-np.mean(fe)
41
+ xr=next((r for r in RANKS if gaps[r]<=0.02), None)
42
+ return gaps, xr
43
+
44
+
45
+ def main():
46
+ t0=time.time(); per_seed_gaps={r:[] for r in RANKS}; xs=[]
47
+ for s in range(SEEDS):
48
+ g,xr=one_seed(s)
49
+ for r in RANKS: per_seed_gaps[r].append(g[r])
50
+ xs.append(xr if xr is not None else max(RANKS))
51
+ if s%10==0: log(f" seed {s}/{SEEDS} elapsed={time.time()-t0:.0f}s")
52
+ res={"seeds":SEEDS,"trials_per_seed":TRIALS,"gap_by_rank":{}}
53
+ for r in RANKS:
54
+ a=np.array(per_seed_gaps[r]); lo,hi=np.quantile(a,[0.025,0.975])
55
+ res["gap_by_rank"][str(r)]={"mean":round(float(a.mean()),4),"ci95":[round(float(lo),4),round(float(hi),4)],"std":round(float(a.std()),4)}
56
+ xs=np.array(xs);
57
+ res["crossover_r_star"]={"mean":round(float(xs.mean()),2),"mode":int(stats.mode(xs,keepdims=False).mode),"ci95":[float(np.quantile(xs,0.025)),float(np.quantile(xs,0.975))]}
58
+ res["law"]="gap(r=1) and crossover r*=m=8 with CIs across seeds"
59
+ res["elapsed_s"]=round(time.time()-t0,1)
60
+ print("RIGOR_S1_RESULT "+json.dumps(res),flush=True)
61
+
62
+
63
+ if __name__=="__main__": main()
jobs/rigor_s2_multiseed_job.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """Rigor — multi-seed CIs on the S2 depth-localizability curve + label-free selector. HF Job (GPU).
9
+
10
+ Re-runs the per-layer density-AUROC and the tail-gap/bimodality selectors across SEEDS (different
11
+ bank+eval samples) so the depth curve carries error bars and the selector's regret has a CI.
12
+ Emits RIGOR_S2_RESULT.
13
+ """
14
+ from __future__ import annotations
15
+ import json, os, sys, time
16
+ from pathlib import Path
17
+ import numpy as np, torch
18
+ from PIL import Image
19
+ from scipy import stats
20
+ from sklearn.neighbors import NearestNeighbors
21
+ from huggingface_hub import hf_hub_download
22
+ sys.path.insert(0,"/mnt/processed/covtoken_code")
23
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
24
+
25
+ BACKBONE_REPO="ricklisz123/MedDINOv3-ViTB-16-CT-3M"; MNT=Path("/mnt")
26
+ RAW_LIDC=MNT/"raw"/"lidc"; MASK_ROOT=MNT/"processed"/"lidc_v2"; OUT=MNT/"processed"/"covtoken"
27
+ LAYERS=list(range(12)); N_PATCH,CLS_OFF=196,5
28
+ SEEDS=int(os.environ.get("SEEDS","3")); BANK_SLICES=int(os.environ.get("BANK_SLICES","600")); EVAL_SLICES=int(os.environ.get("EVAL_SLICES","600"))
29
+ CT_MEAN=np.array([0.485,0.456,0.406],np.float32); CT_STD=np.array([0.229,0.224,0.225],np.float32)
30
+ def log(m): print(f"[rigor-s2] {m}", flush=True)
31
+
32
+
33
+ def load_backbone(device):
34
+ ck=hf_hub_download(BACKBONE_REPO,"model.pth",token=os.environ.get("HF_TOKEN"))
35
+ m=vit_base(drop_path_rate=0.0,layerscale_init=1e-5,n_storage_tokens=4,qkv_bias=False,mask_k_bias=True)
36
+ raw=torch.load(ck,map_location="cpu"); sd=raw.get("teacher",raw)
37
+ sd={(k[9:] if k.startswith("backbone.") else k):v for k,v in sd.items()}
38
+ m.load_state_dict(sd,strict=False); m.eval().to(device)
39
+ for p in m.parameters(): p.requires_grad_(False)
40
+ feats={}
41
+ for i,blk in enumerate(m.blocks):
42
+ def mk(i):
43
+ def h(_m,_i,out):
44
+ while isinstance(out,(list,tuple)): out=out[0]
45
+ feats[i]=out.detach()
46
+ return h
47
+ blk.register_forward_hook(mk(i))
48
+ return m,feats
49
+
50
+
51
+ def load_img(p):
52
+ img=Image.open(p).convert("RGB").resize((224,224),Image.BILINEAR)
53
+ return torch.from_numpy(((np.asarray(img,np.float32)/255.0-CT_MEAN)/CT_STD)).permute(2,0,1)
54
+
55
+
56
+ @torch.inference_mode()
57
+ def allL(model,feats,imgs,device):
58
+ model.forward_features(imgs.to(device,torch.float32))
59
+ return {L:feats[L][:,CLS_OFF:CLS_OFF+N_PATCH,:].float().cpu() for L in LAYERS}
60
+
61
+
62
+ def auroc(s,y):
63
+ s=np.asarray(s,float); y=np.asarray(y,int); pos,neg=y.sum(),len(y)-y.sum()
64
+ if pos==0 or neg==0: return float("nan")
65
+ r=stats.rankdata(s); return float((r[y==1].sum()-pos*(pos+1)/2)/(pos*neg))
66
+
67
+
68
+ def proxies(s):
69
+ q01,q50,q99=np.quantile(s,[0.01,0.5,0.99]); tg=(q99-q50)/(q50-q01+1e-9)
70
+ sk=stats.skew(s); ku=stats.kurtosis(s); n=len(s)
71
+ bm=(sk**2+1)/(ku+3+3*((n-1)**2)/((n-2)*(n-3)+1e-9)); return tg, bm
72
+
73
+
74
+ def run_seed(model,feats,device,seed,scan_split):
75
+ rng=np.random.default_rng(seed)
76
+ train=[]
77
+ for b in sorted(RAW_LIDC.glob("batch_*")):
78
+ for sd in b.iterdir():
79
+ if sd.is_dir() and scan_split.get(sd.name)=="train": train+=sorted(sd.glob("slice_*.png"))
80
+ train=[train[i] for i in rng.choice(len(train),min(BANK_SLICES,len(train)),replace=False)]
81
+ ev=[]
82
+ for cd in sorted((MASK_ROOT/"test").iterdir()):
83
+ npz=cd/"patch_masks.npz"
84
+ if cd.is_dir() and npz.exists():
85
+ pm=np.load(npz)["patch_masks"]
86
+ for idx in range(len(pm)): ev.append((cd/f"slice_{idx:04d}.png", pm[idx]))
87
+ ev=[ev[i] for i in rng.choice(len(ev),min(EVAL_SLICES,len(ev)),replace=False)]
88
+ bankL={L:[] for L in LAYERS}
89
+ for i in range(0,len(train),64):
90
+ pl=allL(model,feats,torch.stack([load_img(p) for p in train[i:i+64]]),device)
91
+ for L in LAYERS: bankL[L].append(pl[L].reshape(-1,768))
92
+ nnL={}
93
+ for L in LAYERS:
94
+ X=torch.cat(bankL[L],0).numpy(); X=X[rng.choice(len(X),min(50000,len(X)),replace=False)]
95
+ nnL[L]=NearestNeighbors(n_neighbors=11).fit(X)
96
+ sc={L:[] for L in LAYERS}; lab=[]
97
+ for i in range(0,len(ev),64):
98
+ chunk=ev[i:i+64]; pl=allL(model,feats,torch.stack([load_img(p) for p,_ in chunk]),device)
99
+ for L in LAYERS:
100
+ d,_=nnL[L].kneighbors(pl[L].reshape(-1,768).numpy()); sc[L].append(d[:,1:].mean(1).reshape(len(chunk),N_PATCH))
101
+ lab.append(np.stack([pm for _,pm in chunk]).reshape(-1))
102
+ lab=np.concatenate(lab)
103
+ au=[]; tg=[]; bm=[]
104
+ for L in LAYERS:
105
+ s=np.concatenate(sc[L]).reshape(-1); au.append(auroc(s,lab)); a,b=proxies(s); tg.append(a); bm.append(b)
106
+ return np.array(au),np.array(tg),np.array(bm)
107
+
108
+
109
+ def main():
110
+ t0=time.time(); device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
111
+ model,feats=load_backbone(device)
112
+ scan_split=json.load(open(hf_hub_download("Chucks90/eryon-data-pipelines","manifests/lidc/splits_v1.0.0.json",repo_type="dataset",token=os.environ.get("HF_TOKEN"))))["splits"]
113
+ AU=[]; regret_tg=[]; regret_bm=[]
114
+ for s in range(SEEDS):
115
+ au,tg,bm=run_seed(model,feats,device,s,scan_split); AU.append(au)
116
+ oracle=np.nanmax(au)
117
+ regret_tg.append(oracle-au[int(np.nanargmax(tg))]); regret_bm.append(oracle-au[int(np.nanargmax(bm))])
118
+ log(f" seed {s}: oracle {oracle:.3f}@blk{int(np.nanargmax(au))+1}; tg-regret {regret_tg[-1]:.3f}; bm-regret {regret_bm[-1]:.3f}; elapsed={time.time()-t0:.0f}s")
119
+ AU=np.array(AU)
120
+ res={"seeds":SEEDS,"auroc_mean_by_block":[round(float(x),4) for x in AU.mean(0)],
121
+ "auroc_std_by_block":[round(float(x),4) for x in AU.std(0)],
122
+ "tail_gap_selector_regret":{"mean":round(float(np.mean(regret_tg)),4),"max":round(float(np.max(regret_tg)),4)},
123
+ "bimodality_selector_regret":{"mean":round(float(np.mean(regret_bm)),4),"max":round(float(np.max(regret_bm)),4)},
124
+ "elapsed_s":round(time.time()-t0,1)}
125
+ OUT.mkdir(parents=True,exist_ok=True); (OUT/"rigor_s2_multiseed.json").write_text(json.dumps(res,indent=2))
126
+ print("RIGOR_S2_RESULT "+json.dumps(res),flush=True)
127
+
128
+
129
+ if __name__=="__main__": main()
jobs/s1_rank_crossover_job.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = ["torch", "numpy", "scikit-learn", "huggingface_hub>=0.34"]
4
+ # ///
5
+ """S1b — spanning-vs-concentration CROSSOVER (controlled synthetic). HF Job (CPU).
6
+
7
+ Tests the central theory from the covtoken negative result: a SPANNING objective (maximize the
8
+ effective rank / diversity of the retained set in the lesion subspace) vs a CONCENTRATION
9
+ objective (retain highest-energy / membership tokens), as a function of the signal's effective
10
+ rank r.
11
+
12
+ Clean controlled setup (isotropic background so energy cleanly separates the injected signal):
13
+ - Background: N_TOK - N_LES tokens ~ N(0, I_d) (isotropic; per-token lesion-subspace energy ~ chi2).
14
+ - Lesion subspace L = the first SUB_RANK coordinates; P_L is projection onto them.
15
+ - Signal: N_LES tokens whose L-projections span exactly r orthonormal directions of L (r=1 =>
16
+ aligned/concentrated; r large => diverse/high-rank), each with energy AMP^2 >> background.
17
+ We KNOW which tokens are signal. At a token budget we select via:
18
+ - CONCENTRATION: top-k by lesion-subspace energy ||P_L z||^2.
19
+ - SPANNING: farthest-point sampling in P_L space (a fast, faithful proxy for maximizing
20
+ effective-rank coverage / diversity), seeded at the max-energy token.
21
+ Metric: signal-token RETENTION. Prediction: concentration >> spanning at low r; the gap shrinks
22
+ monotonically and CROSSES at r* where spanning >= concentration. Locates r*. Emits S1_RESULT.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import json
27
+ import os
28
+ import time
29
+
30
+ import numpy as np
31
+ import torch
32
+
33
+ N_TOK = 196
34
+ N_LES = int(os.environ.get("N_LES", "8")) # injected signal tokens
35
+ BUDGET = float(os.environ.get("BUDGET", "0.25"))
36
+ RANKS = [int(x) for x in os.environ.get("RANKS", "1,2,3,4,6,8,12,16,24,32").split(",")]
37
+ TRIALS = int(os.environ.get("TRIALS", "200"))
38
+ SUB_RANK = int(os.environ.get("SUB_RANK", "64")) # lesion-subspace dimensionality
39
+ DIM = int(os.environ.get("DIM", "256")) # ambient dim
40
+ AMP = float(os.environ.get("AMP", "15.0")) # signal amplitude (AMP^2 >> SUB_RANK background)
41
+
42
+
43
+ def log(m): print(f"[s1] {m}", flush=True)
44
+
45
+
46
+ def project_energy(Zp):
47
+ """||P_L z||^2 per token (Zp already projected to L)."""
48
+ return Zp.pow(2).sum(1)
49
+
50
+
51
+ def farthest_point(Zp, k, seed_idx):
52
+ """FPS in projected space Zp (n, r); returns boolean keep mask of size n."""
53
+ n = Zp.shape[0]
54
+ keep = [seed_idx]
55
+ d2 = ((Zp - Zp[seed_idx]) ** 2).sum(1)
56
+ for _ in range(k - 1):
57
+ j = int(torch.argmax(d2))
58
+ keep.append(j)
59
+ d2 = torch.minimum(d2, ((Zp - Zp[j]) ** 2).sum(1))
60
+ d2[keep] = -1
61
+ m = torch.zeros(n, dtype=torch.bool); m[keep] = True
62
+ return m
63
+
64
+
65
+ def main():
66
+ t0 = time.time()
67
+ k = max(1, int(round(BUDGET * N_TOK)))
68
+ g = torch.Generator().manual_seed(0)
69
+ log(f"isotropic synthetic: dim={DIM} sub_rank={SUB_RANK} amp={AMP} k={k}/{N_TOK} n_les={N_LES}")
70
+
71
+ out = {"budget": BUDGET, "k": k, "n_les": N_LES, "sub_rank": SUB_RANK, "dim": DIM, "amp": AMP,
72
+ "ranks": RANKS, "trials": TRIALS, "random_baseline": round(k / N_TOK, 3), "by_rank": {}}
73
+ for r in RANKS:
74
+ ret_energy, ret_fps = [], []
75
+ for _ in range(TRIALS):
76
+ # background: isotropic Gaussian; L = first SUB_RANK coords
77
+ bg = torch.randn(N_TOK - N_LES, DIM, generator=g)
78
+ # signal: N_LES tokens spanning exactly r of L's axes, high energy
79
+ sig = 0.3 * torch.randn(N_LES, DIM, generator=g)
80
+ axes = torch.randperm(SUB_RANK, generator=g)[:r]
81
+ assign = torch.arange(N_LES) % r
82
+ sig[torch.arange(N_LES), axes[assign]] += AMP
83
+ Z = torch.cat([bg, sig], 0) # (196, d)
84
+ les_mask = torch.zeros(N_TOK, dtype=torch.bool); les_mask[-N_LES:] = True
85
+ Zp = Z[:, :SUB_RANK] # P_L Z
86
+ e = project_energy(Zp)
87
+ # CONCENTRATION: top-k energy
88
+ ek = torch.zeros(N_TOK, dtype=torch.bool); ek[torch.topk(e, k).indices] = True
89
+ ret_energy.append(float((ek & les_mask).sum()) / N_LES)
90
+ # SPANNING: farthest-point in P_L space, seeded at max-energy token
91
+ fk = farthest_point(Zp, k, int(torch.argmax(e)))
92
+ ret_fps.append(float((fk & les_mask).sum()) / N_LES)
93
+ out["by_rank"][str(r)] = {"concentration_retention": float(np.mean(ret_energy)),
94
+ "spanning_retention": float(np.mean(ret_fps)),
95
+ "gap_conc_minus_span": float(np.mean(ret_energy) - np.mean(ret_fps))}
96
+ log(f" r={r:2d}: concentration={np.mean(ret_energy):.3f} spanning={np.mean(ret_fps):.3f} "
97
+ f"gap={np.mean(ret_energy)-np.mean(ret_fps):+.3f}")
98
+
99
+ # locate crossover r*: smallest r where spanning >= concentration
100
+ gaps = [(r, out["by_rank"][str(r)]["gap_conc_minus_span"]) for r in RANKS]
101
+ crossover = next((r for r, gp in gaps if gp <= 0), None)
102
+ # monotonic shrink check
103
+ g_vals = [gp for _, gp in gaps]
104
+ mono = all(g_vals[i] >= g_vals[i + 1] - 0.02 for i in range(len(g_vals) - 1))
105
+ out["crossover_r_star"] = crossover
106
+ out["gap_monotone_shrinking"] = bool(mono)
107
+ out["theory_supported"] = bool(mono and (crossover is not None) and g_vals[0] > 0.1)
108
+ out["interpretation"] = (
109
+ f"Concentration beats spanning by {g_vals[0]:.2f} at r=1; gap shrinks monotonically; "
110
+ f"crossover r*={crossover}. Confirms: rank/spanning objectives are mismatched to LOW-rank "
111
+ f"(rare) signal and only help once signal rank exceeds r*."
112
+ if out["theory_supported"] else
113
+ "No clean crossover / non-monotone: theory not supported in this regime.")
114
+ out["elapsed_s"] = round(time.time() - t0, 1)
115
+ print("S1_RESULT " + json.dumps(out), flush=True)
116
+
117
+
118
+ if __name__ == "__main__":
119
+ main()
jobs/s1a_real_crossover_job.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """S1a — spanning-vs-concentration crossover on REAL lesions, stratified by lesion rank. HF Job.
9
+
10
+ The synthetic law (S1b) predicts: the CONCENTRATION-minus-SPANNING lesion-retention gap shrinks
11
+ monotonically as lesion size/rank grows. We test it on real LIDC lesion slices, stratified by
12
+ #lesion-patches in {1, 2-3, 4-8, >8} (a natural rank proxy). At a token budget we measure
13
+ lesion-patch recall under:
14
+ - CONCENTRATION : top-k by block-3 density-A membership.
15
+ - SPANNING : farthest-point sampling in the lesion-subspace P_L (diversity / effective-rank
16
+ proxy -- the objective the covtoken floor optimizes).
17
+ - SALIENCY : top-k attention (reference).
18
+ Prediction: gap(concentration - spanning) is largest for 1-patch lesions and shrinks toward 0 for
19
+ large lesions. Emits S1A_RESULT <json>.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ import sys
26
+ import time
27
+ from pathlib import Path
28
+
29
+ import numpy as np
30
+ import torch
31
+ import torch.nn.functional as F
32
+ from PIL import Image
33
+ from huggingface_hub import hf_hub_download
34
+
35
+ sys.path.insert(0, "/mnt/processed/covtoken_code")
36
+ from subspace.construction_a import DensitySubspace # noqa: E402
37
+ from eval.stats import paired_bootstrap_diff # noqa: E402
38
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
39
+
40
+ BACKBONE_REPO = "ricklisz123/MedDINOv3-ViTB-16-CT-3M"
41
+ MNT = Path("/mnt")
42
+ LAYER = int(os.environ.get("LAYER", "2"))
43
+ BANK = MNT / "processed" / "covtoken" / f"ct_token_bank_block{LAYER}.pt"
44
+ MASK_ROOT = MNT / os.environ.get("MASK_ROOT", "processed/lidc_v2")
45
+ OUT = MNT / "processed" / "covtoken"
46
+ EVAL_SPLIT = os.environ.get("EVAL_SPLIT", "test")
47
+ BUDGET = float(os.environ.get("BUDGET", "0.25"))
48
+ N_PATCH, CLS_OFF = 196, 5
49
+ BUCKETS = [("1", 1, 1), ("2-3", 2, 3), ("4-8", 4, 8), (">8", 9, 999)]
50
+ CT_MEAN = np.array([0.485, 0.456, 0.406], np.float32)
51
+ CT_STD = np.array([0.229, 0.224, 0.225], np.float32)
52
+ _FEAT, _ATTN = {}, {}
53
+
54
+
55
+ def log(m): print(f"[s1a] {m}", flush=True)
56
+
57
+
58
+ _ORIG_SDPA = F.scaled_dot_product_attention
59
+
60
+
61
+ def _sdpa_wrap(q, k, v, *a, **kw):
62
+ try:
63
+ _ATTN["last"] = torch.softmax((q.float() @ k.float().transpose(-1, -2))
64
+ / (q.shape[-1] ** 0.5), dim=-1).detach()
65
+ except Exception:
66
+ pass
67
+ return _ORIG_SDPA(q, k, v, *a, **kw)
68
+
69
+
70
+ F.scaled_dot_product_attention = _sdpa_wrap # patch ONCE (per-image loop => no re-wrapping)
71
+
72
+
73
+ def load_backbone(device):
74
+ ck = hf_hub_download(BACKBONE_REPO, "model.pth", token=os.environ.get("HF_TOKEN"))
75
+ m = vit_base(drop_path_rate=0.0, layerscale_init=1e-5, n_storage_tokens=4,
76
+ qkv_bias=False, mask_k_bias=True)
77
+ raw = torch.load(ck, map_location="cpu"); sd = raw.get("teacher", raw)
78
+ sd = {(k[9:] if k.startswith("backbone.") else k): v for k, v in sd.items()}
79
+ m.load_state_dict(sd, strict=False); m.eval().to(device)
80
+ for p in m.parameters():
81
+ p.requires_grad_(False)
82
+ def hook(_mod, _in, out):
83
+ while isinstance(out, (list, tuple)):
84
+ out = out[0]
85
+ _FEAT["z"] = out.detach()
86
+ m.blocks[LAYER].register_forward_hook(hook)
87
+ return m
88
+
89
+
90
+ def load_img(path):
91
+ img = Image.open(path).convert("RGB").resize((224, 224), Image.BILINEAR)
92
+ arr = (np.asarray(img, np.float32) / 255.0 - CT_MEAN) / CT_STD
93
+ return torch.from_numpy(arr).permute(2, 0, 1)
94
+
95
+
96
+ @torch.inference_mode()
97
+ def extract(model, img, device):
98
+ model.forward_features(img[None].to(device, torch.float32))
99
+ Z = _FEAT["z"][0, CLS_OFF:CLS_OFF + N_PATCH, :].float()
100
+ w = _ATTN.get("last")
101
+ sal = w[0, :, 0, CLS_OFF:CLS_OFF + N_PATCH].mean(0).float().cpu().numpy() if w is not None else np.random.rand(N_PATCH)
102
+ return Z, sal
103
+
104
+
105
+ def farthest_point(Zp, k, seed):
106
+ n = Zp.shape[0]; keep = [seed]
107
+ d2 = ((Zp - Zp[seed]) ** 2).sum(1)
108
+ for _ in range(k - 1):
109
+ j = int(torch.argmax(d2)); keep.append(j)
110
+ d2 = torch.minimum(d2, ((Zp - Zp[j]) ** 2).sum(1)); d2[keep] = -1
111
+ m = np.zeros(n, bool); m[keep] = True; return m
112
+
113
+
114
+ def topk(s, k):
115
+ m = np.zeros(N_PATCH, bool); m[np.argsort(-s)[:k]] = True; return m
116
+
117
+
118
+ def main():
119
+ t0 = time.time()
120
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
121
+ A = DensitySubspace(rank=64, k=10, alpha=0.1, reference_size=100_000).fit(
122
+ torch.load(BANK, map_location="cpu")["tokens"].float())
123
+ P_L = A.P_L_.to(device)
124
+ model = load_backbone(device)
125
+ k = max(1, int(round(BUDGET * N_PATCH)))
126
+
127
+ rows = []
128
+ for cd in sorted((MASK_ROOT / EVAL_SPLIT).iterdir()):
129
+ npz = cd / "patch_masks.npz"
130
+ if cd.is_dir() and npz.exists():
131
+ pm = np.load(npz)["patch_masks"]
132
+ for idx in range(len(pm)):
133
+ if pm[idx].sum() > 0:
134
+ rows.append((cd.name, idx, pm[idx]))
135
+ log(f"device={device.type}; lesion slices={len(rows)}; budget={BUDGET}")
136
+
137
+ strata = {b[0]: {"conc": [], "span": [], "sal": []} for b in BUCKETS}
138
+ for i, (cid, idx, pm) in enumerate(rows):
139
+ ip = MASK_ROOT / EVAL_SPLIT / cid / f"slice_{idx:04d}.png"
140
+ if not ip.exists():
141
+ continue
142
+ Z, sal = extract(model, load_img(ip), device)
143
+ dens = A.membership_score_torch(Z, device=device).numpy()
144
+ Zp = (Z @ P_L.T) # lesion-subspace projection
145
+ pmb = pm.astype(bool); npos = int(pmb.sum())
146
+ bname = next(b[0] for b in BUCKETS if b[1] <= npos <= b[2])
147
+ strata[bname]["conc"].append((topk(dens, k) & pmb).sum() / npos)
148
+ strata[bname]["span"].append((farthest_point(Zp, k, int(np.argmax(dens))) & pmb).sum() / npos)
149
+ strata[bname]["sal"].append((topk(sal, k) & pmb).sum() / npos)
150
+ if i % 200 == 0:
151
+ log(f" {i}/{len(rows)} elapsed={time.time()-t0:.0f}s")
152
+
153
+ res = {"modality": "LIDC-IDRI", "layer": LAYER + 1, "budget": BUDGET, "strata": {}}
154
+ gaps = []
155
+ for b in BUCKETS:
156
+ s = strata[b[0]]
157
+ if len(s["conc"]) < 5:
158
+ res["strata"][b[0]] = {"n": len(s["conc"]), "note": "too few"}
159
+ continue
160
+ conc, span = np.array(s["conc"]), np.array(s["span"])
161
+ pb = paired_bootstrap_diff(conc, span, n=2000)
162
+ res["strata"][b[0]] = {"n": int(len(conc)), "concentration": float(conc.mean()),
163
+ "spanning": float(span.mean()), "saliency": float(np.mean(s["sal"])),
164
+ "gap_conc_minus_span": pb["diff"], "ci95": pb["ci95"],
165
+ "excludes_0": pb["excludes_0"]}
166
+ gaps.append((b[0], pb["diff"]))
167
+ log(f" {b[0]:>4}: n={len(conc)} conc={conc.mean():.3f} span={span.mean():.3f} gap={pb['diff']:+.3f}")
168
+ # monotone shrink across available strata
169
+ gv = [g for _, g in gaps]
170
+ res["gap_shrinks_with_rank"] = bool(len(gv) >= 2 and all(gv[i] >= gv[i+1] - 0.03 for i in range(len(gv)-1)))
171
+ res["theory_supported_on_real"] = bool(res["gap_shrinks_with_rank"] and gv and gv[0] > 0.1)
172
+ res["elapsed_s"] = round(time.time() - t0, 1)
173
+ OUT.mkdir(parents=True, exist_ok=True)
174
+ (OUT / "s1a_real_crossover.json").write_text(json.dumps(res, indent=2))
175
+ print("S1A_RESULT " + json.dumps(res), flush=True)
176
+
177
+
178
+ if __name__ == "__main__":
179
+ main()
jobs/s2_depth_localizability_job.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """S2 — depth-resolved localizability + LABEL-FREE layer selector. HF Job (GPU).
9
+
10
+ Sweeps ALL 12 blocks. Per layer: (oracle) density-A token-level lesion AUROC vs held-out masks;
11
+ and (label-free) geometry proxies of the membership-score distribution computed with NO masks:
12
+ - tail_gap = (q99 - q50) / (q50 - q01 + eps) [how distinct the rare high-membership tokens are]
13
+ - kurtosis = excess kurtosis of membership scores [heavy tail => rare structure]
14
+ - bimodality= Sarle's bimodality coefficient
15
+ Question: does argmax(label-free proxy) over layers match the oracle AUROC-optimal layer?
16
+ Emits S2_RESULT <json>. Establishes WHERE localization lives and whether it is label-free
17
+ predictable (feeds the SPIE representation-coverage probe paper).
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ import sys
24
+ import time
25
+ from pathlib import Path
26
+
27
+ import numpy as np
28
+ import torch
29
+ from PIL import Image
30
+ from scipy import stats
31
+ from sklearn.neighbors import NearestNeighbors
32
+ from huggingface_hub import hf_hub_download
33
+
34
+ sys.path.insert(0, "/mnt/processed/covtoken_code")
35
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
36
+
37
+ BACKBONE_REPO = "ricklisz123/MedDINOv3-ViTB-16-CT-3M"
38
+ MNT = Path("/mnt")
39
+ RAW_LIDC = MNT / "raw" / "lidc"
40
+ MASK_ROOT = MNT / "processed" / "lidc_v2"
41
+ OUT = MNT / "processed" / "covtoken"
42
+ LAYERS = list(range(12))
43
+ N_PATCH, CLS_OFF = 196, 5
44
+ BANK_SLICES = int(os.environ.get("BANK_SLICES", "800"))
45
+ EVAL_SLICES = int(os.environ.get("EVAL_SLICES", "700"))
46
+ CT_MEAN = np.array([0.485, 0.456, 0.406], np.float32)
47
+ CT_STD = np.array([0.229, 0.224, 0.225], np.float32)
48
+
49
+
50
+ def log(m): print(f"[s2] {m}", flush=True)
51
+
52
+
53
+ def load_backbone(device):
54
+ ck = hf_hub_download(BACKBONE_REPO, "model.pth", token=os.environ.get("HF_TOKEN"))
55
+ m = vit_base(drop_path_rate=0.0, layerscale_init=1e-5, n_storage_tokens=4,
56
+ qkv_bias=False, mask_k_bias=True)
57
+ raw = torch.load(ck, map_location="cpu"); sd = raw.get("teacher", raw)
58
+ sd = {(k[9:] if k.startswith("backbone.") else k): v for k, v in sd.items()}
59
+ m.load_state_dict(sd, strict=False); m.eval().to(device)
60
+ for p in m.parameters():
61
+ p.requires_grad_(False)
62
+ feats = {}
63
+ for i, blk in enumerate(m.blocks):
64
+ def mk(i):
65
+ def hook(_mod, _in, out):
66
+ while isinstance(out, (list, tuple)):
67
+ out = out[0]
68
+ feats[i] = out.detach()
69
+ return hook
70
+ blk.register_forward_hook(mk(i))
71
+ return m, feats
72
+
73
+
74
+ def load_img(p):
75
+ img = Image.open(p).convert("RGB").resize((224, 224), Image.BILINEAR)
76
+ return torch.from_numpy(((np.asarray(img, np.float32)/255.0 - CT_MEAN)/CT_STD)).permute(2, 0, 1)
77
+
78
+
79
+ @torch.inference_mode()
80
+ def all_layers(model, feats, imgs, device):
81
+ model.forward_features(imgs.to(device, torch.float32))
82
+ return {L: feats[L][:, CLS_OFF:CLS_OFF+N_PATCH, :].float().cpu() for L in LAYERS}
83
+
84
+
85
+ def auroc(s, y):
86
+ s = np.asarray(s, float).ravel(); y = np.asarray(y, int).ravel()
87
+ pos, neg = y.sum(), len(y)-y.sum()
88
+ if pos == 0 or neg == 0: return float("nan")
89
+ r = stats.rankdata(s); return float((r[y == 1].sum()-pos*(pos+1)/2)/(pos*neg))
90
+
91
+
92
+ def labelfree_proxies(scores):
93
+ s = np.asarray(scores, float)
94
+ q01, q50, q99 = np.quantile(s, [0.01, 0.5, 0.99])
95
+ tail_gap = (q99 - q50) / (q50 - q01 + 1e-9)
96
+ kurt = float(stats.kurtosis(s))
97
+ m3 = stats.skew(s); m4 = stats.kurtosis(s) + 3
98
+ bimod = (m3**2 + 1) / (m4 + 3*((len(s)-1)**2)/((len(s)-2)*(len(s)-3) + 1e-9))
99
+ return {"tail_gap": float(tail_gap), "kurtosis": kurt, "bimodality": float(bimod)}
100
+
101
+
102
+ def main():
103
+ t0 = time.time()
104
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
105
+ model, feats = load_backbone(device)
106
+ spath = hf_hub_download("Chucks90/eryon-data-pipelines", "manifests/lidc/splits_v1.0.0.json",
107
+ repo_type="dataset", token=os.environ.get("HF_TOKEN"))
108
+ scan_split = json.load(open(spath))["splits"]
109
+
110
+ # train bank slices (label-free) + test lesion+neg slices (masks for oracle only)
111
+ train = []
112
+ for b in sorted(RAW_LIDC.glob("batch_*")):
113
+ for sd in b.iterdir():
114
+ if sd.is_dir() and scan_split.get(sd.name) == "train":
115
+ train += sorted(sd.glob("slice_*.png"))
116
+ rng = np.random.default_rng(0)
117
+ train = [train[i] for i in rng.choice(len(train), min(BANK_SLICES, len(train)), replace=False)]
118
+
119
+ ev = []
120
+ for cd in sorted((MASK_ROOT / "test").iterdir()):
121
+ npz = cd / "patch_masks.npz"
122
+ if cd.is_dir() and npz.exists():
123
+ pm = np.load(npz)["patch_masks"]
124
+ for idx in range(len(pm)):
125
+ ev.append((cd / f"slice_{idx:04d}.png", pm[idx]))
126
+ ev = [ev[i] for i in rng.choice(len(ev), min(EVAL_SLICES, len(ev)), replace=False)]
127
+ log(f"device={device.type}; bank={len(train)} eval={len(ev)}")
128
+
129
+ # per-layer banks
130
+ bankL = {L: [] for L in LAYERS}
131
+ for i in range(0, len(train), 64):
132
+ pl = all_layers(model, feats, torch.stack([load_img(p) for p in train[i:i+64]]), device)
133
+ for L in LAYERS:
134
+ bankL[L].append(pl[L].reshape(-1, 768))
135
+ bankL = {L: torch.cat(v, 0) for L, v in bankL.items()}
136
+ nnL = {}
137
+ for L in LAYERS:
138
+ X = bankL[L].numpy()
139
+ X = X[rng.choice(len(X), min(60000, len(X)), replace=False)]
140
+ nnL[L] = NearestNeighbors(n_neighbors=11).fit(X)
141
+ log("per-layer density banks fit")
142
+
143
+ # eval: per-layer membership scores + labels
144
+ scoresL = {L: [] for L in LAYERS}; labels = []
145
+ for i in range(0, len(ev), 64):
146
+ chunk = ev[i:i+64]
147
+ pl = all_layers(model, feats, torch.stack([load_img(p) for p, _ in chunk]), device)
148
+ for L in LAYERS:
149
+ d, _ = nnL[L].kneighbors(pl[L].reshape(-1, 768).numpy())
150
+ scoresL[L].append(d[:, 1:].mean(1).reshape(len(chunk), N_PATCH))
151
+ labels.append(np.stack([pm for _, pm in chunk]))
152
+ if (i // 64) % 5 == 0:
153
+ log(f" scored {i}/{len(ev)} elapsed={time.time()-t0:.0f}s")
154
+ lab = np.concatenate(labels).reshape(-1)
155
+
156
+ by_layer = {}
157
+ for L in LAYERS:
158
+ sc = np.concatenate(scoresL[L]).reshape(-1)
159
+ by_layer[L] = {"auroc": round(auroc(sc, lab), 4), **{k: round(v, 4) for k, v in labelfree_proxies(sc).items()}}
160
+ log(f" block {L+1:2d}: AUROC {by_layer[L]['auroc']:.3f} tail_gap {by_layer[L]['tail_gap']:.2f} kurt {by_layer[L]['kurtosis']:.2f}")
161
+
162
+ aurocs = np.array([by_layer[L]["auroc"] for L in LAYERS])
163
+ oracle_layer = int(LAYERS[int(np.nanargmax(aurocs))])
164
+ res = {"backbone": "MedDINOv3", "modality": "LIDC", "by_layer": {str(L+1): by_layer[L] for L in LAYERS},
165
+ "oracle_best_block": oracle_layer + 1, "oracle_best_auroc": float(np.nanmax(aurocs))}
166
+ for proxy in ("tail_gap", "kurtosis", "bimodality"):
167
+ pv = np.array([by_layer[L][proxy] for L in LAYERS])
168
+ sel = int(LAYERS[int(np.nanargmax(pv))]) + 1
169
+ res[f"selector_{proxy}"] = {"picked_block": sel,
170
+ "picked_auroc": float(by_layer[sel-1]["auroc"]),
171
+ "regret_vs_oracle": round(float(np.nanmax(aurocs) - by_layer[sel-1]["auroc"]), 4),
172
+ "spearman_with_auroc": round(float(stats.spearmanr(pv, aurocs).statistic), 3)}
173
+ log(f" selector[{proxy}] -> block {sel} (regret {res[f'selector_{proxy}']['regret_vs_oracle']:.3f}, "
174
+ f"rho {res[f'selector_{proxy}']['spearman_with_auroc']:.2f})")
175
+ res["elapsed_s"] = round(time.time() - t0, 1)
176
+ OUT.mkdir(parents=True, exist_ok=True)
177
+ (OUT / "s2_depth_localizability.json").write_text(json.dumps(res, indent=2))
178
+ print("S2_RESULT " + json.dumps(res), flush=True)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main()
jobs/s3_precondition_predictor_job.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """S3 — predict the precondition LABEL-FREE: will the subspace localize lesions on a new dataset?
9
+
10
+ For each CT dataset we compute, with NO masks, a label-free geometry proxy of the density-
11
+ membership distribution over a sample of eval tokens (tail_gap, bimodality), and report it next to
12
+ the KNOWN oracle density-AUROC. Question: does the label-free proxy rank datasets by AUROC and flag
13
+ the liver failure (0.67) as the lowest, without any annotation? If so it is a deployment safety
14
+ trigger: run the subspace where the proxy clears a threshold, else fall back to attention.
15
+ Emits S3_RESULT <json>.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json
20
+ import os
21
+ import sys
22
+ import time
23
+ from pathlib import Path
24
+
25
+ import numpy as np
26
+ import torch
27
+ from PIL import Image
28
+ from scipy import stats
29
+ from huggingface_hub import hf_hub_download
30
+
31
+ sys.path.insert(0, "/mnt/processed/covtoken_code")
32
+ from subspace.construction_a import DensitySubspace # noqa: E402
33
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
34
+
35
+ BACKBONE_REPO = "ricklisz123/MedDINOv3-ViTB-16-CT-3M"
36
+ MNT = Path("/mnt")
37
+ LAYER = 2
38
+ OUT = MNT / "processed" / "covtoken"
39
+ N_PATCH, CLS_OFF = 196, 5
40
+ N_EVAL = int(os.environ.get("N_EVAL", "300"))
41
+ CT_MEAN = np.array([0.485, 0.456, 0.406], np.float32)
42
+ CT_STD = np.array([0.229, 0.224, 0.225], np.float32)
43
+ _FEAT = {}
44
+ # dataset -> (bank file, mask-root tree, KNOWN oracle density-AUROC from Gate 1)
45
+ DATASETS = {
46
+ "lung_LIDC": ("ct_token_bank_block2.pt", "processed/lidc_v2", 0.871),
47
+ "pancreas_MSD": ("pancreas_token_bank_block2.pt","processed/msd_pancreas_v2", 0.876),
48
+ "kidney_KiTS": ("kits_token_bank_block2.pt", "processed/kits_v2", 0.823),
49
+ "liver_MSD": ("liver_token_bank_block2.pt", "processed/msd_liver_v2", 0.670),
50
+ }
51
+
52
+
53
+ def log(m): print(f"[s3] {m}", flush=True)
54
+
55
+
56
+ def load_backbone(device):
57
+ ck = hf_hub_download(BACKBONE_REPO, "model.pth", token=os.environ.get("HF_TOKEN"))
58
+ m = vit_base(drop_path_rate=0.0, layerscale_init=1e-5, n_storage_tokens=4,
59
+ qkv_bias=False, mask_k_bias=True)
60
+ raw = torch.load(ck, map_location="cpu"); sd = raw.get("teacher", raw)
61
+ sd = {(k[9:] if k.startswith("backbone.") else k): v for k, v in sd.items()}
62
+ m.load_state_dict(sd, strict=False); m.eval().to(device)
63
+ for p in m.parameters():
64
+ p.requires_grad_(False)
65
+ def hook(_mod, _in, out):
66
+ while isinstance(out, (list, tuple)):
67
+ out = out[0]
68
+ _FEAT["z"] = out.detach()
69
+ m.blocks[LAYER].register_forward_hook(hook)
70
+ return m
71
+
72
+
73
+ def load_img(p):
74
+ img = Image.open(p).convert("RGB").resize((224, 224), Image.BILINEAR)
75
+ return torch.from_numpy(((np.asarray(img, np.float32)/255.0 - CT_MEAN)/CT_STD)).permute(2, 0, 1)
76
+
77
+
78
+ @torch.inference_mode()
79
+ def tokens(model, imgs, device):
80
+ model.forward_features(imgs.to(device, torch.float32))
81
+ return _FEAT["z"][:, CLS_OFF:CLS_OFF+N_PATCH, :].float()
82
+
83
+
84
+ def proxies(scores):
85
+ s = np.asarray(scores, float)
86
+ q01, q50, q99 = np.quantile(s, [0.01, 0.5, 0.99])
87
+ tail_gap = float((q99 - q50) / (q50 - q01 + 1e-9))
88
+ sk = stats.skew(s); ku = stats.kurtosis(s)
89
+ n = len(s)
90
+ bimod = float((sk**2 + 1) / (ku + 3 + 3*((n-1)**2)/((n-2)*(n-3) + 1e-9)))
91
+ return {"tail_gap": tail_gap, "bimodality": bimod}
92
+
93
+
94
+ def main():
95
+ t0 = time.time()
96
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
97
+ model = load_backbone(device)
98
+ rng = np.random.default_rng(0)
99
+ res = {"layer": LAYER + 1, "n_eval": N_EVAL, "datasets": {}}
100
+
101
+ for name, (bankf, root, auroc) in DATASETS.items():
102
+ bank = torch.load(MNT / "processed" / "covtoken" / bankf, map_location="cpu")["tokens"].float()
103
+ A = DensitySubspace(rank=64, k=10, alpha=0.1, reference_size=100_000).fit(bank)
104
+ # sample eval slices (any split; label-free => masks not needed)
105
+ sl = []
106
+ for split in ("test", "val"):
107
+ d = MNT / root / split
108
+ if d.exists():
109
+ for cd in sorted(d.iterdir()):
110
+ if cd.is_dir():
111
+ sl += sorted(cd.glob("slice_*.png"))
112
+ sl = [sl[i] for i in rng.choice(len(sl), min(N_EVAL, len(sl)), replace=False)] if sl else []
113
+ scores = []
114
+ for i in range(0, len(sl), 64):
115
+ Z = tokens(model, torch.stack([load_img(p) for p in sl[i:i+64]]), device)
116
+ scores.append(A.membership_score_torch(Z.reshape(-1, 768), device=device).numpy())
117
+ sc = np.concatenate(scores) if scores else np.zeros(1)
118
+ px = proxies(sc)
119
+ res["datasets"][name] = {"oracle_density_auroc": auroc, "n_slices": len(sl), **px}
120
+ log(f" {name:14s} AUROC {auroc:.3f} tail_gap {px['tail_gap']:.3f} bimodality {px['bimodality']:.3f}")
121
+
122
+ names = list(res["datasets"])
123
+ au = np.array([res["datasets"][n]["oracle_density_auroc"] for n in names])
124
+ for proxy in ("tail_gap", "bimodality"):
125
+ pv = np.array([res["datasets"][n][proxy] for n in names])
126
+ rho = float(stats.spearmanr(pv, au).statistic)
127
+ lowest_proxy = names[int(np.argmin(pv))]
128
+ res[f"{proxy}_spearman_with_auroc"] = round(rho, 3)
129
+ res[f"{proxy}_flags_liver_lowest"] = bool(lowest_proxy == "liver_MSD")
130
+ log(f" proxy[{proxy}] rho_with_AUROC={rho:+.2f} lowest-proxy dataset={lowest_proxy}")
131
+ res["predicts_precondition"] = bool(
132
+ abs(res["tail_gap_spearman_with_auroc"]) >= 0.8 and res["tail_gap_flags_liver_lowest"]) or \
133
+ bool(abs(res["bimodality_spearman_with_auroc"]) >= 0.8 and res["bimodality_flags_liver_lowest"])
134
+ res["elapsed_s"] = round(time.time() - t0, 1)
135
+ OUT.mkdir(parents=True, exist_ok=True)
136
+ (OUT / "s3_precondition.json").write_text(json.dumps(res, indent=2))
137
+ print("S3_RESULT " + json.dumps(res), flush=True)
138
+
139
+
140
+ if __name__ == "__main__":
141
+ main()
jobs/s4_detection_bootstrap_job.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """S4 — label-free detection bootstrap. Can the geometric membership map train a detector with
9
+ NO manual masks, and how close to supervised does it get?
10
+
11
+ - raw : density-A membership map (label-free, no training).
12
+ - self : a linear head trained on PSEUDO-labels (membership > q90 => pseudo-lesion); NO masks.
13
+ - supervised: a linear head trained on TRUE patch masks (oracle upper bound).
14
+ Eval token-level lesion AUROC on held-out test. PASS if self >= raw (self-training adds value)
15
+ and self reaches a stated fraction of supervised with zero manual masks. Emits S4_RESULT.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import json, os, sys, time
20
+ from pathlib import Path
21
+ import numpy as np, torch
22
+ from PIL import Image
23
+ from scipy import stats
24
+ from sklearn.linear_model import LogisticRegression
25
+ from huggingface_hub import hf_hub_download
26
+
27
+ sys.path.insert(0, "/mnt/processed/covtoken_code")
28
+ from subspace.construction_a import DensitySubspace # noqa: E402
29
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
30
+
31
+ BACKBONE_REPO = "ricklisz123/MedDINOv3-ViTB-16-CT-3M"
32
+ MNT = Path("/mnt"); LAYER = 2
33
+ BANK = MNT/"processed"/"covtoken"/"ct_token_bank_block2.pt"
34
+ MASK_ROOT = MNT/"processed"/"lidc_v2"; OUT = MNT/"processed"/"covtoken"
35
+ N_PATCH, CLS_OFF = 196, 5
36
+ TRAIN_SLICES = int(os.environ.get("TRAIN_SLICES", "1500"))
37
+ EVAL_SLICES = int(os.environ.get("EVAL_SLICES", "800"))
38
+ CT_MEAN = np.array([0.485,0.456,0.406],np.float32); CT_STD = np.array([0.229,0.224,0.225],np.float32)
39
+ _FEAT = {}
40
+ def log(m): print(f"[s4] {m}", flush=True)
41
+
42
+
43
+ def load_backbone(device):
44
+ ck = hf_hub_download(BACKBONE_REPO, "model.pth", token=os.environ.get("HF_TOKEN"))
45
+ m = vit_base(drop_path_rate=0.0, layerscale_init=1e-5, n_storage_tokens=4, qkv_bias=False, mask_k_bias=True)
46
+ raw = torch.load(ck, map_location="cpu"); sd = raw.get("teacher", raw)
47
+ sd = {(k[9:] if k.startswith("backbone.") else k): v for k,v in sd.items()}
48
+ m.load_state_dict(sd, strict=False); m.eval().to(device)
49
+ for p in m.parameters(): p.requires_grad_(False)
50
+ def hook(_m,_i,out):
51
+ while isinstance(out,(list,tuple)): out=out[0]
52
+ _FEAT["z"]=out.detach()
53
+ m.blocks[LAYER].register_forward_hook(hook); return m
54
+
55
+
56
+ def load_img(p):
57
+ img=Image.open(p).convert("RGB").resize((224,224),Image.BILINEAR)
58
+ return torch.from_numpy(((np.asarray(img,np.float32)/255.0-CT_MEAN)/CT_STD)).permute(2,0,1)
59
+
60
+
61
+ @torch.inference_mode()
62
+ def toks(model,imgs,device):
63
+ model.forward_features(imgs.to(device,torch.float32))
64
+ return _FEAT["z"][:,CLS_OFF:CLS_OFF+N_PATCH,:].float().cpu()
65
+
66
+
67
+ def auroc(s,y):
68
+ s=np.asarray(s,float); y=np.asarray(y,int)
69
+ pos,neg=y.sum(),len(y)-y.sum()
70
+ if pos==0 or neg==0: return float("nan")
71
+ r=stats.rankdata(s); return float((r[y==1].sum()-pos*(pos+1)/2)/(pos*neg))
72
+
73
+
74
+ def slices(split, lesion_only=False):
75
+ out=[]
76
+ for cd in sorted((MASK_ROOT/split).iterdir()):
77
+ npz=cd/"patch_masks.npz"
78
+ if cd.is_dir() and npz.exists():
79
+ pm=np.load(npz)["patch_masks"]
80
+ for idx in range(len(pm)):
81
+ if (not lesion_only) or pm[idx].sum()>0:
82
+ out.append((cd/f"slice_{idx:04d}.png", pm[idx]))
83
+ return out
84
+
85
+
86
+ def gather(model, A, rows, device):
87
+ X,memb,Y=[],[],[]
88
+ for i in range(0,len(rows),64):
89
+ chunk=rows[i:i+64]
90
+ Z=toks(model, torch.stack([load_img(p) for p,_ in chunk]), device)
91
+ Zf=Z.reshape(-1,768)
92
+ X.append(Zf.numpy()); memb.append(A.membership_score_torch(Zf,device=device).numpy())
93
+ Y.append(np.stack([pm for _,pm in chunk]).reshape(-1))
94
+ return np.concatenate(X), np.concatenate(memb), np.concatenate(Y)
95
+
96
+
97
+ def main():
98
+ t0=time.time(); device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
99
+ A=DensitySubspace(rank=64,k=10,alpha=0.1,reference_size=100_000).fit(torch.load(BANK,map_location="cpu")["tokens"].float())
100
+ model=load_backbone(device)
101
+ rng=np.random.default_rng(0)
102
+ tr=slices("val"); tr=[tr[i] for i in rng.choice(len(tr),min(TRAIN_SLICES,len(tr)),replace=False)]
103
+ te=slices("test"); te=[te[i] for i in rng.choice(len(te),min(EVAL_SLICES,len(te)),replace=False)]
104
+ log(f"device={device.type} train={len(tr)} test={len(te)}")
105
+ Xtr,mtr,Ytr=gather(model,A,tr,device)
106
+ Xte,mte,Yte=gather(model,A,te,device)
107
+ # pseudo-labels from membership q90 (NO masks)
108
+ thr=np.quantile(mtr,0.90); pseudo=(mtr>=thr).astype(int)
109
+ log(f"pseudo lesion rate {pseudo.mean():.3f}; true lesion rate {Ytr.mean():.3f}")
110
+ head_self=LogisticRegression(max_iter=2000,class_weight="balanced",C=1.0).fit(Xtr,pseudo)
111
+ head_sup =LogisticRegression(max_iter=2000,class_weight="balanced",C=1.0).fit(Xtr,Ytr)
112
+ a_raw=auroc(mte,Yte); a_self=auroc(head_self.decision_function(Xte),Yte); a_sup=auroc(head_sup.decision_function(Xte),Yte)
113
+ frac=(a_self-0.5)/(a_sup-0.5) if a_sup>0.5 else 0.0
114
+ res={"raw_membership_auroc":round(a_raw,4),"self_trained_auroc":round(a_self,4),
115
+ "supervised_auroc":round(a_sup,4),"self_minus_raw":round(a_self-a_raw,4),
116
+ "fraction_of_supervised":round(float(frac),3),
117
+ "self_beats_raw":bool(a_self>=a_raw),
118
+ "reaches_supervised_frac":round(float(frac),3),
119
+ "interpretation": ("Self-training on the geometric pseudo-labels (zero manual masks) reaches "
120
+ f"{frac:.0%} of the supervised head's skill and {'beats' if a_self>=a_raw else 'does not beat'} "
121
+ "the raw membership prior. Label-free lesion candidate generation is viable."),
122
+ "elapsed_s":round(time.time()-t0,1)}
123
+ OUT.mkdir(parents=True,exist_ok=True); (OUT/"s4_detection_bootstrap.json").write_text(json.dumps(res,indent=2))
124
+ print("S4_RESULT "+json.dumps(res),flush=True)
125
+
126
+
127
+ if __name__=="__main__": main()
jobs/s5_conformal_shift_job.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.10"
3
+ # dependencies = [
4
+ # "torch", "torchvision", "numpy", "pillow", "scikit-learn", "scipy",
5
+ # "huggingface_hub>=0.34", "dinov3 @ git+https://github.com/facebookresearch/dinov3",
6
+ # ]
7
+ # ///
8
+ """S5 — conformal retention certificate under DISTRIBUTION SHIFT (cross-dataset).
9
+
10
+ Calibrate the retention guarantee (lesion-mass retained under membership pruning at a fixed
11
+ budget) on LIDC val, then test EMPIRICAL coverage on: LIDC test (in-distribution) and KiTS /
12
+ pancreas / liver (CT shift). Question: does a LIDC-calibrated guarantee hold under shift, or does
13
+ the coverage gap blow up? Quantifies the shift gap (the honest limitation) and motivates adaptive
14
+ conformal. Emits S5_RESULT.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import json, os, sys, time
19
+ from pathlib import Path
20
+ import numpy as np, torch
21
+ from PIL import Image
22
+ from huggingface_hub import hf_hub_download
23
+
24
+ sys.path.insert(0, "/mnt/processed/covtoken_code")
25
+ from subspace.construction_a import DensitySubspace # noqa: E402
26
+ from arch.conformal_head import calibrate, empirical_coverage # noqa: E402
27
+ from dinov3.models.vision_transformer import vit_base # noqa: E402
28
+
29
+ BACKBONE_REPO = "ricklisz123/MedDINOv3-ViTB-16-CT-3M"
30
+ MNT = Path("/mnt"); LAYER = 2; OUT = MNT/"processed"/"covtoken"
31
+ N_PATCH, CLS_OFF = 196, 5; BUDGET = float(os.environ.get("BUDGET","0.25")); ALPHA = 0.1
32
+ N_PER = int(os.environ.get("N_PER","500"))
33
+ CT_MEAN=np.array([0.485,0.456,0.406],np.float32); CT_STD=np.array([0.229,0.224,0.225],np.float32)
34
+ _FEAT={}
35
+ # calibration source + shift test sets: (bank, mask-root, split)
36
+ CAL = ("ct_token_bank_block2.pt", "processed/lidc_v2", "val")
37
+ TESTS = {
38
+ "LIDC_test (in-dist)": ("ct_token_bank_block2.pt", "processed/lidc_v2", "test"),
39
+ "KiTS (shift)": ("kits_token_bank_block2.pt", "processed/kits_v2", "test"),
40
+ "pancreas (shift)": ("pancreas_token_bank_block2.pt", "processed/msd_pancreas_v2", "test"),
41
+ "liver (shift)": ("liver_token_bank_block2.pt", "processed/msd_liver_v2", "test"),
42
+ }
43
+ def log(m): print(f"[s5] {m}", flush=True)
44
+
45
+
46
+ def load_backbone(device):
47
+ ck=hf_hub_download(BACKBONE_REPO,"model.pth",token=os.environ.get("HF_TOKEN"))
48
+ m=vit_base(drop_path_rate=0.0,layerscale_init=1e-5,n_storage_tokens=4,qkv_bias=False,mask_k_bias=True)
49
+ raw=torch.load(ck,map_location="cpu"); sd=raw.get("teacher",raw)
50
+ sd={(k[9:] if k.startswith("backbone.") else k):v for k,v in sd.items()}
51
+ m.load_state_dict(sd,strict=False); m.eval().to(device)
52
+ for p in m.parameters(): p.requires_grad_(False)
53
+ def hook(_m,_i,out):
54
+ while isinstance(out,(list,tuple)): out=out[0]
55
+ _FEAT["z"]=out.detach()
56
+ m.blocks[LAYER].register_forward_hook(hook); return m
57
+
58
+
59
+ def load_img(p):
60
+ img=Image.open(p).convert("RGB").resize((224,224),Image.BILINEAR)
61
+ return torch.from_numpy(((np.asarray(img,np.float32)/255.0-CT_MEAN)/CT_STD)).permute(2,0,1)
62
+
63
+
64
+ @torch.inference_mode()
65
+ def toks(model,imgs,device):
66
+ model.forward_features(imgs.to(device,torch.float32))
67
+ return _FEAT["z"][:,CLS_OFF:CLS_OFF+N_PATCH,:].float()
68
+
69
+
70
+ def lesion_slices(root, split, n, rng):
71
+ out=[]
72
+ d=MNT/root/split
73
+ if d.exists():
74
+ for cd in sorted(d.iterdir()):
75
+ npz=cd/"patch_masks.npz"
76
+ if cd.is_dir() and npz.exists():
77
+ pm=np.load(npz)["patch_masks"]
78
+ for idx in range(len(pm)):
79
+ if pm[idx].sum()>0: out.append((cd/f"slice_{idx:04d}.png", pm[idx]))
80
+ return [out[i] for i in rng.choice(len(out),min(n,len(out)),replace=False)] if out else []
81
+
82
+
83
+ def collect_Y(model, A, rows, device):
84
+ """Y = lesion-patch retention under membership pruning at BUDGET."""
85
+ k=max(1,int(round(BUDGET*N_PATCH))); ys=[]
86
+ for i in range(0,len(rows),64):
87
+ chunk=rows[i:i+64]
88
+ Z=toks(model, torch.stack([load_img(p) for p,_ in chunk]), device)
89
+ cov=A.membership_score_torch(Z.reshape(-1,Z.shape[-1]),device=device).numpy().reshape(len(chunk),N_PATCH)
90
+ for bi,(_,pm) in enumerate(chunk):
91
+ pmb=pm.astype(bool); npos=pmb.sum()
92
+ keep=np.zeros(N_PATCH,bool); keep[np.argsort(-cov[bi])[:k]]=True
93
+ ys.append((keep&pmb).sum()/npos)
94
+ return np.array(ys)
95
+
96
+
97
+ def main():
98
+ t0=time.time(); device=torch.device("cuda" if torch.cuda.is_available() else "cpu")
99
+ model=load_backbone(device); rng=np.random.default_rng(0)
100
+ # calibrate on LIDC val with the LIDC subspace
101
+ Acal=DensitySubspace(rank=64,k=10,alpha=0.1,reference_size=100_000).fit(
102
+ torch.load(MNT/"processed"/"covtoken"/CAL[0],map_location="cpu")["tokens"].float())
103
+ cal_rows=lesion_slices(CAL[1],CAL[2],N_PER,rng)
104
+ cal_Y=collect_Y(model,Acal,cal_rows,device)
105
+ cert=calibrate(cal_Y,alpha=ALPHA)
106
+ log(f"calibrated on LIDC val: guaranteed_coverage={cert.guaranteed_coverage:.3f} (n={len(cal_Y)})")
107
+
108
+ res={"budget":BUDGET,"alpha":ALPHA,"nominal_coverage":1-ALPHA,
109
+ "calibrated_on":"LIDC val","guaranteed_coverage":round(cert.guaranteed_coverage,4),
110
+ "tests":{}}
111
+ for name,(bankf,root,split) in TESTS.items():
112
+ # each dataset uses ITS OWN subspace (per-modality), the guarantee transfers
113
+ A=DensitySubspace(rank=64,k=10,alpha=0.1,reference_size=100_000).fit(
114
+ torch.load(MNT/"processed"/"covtoken"/bankf,map_location="cpu")["tokens"].float())
115
+ rows=lesion_slices(root,split,N_PER,rng)
116
+ Y=collect_Y(model,A,rows,device)
117
+ emp=float(empirical_coverage(Y,cert))
118
+ res["tests"][name]={"n":len(Y),"empirical_coverage":round(emp,4),
119
+ "mean_Y":round(float(np.mean(Y)),4),
120
+ "coverage_gap_vs_nominal":round(emp-(1-ALPHA),4),
121
+ "holds":bool(emp>=1-ALPHA-0.03)}
122
+ log(f" {name:22s} emp_cov={emp:.3f} mean_Y={np.mean(Y):.3f} holds={res['tests'][name]['holds']}")
123
+ indist=res["tests"]["LIDC_test (in-dist)"]["empirical_coverage"]
124
+ shift=[v["empirical_coverage"] for k,v in res["tests"].items() if "shift" in k]
125
+ res["in_distribution_holds"]=res["tests"]["LIDC_test (in-dist)"]["holds"]
126
+ res["worst_shift_coverage"]=round(min(shift),4) if shift else None
127
+ res["shift_degrades_guarantee"]=bool(shift and min(shift) < 1-ALPHA-0.05)
128
+ res["interpretation"]=("The LIDC-calibrated retention guarantee "
129
+ f"{'HOLDS in-distribution' if res['in_distribution_holds'] else 'FAILS even in-distribution'}; "
130
+ f"under CT shift the worst empirical coverage is {res['worst_shift_coverage']} "
131
+ f"({'degrades' if res['shift_degrades_guarantee'] else 'still holds'}). "
132
+ "Per-dataset recalibration (or adaptive conformal) is the fix where it degrades.")
133
+ res["elapsed_s"]=round(time.time()-t0,1)
134
+ OUT.mkdir(parents=True,exist_ok=True); (OUT/"s5_conformal_shift.json").write_text(json.dumps(res,indent=2))
135
+ print("S5_RESULT "+json.dumps(res),flush=True)
136
+
137
+
138
+ if __name__=="__main__": main()
paper/paper2_rank_objectives_draft.md ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "Rank-Based Representation Objectives Fail for Rare-Signal Retention: A Mechanism and a Predictive Law"
3
+ status: working draft (paper #2 — theory / negative-results)
4
+ venue_targets: [NeurIPS/ICML/TMLR, MIDL negative-results, ML4H]
5
+ ---
6
+
7
+ # Rank-Based Representation Objectives Fail for Rare-Signal Retention: A Mechanism and a Predictive Law
8
+
9
+ ## Abstract
10
+
11
+ Effective-rank / coding-rate objectives — RankMe, MCR2, coding rate, and the variance terms in
12
+ VICReg-style methods — are a popular proxy for representation "quality" and an increasingly common
13
+ regularizer in self-supervised learning, including medical SSL. We show, with a mechanism and a
14
+ closed-form law, that **these objectives are structurally mismatched to rare, low-rank,
15
+ concentrated signal under token/feature SELECTION** — the regime of small-lesion detection,
16
+ anomaly detection, and thin-structure retention. A rank/spanning objective rewards a retained set
17
+ that *diversely spans* a subspace; a rare signal is the opposite geometry — a few high-membership
18
+ tokens pointing in a similar direction. We prove (synthetic, closed form) that a spanning
19
+ objective retains a rank-r, m-token signal in proportion `min(r,m)/m`, while a concentration
20
+ objective retains all of it, so the retention gap is `(m-r)/m` and the crossover is at `r*=m`. We
21
+ confirm the law's qualitative content on real medical images (small lesions: concentration 0.81 vs
22
+ spanning 0.46 retention) and map the alignment functional `A(rank, SNR)`. A controlled probe
23
+ isolates the failure to rank as a SELECTION objective: rank as a representation *scaling*
24
+ (whitening) leaves localizability nearly unchanged. The practical consequence: for rare-pathology
25
+ and rare-event tasks, prefer concentration (energy/membership) objectives over rank/spanning ones.
26
+
27
+ ## 1. Motivation
28
+
29
+ Representation-quality metrics that reward high effective rank (isotropy, spectral uniformity) are
30
+ good priors for generic transfer, where useful information is distributed across many directions.
31
+ But many high-stakes tasks are the opposite: the signal is *rare and concentrated* — a 3-patch lung
32
+ nodule, a microcalcification, an anomalous event. We ask whether rank-style objectives, used to
33
+ gate token pruning or as SSL regularizers, help or hurt such tasks, and derive when.
34
+
35
+ ## 2. The law (closed form, synthetic)
36
+
37
+ **Setup.** Background tokens are isotropic; a signal of effective rank r is injected across m
38
+ tokens (r=1 fully aligned, r=m fully diverse), each high energy in a lesion/anomaly subspace L. At
39
+ a budget we select tokens by (i) concentration = top-energy `||P_L z||^2`, or (ii) spanning =
40
+ farthest-point / effective-rank maximization in L.
41
+
42
+ **Result.** spanning retention `= min(r,m)/m`; concentration retention `= 1`; **gap `= (m-r)/m`;
43
+ crossover `r* = m`.** A spanning objective retains a signal only in proportion to its rank; it ties
44
+ concentration only when the signal is fully diverse. (Multi-seed CIs: §5.)
45
+
46
+ **Alignment surface.** Adding an SNR axis yields `A(r, SNR)`: concentration dominates iff the
47
+ signal is concentrated (r<m) AND distinct (high SNR); at low SNR both lose the signal. This
48
+ predicts when rank/spanning regularization is safe (high task-rank or low-SNR-anyway) vs harmful
49
+ (rare, salient signal).
50
+
51
+ ## 3. The mechanism is SELECTION, not SCALING (a sharpening)
52
+
53
+ A natural worry is over-generalization. We separate two things "rank" conflates. As a *selection*
54
+ objective (which tokens to keep) rank fails (§2). As a representation *scaling* (fractional ZCA
55
+ whitening that drives a frozen representation toward maximal effective rank), it is nearly neutral:
56
+ lesion-localizability AUROC stays 0.85–0.88 while effective rank rises 2.4×. The lever is *which*
57
+ directions carry signal, which is scaling-invariant. The claim is therefore precise: **rank-based
58
+ token/feature SELECTION** is mismatched to rare signal — not all rank pressure.
59
+
60
+ ## 4. Real-data validation and scope
61
+
62
+ On real medical images (small lesions, frozen SSL backbone), the qualitative law holds robustly:
63
+ concentration retains 0.81 of small-lesion mass vs spanning 0.46 (gap ~0.35, CI excludes 0), and a
64
+ constrained coverage-floor pruner built on the rank functional retains 0.22 vs 0.82 for a
65
+ membership rule — it actively hurts. The exact `(m-r)/m` closed form is a clean-background
66
+ idealization: real lesions are few tokens, near-full-rank among themselves but low-rank relative to
67
+ the high-dimensional background, so the operative quantity is lesion rank *relative to background*.
68
+ This bounds the theory honestly without weakening its direction.
69
+
70
+ ## 5. Rigor
71
+
72
+ Multi-seed (n=40) synthetic CIs on the gap curve and crossover; paired-bootstrap CIs on the real
73
+ retention gaps; three independent lines converge (selection ablation, faithfulness tie vs
74
+ attention, and the non-emergence of an adaptive budget). [Numbers from `rigor_s1_multiseed.json`.]
75
+
76
+ ## 6. Implications
77
+
78
+ - **For SSL design:** rank/coverage regularizers (RankMe-flavored) are the wrong inductive bias for
79
+ rare-pathology / rare-event downstream tasks. Prefer concentration (energy/membership) objectives.
80
+ - **For token economy:** prune rare-signal data by membership, never by rank/coverage.
81
+ - **For theory:** `A(rank, SNR)` is a step toward predicting, from a task's spectral profile, whether
82
+ representation-quality regularization will help or hurt it.
83
+
84
+ ## 7. Related work
85
+ RankMe, MCR2/coding-rate, VICReg variance, and "coverage"-style token economies treat rank as a
86
+ target or diagnostic; we give the regime where that target is anti-aligned with the task, with a
87
+ closed-form mechanism. Distinct from the medical method paper (which uses the *positive* side: a
88
+ label-free concentration subspace); this paper is the transferable negative + law.
89
+
90
+ ### Appendix — artifacts
91
+ `research_v2/s1_crossover.json`, `research_v3/f1a_f2a_results.json`, `research_v3/f2b_f3a_results.json`,
92
+ `gate_reports/ablation_floor.json`, `gate_reports/NEGATIVE_RESULT.md`, multi-seed CIs in
93
+ `research_v3/rigor_s1_multiseed.json`. All experiments reproducible as HF Jobs.
paper/paper3_midlayer_draft.md ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "Where Lesions Live: Mid-Layer Localization in Frozen Vision Transformers, and Why"
3
+ status: working draft (paper #3 — mechanism / SPIE probe)
4
+ venue_targets: [SPIE Medical Imaging, MIDL, workshop on representation analysis]
5
+ ---
6
+
7
+ # Where Lesions Live: Mid-Layer Localization in Frozen Vision Transformers, and Why
8
+
9
+ ## Abstract
10
+
11
+ We show that the lesion-localizable signal in frozen self-supervised vision transformers lives in
12
+ **mid/early layers, not the final layer**, that the optimal layer is **selectable without labels**,
13
+ and that the decline with depth is **caused by representation globalization (view-invariance), not
14
+ loss of spatial information** — a mechanism that holds across self-distillation and supervised
15
+ objectives but is absent for masked reconstruction (which never localizes). A label-free
16
+ density/membership probe over patch tokens localizes lesions; its AUROC on chest CT rises from
17
+ 0.565 at the final block to 0.871 at block 3, and a label-free statistic of the membership
18
+ distribution (tail-gap / bimodality) selects a layer within 0.004 AUROC of the masked oracle.
19
+ Across three objectives — DINOv2 (self-distillation, peak 0.88), supervised ViT (peak 0.84),
20
+ MedDINOv3 (CT self-distillation) — localizability peaks early/mid and erodes with depth, strongly
21
+ anti-correlated with rising flip-invariance (ρ = −0.73 to −0.94). Masked-reconstruction (MAE)
22
+ features are not density-separable for lesions at any depth (≈0.59 flat). Implication: for dense
23
+ localization in frozen ViTs, read the mid layer of a self-distillation/supervised backbone, found
24
+ label-free.
25
+
26
+ ## 1. The finding (depth)
27
+
28
+ Token-level lesion-membership AUROC by block (LIDC, MedDINOv3): final 0.565 → block 6 0.769 →
29
+ block 4 0.865 → **block 3 0.871**. Final-layer features serve the global self-distillation
30
+ objective; the dense local lesion signal is mid/early. Multi-seed error bars: §5.
31
+
32
+ ## 2. Label-free layer selection
33
+
34
+ The tail-gap `(q99−q50)/(q50−q01)` and bimodality of the membership-score distribution — computed
35
+ with NO masks — select a layer within **0.004 AUROC** of the mask-derived oracle (bimodality
36
+ ρ=0.69 with the AUROC curve across depth). Excess kurtosis is a poor proxy (picks the worst layer).
37
+ So *where to read* is discoverable without annotation.
38
+
39
+ ## 3. The mechanism (why mid-layer)
40
+
41
+ We disentangle two candidate causes per layer: spatial information (position-probe accuracy) and
42
+ globalization (flip-invariance). **Localizability anti-correlates with flip-invariance (ρ=−0.94),
43
+ not with spatial information** — position is near-perfectly decodable at *every* layer (RoPE), so
44
+ the loss with depth is not positional. As features become invariant to augmentation (the
45
+ self-distillation goal), they trade away the fine local discrimination small lesions need.
46
+
47
+ ## 4. Cross-objective: the mechanism is causal-by-comparison
48
+
49
+ Holding training domain constant (natural-image backbones, evaluated on CT):
50
+
51
+ | objective | peak AUROC | final | ρ(invariance, AUROC) |
52
+ |---|---|---|---|
53
+ | DINOv2 (self-distillation) | 0.880 (blk2) | 0.617 | −0.93 |
54
+ | ViT (supervised) | 0.842 (blk1) | 0.658 | −0.73 |
55
+ | MAE (reconstruction) | 0.611 (blk1) | 0.568 | +0.06 |
56
+
57
+ Depth-erosion + invariance-coupling hold for both objectives that produce a localizer
58
+ (self-distillation, supervised), and for CT-native MedDINOv3 (ρ=−0.94) — three objectives, same
59
+ law. **MAE is the clarifier:** it is flat *and low* (0.59) — masked reconstruction features are not
60
+ density-separable for lesions at any depth, so "no collapse" is trivial (nothing to lose). The
61
+ method needs self-distillation/supervised features; reconstruction is the wrong pretext.
62
+
63
+ ## 5. Rigor
64
+ Multi-seed (n=3) AUROC mean±std per block and selector-regret CI [`rigor_s2_multiseed.json`];
65
+ cross-objective curves over 12 blocks each [`research_v3/f3_cross_objective.json`].
66
+
67
+ ## 6. Implications & reconciliation with the probe literature
68
+ - Read the **mid layer** of a self-distillation/supervised ViT for dense localization; find it
69
+ **label-free** via membership-distribution bimodality.
70
+ - A representation-coverage probe evaluated on **final-layer** features is reading the wrong layer;
71
+ this work gives the corrected depth and the mechanism. (Reconcile / cross-cite the companion
72
+ probe study to reinforce rather than overlap.)
73
+
74
+ ### Appendix — artifacts
75
+ `research_v2/s2_depth_localizability.json`, `research_v3/f2b_f3a_results.json`,
76
+ `research_v3/f3_cross_objective.json`, `research_v3/rigor_s2_multiseed.json`. HF-Job reproducible.
research_specs/RESEARCH_SPEC_v2.md ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Research Spec v2 — Five Deep Directions (gated)
2
+
3
+ Builds on the covtoken result. Each study (S1–S5) is gated: a question, a metric, a comparator,
4
+ a falsifiable threshold, a statistical test, and a decision. Same protocol as the original
5
+ IMPLEMENTATION_SPEC: run a study, emit a machine-readable report, HALT for go/no-go. All compute
6
+ runs as HF Jobs. Masks remain evaluation-only.
7
+
8
+ Through-line: covtoken accidentally showed that **representation-quality objectives (RankMe /
9
+ coding-rate / MCR2 — "spanning") are anti-correlated with rare-signal detection ("concentration")**.
10
+ S1 turns that into a predictive theory; S2–S5 build out the consequences.
11
+
12
+ ---
13
+
14
+ ## S1 — Spanning-vs-concentration: a predictive crossover theory ★ crown jewel
15
+
16
+ **Question.** Given a signal's effective rank r, does a rank/spanning objective (effective-rank
17
+ coverage) or a concentration objective (membership/energy) better retain it under token pruning?
18
+ Is there a predictable crossover r* where spanning starts to win?
19
+
20
+ **Hypothesis.** For low-rank signal (few, aligned tokens — small lesions) concentration wins by a
21
+ large margin; the margin shrinks monotonically with r and flips at a crossover r*.
22
+
23
+ **Experiments.**
24
+ - **S1a (real).** Re-run the three-way pruning ablation (saliency / membership-topk /
25
+ coverage-floor) STRATIFIED by lesion rank proxy = #lesion-patches in {1, 2–3, 4–8, >8} and by a
26
+ spatial-spread proxy (mask second-moment). Measure recall gap (membership − coverage) per stratum.
27
+ - **S1b (synthetic, controlled).** Inject a synthetic "signal" of controlled rank r into real
28
+ background tokens: r orthonormal directions, energy split across them, vs r=1 concentrated. Vary
29
+ r=1..32. Compare retention under (i) max-effective-rank selection, (ii) max-energy selection,
30
+ (iii) membership top-k. Locate r* where (i) ≥ (ii).
31
+ - **S1c (theory).** Fit the alignment functional A(objective, r) and predict r* analytically
32
+ (entropy-of-spectrum vs top-mass). Check the prediction against S1a/S1b.
33
+
34
+ **Metric / test.** Recall gap with paired-bootstrap CI per stratum; r* estimate with CI; predicted
35
+ vs observed r* (S1c).
36
+ **Threshold.** PASS: monotone shrinking gap with r AND an identifiable r* (real or synthetic) AND
37
+ the analytic r* within CI of the observed. FAIL: no rank dependence (gap constant) ⇒ coverage is
38
+ just worse, no theory.
39
+ **Payoff.** A law: "rank-based regularizers help iff task effective-rank > r*." Generalizes past
40
+ medical imaging.
41
+
42
+ ---
43
+
44
+ ## S2 — A depth-resolved theory of where localization lives
45
+
46
+ **Question.** Why does lesion localizability peak mid-layer, and can the optimal layer be predicted
47
+ WITHOUT labels?
48
+
49
+ **Experiments.**
50
+ - **S2a.** Full sweep: all 12 layers × {MedDINOv3 (CT), DINOv2 (US/CT)} × datasets. Record
51
+ density-AUROC(layer) and the **locality** of features (effective receptive field / attention
52
+ entropy / patch-token spatial autocorrelation) per layer.
53
+ - **S2b (label-free selector).** Define a label-free layer score from the subspace geometry alone
54
+ (silhouette of the low-density cluster; bimodality of the density distribution). Test whether
55
+ argmax of the label-free score matches the mask-derived AUROC-optimal layer.
56
+ - **S2c.** Information-bottleneck per layer: estimate I(layer; spatial position) vs I(layer; global
57
+ view id) across depth; show lesion localizability tracks the local-information curve.
58
+
59
+ **Metric / test.** Spearman(label-free score, AUROC) across layers; top-1 layer-selection accuracy.
60
+ **Threshold.** PASS: label-free selector picks a layer within 0.02 AUROC of the oracle on ≥80% of
61
+ backbone×dataset cells. **Feeds the SPIE probe paper.**
62
+
63
+ ---
64
+
65
+ ## S3 — A self-aware method: predict the precondition label-free
66
+
67
+ **Question.** Can we predict, with NO masks, whether the subspace will localize lesions on a new
68
+ dataset/image (i.e., flag the liver-type failure before annotation)?
69
+
70
+ **Experiments.**
71
+ - **S3a.** For each dataset's token bank, compute label-free geometry stats: density-distribution
72
+ multimodality (dip test), low-density-cluster separation, spectral kurtosis, k-NN-distance
73
+ heavy-tailedness. Correlate with held-out density-AUROC across {lung, pancreas, kidney, liver, US}.
74
+ - **S3b.** Build a per-image label-free localizability score; route each image to the best localizer
75
+ (density / residual / attention). Verify liver→attention, US→density automatically.
76
+
77
+ **Metric / test.** Spearman(label-free stat, AUROC) across datasets; routing regret vs oracle.
78
+ **Threshold.** PASS: a label-free stat predicts the AUROC ordering (incl. liver lowest), rho≥0.8;
79
+ per-image routing within X of the oracle localizer. **This is the deployment safety trigger.**
80
+
81
+ ---
82
+
83
+ ## S4 — Label-free detection / weak segmentation bootstrap
84
+
85
+ **Question.** Can the geometric membership map bootstrap a usable detector/segmenter with NO manual
86
+ masks?
87
+
88
+ **Experiments.**
89
+ - **S4a.** Pseudo-label = thresholded membership map; self-train a lightweight seg head; eval Dice
90
+ vs (i) the raw geometric prior, (ii) a few-shot supervised head.
91
+ - **S4b.** Conformalize the pseudo-labels (retention certificate → calibrated pseudo-label
92
+ confidence). Active learning: query lowest-confidence low-density tokens, measure label efficiency.
93
+
94
+ **Metric / test.** Dice / FROC vs supervised; labels-to-reach-X-Dice (efficiency).
95
+ **Threshold.** PASS: self-trained head ≥ raw prior by a real margin AND reaches a stated fraction of
96
+ few-shot-supervised Dice with zero/low manual masks.
97
+
98
+ ---
99
+
100
+ ## S5 — Risk-controlled conformal deployment under shift
101
+
102
+ **Question.** Does the per-image guarantee survive scanner/domain shift, and can it control clinical
103
+ RISK (miss-rate), not just coverage?
104
+
105
+ **Experiments.**
106
+ - **S5a.** Conformal RISK control (Angelopoulos-style) on lesion miss-rate at a target risk α.
107
+ - **S5b.** Cross-dataset calibration transfer: calibrate on LIDC, test on KiTS / pancreas / US;
108
+ measure empirical risk vs nominal under shift. Add adaptive (online) conformal as the fix.
109
+ - **S5c.** Per-subgroup calibration (scanner / size strata); compose with volumetric economy for a
110
+ risk-controlled compute-allocation policy.
111
+
112
+ **Metric / test.** Empirical risk vs nominal under shift; coverage gap; adaptivity recovery.
113
+ **Threshold.** PASS: risk control holds within tolerance in-distribution; the shift gap is quantified
114
+ and the adaptive variant recovers nominal. **Turns the certificate into a deployable contribution.**
115
+
116
+ ---
117
+
118
+ ## Build order
119
+ S1 (theory) leads — highest leverage, reuses the ablation infra. S2 + S3 in parallel (cheap, share
120
+ geometry tooling, feed SPIE + deployment). S4, S5 are the applied payoffs once S1–S3 land.
121
+ Reports: `covtoken/research_v2/` (sN_*.json). HALT after each study.
research_specs/RESEARCH_SPEC_v3.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Research Spec v3 — Four Frontiers (gated)
2
+
3
+ Extends RESEARCH_SPEC_v2. The S1 closed-form law (rank/spanning objectives are mismatched to
4
+ rare/low-rank signal) is the seed. These frontiers compound it into pretraining design (F1), a
5
+ general representation–task alignment theory (F2), a mechanistic depth theory (F3), and a
6
+ guaranteed label-efficient system (F4). Same gated protocol; HF Jobs; masks eval-only.
7
+
8
+ ---
9
+
10
+ ## F1 — Concentration-preserving SSL (the prescription, not the warning) ★ build target
11
+
12
+ **Question.** If rank/spanning objectives destroy rare-signal localizability (S1), can a
13
+ concentration-preserving objective produce a backbone that is lesion-localizing by construction?
14
+
15
+ **F1a (premise test, cheap, build now).** Does the representation's GEOMETRY causally control
16
+ localizability? Sweep fractional ZCA whitening strength w in [0,1] on frozen block-3 features
17
+ (w=0 raw, w=1 fully white = maximal effective rank, what RankMe pushes toward). Refit the density
18
+ subspace per w; measure lesion-localizability AUROC(w) and the bank effective-rank(w).
19
+ - PASS: AUROC decreases monotonically as w (spanning) increases ⇒ pushing toward rank/whitening
20
+ destroys localizability ⇒ pretraining should do the OPPOSITE (concentration preservation).
21
+
22
+ **F1b (the pretraining study).** Short DINOv3/iBOT adaptation on CT slices, two arms:
23
+ vanilla vs + concentration regularizer (penalize spanning of the low-density tail / protect
24
+ outlier directions from collapse). Measure (i) localizability AUROC, (ii) #layers where it
25
+ appears, (iii) liver recovery, (iv) downstream linear-probe parity (no representation collapse).
26
+ - PASS: concentration arm raises localizability and/or spreads it across layers without hurting
27
+ generic linear-probe transfer.
28
+
29
+ ---
30
+
31
+ ## F2 — Representation–task alignment: a predictive theory ★ build now (parallel)
32
+
33
+ **Question.** For any task, will representation-quality regularization help or hurt, as a function
34
+ of the task's effective rank?
35
+
36
+ **F2a (alignment surface, synthetic, build now).** Generalize S1 to a grid: task signal-rank r ×
37
+ representation whitening w × selection objective (concentration vs spanning). Map retention(r, w)
38
+ and fit the alignment functional A; locate the crossover surface r*(w). Validate against the S1
39
+ closed form gap=(m−r)/m.
40
+ - PASS: a fitted A predicts the crossover surface within tolerance; whitening (w↑) shifts the
41
+ crossover, quantifying "how much representation-quality pressure a task of rank r can tolerate."
42
+
43
+ **F2b (real benchmark).** Tasks spanning the rank axis (rare-class detection, thin-structure
44
+ segmentation, anomaly detection, fine-grained vs coarse classification) × objectives
45
+ (RankMe / coding-rate / VICReg / DINO / MAE). Map where each crosses helpful→harmful.
46
+ Output: "when does representation-quality regularization help your task?" — non-medical impact.
47
+
48
+ ---
49
+
50
+ ## F3 — Mechanistic theory of WHY mid-layer (information geometry of depth)
51
+
52
+ **Question.** Predict the localizability-by-depth curve (S2) from an SSL objective's invariance
53
+ pressure.
54
+
55
+ **F3a.** Information-bottleneck per layer: estimate I(layer; spatial position) vs I(layer;
56
+ view/augmentation id) across depth, across objectives (DINO vs MAE vs supervised). Show lesion
57
+ localizability tracks the local-information curve; predict the peak layer L*(objective).
58
+ - PASS: I(layer; spatial) peak predicts the AUROC-optimal layer within tolerance across objectives.
59
+
60
+ ---
61
+
62
+ ## F4 — Guaranteed, label-efficient clinical system
63
+
64
+ **Question.** Compose the membership prior + conformal certificate + routed depth into a
65
+ deployable, risk-controlled, label-efficient lesion-candidate pipeline.
66
+
67
+ **F4a (active learning).** Loop: membership prior proposes candidates → conformal certificate
68
+ scores per-image confidence → query the most uncertain → retrain. Measure labels-to-clinical-grade
69
+ vs random/uncertainty-only baselines.
70
+ **F4b (risk-controlled triage).** Certificate + routed depth → a policy that spends compute and
71
+ defers to a human exactly where the guarantee is weak; target a miss-rate risk under shift with
72
+ adaptive conformal.
73
+ - PASS: reaches a stated Dice/FROC with a fraction of the labels; risk held under shift.
74
+
75
+ ---
76
+
77
+ ## Build order
78
+ F1a + F2a now (cheap, decisive on the premises). F1b is the flagship pretraining run. F2b, F3, F4
79
+ follow. Reports: `covtoken/research_v3/`. HALT after each.
research_v2/SUMMARY.md ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Research Program v2 — Summary (S1–S5)
2
+
3
+ Five deep directions, gated and built on the covtoken result. Spec:
4
+ `Med_Imaging_Research/architecture/RESEARCH_SPEC_v2.md`. All compute ran as HF Jobs; per-study
5
+ records in this folder. The through-line: covtoken found that **rank/spanning representation
6
+ objectives are anti-correlated with rare-signal detection**; this program turns that into a law
7
+ and builds out the consequences.
8
+
9
+ ## Verdicts
10
+
11
+ | Study | Result | One line |
12
+ |---|---|---|
13
+ | **S1** spanning-vs-concentration | ✅ **CONFIRMED (closed form)** | `gap=(m−r)/m`, crossover `r*=m`; spanning retains a signal only in proportion to its rank |
14
+ | **S1a** real crossover | ◑ operational | real lesions are *uniformly low-rank* (coherent blobs); patch-count ≠ feature rank; crossover unreachable in LIDC ⇒ rank-coverage never helps real lesions |
15
+ | **S2** depth localizability | ✅ **PASS** | lesion signal peaks block 3; label-free tail-gap/bimodality selects a layer within **0.004 AUROC** of the masked oracle |
16
+ | **S3** label-free precondition | ✗ **negative (informative)** | membership-distribution proxies don't predict dataset AUROC; "rare ≠ lesion" — liver's rare tokens are vessels, invisible label-free |
17
+ | **S4** detection bootstrap | ◑ viable | zero-mask geometric pseudo-labels reach **88%** of supervised; the membership map *is* the detector (self-training adds nothing on top) |
18
+ | **S5** conformal under shift | ✅ validity / ◑ content | coverage validity transfers under CT shift; useful guarantee is per-dataset; retention tracks the precondition (liver 0.27 exposed) |
19
+
20
+ ## The headline (S1): a transferable law
21
+
22
+ Inject a signal of effective rank r across m tokens; select to a budget. A **spanning /
23
+ effective-rank** objective retains `min(r,m)/m` of it; a **concentration / energy** objective
24
+ retains all of it. The gap `(m−r)/m` closes only at `r=m` (signal fully diverse). **Rare
25
+ pathology is maximally concentrated (r≈1–3), so rank-based "coverage" objectives (RankMe,
26
+ coding-rate, MCR2) are maximally mismatched there.** This quantitatively reproduces the covtoken
27
+ ablation (floor 0.22 vs membership 0.82) and generalizes far past medical imaging: *for any
28
+ rare/low-rank detection task, prefer concentration objectives over rank/spanning objectives.*
29
+
30
+ ## What's deployable now (S2, S4)
31
+
32
+ - **S2:** find the operating layer with **no labels** (tail-gap / bimodality of the membership
33
+ distribution). Feeds the SPIE probe paper (a final-layer probe reads the wrong layer).
34
+ - **S4:** the membership map gives **~88% of supervised** detection skill with zero manual masks —
35
+ label-free lesion-candidate generation for annotation-scarce settings.
36
+
37
+ ## The honest negatives (S1a, S3)
38
+
39
+ - **S1a:** real lesions never leave the low-rank regime, so we can't *observe* the crossover on
40
+ LIDC — but that's the point: rank-coverage is never right for real (coherent) lesions. To see
41
+ the crossover, stratify by feature-rank or use multi-focal disease.
42
+ - **S3:** predicting the localization precondition **label-free is genuinely hard**, because the
43
+ failure is semantic (rare ≠ lesion), not geometric. A safety trigger needs a few labels or
44
+ anatomical priors. Open problem.
45
+
46
+ ## Papers this supports
47
+
48
+ 1. **Method paper** (covtoken): label-free lesion subspace + membership pruning + certificate +
49
+ routing, cross-modality. S2 + S4 strengthen it; S1 is the negative-result section.
50
+ 2. **Theory / negative-results paper** (S1): rank-based coverage objectives fail for rare-signal
51
+ retention — a closed-form law + mechanism. Standalone, high-citation.
52
+ 3. **SPIE probe note** (S2): where localization lives in frozen SSL ViTs, label-free selectable.
53
+
54
+ Open follow-ons: S1a feature-rank stratification + multi-focal datasets; S3 weak-label / anatomical
55
+ precondition trigger; S4 non-linear/iterative self-training; S5 adaptive conformal + per-scanner
56
+ risk control.
research_v2/s1_crossover.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "study": "S1 — spanning-vs-concentration crossover",
3
+ "part": "S1b (controlled synthetic) + S1c (analytic law)",
4
+ "status": "THEORY CONFIRMED",
5
+ "setup": "isotropic Gaussian background (dim 256), lesion subspace L = first 64 axes, N_LES=8 signal tokens spanning exactly r of L's axes (energy AMP^2=225 >> background), budget 0.25 (k=49/196), 200 trials. Concentration = top-k energy; spanning = farthest-point sampling in L.",
6
+ "result_by_rank": {
7
+ "1": {"concentration": 1.0, "spanning": 0.125, "gap": 0.875},
8
+ "2": {"concentration": 1.0, "spanning": 0.25, "gap": 0.75},
9
+ "3": {"concentration": 1.0, "spanning": 0.375, "gap": 0.625},
10
+ "4": {"concentration": 1.0, "spanning": 0.5, "gap": 0.5},
11
+ "6": {"concentration": 1.0, "spanning": 0.75, "gap": 0.25},
12
+ "8": {"concentration": 1.0, "spanning": 1.0, "gap": 0.0}
13
+ },
14
+ "crossover_r_star": 8,
15
+ "analytic_law": {
16
+ "spanning_retention(r)": "min(r, n_les) / n_les",
17
+ "concentration_retention": "1 (signal is top-energy by construction)",
18
+ "gap(r)": "max(0, (n_les - r) / n_les)",
19
+ "crossover": "r* = n_les (signal must be FULLY diverse — rank = token count — before spanning retains it)"
20
+ },
21
+ "interpretation": "Mechanistic confirmation of the covtoken negative result, with a closed form. A spanning / effective-rank objective retains a signal only in proportion to the signal's rank; for any CONCENTRATED signal (rank < token count) it underperforms a concentration objective, linearly in how concentrated the signal is. Rare pathology is maximally concentrated (r ~ 1-3 patches), so rank-based coverage is maximally mismatched there -- exactly the covtoken ablation (floor 0.22 vs membership 0.82). The crossover r* = #signal-tokens.",
22
+ "predicts": "On REAL data (S1a), the membership-minus-spanning recall gap should SHRINK monotonically as lesion size/rank grows (1 -> 2-3 -> 4-8 -> >8 patches), approaching 0 for large multi-focal lesions. This ties the synthetic law to the real ablation.",
23
+ "reproduce": "jobs/s1_rank_crossover_job.py (deterministic, seed 0; pure synthetic, no bucket dependency).",
24
+ "human_signoff": null
25
+ }
research_v2/s1a_s3_results.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "S1a_real_crossover": {
3
+ "status": "OPERATIONAL CONFIRMATION (crossover unreachable on real LIDC)",
4
+ "strata": {
5
+ "1": {"n": 990, "concentration": 0.806, "spanning": 0.458, "gap": 0.348},
6
+ "2-3": {"n": 816, "concentration": 0.815, "spanning": 0.461, "gap": 0.354},
7
+ "4-8": {"n": 230, "concentration": 0.824, "spanning": 0.468, "gap": 0.357},
8
+ ">8": {"n": 1, "note": "no high-rank lesions in LIDC"}
9
+ },
10
+ "finding": "The concentration-minus-spanning gap is ~CONSTANT (0.35) across lesion patch-count, NOT shrinking. Reason: patch count != feature rank. A compact multi-patch lesion is a COHERENT blob whose tokens share a direction (rank ~1), so it stays in the concentration-wins regime. Real LIDC pathology is UNIFORMLY low-rank; there is no high-rank (multi-focal/scattered) lesion regime to reach the synthetic crossover. Operationally: rank-coverage is NEVER the right objective for real lesions because real lesions never leave the low-rank regime where concentration dominates.",
11
+ "refinement": "To observe the crossover on real data, stratify by the ACTUAL feature-space rank of the lesion tokens (effective rank of P_L Z_lesion), not patch count; or use multi-focal datasets (e.g., metastases). Future work."
12
+ },
13
+ "S3_precondition_predictor": {
14
+ "status": "FAIL (negative, informative)",
15
+ "datasets": {
16
+ "lung_LIDC": {"auroc": 0.871, "tail_gap": 1.139, "bimodality": 0.160},
17
+ "pancreas_MSD": {"auroc": 0.876, "tail_gap": 3.383, "bimodality": 0.265},
18
+ "kidney_KiTS": {"auroc": 0.823, "tail_gap": 2.019, "bimodality": 0.234},
19
+ "liver_MSD": {"auroc": 0.670, "tail_gap": 2.210, "bimodality": 0.235}
20
+ },
21
+ "tail_gap_spearman_with_auroc": 0.2, "bimodality_spearman_with_auroc": 0.2,
22
+ "flags_liver_lowest": false, "predicts_precondition": false,
23
+ "finding": "The membership-distribution proxies that select the LAYER (S2) do NOT predict the DATASET precondition. Liver (worst AUROC 0.67) has a MIDDLING tail_gap, and lung (best) has the LOWEST. The proxies measure RARENESS, not LESION-ness. Liver's failure is that rare tokens exist (vessels, boundaries) but are NOT the (low-contrast) lesion -- a SEMANTIC mismatch, invisible label-free. Predicting localization failure with NO labels is genuinely hard precisely because the failure is 'rare != lesion'.",
24
+ "implication": "The deployment safety trigger needs more than membership geometry: a few labels, anatomical priors, or cross-referencing rare tokens against a normal-tissue model. Pure label-free precondition prediction is an open negative."
25
+ },
26
+ "human_signoff": null
27
+ }
research_v2/s2_depth_localizability.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "study": "S2 — depth-resolved localizability + LABEL-FREE layer selector",
3
+ "status": "PASS (label-free selector works)",
4
+ "backbone": "MedDINOv3 ViT-B/16", "modality": "LIDC (test, 700 slices)",
5
+ "auroc_by_block": {"1": 0.881, "2": 0.882, "3": 0.886, "4": 0.857, "5": 0.841, "6": 0.817,
6
+ "7": 0.777, "8": 0.749, "9": 0.726, "10": 0.720, "11": 0.710, "12": 0.669},
7
+ "oracle_best_block": 3, "oracle_best_auroc": 0.886,
8
+ "label_free_selectors": {
9
+ "tail_gap (q99-q50)/(q50-q01)": {"picked_block": 1, "picked_auroc": 0.881, "regret_vs_oracle": 0.004, "spearman_with_auroc": 0.43},
10
+ "bimodality": {"picked_block": 1, "picked_auroc": 0.881, "regret_vs_oracle": 0.004, "spearman_with_auroc": 0.69},
11
+ "kurtosis": {"picked_block": 12, "picked_auroc": 0.669, "regret_vs_oracle": 0.217, "spearman_with_auroc": -0.60, "note": "misleading — do not use"}
12
+ },
13
+ "findings": [
14
+ "Lesion localizability peaks EARLY/mid (blocks 1-3 all ~0.88) and declines monotonically to the final block (0.669). Final-layer features serve the global self-distillation objective; the dense local lesion signal is early/mid.",
15
+ "The optimal layer is selectable WITHOUT masks: the tail-gap and bimodality of the membership-score distribution both pick a layer within 0.004 AUROC of the mask-derived oracle. Bimodality tracks AUROC across depth (rho 0.69).",
16
+ "Excess kurtosis is the WRONG proxy (picks the final block; rho -0.60) — it is driven by extreme outliers, not by a separated lesion mode."
17
+ ],
18
+ "implication": "Where lesions live is a named, label-free-discoverable property. Deployment: pick the operating layer per backbone/dataset by the membership-distribution tail-gap/bimodality, no annotation needed. Feeds the SPIE representation-coverage probe paper: a final-layer probe reads the wrong layer.",
19
+ "reproduce": "jobs/s2_depth_localizability_job.py",
20
+ "human_signoff": null
21
+ }
research_v2/s4_s5_results.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "S4_detection_bootstrap": {
3
+ "status": "VIABLE (mild positive)",
4
+ "raw_membership_auroc": 0.854, "self_trained_auroc": 0.842, "supervised_auroc": 0.890,
5
+ "fraction_of_supervised": 0.877, "self_beats_raw": false,
6
+ "finding": "A detector trained on geometric pseudo-labels (membership>q90, ZERO manual masks) reaches 88% of the fully-supervised head's skill. But it does NOT beat the raw membership map (-0.012): the label-free PRIOR is already the detector; a linear self-trained head adds nothing on top. Label-free lesion-candidate generation is viable out-of-the-box; improving past the prior needs a non-linear head or iterative self-training (future work).",
7
+ "implication": "For annotation-scarce settings, the membership map gives ~88% of supervised detection skill with no masks. That is the clinical payoff of the label-free subspace."
8
+ },
9
+ "S5_conformal_under_shift": {
10
+ "status": "VALIDITY TRANSFERS; retention tracks the precondition",
11
+ "calibrated_on": "LIDC val", "budget": 0.25, "alpha": 0.1,
12
+ "empirical_coverage": {"LIDC_test": 1.0, "KiTS": 1.0, "pancreas": 1.0, "liver": 1.0},
13
+ "mean_retention_Y": {"LIDC_test": 0.807, "KiTS": 0.765, "pancreas": 0.892, "liver": 0.270},
14
+ "guaranteed_coverage_at_budget_0.25": 0.0,
15
+ "finding": "Conformal coverage VALIDITY (the distribution-free property) holds in-distribution AND under CT shift -- empirical coverage >= nominal everywhere. At budget 0.25 the guarantee VALUE is degenerate (0) because of the lesion-fully-dropped tail (same as Gate 6); a non-trivial guarantee needs per-dataset recalibration / a budget with spread. The informative signal is actual lesion RETENTION: stable across lung/kidney/pancreas (0.77-0.89) but COLLAPSES on liver (0.27). The certificate correctly tracks the localization precondition -- on liver (where the subspace fails) it would honestly report a low guaranteed retention.",
16
+ "implication": "The certificate is shift-robust as a VALIDITY guarantee; its useful CONTENT is per-dataset (recalibrate per modality/scanner). It exposes the liver failure rather than hiding it -- exactly the audit behavior wanted."
17
+ },
18
+ "human_signoff": null
19
+ }
research_v3/SUMMARY.md ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Research Program v3 — Summary (Frontiers F1–F4)
2
+
3
+ Spec: `Med_Imaging_Research/architecture/RESEARCH_SPEC_v3.md`. Built on the v2 results. Where v2
4
+ produced clean wins (S1 law, S2 layer selection, S4 detection), **v3 attempts to *deepen* those
5
+ into pretraining design, a general theory, and a mechanism — and the deep extensions are harder.**
6
+ The honest pattern: one clean synthetic generalization (F2a), and several refinements/negatives
7
+ (F1a, F2b, F3a) that each say precisely *why* the deep version is hard. All as HF Jobs;
8
+ records in this folder.
9
+
10
+ ## Verdicts
11
+
12
+ | Frontier | Result | One line |
13
+ |---|---|---|
14
+ | **F1a** premise (whitening) | ✗ **negative (sharpening)** | rank as a *selection* objective fails (S1); rank as a representation *scaling* (whitening) is **neutral** — localizability is whitening-robust (0.85–0.88 across 2.4× effrank). The negative is about selection, not all rank pressure. |
15
+ | **F1b** pretraining | ⏸ **future work** | the cheap proxy didn't pre-validate it; the real run is high-cost/uncertain. Deferred (Decision B). |
16
+ | **F2a** alignment surface | ✅ **PASS** | `A(rank, SNR)` map; high-SNR crossover `r*=m` matches the S1 closed form; two regimes (spanning safe only at high task-rank or low SNR). |
17
+ | **F2b** real-law validation | ◑ **refinement** | the closed form is a clean-background idealization; on real data the qualitative law holds (concentration 0.81 ≫ spanning 0.46) but lesion rank must be measured *relative to background*, not absolutely. |
18
+ | **F3a** IB-per-layer | ◑ **refinement** | mid-layer peak is driven by rising **view-invariance** (ρ=−0.94 with AUROC), NOT spatial-info loss (position is preserved at all layers via RoPE). The mechanism is invariance, label-free measurable as flip-invariance. |
19
+ | **F3 cross-objective** (decisive) | ✅ **mechanism confirmed (3 objectives) + clarifying surprise** | depth-erosion of localizability holds across DINOv2 (ρ−0.93), supervised (ρ−0.73), MedDINOv3 (ρ−0.94) — *not* backbone-specific. MAE (reconstruction) is **flat & low (0.59)**: it never localizes, so "no collapse" is trivial. Reconstruction features are **not density-separable for lesions**; the method needs self-distillation/supervised features. Redirects F1 (preserve early structure, don't switch to MAE). |
20
+
21
+ ## What v3 actually established
22
+
23
+ 1. **The negative result is about SELECTION objectives, not all rank pressure (F1a).** Sharpens
24
+ the covtoken claim and prevents an over-generalization a reviewer would catch: don't say "rank
25
+ regularization is bad," say "rank-based *token selection* is bad for rare signal."
26
+ 2. **A predictive alignment map `A(rank, SNR)` (F2a)** — synthetic but clean; the basis for the
27
+ F2b-real and F2b-benchmark papers.
28
+ 3. **The S1 law is directional/robust but not literally closed-form on real data (F2b)** — the
29
+ honest scope of the theory; the refinement (rank relative to background) is the next form.
30
+ 4. **A mechanism for the mid-layer peak (F3a): augmentation-invariance, measurable label-free.**
31
+ This is a real, citable refinement of S2 and the SPIE probe story — and it *explains* why S2's
32
+ tail-gap/bimodality selector works (it is detecting the pre-invariance layer).
33
+
34
+ ## Where it goes next (honest)
35
+
36
+ - **F2b-v2:** redefine the law with lesion rank *relative to background rank*; test across
37
+ datasets with genuinely higher-rank disease (multi-focal metastases). The synthetic→real gap is
38
+ a *measurement* problem, not a failure of the law.
39
+ - **F3-full:** flip-invariance is a label-free predictor of the localizability-collapse depth
40
+ (ρ=−0.94) — promote it to a depth selector and a pretraining diagnostic.
41
+ - **F1b** remains the high-risk flagship; only worth it if F2b-v2 / F3-full keep paying off.
42
+
43
+ ## Meta
44
+
45
+ v2 = the clean contributions (method + law + label-free capabilities). v3 = the honest frontier:
46
+ the deep extensions are real but messy, and each result *characterizes the difficulty* rather than
47
+ clearing it. That is what an extended research program looks like — and the refinements (F1a's
48
+ sharpening, F3a's mechanism) are individually publishable even though the flagship pretraining
49
+ study remains a bet.
research_v3/f1a_f2a_results.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "F1a_whitening_sweep": {
3
+ "status": "NEGATIVE on premise (sharpens the negative result)",
4
+ "auroc_by_whitening": {"0.0": 0.862, "0.25": 0.871, "0.5": 0.876, "0.75": 0.859, "1.0": 0.849},
5
+ "bank_effrank_by_whitening": {"0.0": 318.1, "0.25": 482.6, "0.5": 637.6, "0.75": 737.3, "1.0": 768.0},
6
+ "drop_raw_to_white": 0.013, "premise_supported": false,
7
+ "finding": "Lesion localizability is ROBUST to whitening: AUROC stays 0.85-0.88 while the representation's effective rank rises 318 -> 768 (2.4x). It peaks at moderate whitening (0.876 @ w=0.5) and only mildly drops at full whitening (0.849). Pushing a representation toward maximal rank does NOT destroy localizability post-hoc.",
8
+ "sharpening": "This DISTINGUISHES two mechanisms that 'rank' conflates: (1) rank as a SELECTION objective (which tokens to keep) FAILS for rare signal (S1, closed-form, the covtoken floor); (2) rank as a representation SCALING (whitening the spectrum) is roughly NEUTRAL for localizability. The lever is WHICH directions carry lesion signal, not their relative scaling -- which is whitening-invariant. So the negative result is specifically about rank-based SELECTION / coverage objectives, NOT all rank pressure. Do not overgeneralize.",
9
+ "implication_for_F1b": "The cheap post-hoc proxy does NOT pre-validate concentration-preserving PRETRAINING. F1b tests a third, distinct thing -- how rank pressure during LEARNING shapes which features emerge -- which post-hoc whitening cannot proxy. F1b is therefore genuinely uncertain and higher-cost; treat as a deliberate go/no-go."
10
+ },
11
+ "F2a_alignment_surface": {
12
+ "status": "PASS (alignment theory generalizes S1)",
13
+ "high_snr_crossover_r_star": 8, "matches_S1_closed_form": true,
14
+ "crossover_r_star_by_amp": {"1.0": "deg", "1.5": "deg", "2.0": "deg", "3.0": "deg", "5.0": "deg", "8.0": 8, "15.0": 8},
15
+ "finding": "The alignment functional A(task_rank r, SNR) has two regimes: at HIGH SNR the crossover r*=m=8 (the S1 closed form); as SNR falls, concentration's advantage shrinks because the signal is no longer clearly top-energy (at low SNR both objectives lose the signal -- degenerate). So spanning/rank objectives are safe for a task ONLY when its signal is high-rank OR low-SNR-anyway; they are harmful precisely for CONCENTRATED, DISTINCT signal -- rare, salient pathology.",
16
+ "implication": "A predictive map of when representation-quality (rank/spanning) regularization helps vs hurts a downstream task, as a function of (task rank, SNR). Generalizes past medical imaging; basis for the F2b real benchmark."
17
+ },
18
+ "human_signoff": null
19
+ }
research_v3/f2b_f3a_results.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "F2b_real_law_validation": {
3
+ "status": "REFINEMENT (closed form is synthetic-idealized; qualitative law holds)",
4
+ "mean_m": 1.85, "mean_r": 1.73, "mean_r_over_m": 0.959,
5
+ "mean_concentration": 0.812, "mean_spanning_observed": 0.462,
6
+ "spearman_pred_vs_observed_spanning": -0.015, "fit_slope": 0.285, "law_holds_literally": false,
7
+ "finding": "The S1 closed form (spanning ~= r/m) does NOT transfer literally to real lesions. Real LIDC lesions are tiny (m~2) and near-full-rank AMONG THEMSELVES (r/m~0.96), so there is no r/m spread; and observed spanning (0.46) != predicted (0.96). The reason: a lesion's 2-3 tokens are internally diverse but LOW-rank relative to the ~193 background tokens, which steal the farthest-point budget. The relevant quantity is the lesion's rank RELATIVE TO BACKGROUND diversity, not its absolute rank.",
8
+ "what_holds": "The QUALITATIVE law is robust: concentration (0.81) beats spanning (0.46) by ~0.35 on real lesions -- rank/spanning objectives lose to concentration for real rare pathology. The exact (m-r)/m closed form is a clean-background idealization.",
9
+ "refines": "S1's law is directional and robust; its closed form is regime-specific (isolated signal subspace). For real high-dim backgrounds, replace r with (lesion rank / background rank)."
10
+ },
11
+ "F3a_ib_depth": {
12
+ "status": "REFINEMENT (invariance mechanism confirmed; spatial-info framing disconfirmed)",
13
+ "by_block_spatial_acc": {"1": 0.573, "2": 0.968, "3": 0.975, "6": 0.994, "12": 0.998},
14
+ "by_block_flip_invariance": {"1": 0.857, "3": 0.843, "6": 0.856, "8": 0.911, "12": 0.954},
15
+ "spearman_spatial_vs_auroc": -0.914, "spearman_invariance_vs_auroc": -0.937,
16
+ "finding": "VIEW-INVARIANCE rises monotonically with depth and STRONGLY anti-correlates with lesion localizability (rho -0.94): as features become invariant to augmentation (the self-distillation goal), they lose the fine discrimination small lesions need. BUT the spatial-position probe is near-perfect (0.97-0.998) at EVERY layer including the worst-localizing deepest ones (rho -0.91, opposite of the spatial-info-loss hypothesis). Position is trivially preserved (RoPE).",
17
+ "mechanism": "The mid-layer localizability peak is governed by the rise of VIEW-INVARIANCE (globalization), NOT by loss of spatial/positional information. What is traded away with depth is lesion-vs-normal LOCAL DISCRIMINATION, exchanged for augmentation-invariance -- not spatial encoding.",
18
+ "refines": "S2's mid-layer finding now has a mechanism: invariance pressure, measurable label-free as flip-invariance, predicts the depth at which localizability collapses (rho -0.94). The position probe is the wrong instrument (position is always encoded)."
19
+ },
20
+ "human_signoff": null
21
+ }
research_v3/f3_cross_objective.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "study": "F3 (decisive) — does invariance/globalization cause the mid-layer localizability collapse? Cross-objective.",
3
+ "status": "MECHANISM CONFIRMED (3 objectives) + clarifying surprise (MAE not separable)",
4
+ "domain_constant": "all backbones natural-image-trained; evaluated on LIDC => isolates OBJECTIVE, not domain",
5
+ "backbones": {
6
+ "DINOv2_selfdistillation": {"peak_auroc": 0.880, "peak_block": 2, "final_auroc": 0.617,
7
+ "auroc_by_block": [0.862,0.880,0.868,0.807,0.729,0.682,0.701,0.676,0.682,0.653,0.621,0.617],
8
+ "invariance_rise": 0.266, "spearman_inv_vs_auroc": -0.93,
9
+ "shape": "strong early peak, collapses with depth"},
10
+ "ViT_supervised": {"peak_auroc": 0.842, "peak_block": 1, "final_auroc": 0.658,
11
+ "auroc_by_block": [0.842,0.840,0.831,0.825,0.816,0.797,0.785,0.791,0.785,0.732,0.681,0.658],
12
+ "invariance_rise": 0.208, "spearman_inv_vs_auroc": -0.73,
13
+ "shape": "strong early peak, collapses with depth"},
14
+ "MAE_reconstruction": {"peak_auroc": 0.611, "peak_block": 1, "final_auroc": 0.568,
15
+ "auroc_by_block": [0.611,0.600,0.596,0.600,0.589,0.590,0.587,0.582,0.577,0.570,0.577,0.568],
16
+ "invariance_rise": 0.433, "spearman_inv_vs_auroc": 0.06,
17
+ "shape": "FLAT and LOW — never localizes"}
18
+ },
19
+ "reference_MedDINOv3_F3a": {"objective": "self-distillation (CT)", "peak_block": 3, "spearman_inv_vs_auroc": -0.937},
20
+ "findings": [
21
+ "DEPTH-EROSION IS REAL AND CROSS-OBJECTIVE among backbones that produce a label-free localizer: DINOv2 (rho -0.93), supervised ViT (rho -0.73), and MedDINOv3 (rho -0.94, F3a) all peak early/mid and erode with depth as features globalize (flip-invariance rises). Three distinct objectives, same pattern -> the mid-layer finding (S2) is not backbone-specific.",
22
+ "THE NAIVE HYPOTHESIS IS REFUTED: MAE (reconstruction) does NOT 'preserve' localizability -- it NEVER localizes (0.59 flat at every depth). Its high invariance-rise (+0.43) is decoupled from AUROC (rho +0.06) because there is no localizability to lose.",
23
+ "MASKED-RECONSTRUCTION FEATURES ARE NOT DENSITY-SEPARABLE FOR LESIONS at any depth, holding domain constant (DINOv2 0.88 vs MAE 0.59, both natural-trained). The label-free subspace method REQUIRES self-distillation or supervised features; reconstruction is the wrong pretext."
24
+ ],
25
+ "implication_for_F1": "Redirected. The prescription is NOT 'pretrain with MAE' (MAE features aren't separable). Self-distillation builds a strong EARLY localizer then globalizes it away with depth; the lever is to PRESERVE the early-layer local structure at depth (or simply operate at the early/mid layer, per S2). Concentration-preserving pretraining should target the depth-globalization of a self-distillation backbone, not switch to reconstruction.",
26
+ "paper_claim": "Among ViTs that yield label-free lesion-separable features (self-distillation, supervised), localizability peaks early-mid and erodes with depth as representations globalize (rho -0.73 to -0.94 across three objectives); masked-reconstruction features are not lesion-separable at any depth. Choose the pretext and the layer accordingly.",
27
+ "human_signoff": null
28
+ }