thenukegun10x commited on
Commit
12db9dc
·
verified ·
1 Parent(s): 7198674

Update src\models\mricore_stem.py

Browse files
Files changed (1) hide show
  1. src/models/mricore_stem.py +108 -0
src/models/mricore_stem.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MRI-CORE stem: MRI-pretrained ViT-B feature extractor.
2
+
3
+ Loads the MRI-CORE checkpoint (DINOv2-style ViT-B, SAM-init, pretrained on
4
+ 6.1M MRI slices) and exposes a timm-compatible surface so the existing
5
+ MoR/MoE cores can use it as their frozen stem:
6
+
7
+ stem.embed_dim -> 768
8
+ stem.num_prefix_tokens -> 1 (CLS only, no registers)
9
+ stem.pos_embed -> [1, 197, 768]
10
+ stem.forward_features(x)-> [S, 197, 768] (frozen, eval mode)
11
+
12
+ Key layout in the checkpoint: teacher.backbone.{cls_token, pos_embed,
13
+ patch_embed.proj, blocks.<g>.<i>, norm} with blocks nested 4 x 3 = 12.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import os
18
+
19
+ import torch
20
+ import torch.nn as nn
21
+ import torch.nn.functional as F
22
+
23
+ _CKPT = "MRI_CORE_vitb.pth"
24
+
25
+
26
+ class Block(nn.Module):
27
+ def __init__(self, dim: int = 768, n_heads: int = 12, mlp_ratio: float = 4.0):
28
+ super().__init__()
29
+ self.norm1 = nn.LayerNorm(dim)
30
+ self.attn = nn.MultiheadAttention(dim, n_heads, batch_first=True)
31
+ self.norm2 = nn.LayerNorm(dim)
32
+ self.mlp = nn.Sequential(
33
+ nn.Linear(dim, int(dim * mlp_ratio)),
34
+ nn.GELU(),
35
+ nn.Linear(int(dim * mlp_ratio), dim),
36
+ )
37
+
38
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
39
+ x = x + self.attn(self.norm1(x), self.norm1(x), self.norm1(x))[0]
40
+ x = x + self.mlp(self.norm2(x))
41
+ return x
42
+
43
+
44
+ class MRICoreStem(nn.Module):
45
+ """Frozen MRI-CORE ViT-B feature extractor (embed_dim=768, 197 tokens)."""
46
+
47
+ def __init__(self, ckpt_path: str | None = None, freeze: bool = True):
48
+ super().__init__()
49
+ if ckpt_path is None:
50
+ for base in (
51
+ r"G:\RSNA-Knee\cache",
52
+ os.path.join(os.path.dirname(__file__), "..", "..", "weights"),
53
+ ):
54
+ cand = os.path.join(base, _CKPT)
55
+ if os.path.isfile(cand):
56
+ ckpt_path = cand
57
+ break
58
+ if ckpt_path is None or not os.path.isfile(ckpt_path):
59
+ raise FileNotFoundError(f"MRI-CORE checkpoint not found ({_CKPT})")
60
+ self.embed_dim = 768
61
+ self.num_prefix_tokens = 1
62
+ self.patch_embed = nn.Conv2d(3, 768, kernel_size=16, stride=16, bias=True)
63
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, 768))
64
+ self.pos_embed = nn.Parameter(torch.zeros(1, 197, 768))
65
+ self.mask_token = nn.Parameter(torch.zeros(1, 768))
66
+ self.blocks = nn.ModuleList(
67
+ [nn.ModuleList([Block() for _ in range(3)]) for _ in range(4)]
68
+ )
69
+ self.norm = nn.LayerNorm(768)
70
+ st = torch.load(ckpt_path, map_location="cpu", weights_only=True)
71
+ inner = st["teacher"]
72
+ if isinstance(inner, dict) and "state_dict" in inner:
73
+ inner = inner["state_dict"]
74
+ own = self.state_dict()
75
+ prefix = "backbone."
76
+ missing = []
77
+ for k in list(inner.keys()):
78
+ if not k.startswith(prefix):
79
+ inner.pop(k)
80
+ continue
81
+ name = k[len(prefix):]
82
+ if name not in own:
83
+ continue
84
+ if tuple(inner[k].shape) == tuple(own[name].shape):
85
+ own[name] = inner[k]
86
+ else:
87
+ missing.append((name, tuple(inner[k].shape), tuple(own[name].shape)))
88
+ self.load_state_dict(own)
89
+ if missing:
90
+ print(f"mricore: {len(missing)} shape mismatches skipped "
91
+ f"(e.g. {missing[0]})", flush=True)
92
+ if freeze:
93
+ for p in self.parameters():
94
+ p.requires_grad_(False)
95
+ self.eval()
96
+
97
+ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
98
+ """[S, 3, H, W] -> [S, 197, 768] (CLS + 196 patch tokens)."""
99
+ x = self.patch_embed(x).flatten(2).transpose(1, 2) # [S, 196, 768]
100
+ x = torch.cat([self.cls_token.expand(x.size(0), -1, -1), x], dim=1)
101
+ x = x + self.pos_embed
102
+ for group in self.blocks:
103
+ for blk in group:
104
+ x = blk(x)
105
+ return self.norm(x)
106
+
107
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
108
+ return self.forward_features(x)