harvinderbhullar commited on
Commit
7222aae
·
verified ·
1 Parent(s): bebe09e

Second Eye: FracAtlas state head (C-RADIOv2-H)

Browse files
Files changed (4) hide show
  1. README.md +87 -0
  2. calibration.json +9 -0
  3. config.json +19 -0
  4. model.safetensors +3 -0
README.md ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: pytorch
4
+ pipeline_tag: image-classification
5
+ tags:
6
+ - medical-imaging
7
+ - x-ray
8
+ - fracture-detection
9
+ - contrastive
10
+ - clm
11
+ - research-prototype
12
+ datasets:
13
+ - FracAtlas
14
+ ---
15
+
16
+ # Second Eye: CLM image state head for fracture detection (FracAtlas, C-RADIOv2-H)
17
+
18
+ > **Research prototype, not a medical device.** Not validated for clinical use. Do not use it to
19
+ > diagnose or treat anyone.
20
+
21
+ A small trainable image **state head** for [CLM (Contrastive Language Models)](https://github.com/Contrastive-LM/CLM).
22
+ Frozen `nvidia/C-RADIOv2-H` image features (the vision tower of NVIDIA Nemotron Nano 2 VL) go
23
+ through this head into CLM's 512-d action space. There they are scored by `scale · cos` against
24
+ CLM's frozen **action tower** (Qwen3-8B last-token pooling + the released
25
+ [CLM-v0.1-8B](https://huggingface.co/Contrastive-LM/CLM-v0.1-8B) action head) embedding the
26
+ answer options:
27
+
28
+ - Question: *"Is there a bone fracture in this X-ray?"*
29
+ - Options: *"Yes, there is a fracture."* / *"No, there is no fracture."*
30
+
31
+ Code, training and evaluation: [https://github.com/HarvinderBhullar/second-eye/tree/hackathon](https://github.com/HarvinderBhullar/second-eye/tree/hackathon) (see `docs/VISION.md`).
32
+
33
+ ## Files
34
+
35
+ | file | |
36
+ |---|---|
37
+ | `model.safetensors` | head weights (incl. train-set input standardisation) and `options`: the 2 frozen option vectors it was trained against |
38
+ | `config.json` | architecture (3840 → 1024 → 512, GELU), encoder, tile size, pooling, question and options |
39
+ | `calibration.json` | Platt scaling (`a`, `b`) and two thresholds on calibrated p(fracture), all fit on the validation split |
40
+
41
+ ## Usage
42
+
43
+ ```bash
44
+ git clone -b hackathon https://github.com/HarvinderBhullar/second-eye && cd second-eye
45
+ scripts/vision/step1_env.sh
46
+ python -m clm.vision.serve --model harvinderbhullar/second-eye --port 8701
47
+ curl -s -X POST localhost:8701/predict -F image=@xray.jpg
48
+ ```
49
+
50
+ Input: one `512×512` RGB tile per image (greyscale X-rays converted to
51
+ RGB, bicubic resize), RADIO summary vector, L2-normalised. Calibrated probabilities and the
52
+ decision (`p ≥ 0.0675`, the val operating point with sensitivity ≥ 0.90) are
53
+ valid only for the two trained options. Reworded options keep the ranking but not the calibration.
54
+
55
+ ## Evaluation
56
+
57
+ FracAtlas after integrity cleaning (truncated JPEGs, uniform bottom fills and byte duplicates
58
+ removed): 3,938 images, 70/15/15 split stratified on fracture, seed 42. Every choice was made on
59
+ validation. The test split (591 images, 104 fractured) was evaluated
60
+ once. AUROC CI: 1,000-sample stratified bootstrap.
61
+
62
+ | method | test AUROC [95% CI] | sens / spec @ val ≥0.90-sens threshold | ECE raw → Platt |
63
+ |---|---:|---:|---:|
64
+ | CLM head · radio_summary | 0.925 [0.891, 0.953] | 0.913 / 0.723 | 0.062 → 0.020 |
65
+ | Linear probe · radio_summary | 0.893 [0.850, 0.931] | 0.885 / 0.612 | 0.085 → 0.041 |
66
+ | Resolution only (leakage check) | 0.616 [0.577, 0.656] | 0.933 / 0.269 | 0.323 → 0.025 |
67
+ | (a) random option vectors · radio_summary | 0.913 [0.878, 0.945] | 0.875 / 0.733 | 0.075 → 0.027 |
68
+
69
+ **Limitations.**
70
+ - With two fixed options, random option vectors do about as well as the CLM option embeddings
71
+ (see the last row). The head is effectively a classifier, and its value is the CLM interface,
72
+ not accuracy.
73
+ - The head is ahead of a linear probe in point estimate, but the CIs overlap.
74
+ - It is trained on one public dataset from a limited set of sources, with no patient IDs and no
75
+ external validation.
76
+ - Image size alone reaches about 0.62 AUROC on this data (a body-region confounder).
77
+
78
+ ## Requirements and licences
79
+
80
+ - These head weights: Apache-2.0.
81
+ - Using them requires [`nvidia/C-RADIOv2-H`](https://huggingface.co/nvidia/C-RADIOv2-H) (NVIDIA,
82
+ under its own licence) for the features, and Qwen3-8B plus the CLM action head only for custom
83
+ options.
84
+ - Training data: **FracAtlas**, Abedeen et al., *Scientific Data* (2023),
85
+ [doi:10.6084/m9.figshare.22363012](https://doi.org/10.6084/m9.figshare.22363012), CC BY 4.0.
86
+ - Built on [Contrastive-LM/CLM](https://github.com/Contrastive-LM/CLM) (Apache-2.0) at the Nebius × NVIDIA hackathon,
87
+ Berlin, October 2026.
calibration.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "variant": "radio_summary",
3
+ "platt": {
4
+ "a": 0.48017218708992004,
5
+ "b": -0.5284084677696228
6
+ },
7
+ "threshold_youden": 0.3394530117511749,
8
+ "threshold_sens90": 0.0674779936671257
9
+ }
config.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "in_dim": 3840,
3
+ "out_dim": 512,
4
+ "hidden": 1024,
5
+ "variant": "radio_summary",
6
+ "encoder": "nvidia/C-RADIOv2-H",
7
+ "tile": 512,
8
+ "pooling": "RADIO summary vector",
9
+ "question": "Is there a bone fracture in this X-ray?",
10
+ "options": [
11
+ "Yes, there is a fracture.",
12
+ "No, there is no fracture."
13
+ ],
14
+ "positive_option": 0,
15
+ "action_tower": {
16
+ "encoder": "Qwen/Qwen3-8B (last-token pooling)",
17
+ "head": "Contrastive-LM/CLM-v0.1-8B/CLM_v0.1-8B.pt"
18
+ }
19
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a48d1787a88cc6bcda5935a95dc974b0cae55ac15284d451f055b2fe713b85f2
3
+ size 17867364