Duplicate from microsoft/rad-dino
Browse filesCo-authored-by: Fernando Pérez-García <fepegar@users.noreply.huggingface.co>
- .gitattributes +36 -0
- .gitignore +17 -0
- LICENSE +21 -0
- README.md +219 -0
- augmentations.py +147 -0
- backbone_compatible.safetensors +3 -0
- config.json +47 -0
- dino_head.safetensors +3 -0
- model.safetensors +3 -0
- preprocessor_config.json +44 -0
- pyproject.toml +34 -0
- src/rad_dino/__init__.py +74 -0
- src/rad_dino/utils.py +22 -0
- ssl_default_config.yaml +135 -0
- training_images.csv +3 -0
- vitb14_cxr.yaml +31 -0
.gitattributes
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
training_images.csv filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python-generated files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[oc]
|
| 4 |
+
build/
|
| 5 |
+
dist/
|
| 6 |
+
wheels/
|
| 7 |
+
*.egg-info
|
| 8 |
+
|
| 9 |
+
# Virtual environments
|
| 10 |
+
.venv
|
| 11 |
+
|
| 12 |
+
.vscode/
|
| 13 |
+
|
| 14 |
+
*.ipynb
|
| 15 |
+
uv.lock
|
| 16 |
+
*.txt
|
| 17 |
+
.python-version
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) Microsoft Corporation.
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE
|
README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license_name: mit
|
| 3 |
+
pipeline_tag: image-feature-extraction
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
# Model card for RAD-DINO
|
| 7 |
+
|
| 8 |
+
<!-- Provide a quick summary of what the model is/does. -->
|
| 9 |
+
|
| 10 |
+
RAD-DINO is a vision transformer model trained to encode chest X-rays using the self-supervised learning method [DINOv2](https://openreview.net/forum?id=a68SUt6zFt).
|
| 11 |
+
|
| 12 |
+
## Model description
|
| 13 |
+
|
| 14 |
+
<!-- Provide a longer summary of what this model is. -->
|
| 15 |
+
|
| 16 |
+
RAD-DINO is described in detail in [Exploring Scalable Medical Image Encoders Beyond Text Supervision (F. Pérez-García, H. Sharma, S. Bond-Taylor, et al., 2025)](https://www.nature.com/articles/s42256-024-00965-w).
|
| 17 |
+
|
| 18 |
+
- **Developed by:** Microsoft Health Futures
|
| 19 |
+
- **Model type:** Vision transformer
|
| 20 |
+
- **License:** [MIT](./LICENSE)
|
| 21 |
+
- **Finetuned from model:** [`dinov2-base`](https://huggingface.co/facebook/dinov2-base)
|
| 22 |
+
|
| 23 |
+
## Uses
|
| 24 |
+
|
| 25 |
+
<!-- Address questions around how the model is intended to be used, including the foreseeable users of the model and those affected by the model. -->
|
| 26 |
+
|
| 27 |
+
RAD-DINO is shared for research purposes only.
|
| 28 |
+
It is **not meant to be used for clinical practice**.
|
| 29 |
+
|
| 30 |
+
<!-- ### Downstream use -->
|
| 31 |
+
|
| 32 |
+
<!-- This section is for the model use when fine-tuned for a task, or when plugged into a larger ecosystem/app -->
|
| 33 |
+
|
| 34 |
+
The model is a vision backbone that can be plugged to other models for downstream tasks.
|
| 35 |
+
Some potential uses are:
|
| 36 |
+
|
| 37 |
+
- Image classification, with a classifier trained on top of the `CLS` token
|
| 38 |
+
- Image segmentation, with a decoder trained using the patch tokens
|
| 39 |
+
- Clustering, using the image embeddings directly
|
| 40 |
+
- Image retrieval, using nearest neighbors of the CLS token
|
| 41 |
+
- Report generation, with a language model to decode text
|
| 42 |
+
|
| 43 |
+
Fine-tuning RAD-DINO is typically not necessary to obtain good performance in downstream tasks.
|
| 44 |
+
|
| 45 |
+
<!-- ### Out-of-scope use -->
|
| 46 |
+
|
| 47 |
+
<!-- This section addresses misuse, malicious use, and uses that the model will not work well for. -->
|
| 48 |
+
|
| 49 |
+
## Biases, risks, and limitations
|
| 50 |
+
|
| 51 |
+
<!-- This section is meant to convey both technical and sociotechnical limitations. -->
|
| 52 |
+
|
| 53 |
+
RAD-DINO was trained with data from three countries, therefore it might be biased towards population in the training data.
|
| 54 |
+
Underlying biases of the training datasets may not be well characterized.
|
| 55 |
+
|
| 56 |
+
## Installation
|
| 57 |
+
|
| 58 |
+
```shell
|
| 59 |
+
pip install rad-dino
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
## Usage
|
| 63 |
+
|
| 64 |
+
### Encode an image
|
| 65 |
+
|
| 66 |
+
```python
|
| 67 |
+
>>> from rad_dino import RadDino
|
| 68 |
+
>>> from rad_dino.utils import download_sample_image
|
| 69 |
+
>>> encoder = RadDino()
|
| 70 |
+
>>> image = download_sample_image()
|
| 71 |
+
>>> image
|
| 72 |
+
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=2765x2505 at 0x7CCD5C014050>
|
| 73 |
+
>>> cls_embeddings, patch_embeddings = encoder.extract_features(image)
|
| 74 |
+
>>> cls_embeddings.shape, patch_embeddings.shape
|
| 75 |
+
(torch.Size([1, 768]), torch.Size([1, 768, 37, 37]))
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### Weights for fine-tuning
|
| 79 |
+
|
| 80 |
+
We have released a checkpoint compatible with [the original DINOv2 code](https://github.com/facebookresearch/dinov2) to help researchers fine-tune our model.
|
| 81 |
+
|
| 82 |
+
We can use the hub model and load the RAD-DINO weights.
|
| 83 |
+
Let's clone the DINOv2 repository so we can import the code for the head.
|
| 84 |
+
|
| 85 |
+
```shell
|
| 86 |
+
git clone https://github.com/facebookresearch/dinov2.git
|
| 87 |
+
```
|
| 88 |
+
|
| 89 |
+
```python
|
| 90 |
+
>>> import torch
|
| 91 |
+
>>> from rad_dino.utils import safetensors_to_state_dict
|
| 92 |
+
>>> rad_dino_gh = torch.hub.load("./dinov2", "dinov2_vitb14")
|
| 93 |
+
>>> backbone_state_dict = safetensors_to_state_dict("backbone_compatible.safetensors")
|
| 94 |
+
>>> rad_dino_gh.load_state_dict(backbone_state_dict, strict=True)
|
| 95 |
+
<All keys matched successfully>
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
The weights of the head are also released:
|
| 99 |
+
|
| 100 |
+
```python
|
| 101 |
+
>>> from dinov2.layers import DINOHead
|
| 102 |
+
>>> rad_dino_head_gh = DINOHead(
|
| 103 |
+
... in_dim=768,
|
| 104 |
+
... out_dim=65536,
|
| 105 |
+
... hidden_dim=2048,
|
| 106 |
+
... bottleneck_dim=256,
|
| 107 |
+
... nlayers=3,
|
| 108 |
+
... )
|
| 109 |
+
>>> head_state_dict = safetensors_to_state_dict("dino_head.safetensors")
|
| 110 |
+
>>> rad_dino_head_gh.load_state_dict(head_state_dict, strict=True)
|
| 111 |
+
<All keys matched successfully>
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
### Configs and augmentation
|
| 115 |
+
|
| 116 |
+
The configuration files [`ssl_default_config.yaml`](./ssl_default_config.yaml) and [`vitb14_cxr.yaml`](./vitb14_cxr.yaml), and the [`augmentations`](./augmentations.py) module are also available in the repository to help researchers reproduce the training procedure with our hyperparameters.
|
| 117 |
+
|
| 118 |
+
## Training details
|
| 119 |
+
|
| 120 |
+
### Training data
|
| 121 |
+
|
| 122 |
+
<!-- This should link to a Dataset Card, perhaps with a short stub of information on what the training data is all about as well as documentation related to data pre-processing or additional filtering. -->
|
| 123 |
+
|
| 124 |
+
We used images from five public, deidentified chest X-ray datasets to train this checkpoint of RAD-DINO.
|
| 125 |
+
|
| 126 |
+
| Dataset | Num. images |
|
| 127 |
+
| --------- | ----------: |
|
| 128 |
+
| [MIMIC-CXR](https://www.nature.com/articles/s41597-019-0322-0) | 368 960 |
|
| 129 |
+
| [CheXpert](https://ojs.aaai.org/index.php/AAAI/article/view/3834) | 223 648 |
|
| 130 |
+
| [NIH-CXR](https://openaccess.thecvf.com/content_cvpr_2017/html/Wang_ChestX-ray8_Hospital-Scale_Chest_CVPR_2017_paper.html) | 112 120 |
|
| 131 |
+
| [PadChest](https://www.sciencedirect.com/science/article/abs/pii/S1361841520301614) | 136 787 |
|
| 132 |
+
| [BRAX](https://www.nature.com/articles/s41597-022-01608-8) | 41 260 |
|
| 133 |
+
| **TOTAL** | 882 775 |
|
| 134 |
+
|
| 135 |
+
Images in the validation and test sets used to train [MAIRA](https://arxiv.org/abs/2311.13668) were excluded from the training set of RAD-DINO.
|
| 136 |
+
The list of image files used for training is available at [`./training_images.csv`](./training_images.csv).
|
| 137 |
+
|
| 138 |
+
Note this checkpoint is different from the one in the paper, where some private data was used (and fewer GPUs).
|
| 139 |
+
The checkpoint shared here is trained for 35 000 iterations (the total number of iterations in the run was 100 000, but we selected this checkpoint using linear probing on the validation sets of the evaluation datasets described in the paper).
|
| 140 |
+
We used 16 nodes with 4 A100 GPUs each, and a batch size of 40 images per GPU.
|
| 141 |
+
|
| 142 |
+
### Training procedure
|
| 143 |
+
|
| 144 |
+
<!-- This relates heavily to the Technical Specifications. Content here should link to that section when it is relevant to the training procedure. -->
|
| 145 |
+
|
| 146 |
+
We refer to the [manuscript](https://www.nature.com/articles/s42256-024-00965-w) for a detailed description of the training procedure.
|
| 147 |
+
|
| 148 |
+
#### Preprocessing
|
| 149 |
+
|
| 150 |
+
All DICOM files were resized using B-spline interpolation so that their shorter size was 518, min-max scaled to [0, 255], and stored as PNG files.
|
| 151 |
+
|
| 152 |
+
#### Training hyperparameters
|
| 153 |
+
|
| 154 |
+
- **Training regime:** fp16 using PyTorch-FSDP mixed-precision.
|
| 155 |
+
|
| 156 |
+
<!--fp32, fp16 mixed precision, bf16 mixed precision, bf16 non-mixed precision, fp16 non-mixed precision, fp8 mixed precision -->
|
| 157 |
+
|
| 158 |
+
## Evaluation
|
| 159 |
+
|
| 160 |
+
<!-- This section describes the evaluation protocols and provides the results. -->
|
| 161 |
+
|
| 162 |
+
Our evaluation is best described in the [manuscript](https://www.nature.com/articles/s42256-024-00965-w).
|
| 163 |
+
|
| 164 |
+
## Environmental impact
|
| 165 |
+
|
| 166 |
+
<!-- Total emissions (in grams of CO2eq) and additional considerations, such as electricity usage, go here. Edit the suggested text below accordingly -->
|
| 167 |
+
|
| 168 |
+
<!-- Carbon emissions can be estimated using the [Machine Learning Impact calculator](https://mlco2.github.io/impact#compute) presented in [Lacoste et al. (2019)](https://arxiv.org/abs/1910.09700). -->
|
| 169 |
+
|
| 170 |
+
<!-- Hardware type: A100 PCIe -->
|
| 171 |
+
<!-- Hours: 1d 16h = 40h -->
|
| 172 |
+
<!-- Cloud provider: Azure -->
|
| 173 |
+
<!-- Region: Italy North -->
|
| 174 |
+
|
| 175 |
+
- **Hardware type:** NVIDIA A100 GPUs
|
| 176 |
+
- **Hours used:** 40 hours/GPU × 16 nodes × 4 GPUs/node = 2560 GPU-hours
|
| 177 |
+
- **Cloud provider:** Azure
|
| 178 |
+
- **Compute region:** West US 2
|
| 179 |
+
- **Carbon emitted:** 222 kg CO₂ eq.
|
| 180 |
+
|
| 181 |
+
### Compute infrastructure
|
| 182 |
+
|
| 183 |
+
RAD-DINO was trained on [Azure Machine Learning](https://azure.microsoft.com/en-us/products/machine-learning).
|
| 184 |
+
|
| 185 |
+
#### Hardware
|
| 186 |
+
|
| 187 |
+
We used 16 `Standard_NC96ads_A100_v4` nodes with four NVIDIA A100 (80 GB) GPUs each.
|
| 188 |
+
|
| 189 |
+
#### Software
|
| 190 |
+
|
| 191 |
+
We leveraged the code in [DINOv2](https://openreview.net/forum?id=a68SUt6zFt) for training.
|
| 192 |
+
We used [SimpleITK](https://simpleitk.org/) and [Pydicom](https://pydicom.github.io/) for processing of DICOM files.
|
| 193 |
+
|
| 194 |
+
## Citation
|
| 195 |
+
|
| 196 |
+
<!-- If there is a paper or blog post introducing the model, the APA and Bibtex information for that should go in this section. -->
|
| 197 |
+
|
| 198 |
+
**BibTeX:**
|
| 199 |
+
|
| 200 |
+
```bibtex
|
| 201 |
+
@article{perez-garcia_exploring_2025,
|
| 202 |
+
title = {Exploring scalable medical image encoders beyond text supervision},
|
| 203 |
+
issn = {2522-5839},
|
| 204 |
+
url = {https://doi.org/10.1038/s42256-024-00965-w},
|
| 205 |
+
doi = {10.1038/s42256-024-00965-w},
|
| 206 |
+
journal = {Nature Machine Intelligence},
|
| 207 |
+
author = {P{\'e}rez-Garc{\'i}a, Fernando and Sharma, Harshita and Bond-Taylor, Sam and Bouzid, Kenza and Salvatelli, Valentina and Ilse, Maximilian and Bannur, Shruthi and Castro, Daniel C. and Schwaighofer, Anton and Lungren, Matthew P. and Wetscherek, Maria Teodora and Codella, Noel and Hyland, Stephanie L. and Alvarez-Valle, Javier and Oktay, Ozan},
|
| 208 |
+
month = jan,
|
| 209 |
+
year = {2025},
|
| 210 |
+
}
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
**APA:**
|
| 214 |
+
|
| 215 |
+
> Pérez-García, F., Sharma, H., Bond-Taylor, S., Bouzid, K., Salvatelli, V., Ilse, M., Bannur, S., Castro, D. C., Schwaighofer, A., Lungren, M. P., Wetscherek, M. T., Codella, N., Hyland, S. L., Alvarez-Valle, J., & Oktay, O. (2025). *Exploring scalable medical image encoders beyond text supervision*. In Nature Machine Intelligence. Springer Science and Business Media LLC. <https://doi.org/10.1038/s42256-024-00965-w>
|
| 216 |
+
|
| 217 |
+
## Model card contact
|
| 218 |
+
|
| 219 |
+
Fernando Pérez-García ([`fperezgarcia@microsoft.com`](mailto:fperezgarcia@microsoft.com)).
|
augmentations.py
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
| 2 |
+
# See LICENSE in the repo root for license information.
|
| 3 |
+
#
|
| 4 |
+
# Portions:
|
| 5 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 6 |
+
#
|
| 7 |
+
# This source code is licensed under the Apache License, Version 2.0
|
| 8 |
+
# found in the LICENSE file in the root directory of this source tree.
|
| 9 |
+
|
| 10 |
+
import logging
|
| 11 |
+
|
| 12 |
+
from PIL import Image
|
| 13 |
+
from torchvision import transforms
|
| 14 |
+
|
| 15 |
+
from .transforms import (
|
| 16 |
+
GaussianBlur,
|
| 17 |
+
MaybeToTensor,
|
| 18 |
+
make_normalize_transform,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
logger = logging.getLogger("dinov2")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class DataAugmentationDINO(object):
|
| 26 |
+
def __init__(
|
| 27 |
+
self,
|
| 28 |
+
global_crops_scale,
|
| 29 |
+
local_crops_scale,
|
| 30 |
+
local_crops_number,
|
| 31 |
+
global_crops_size=224,
|
| 32 |
+
local_crops_size=96,
|
| 33 |
+
):
|
| 34 |
+
self.global_crops_scale = global_crops_scale
|
| 35 |
+
self.local_crops_scale = local_crops_scale
|
| 36 |
+
self.local_crops_number = local_crops_number
|
| 37 |
+
self.global_crops_size = global_crops_size
|
| 38 |
+
self.local_crops_size = local_crops_size
|
| 39 |
+
|
| 40 |
+
logger.info("###################################")
|
| 41 |
+
logger.info("Using data augmentation parameters:")
|
| 42 |
+
logger.info(f"global_crops_scale: {global_crops_scale}")
|
| 43 |
+
logger.info(f"local_crops_scale: {local_crops_scale}")
|
| 44 |
+
logger.info(f"local_crops_number: {local_crops_number}")
|
| 45 |
+
logger.info(f"global_crops_size: {global_crops_size}")
|
| 46 |
+
logger.info(f"local_crops_size: {local_crops_size}")
|
| 47 |
+
logger.info("###################################")
|
| 48 |
+
|
| 49 |
+
# random resized crop and flip
|
| 50 |
+
self.geometric_augmentation_global = transforms.Compose(
|
| 51 |
+
[
|
| 52 |
+
transforms.RandomResizedCrop(
|
| 53 |
+
global_crops_size, scale=global_crops_scale, interpolation=transforms.InterpolationMode.BICUBIC
|
| 54 |
+
),
|
| 55 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
| 56 |
+
]
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
self.geometric_augmentation_local = transforms.Compose(
|
| 60 |
+
[
|
| 61 |
+
transforms.RandomResizedCrop(
|
| 62 |
+
local_crops_size, scale=local_crops_scale, interpolation=transforms.InterpolationMode.BICUBIC
|
| 63 |
+
),
|
| 64 |
+
transforms.RandomHorizontalFlip(p=0.5),
|
| 65 |
+
]
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# color distorsions / blurring
|
| 69 |
+
color_jittering = transforms.Compose(
|
| 70 |
+
[
|
| 71 |
+
transforms.RandomApply(
|
| 72 |
+
[transforms.ColorJitter(brightness=0.4, contrast=0.4, saturation=0.2, hue=0.1)],
|
| 73 |
+
p=0.8,
|
| 74 |
+
),
|
| 75 |
+
transforms.RandomGrayscale(p=0.2),
|
| 76 |
+
]
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
global_transfo1_extra = GaussianBlur(p=0.5)
|
| 80 |
+
|
| 81 |
+
global_transfo2_extra = transforms.Compose(
|
| 82 |
+
[
|
| 83 |
+
GaussianBlur(p=0.1),
|
| 84 |
+
]
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
local_transfo_extra = GaussianBlur(p=0.5)
|
| 88 |
+
|
| 89 |
+
# normalization
|
| 90 |
+
self.normalize = transforms.Compose(
|
| 91 |
+
[
|
| 92 |
+
MaybeToTensor(),
|
| 93 |
+
make_normalize_transform(),
|
| 94 |
+
]
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
self.global_transfo1 = transforms.Compose([color_jittering, global_transfo1_extra, self.normalize])
|
| 98 |
+
self.global_transfo2 = transforms.Compose([color_jittering, global_transfo2_extra, self.normalize])
|
| 99 |
+
self.local_transfo = transforms.Compose([color_jittering, local_transfo_extra, self.normalize])
|
| 100 |
+
|
| 101 |
+
def __call__(self, image):
|
| 102 |
+
output = {}
|
| 103 |
+
|
| 104 |
+
# global crops:
|
| 105 |
+
im1_base = self.geometric_augmentation_global(image)
|
| 106 |
+
global_crop_1 = self.global_transfo1(im1_base)
|
| 107 |
+
|
| 108 |
+
im2_base = self.geometric_augmentation_global(image)
|
| 109 |
+
global_crop_2 = self.global_transfo2(im2_base)
|
| 110 |
+
|
| 111 |
+
output["global_crops"] = [global_crop_1, global_crop_2]
|
| 112 |
+
|
| 113 |
+
# global crops for teacher:
|
| 114 |
+
output["global_crops_teacher"] = [global_crop_1, global_crop_2]
|
| 115 |
+
|
| 116 |
+
# local crops:
|
| 117 |
+
local_crops = [
|
| 118 |
+
self.local_transfo(self.geometric_augmentation_local(image)) for _ in range(self.local_crops_number)
|
| 119 |
+
]
|
| 120 |
+
output["local_crops"] = local_crops
|
| 121 |
+
output["offsets"] = ()
|
| 122 |
+
|
| 123 |
+
return output
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def get_online_classification_augmentation_from_config(cfg) -> transforms.Compose:
|
| 127 |
+
augmentation_config = cfg.evaluation.online.augmentation
|
| 128 |
+
interpolation = getattr(Image.Resampling, augmentation_config.interpolation)
|
| 129 |
+
resize_size = crop_size = cfg.crops.global_crops_size
|
| 130 |
+
resize = transforms.Resize(resize_size, interpolation=interpolation)
|
| 131 |
+
crop = transforms.CenterCrop(crop_size)
|
| 132 |
+
affine = transforms.RandomAffine(
|
| 133 |
+
degrees=augmentation_config.degrees,
|
| 134 |
+
scale=augmentation_config.scale,
|
| 135 |
+
shear=augmentation_config.shear,
|
| 136 |
+
interpolation=interpolation,
|
| 137 |
+
)
|
| 138 |
+
transforms_list = [
|
| 139 |
+
resize,
|
| 140 |
+
crop,
|
| 141 |
+
affine,
|
| 142 |
+
MaybeToTensor(),
|
| 143 |
+
make_normalize_transform(),
|
| 144 |
+
]
|
| 145 |
+
if augmentation_config.horizontal_flip:
|
| 146 |
+
transforms_list.append(transforms.RandomHorizontalFlip())
|
| 147 |
+
return transforms.Compose(transforms_list)
|
backbone_compatible.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1eac0464b2a00d368aa3eea1dc029964b10320fbabc59a8a4e768c43a23d26f4
|
| 3 |
+
size 346338024
|
config.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"apply_layernorm": true,
|
| 3 |
+
"architectures": [
|
| 4 |
+
"Dinov2Model"
|
| 5 |
+
],
|
| 6 |
+
"attention_probs_dropout_prob": 0.0,
|
| 7 |
+
"drop_path_rate": 0.0,
|
| 8 |
+
"hidden_act": "gelu",
|
| 9 |
+
"hidden_dropout_prob": 0.0,
|
| 10 |
+
"hidden_size": 768,
|
| 11 |
+
"image_size": 518,
|
| 12 |
+
"initializer_range": 0.02,
|
| 13 |
+
"layer_norm_eps": 1e-06,
|
| 14 |
+
"layerscale_value": 1.0,
|
| 15 |
+
"mlp_ratio": 4,
|
| 16 |
+
"model_type": "dinov2",
|
| 17 |
+
"num_attention_heads": 12,
|
| 18 |
+
"num_channels": 3,
|
| 19 |
+
"num_hidden_layers": 12,
|
| 20 |
+
"out_features": [
|
| 21 |
+
"stage12"
|
| 22 |
+
],
|
| 23 |
+
"out_indices": [
|
| 24 |
+
12
|
| 25 |
+
],
|
| 26 |
+
"patch_size": 14,
|
| 27 |
+
"qkv_bias": true,
|
| 28 |
+
"reshape_hidden_states": true,
|
| 29 |
+
"stage_names": [
|
| 30 |
+
"stem",
|
| 31 |
+
"stage1",
|
| 32 |
+
"stage2",
|
| 33 |
+
"stage3",
|
| 34 |
+
"stage4",
|
| 35 |
+
"stage5",
|
| 36 |
+
"stage6",
|
| 37 |
+
"stage7",
|
| 38 |
+
"stage8",
|
| 39 |
+
"stage9",
|
| 40 |
+
"stage10",
|
| 41 |
+
"stage11",
|
| 42 |
+
"stage12"
|
| 43 |
+
],
|
| 44 |
+
"torch_dtype": "float32",
|
| 45 |
+
"transformers_version": "4.41.0",
|
| 46 |
+
"use_swiglu_ffn": false
|
| 47 |
+
}
|
dino_head.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5b3599663464ed1054f7777f547db02f518581acc5becdd3eddffc8c507f3778
|
| 3 |
+
size 92554920
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dbfb9f54459c38773505de64a6ab7807bdcb392610fe1e697166342e43fb91ae
|
| 3 |
+
size 346345912
|
preprocessor_config.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_valid_processor_keys": [
|
| 3 |
+
"images",
|
| 4 |
+
"do_resize",
|
| 5 |
+
"size",
|
| 6 |
+
"resample",
|
| 7 |
+
"do_center_crop",
|
| 8 |
+
"crop_size",
|
| 9 |
+
"do_rescale",
|
| 10 |
+
"rescale_factor",
|
| 11 |
+
"do_normalize",
|
| 12 |
+
"image_mean",
|
| 13 |
+
"image_std",
|
| 14 |
+
"do_convert_rgb",
|
| 15 |
+
"return_tensors",
|
| 16 |
+
"data_format",
|
| 17 |
+
"input_data_format"
|
| 18 |
+
],
|
| 19 |
+
"crop_size": {
|
| 20 |
+
"height": 518,
|
| 21 |
+
"width": 518
|
| 22 |
+
},
|
| 23 |
+
"do_center_crop": true,
|
| 24 |
+
"do_convert_rgb": true,
|
| 25 |
+
"do_normalize": true,
|
| 26 |
+
"do_rescale": true,
|
| 27 |
+
"do_resize": true,
|
| 28 |
+
"image_mean": [
|
| 29 |
+
0.5307,
|
| 30 |
+
0.5307,
|
| 31 |
+
0.5307
|
| 32 |
+
],
|
| 33 |
+
"image_processor_type": "BitImageProcessor",
|
| 34 |
+
"image_std": [
|
| 35 |
+
0.2583,
|
| 36 |
+
0.2583,
|
| 37 |
+
0.2583
|
| 38 |
+
],
|
| 39 |
+
"resample": 3,
|
| 40 |
+
"rescale_factor": 0.00392156862745098,
|
| 41 |
+
"size": {
|
| 42 |
+
"shortest_edge": 518
|
| 43 |
+
}
|
| 44 |
+
}
|
pyproject.toml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "rad-dino"
|
| 3 |
+
version = "0.1.1"
|
| 4 |
+
description = "Vision encoder for chest X-rays."
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
authors = [
|
| 7 |
+
{ name = "Microsoft Health Futures", email = "innereyedev@microsoft.com" },
|
| 8 |
+
]
|
| 9 |
+
requires-python = ">=3.10"
|
| 10 |
+
dependencies = [
|
| 11 |
+
"einops",
|
| 12 |
+
"jaxtyping",
|
| 13 |
+
"pillow",
|
| 14 |
+
"requests",
|
| 15 |
+
"safetensors",
|
| 16 |
+
"transformers[torch]",
|
| 17 |
+
"typer",
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
[project.urls]
|
| 21 |
+
Homepage = "https://huggingface.co/microsoft/rad-dino"
|
| 22 |
+
Source = "https://huggingface.co/microsoft/rad-dino"
|
| 23 |
+
"Issue tracker" = "https://huggingface.co/microsoft/rad-dino/discussions/new"
|
| 24 |
+
Documentation = "https://huggingface.co/microsoft/rad-dino/blob/main/README.md"
|
| 25 |
+
|
| 26 |
+
[build-system]
|
| 27 |
+
requires = ["uv_build"]
|
| 28 |
+
build-backend = "uv_build"
|
| 29 |
+
|
| 30 |
+
[dependency-groups]
|
| 31 |
+
dev = [
|
| 32 |
+
"ipykernel",
|
| 33 |
+
"ipywidgets",
|
| 34 |
+
]
|
src/rad_dino/__init__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from einops import rearrange
|
| 3 |
+
from jaxtyping import Float
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from torch import Tensor
|
| 6 |
+
from torch import nn
|
| 7 |
+
from transformers import AutoImageProcessor
|
| 8 |
+
from transformers import AutoModel
|
| 9 |
+
from transformers.feature_extraction_utils import BatchFeature
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
__version__ = "0.1.0"
|
| 13 |
+
|
| 14 |
+
TypeClsToken = Float[Tensor, "batch_size embed_dim"]
|
| 15 |
+
TypePatchTokensFlat = Float[Tensor, "batch_size (height width) embed_dim"]
|
| 16 |
+
TypePatchTokens = Float[Tensor, "batch_size embed_dim height width"]
|
| 17 |
+
TypeInputImages = Image.Image | list[Image.Image]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class RadDino(nn.Module):
|
| 21 |
+
_REPO = "microsoft/rad-dino"
|
| 22 |
+
|
| 23 |
+
def __init__(self):
|
| 24 |
+
super().__init__()
|
| 25 |
+
self.model = AutoModel.from_pretrained(self._REPO).eval()
|
| 26 |
+
self.processor = AutoImageProcessor.from_pretrained(self._REPO, use_fast=False)
|
| 27 |
+
|
| 28 |
+
@property
|
| 29 |
+
def device(self) -> torch.device:
|
| 30 |
+
return next(self.model.parameters()).device
|
| 31 |
+
|
| 32 |
+
def preprocess(self, image_or_images: TypeInputImages) -> BatchFeature:
|
| 33 |
+
return self.processor(image_or_images, return_tensors="pt")
|
| 34 |
+
|
| 35 |
+
def encode(self, inputs: BatchFeature) -> tuple[TypeClsToken, TypePatchTokensFlat]:
|
| 36 |
+
outputs = self.model(**inputs)
|
| 37 |
+
cls_token = outputs.last_hidden_state[:, 0]
|
| 38 |
+
patch_tokens = outputs.last_hidden_state[:, 1:]
|
| 39 |
+
return cls_token, patch_tokens
|
| 40 |
+
|
| 41 |
+
def reshape_patch_tokens(
|
| 42 |
+
self,
|
| 43 |
+
patch_tokens_flat: TypePatchTokensFlat,
|
| 44 |
+
) -> TypePatchTokens:
|
| 45 |
+
input_size = self.processor.crop_size["height"]
|
| 46 |
+
patch_size = self.model.config.patch_size
|
| 47 |
+
embeddings_size = input_size // patch_size
|
| 48 |
+
patches_grid = rearrange(
|
| 49 |
+
patch_tokens_flat,
|
| 50 |
+
"batch (height width) embed_dim -> batch embed_dim height width",
|
| 51 |
+
height=embeddings_size,
|
| 52 |
+
)
|
| 53 |
+
return patches_grid
|
| 54 |
+
|
| 55 |
+
@torch.inference_mode()
|
| 56 |
+
def extract_features(
|
| 57 |
+
self,
|
| 58 |
+
image_or_images: TypeInputImages,
|
| 59 |
+
) -> tuple[TypeClsToken, TypePatchTokens]:
|
| 60 |
+
inputs = self.preprocess(image_or_images).to(self.device)
|
| 61 |
+
cls_token, patch_tokens_flat = self.encode(inputs)
|
| 62 |
+
patch_tokens = self.reshape_patch_tokens(patch_tokens_flat)
|
| 63 |
+
return cls_token, patch_tokens
|
| 64 |
+
|
| 65 |
+
def extract_cls_token(self, image_or_images: TypeInputImages) -> TypeClsToken:
|
| 66 |
+
cls_token, _ = self.extract_features(image_or_images)
|
| 67 |
+
return cls_token
|
| 68 |
+
|
| 69 |
+
def extract_patch_tokens(self, image_or_images: TypeInputImages) -> TypePatchTokens:
|
| 70 |
+
_, patch_tokens = self.extract_features(image_or_images)
|
| 71 |
+
return patch_tokens
|
| 72 |
+
|
| 73 |
+
def forward(self, *args) -> tuple[TypeClsToken, TypePatchTokens]:
|
| 74 |
+
return self.extract_features(*args)
|
src/rad_dino/utils.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import torch
|
| 3 |
+
from PIL import Image
|
| 4 |
+
from safetensors import safe_open
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def download_sample_image() -> Image.Image:
|
| 8 |
+
"""Download chest X-ray with CC license."""
|
| 9 |
+
base_url = "https://upload.wikimedia.org/wikipedia/commons"
|
| 10 |
+
path = "2/20/Chest_X-ray_in_influenza_and_Haemophilus_influenzae.jpg"
|
| 11 |
+
image_url = f"{base_url}/{path}"
|
| 12 |
+
headers = {"User-Agent": "RAD-DINO"}
|
| 13 |
+
response = requests.get(image_url, headers=headers, stream=True)
|
| 14 |
+
return Image.open(response.raw)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def safetensors_to_state_dict(checkpoint_path: str) -> dict[str, torch.Tensor]:
|
| 18 |
+
state_dict = {}
|
| 19 |
+
with safe_open(checkpoint_path, framework="pt") as ckpt_file:
|
| 20 |
+
for key in ckpt_file.keys():
|
| 21 |
+
state_dict[key] = ckpt_file.get_tensor(key)
|
| 22 |
+
return state_dict
|
ssl_default_config.yaml
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
WEIGHTS: ''
|
| 3 |
+
compute_precision:
|
| 4 |
+
grad_scaler: true
|
| 5 |
+
teacher:
|
| 6 |
+
backbone:
|
| 7 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 8 |
+
mixed_precision:
|
| 9 |
+
param_dtype: fp16
|
| 10 |
+
reduce_dtype: fp16
|
| 11 |
+
buffer_dtype: fp32
|
| 12 |
+
dino_head:
|
| 13 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 14 |
+
mixed_precision:
|
| 15 |
+
param_dtype: fp16
|
| 16 |
+
reduce_dtype: fp16
|
| 17 |
+
buffer_dtype: fp32
|
| 18 |
+
ibot_head:
|
| 19 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 20 |
+
mixed_precision:
|
| 21 |
+
param_dtype: fp16
|
| 22 |
+
reduce_dtype: fp16
|
| 23 |
+
buffer_dtype: fp32
|
| 24 |
+
student:
|
| 25 |
+
backbone:
|
| 26 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 27 |
+
mixed_precision:
|
| 28 |
+
param_dtype: fp16
|
| 29 |
+
reduce_dtype: fp16
|
| 30 |
+
buffer_dtype: fp32
|
| 31 |
+
dino_head:
|
| 32 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 33 |
+
mixed_precision:
|
| 34 |
+
param_dtype: fp16
|
| 35 |
+
reduce_dtype: fp32
|
| 36 |
+
buffer_dtype: fp32
|
| 37 |
+
ibot_head:
|
| 38 |
+
sharding_strategy: SHARD_GRAD_OP
|
| 39 |
+
mixed_precision:
|
| 40 |
+
param_dtype: fp16
|
| 41 |
+
reduce_dtype: fp32
|
| 42 |
+
buffer_dtype: fp32
|
| 43 |
+
dino:
|
| 44 |
+
loss_weight: 1.0
|
| 45 |
+
head_n_prototypes: 65536
|
| 46 |
+
head_bottleneck_dim: 256
|
| 47 |
+
head_nlayers: 3
|
| 48 |
+
head_hidden_dim: 2048
|
| 49 |
+
koleo_loss_weight: 0.1
|
| 50 |
+
ibot:
|
| 51 |
+
loss_weight: 1.0
|
| 52 |
+
mask_sample_probability: 0.5
|
| 53 |
+
mask_ratio_min_max:
|
| 54 |
+
- 0.1
|
| 55 |
+
- 0.5
|
| 56 |
+
separate_head: false
|
| 57 |
+
head_n_prototypes: 65536
|
| 58 |
+
head_bottleneck_dim: 256
|
| 59 |
+
head_nlayers: 3
|
| 60 |
+
head_hidden_dim: 2048
|
| 61 |
+
train:
|
| 62 |
+
batch_size_per_gpu: 64
|
| 63 |
+
dataset_path: ImageNet:split=TRAIN
|
| 64 |
+
output_dir: .
|
| 65 |
+
saveckp_every_n_epoch: 5
|
| 66 |
+
seed: 0
|
| 67 |
+
num_workers: 10
|
| 68 |
+
OFFICIAL_EPOCH_LENGTH: 0 # automatic rescaling based on the dataset len is applied if this is set to 0
|
| 69 |
+
cache_dataset: true
|
| 70 |
+
centering: "centering" # or "sinkhorn_knopp"
|
| 71 |
+
student:
|
| 72 |
+
arch: vit_large
|
| 73 |
+
patch_size: 16
|
| 74 |
+
drop_block_rate: 0.0
|
| 75 |
+
drop_path_rate: 0.3
|
| 76 |
+
layerscale: 1.0e-05
|
| 77 |
+
drop_path_uniform: true
|
| 78 |
+
pretrained_weights: ''
|
| 79 |
+
ffn_layer: "mlp"
|
| 80 |
+
block_chunks: 0
|
| 81 |
+
qkv_bias: true
|
| 82 |
+
proj_bias: true
|
| 83 |
+
ffn_bias: true
|
| 84 |
+
num_register_tokens: 0
|
| 85 |
+
interpolate_antialias: false
|
| 86 |
+
interpolate_offset: 0.1
|
| 87 |
+
load_weights: true
|
| 88 |
+
checkpoints_dir: null
|
| 89 |
+
teacher:
|
| 90 |
+
momentum_teacher: 0.992
|
| 91 |
+
final_momentum_teacher: 1
|
| 92 |
+
warmup_teacher_temp: 0.04
|
| 93 |
+
teacher_temp: 0.07
|
| 94 |
+
warmup_teacher_temp_epochs: 30
|
| 95 |
+
optim:
|
| 96 |
+
epochs: 100
|
| 97 |
+
weight_decay: 0.04
|
| 98 |
+
weight_decay_end: 0.4
|
| 99 |
+
base_lr: 0.004 # learning rate for a batch size of 1024
|
| 100 |
+
lr: 0. # will be set after applying scaling rule
|
| 101 |
+
warmup_epochs: 10
|
| 102 |
+
min_lr: 1.0e-06
|
| 103 |
+
clip_grad: 3.0
|
| 104 |
+
freeze_last_layer_epochs: 1
|
| 105 |
+
scaling_rule: sqrt_wrt_1024
|
| 106 |
+
patch_embed_lr_mult: 0.2
|
| 107 |
+
layerwise_decay: 0.9
|
| 108 |
+
adamw_beta1: 0.9
|
| 109 |
+
adamw_beta2: 0.999
|
| 110 |
+
crops:
|
| 111 |
+
global_crops_scale:
|
| 112 |
+
- 0.32
|
| 113 |
+
- 1.0
|
| 114 |
+
local_crops_number: 8
|
| 115 |
+
local_crops_scale:
|
| 116 |
+
- 0.05
|
| 117 |
+
- 0.32
|
| 118 |
+
global_crops_size: 224
|
| 119 |
+
local_crops_size: 96
|
| 120 |
+
evaluation:
|
| 121 |
+
eval_period_iterations: 12500
|
| 122 |
+
dataset_str: None
|
| 123 |
+
online: # see dinov2.eval.linear_callback for documentation
|
| 124 |
+
learning_rate: 1e-6 # will be multiplied by batch size and number of devices
|
| 125 |
+
num_last_blocks: 1
|
| 126 |
+
add_avg_pool: true
|
| 127 |
+
num_update_epochs_per_eval: 3
|
| 128 |
+
augmentation:
|
| 129 |
+
degrees: 30
|
| 130 |
+
scale:
|
| 131 |
+
- 0.8
|
| 132 |
+
- 1.2
|
| 133 |
+
shear: 15
|
| 134 |
+
interpolation: BICUBIC
|
| 135 |
+
horizontal_flip: true
|
training_images.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:baab4b9b33f036deb38298b46095de32e39fbc86ffb7c153f1e160c6ce2db007
|
| 3 |
+
size 94929974
|
vitb14_cxr.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# this corresponds to the CXR config
|
| 2 |
+
train:
|
| 3 |
+
batch_size_per_gpu: 40 # For nodes with v100s (32 GB), use 20.
|
| 4 |
+
saveckp_every_n_epoch: 25
|
| 5 |
+
student:
|
| 6 |
+
arch: vit_base
|
| 7 |
+
block_chunks: 4
|
| 8 |
+
patch_size: 14
|
| 9 |
+
drop_block_rate: 0.00
|
| 10 |
+
drop_path_rate: 0.30
|
| 11 |
+
teacher:
|
| 12 |
+
warmup_teacher_temp_epochs: 50
|
| 13 |
+
optim:
|
| 14 |
+
epochs: 100
|
| 15 |
+
warmup_epochs: 5
|
| 16 |
+
base_lr: 0.001
|
| 17 |
+
evaluation:
|
| 18 |
+
eval_period_iterations: 300
|
| 19 |
+
tasks: # from the metadata.csv file of the CANDID processed dataset
|
| 20 |
+
- pneumothorax
|
| 21 |
+
crops:
|
| 22 |
+
global_crops_size: 518
|
| 23 |
+
local_crops_size: 196
|
| 24 |
+
global_crops_scale:
|
| 25 |
+
- 0.50
|
| 26 |
+
- 1.00
|
| 27 |
+
local_crops_number: 8
|
| 28 |
+
local_crops_scale:
|
| 29 |
+
- 0.20
|
| 30 |
+
- 0.50
|
| 31 |
+
pretrained: true
|