Second Eye: FracAtlas state head (C-RADIOv2-H)
Browse files- README.md +87 -0
- calibration.json +9 -0
- config.json +19 -0
- 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
|