WaelK fepegar commited on
Commit
5161583
·
0 Parent(s):

Duplicate from microsoft/rad-dino

Browse files

Co-authored-by: Fernando Pérez-García <fepegar@users.noreply.huggingface.co>

.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