taejoon89 commited on
Commit
d852fa4
·
verified ·
1 Parent(s): 006b93f

Upload folder using huggingface_hub

Browse files
OpenPath/dinov2/hub/__init__.py DELETED
@@ -1,4 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
 
 
 
 
 
OpenPath/dinov2/hub/backbones.py DELETED
@@ -1,174 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from enum import Enum
7
- from pathlib import Path
8
- from typing import Optional, Union
9
- from urllib.parse import urlparse
10
-
11
- import torch
12
-
13
- from .utils import _DINOV2_BASE_URL, _make_dinov2_model_name
14
-
15
-
16
- class Weights(Enum):
17
- LVD142M = "LVD142M"
18
- XRAY_DINO = "XRay-DINO"
19
-
20
-
21
- def is_url(path: str) -> bool:
22
- parsed = urlparse(path)
23
- return parsed.scheme in ("https", "file")
24
-
25
-
26
- def convert_path_or_url_to_url(path: str) -> str:
27
- if is_url(path):
28
- return path
29
- return Path(path).expanduser().resolve().as_uri()
30
-
31
-
32
- def _make_dinov2_model(
33
- *,
34
- arch_name: str = "vit_large",
35
- img_size: int = 518,
36
- patch_size: int = 14,
37
- init_values: float = 1.0,
38
- ffn_layer: str = "mlp",
39
- block_chunks: int = 0,
40
- num_register_tokens: int = 0,
41
- interpolate_antialias: bool = False,
42
- interpolate_offset: float = 0.1,
43
- pretrained: bool = True,
44
- weights: Union[Weights, str] = Weights.LVD142M,
45
- hash: Optional[str] = None,
46
- check_hash: bool = False,
47
- **kwargs,
48
- ):
49
- from ..models import vision_transformer as vits
50
-
51
- model_base_name = _make_dinov2_model_name(arch_name, patch_size)
52
- vit_kwargs = dict(
53
- img_size=img_size,
54
- patch_size=patch_size,
55
- init_values=init_values,
56
- ffn_layer=ffn_layer,
57
- block_chunks=block_chunks,
58
- num_register_tokens=num_register_tokens,
59
- interpolate_antialias=interpolate_antialias,
60
- interpolate_offset=interpolate_offset,
61
- )
62
- vit_kwargs.update(**kwargs)
63
- model = vits.__dict__[arch_name](**vit_kwargs)
64
-
65
- if pretrained:
66
- if type(weights) is Weights and weights not in {
67
- Weights.LVD142M,
68
- Weights.XRAY_DINO,
69
- }:
70
- raise ValueError(f"Unsupported weights for the backbone: {weights}")
71
- elif type(weights) is Weights:
72
- model_full_name = _make_dinov2_model_name(arch_name, patch_size, num_register_tokens)
73
- url = _DINOV2_BASE_URL + f"/{model_base_name}/{model_full_name}_pretrain.pth"
74
- else:
75
- url = convert_path_or_url_to_url(weights)
76
- state_dict = torch.hub.load_state_dict_from_url(url, map_location="cpu", check_hash=check_hash)
77
- model.load_state_dict(state_dict, strict=True)
78
-
79
- return model
80
-
81
-
82
- def dinov2_vits14(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.LVD142M, **kwargs):
83
- """
84
- DINOv2 ViT-S/14 model (optionally) pretrained on the LVD-142M dataset.
85
- """
86
- return _make_dinov2_model(arch_name="vit_small", pretrained=pretrained, weights=weights, **kwargs)
87
-
88
-
89
- def dinov2_vitb14(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.LVD142M, **kwargs):
90
- """
91
- DINOv2 ViT-B/14 model (optionally) pretrained on the LVD-142M dataset.
92
- """
93
- return _make_dinov2_model(arch_name="vit_base", pretrained=pretrained, weights=weights, **kwargs)
94
-
95
-
96
- def dinov2_vitl14(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.LVD142M, **kwargs):
97
- """
98
- DINOv2 ViT-L/14 model (optionally) pretrained on the LVD-142M dataset.
99
- """
100
- return _make_dinov2_model(arch_name="vit_large", pretrained=pretrained, weights=weights, **kwargs)
101
-
102
-
103
- def dinov2_vitg14(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.LVD142M, **kwargs):
104
- """
105
- DINOv2 ViT-g/14 model (optionally) pretrained on the LVD-142M dataset.
106
- """
107
- return _make_dinov2_model(
108
- arch_name="vit_giant2",
109
- ffn_layer="swiglufused",
110
- weights=weights,
111
- pretrained=pretrained,
112
- **kwargs,
113
- )
114
-
115
-
116
- def dinov2_vits14_reg(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.LVD142M, **kwargs):
117
- """
118
- DINOv2 ViT-S/14 model with registers (optionally) pretrained on the LVD-142M dataset.
119
- """
120
- return _make_dinov2_model(
121
- arch_name="vit_small",
122
- pretrained=pretrained,
123
- weights=weights,
124
- num_register_tokens=4,
125
- interpolate_antialias=True,
126
- interpolate_offset=0.0,
127
- **kwargs,
128
- )
129
-
130
-
131
- def dinov2_vitb14_reg(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.LVD142M, **kwargs):
132
- """
133
- DINOv2 ViT-B/14 model with registers (optionally) pretrained on the LVD-142M dataset.
134
- """
135
- return _make_dinov2_model(
136
- arch_name="vit_base",
137
- pretrained=pretrained,
138
- weights=weights,
139
- num_register_tokens=4,
140
- interpolate_antialias=True,
141
- interpolate_offset=0.0,
142
- **kwargs,
143
- )
144
-
145
-
146
- def dinov2_vitl14_reg(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.LVD142M, **kwargs):
147
- """
148
- DINOv2 ViT-L/14 model with registers (optionally) pretrained on the LVD-142M dataset.
149
- """
150
- return _make_dinov2_model(
151
- arch_name="vit_large",
152
- pretrained=pretrained,
153
- weights=weights,
154
- num_register_tokens=4,
155
- interpolate_antialias=True,
156
- interpolate_offset=0.0,
157
- **kwargs,
158
- )
159
-
160
-
161
- def dinov2_vitg14_reg(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.LVD142M, **kwargs):
162
- """
163
- DINOv2 ViT-g/14 model with registers (optionally) pretrained on the LVD-142M dataset.
164
- """
165
- return _make_dinov2_model(
166
- arch_name="vit_giant2",
167
- ffn_layer="swiglufused",
168
- weights=weights,
169
- pretrained=pretrained,
170
- num_register_tokens=4,
171
- interpolate_antialias=True,
172
- interpolate_offset=0.0,
173
- **kwargs,
174
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/cell_dino/backbones.py DELETED
@@ -1,182 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the CC-by-NC licence,
4
- # found in the LICENSE_CELL_DINO_CODE file in the root directory of this source tree.
5
-
6
- from enum import Enum
7
- from typing import Optional, Union
8
-
9
- import torch
10
-
11
-
12
- class Weights(Enum):
13
- CELL_DINO = "CELL-DINO"
14
-
15
-
16
- def _make_cell_dino_model(
17
- *,
18
- arch_name: str = "vit_large",
19
- img_size: int = 518,
20
- patch_size: int = 14,
21
- init_values: float = 1.0,
22
- ffn_layer: str = "mlp",
23
- block_chunks: int = 0,
24
- num_register_tokens: int = 0,
25
- interpolate_antialias: bool = False,
26
- interpolate_offset: float = 0.1,
27
- pretrained: bool = True,
28
- channel_adaptive: bool = False,
29
- weights: Union[Weights, str] = Weights.CELL_DINO,
30
- pretrained_url: Optional[str] = None,
31
- pretrained_path: Optional[str] = None,
32
- **kwargs,
33
- ):
34
- from ...models import vision_transformer as vits
35
-
36
- if isinstance(weights, str):
37
- try:
38
- weights = Weights[weights]
39
- except KeyError:
40
- raise AssertionError(f"Unsupported weights: {weights}")
41
-
42
- vit_kwargs = dict(
43
- img_size=img_size,
44
- patch_size=patch_size,
45
- init_values=init_values,
46
- ffn_layer=ffn_layer,
47
- block_chunks=block_chunks,
48
- num_register_tokens=num_register_tokens,
49
- interpolate_antialias=interpolate_antialias,
50
- interpolate_offset=interpolate_offset,
51
- channel_adaptive=channel_adaptive,
52
- )
53
- vit_kwargs.update(**kwargs)
54
- model = vits.__dict__[arch_name](**vit_kwargs)
55
-
56
- if pretrained:
57
- if pretrained_path is not None:
58
- state_dict = torch.load(pretrained_path, map_location="cpu")
59
- else:
60
- pretrained_url is not None
61
- state_dict = torch.hub.load_state_dict_from_url(pretrained_url, map_location="cpu")
62
- model.load_state_dict(state_dict, strict=True)
63
-
64
- return model
65
-
66
-
67
- def cell_dino_hpa_vitl16(
68
- *,
69
- pretrained_url: Optional[str] = None,
70
- pretrained_path: Optional[str] = None,
71
- pretrained: bool = True,
72
- weights: Union[Weights, str] = Weights.CELL_DINO,
73
- in_channels: int = 4,
74
- **kwargs,
75
- ):
76
- """
77
- Cell-DINO ViT-L/16 model dataset pretrained on HPA dataset.
78
- """
79
- return _make_cell_dino_model(
80
- arch_name="vit_large",
81
- patch_size=16,
82
- img_size=224,
83
- num_register_tokens=0,
84
- interpolate_antialias=False,
85
- interpolate_offset=0.1,
86
- block_chunks=4,
87
- pretrained_url=pretrained_url,
88
- pretrained_path=pretrained_path,
89
- pretrained=pretrained,
90
- weights=weights,
91
- in_chans=in_channels,
92
- **kwargs,
93
- )
94
-
95
-
96
- def cell_dino_hpa_vitl14(
97
- *,
98
- pretrained_url: Optional[str] = None,
99
- pretrained_path: Optional[str] = None,
100
- pretrained: bool = True,
101
- weights: Union[Weights, str] = Weights.CELL_DINO,
102
- in_channels: int = 4,
103
- **kwargs,
104
- ):
105
- """
106
- Cell-DINO ViT-L/14 model dataset pretrained on LVD, then on HPA dataset.
107
- """
108
- return _make_cell_dino_model(
109
- arch_name="vit_large",
110
- patch_size=14,
111
- img_size=518,
112
- num_register_tokens=0,
113
- interpolate_antialias=False,
114
- interpolate_offset=0.1,
115
- block_chunks=4,
116
- pretrained_url=pretrained_url,
117
- pretrained_path=pretrained_path,
118
- pretrained=pretrained,
119
- weights=weights,
120
- in_chans=in_channels,
121
- **kwargs,
122
- )
123
-
124
-
125
- def cell_dino_cp_vits8(
126
- *,
127
- pretrained_url: Optional[str] = None,
128
- pretrained_path: Optional[str] = None,
129
- pretrained: bool = True,
130
- weights: Union[Weights, str] = Weights.CELL_DINO,
131
- in_channels: int = 5,
132
- **kwargs,
133
- ):
134
- """
135
- Cell-DINO ViT-S/8 model dataset pretrained on the combined cell painting dataset.
136
- """
137
- return _make_cell_dino_model(
138
- arch_name="vit_small",
139
- patch_size=8,
140
- img_size=128,
141
- num_register_tokens=0,
142
- interpolate_antialias=False,
143
- interpolate_offset=0.1,
144
- block_chunks=4,
145
- pretrained_url=pretrained_url,
146
- pretrained_path=pretrained_path,
147
- pretrained=pretrained,
148
- weights=weights,
149
- in_chans=in_channels,
150
- **kwargs,
151
- )
152
-
153
-
154
- def channel_adaptive_dino_vitl16(
155
- *,
156
- pretrained_url: Optional[str] = None,
157
- pretrained_path: Optional[str] = None,
158
- pretrained: bool = True,
159
- weights: Union[Weights, str] = Weights.CELL_DINO,
160
- in_channels: int = 1,
161
- channel_adaptive: bool = True,
162
- **kwargs,
163
- ):
164
- """
165
- Cell-DINO ViT-L/16 model dataset pretrained on HPA dataset.
166
- """
167
- return _make_cell_dino_model(
168
- arch_name="vit_large",
169
- patch_size=16,
170
- img_size=224,
171
- num_register_tokens=0,
172
- interpolate_antialias=False,
173
- interpolate_offset=0.1,
174
- block_chunks=4,
175
- pretrained_url=pretrained_url,
176
- pretrained_path=pretrained_path,
177
- pretrained=pretrained,
178
- weights=weights,
179
- in_chans=in_channels,
180
- channel_adaptive=channel_adaptive,
181
- **kwargs,
182
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/classifiers.py DELETED
@@ -1,268 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from enum import Enum
7
- from typing import Union
8
-
9
- import torch
10
- import torch.nn as nn
11
-
12
- from .backbones import _make_dinov2_model
13
- from .utils import _DINOV2_BASE_URL, _make_dinov2_model_name
14
-
15
-
16
- class Weights(Enum):
17
- IMAGENET1K = "IMAGENET1K"
18
-
19
-
20
- def _make_dinov2_linear_classification_head(
21
- *,
22
- arch_name: str = "vit_large",
23
- patch_size: int = 14,
24
- embed_dim: int = 1024,
25
- layers: int = 4,
26
- pretrained: bool = True,
27
- weights: Union[Weights, str] = Weights.IMAGENET1K,
28
- num_register_tokens: int = 0,
29
- **kwargs,
30
- ):
31
- if layers not in (1, 4):
32
- raise AssertionError(f"Unsupported number of layers: {layers}")
33
- if isinstance(weights, str):
34
- try:
35
- weights = Weights[weights]
36
- except KeyError:
37
- raise AssertionError(f"Unsupported weights: {weights}")
38
-
39
- linear_head = nn.Linear((1 + layers) * embed_dim, 1_000)
40
-
41
- if pretrained:
42
- model_base_name = _make_dinov2_model_name(arch_name, patch_size)
43
- model_full_name = _make_dinov2_model_name(arch_name, patch_size, num_register_tokens)
44
- layers_str = str(layers) if layers == 4 else ""
45
- url = _DINOV2_BASE_URL + f"/{model_base_name}/{model_full_name}_linear{layers_str}_head.pth"
46
- state_dict = torch.hub.load_state_dict_from_url(url, map_location="cpu")
47
- linear_head.load_state_dict(state_dict, strict=True)
48
-
49
- return linear_head
50
-
51
-
52
- class _LinearClassifierWrapper(nn.Module):
53
- def __init__(self, *, backbone: nn.Module, linear_head: nn.Module, layers: int = 4):
54
- super().__init__()
55
- self.backbone = backbone
56
- self.linear_head = linear_head
57
- self.layers = layers
58
-
59
- def forward(self, x):
60
- if self.layers == 1:
61
- x = self.backbone.forward_features(x)
62
- cls_token = x["x_norm_clstoken"]
63
- patch_tokens = x["x_norm_patchtokens"]
64
- # fmt: off
65
- linear_input = torch.cat([
66
- cls_token,
67
- patch_tokens.mean(dim=1),
68
- ], dim=1)
69
- # fmt: on
70
- elif self.layers == 4:
71
- x = self.backbone.get_intermediate_layers(x, n=4, return_class_token=True)
72
- # fmt: off
73
- linear_input = torch.cat([
74
- x[0][1],
75
- x[1][1],
76
- x[2][1],
77
- x[3][1],
78
- x[3][0].mean(dim=1),
79
- ], dim=1)
80
- # fmt: on
81
- else:
82
- assert False, f"Unsupported number of layers: {self.layers}"
83
- return self.linear_head(linear_input)
84
-
85
-
86
- def _make_dinov2_linear_classifier(
87
- *,
88
- arch_name: str = "vit_large",
89
- layers: int = 4,
90
- pretrained: bool = True,
91
- weights: Union[Weights, str] = Weights.IMAGENET1K,
92
- num_register_tokens: int = 0,
93
- interpolate_antialias: bool = False,
94
- interpolate_offset: float = 0.1,
95
- **kwargs,
96
- ):
97
- backbone = _make_dinov2_model(
98
- arch_name=arch_name,
99
- pretrained=pretrained,
100
- num_register_tokens=num_register_tokens,
101
- interpolate_antialias=interpolate_antialias,
102
- interpolate_offset=interpolate_offset,
103
- **kwargs,
104
- )
105
-
106
- embed_dim = backbone.embed_dim
107
- patch_size = backbone.patch_size
108
- linear_head = _make_dinov2_linear_classification_head(
109
- arch_name=arch_name,
110
- patch_size=patch_size,
111
- embed_dim=embed_dim,
112
- layers=layers,
113
- pretrained=pretrained,
114
- weights=weights,
115
- num_register_tokens=num_register_tokens,
116
- )
117
-
118
- return _LinearClassifierWrapper(backbone=backbone, linear_head=linear_head, layers=layers)
119
-
120
-
121
- def dinov2_vits14_lc(
122
- *,
123
- layers: int = 4,
124
- pretrained: bool = True,
125
- weights: Union[Weights, str] = Weights.IMAGENET1K,
126
- **kwargs,
127
- ):
128
- """
129
- Linear classifier (1 or 4 layers) on top of a DINOv2 ViT-S/14 backbone (optionally) pretrained on the LVD-142M dataset and trained on ImageNet-1k.
130
- """
131
- return _make_dinov2_linear_classifier(
132
- arch_name="vit_small",
133
- layers=layers,
134
- pretrained=pretrained,
135
- weights=weights,
136
- **kwargs,
137
- )
138
-
139
-
140
- def dinov2_vitb14_lc(
141
- *,
142
- layers: int = 4,
143
- pretrained: bool = True,
144
- weights: Union[Weights, str] = Weights.IMAGENET1K,
145
- **kwargs,
146
- ):
147
- """
148
- Linear classifier (1 or 4 layers) on top of a DINOv2 ViT-B/14 backbone (optionally) pretrained on the LVD-142M dataset and trained on ImageNet-1k.
149
- """
150
- return _make_dinov2_linear_classifier(
151
- arch_name="vit_base",
152
- layers=layers,
153
- pretrained=pretrained,
154
- weights=weights,
155
- **kwargs,
156
- )
157
-
158
-
159
- def dinov2_vitl14_lc(
160
- *,
161
- layers: int = 4,
162
- pretrained: bool = True,
163
- weights: Union[Weights, str] = Weights.IMAGENET1K,
164
- **kwargs,
165
- ):
166
- """
167
- Linear classifier (1 or 4 layers) on top of a DINOv2 ViT-L/14 backbone (optionally) pretrained on the LVD-142M dataset and trained on ImageNet-1k.
168
- """
169
- return _make_dinov2_linear_classifier(
170
- arch_name="vit_large",
171
- layers=layers,
172
- pretrained=pretrained,
173
- weights=weights,
174
- **kwargs,
175
- )
176
-
177
-
178
- def dinov2_vitg14_lc(
179
- *,
180
- layers: int = 4,
181
- pretrained: bool = True,
182
- weights: Union[Weights, str] = Weights.IMAGENET1K,
183
- **kwargs,
184
- ):
185
- """
186
- Linear classifier (1 or 4 layers) on top of a DINOv2 ViT-g/14 backbone (optionally) pretrained on the LVD-142M dataset and trained on ImageNet-1k.
187
- """
188
- return _make_dinov2_linear_classifier(
189
- arch_name="vit_giant2",
190
- layers=layers,
191
- ffn_layer="swiglufused",
192
- pretrained=pretrained,
193
- weights=weights,
194
- **kwargs,
195
- )
196
-
197
-
198
- def dinov2_vits14_reg_lc(
199
- *, layers: int = 4, pretrained: bool = True, weights: Union[Weights, str] = Weights.IMAGENET1K, **kwargs
200
- ):
201
- """
202
- Linear classifier (1 or 4 layers) on top of a DINOv2 ViT-S/14 backbone with registers (optionally) pretrained on the LVD-142M dataset and trained on ImageNet-1k.
203
- """
204
- return _make_dinov2_linear_classifier(
205
- arch_name="vit_small",
206
- layers=layers,
207
- pretrained=pretrained,
208
- weights=weights,
209
- num_register_tokens=4,
210
- interpolate_antialias=True,
211
- interpolate_offset=0.0,
212
- **kwargs,
213
- )
214
-
215
-
216
- def dinov2_vitb14_reg_lc(
217
- *, layers: int = 4, pretrained: bool = True, weights: Union[Weights, str] = Weights.IMAGENET1K, **kwargs
218
- ):
219
- """
220
- Linear classifier (1 or 4 layers) on top of a DINOv2 ViT-B/14 backbone with registers (optionally) pretrained on the LVD-142M dataset and trained on ImageNet-1k.
221
- """
222
- return _make_dinov2_linear_classifier(
223
- arch_name="vit_base",
224
- layers=layers,
225
- pretrained=pretrained,
226
- weights=weights,
227
- num_register_tokens=4,
228
- interpolate_antialias=True,
229
- interpolate_offset=0.0,
230
- **kwargs,
231
- )
232
-
233
-
234
- def dinov2_vitl14_reg_lc(
235
- *, layers: int = 4, pretrained: bool = True, weights: Union[Weights, str] = Weights.IMAGENET1K, **kwargs
236
- ):
237
- """
238
- Linear classifier (1 or 4 layers) on top of a DINOv2 ViT-L/14 backbone with registers (optionally) pretrained on the LVD-142M dataset and trained on ImageNet-1k.
239
- """
240
- return _make_dinov2_linear_classifier(
241
- arch_name="vit_large",
242
- layers=layers,
243
- pretrained=pretrained,
244
- weights=weights,
245
- num_register_tokens=4,
246
- interpolate_antialias=True,
247
- interpolate_offset=0.0,
248
- **kwargs,
249
- )
250
-
251
-
252
- def dinov2_vitg14_reg_lc(
253
- *, layers: int = 4, pretrained: bool = True, weights: Union[Weights, str] = Weights.IMAGENET1K, **kwargs
254
- ):
255
- """
256
- Linear classifier (1 or 4 layers) on top of a DINOv2 ViT-g/14 backbone with registers (optionally) pretrained on the LVD-142M dataset and trained on ImageNet-1k.
257
- """
258
- return _make_dinov2_linear_classifier(
259
- arch_name="vit_giant2",
260
- layers=layers,
261
- ffn_layer="swiglufused",
262
- pretrained=pretrained,
263
- weights=weights,
264
- num_register_tokens=4,
265
- interpolate_antialias=True,
266
- interpolate_offset=0.0,
267
- **kwargs,
268
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/depth/__init__.py DELETED
@@ -1,7 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from .decode_heads import BNHead, DPTHead
7
- from .encoder_decoder import DepthEncoderDecoder
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/depth/decode_heads.py DELETED
@@ -1,747 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import copy
7
- from functools import partial
8
- import math
9
- import warnings
10
-
11
- import torch
12
- import torch.nn as nn
13
-
14
- from .ops import resize
15
-
16
-
17
- # XXX: (Untested) replacement for mmcv.imdenormalize()
18
- def _imdenormalize(img, mean, std, to_bgr=True):
19
- import numpy as np
20
-
21
- mean = mean.reshape(1, -1).astype(np.float64)
22
- std = std.reshape(1, -1).astype(np.float64)
23
- img = (img * std) + mean
24
- if to_bgr:
25
- img = img[::-1]
26
- return img
27
-
28
-
29
- class DepthBaseDecodeHead(nn.Module):
30
- """Base class for BaseDecodeHead.
31
-
32
- Args:
33
- in_channels (List): Input channels.
34
- channels (int): Channels after modules, before conv_depth.
35
- conv_layer (nn.Module): Conv layers. Default: None.
36
- act_layer (nn.Module): Activation layers. Default: nn.ReLU.
37
- loss_decode (dict): Config of decode loss.
38
- Default: ().
39
- sampler (dict|None): The config of depth map sampler.
40
- Default: None.
41
- align_corners (bool): align_corners argument of F.interpolate.
42
- Default: False.
43
- min_depth (int): Min depth in dataset setting.
44
- Default: 1e-3.
45
- max_depth (int): Max depth in dataset setting.
46
- Default: None.
47
- norm_layer (dict|None): Norm layers.
48
- Default: None.
49
- classify (bool): Whether predict depth in a cls.-reg. manner.
50
- Default: False.
51
- n_bins (int): The number of bins used in cls. step.
52
- Default: 256.
53
- bins_strategy (str): The discrete strategy used in cls. step.
54
- Default: 'UD'.
55
- norm_strategy (str): The norm strategy on cls. probability
56
- distribution. Default: 'linear'
57
- scale_up (str): Whether predict depth in a scale-up manner.
58
- Default: False.
59
- """
60
-
61
- def __init__(
62
- self,
63
- in_channels,
64
- conv_layer=None,
65
- act_layer=nn.ReLU,
66
- channels=96,
67
- loss_decode=(),
68
- sampler=None,
69
- align_corners=False,
70
- min_depth=1e-3,
71
- max_depth=None,
72
- norm_layer=None,
73
- classify=False,
74
- n_bins=256,
75
- bins_strategy="UD",
76
- norm_strategy="linear",
77
- scale_up=False,
78
- ):
79
- super(DepthBaseDecodeHead, self).__init__()
80
-
81
- self.in_channels = in_channels
82
- self.channels = channels
83
- self.conf_layer = conv_layer
84
- self.act_layer = act_layer
85
- self.loss_decode = loss_decode
86
- self.align_corners = align_corners
87
- self.min_depth = min_depth
88
- self.max_depth = max_depth
89
- self.norm_layer = norm_layer
90
- self.classify = classify
91
- self.n_bins = n_bins
92
- self.scale_up = scale_up
93
-
94
- if self.classify:
95
- assert bins_strategy in ["UD", "SID"], "Support bins_strategy: UD, SID"
96
- assert norm_strategy in ["linear", "softmax", "sigmoid"], "Support norm_strategy: linear, softmax, sigmoid"
97
-
98
- self.bins_strategy = bins_strategy
99
- self.norm_strategy = norm_strategy
100
- self.softmax = nn.Softmax(dim=1)
101
- self.conv_depth = nn.Conv2d(channels, n_bins, kernel_size=3, padding=1, stride=1)
102
- else:
103
- self.conv_depth = nn.Conv2d(channels, 1, kernel_size=3, padding=1, stride=1)
104
-
105
- self.relu = nn.ReLU()
106
- self.sigmoid = nn.Sigmoid()
107
-
108
- def forward(self, inputs, img_metas):
109
- """Placeholder of forward function."""
110
- pass
111
-
112
- def forward_train(self, img, inputs, img_metas, depth_gt):
113
- """Forward function for training.
114
- Args:
115
- inputs (list[Tensor]): List of multi-level img features.
116
- img_metas (list[dict]): List of image info dict where each dict
117
- has: 'img_shape', 'scale_factor', 'flip', and may also contain
118
- 'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
119
- For details on the values of these keys see
120
- `depth/datasets/pipelines/formatting.py:Collect`.
121
- depth_gt (Tensor): GT depth
122
-
123
- Returns:
124
- dict[str, Tensor]: a dictionary of loss components
125
- """
126
- depth_pred = self.forward(inputs, img_metas)
127
- losses = self.losses(depth_pred, depth_gt)
128
-
129
- log_imgs = self.log_images(img[0], depth_pred[0], depth_gt[0], img_metas[0])
130
- losses.update(**log_imgs)
131
-
132
- return losses
133
-
134
- def forward_test(self, inputs, img_metas):
135
- """Forward function for testing.
136
- Args:
137
- inputs (list[Tensor]): List of multi-level img features.
138
- img_metas (list[dict]): List of image info dict where each dict
139
- has: 'img_shape', 'scale_factor', 'flip', and may also contain
140
- 'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
141
- For details on the values of these keys see
142
- `depth/datasets/pipelines/formatting.py:Collect`.
143
-
144
- Returns:
145
- Tensor: Output depth map.
146
- """
147
- return self.forward(inputs, img_metas)
148
-
149
- def depth_pred(self, feat):
150
- """Prediction each pixel."""
151
- if self.classify:
152
- logit = self.conv_depth(feat)
153
-
154
- if self.bins_strategy == "UD":
155
- bins = torch.linspace(self.min_depth, self.max_depth, self.n_bins, device=feat.device)
156
- elif self.bins_strategy == "SID":
157
- bins = torch.logspace(self.min_depth, self.max_depth, self.n_bins, device=feat.device)
158
-
159
- # following Adabins, default linear
160
- if self.norm_strategy == "linear":
161
- logit = torch.relu(logit)
162
- eps = 0.1
163
- logit = logit + eps
164
- logit = logit / logit.sum(dim=1, keepdim=True)
165
- elif self.norm_strategy == "softmax":
166
- logit = torch.softmax(logit, dim=1)
167
- elif self.norm_strategy == "sigmoid":
168
- logit = torch.sigmoid(logit)
169
- logit = logit / logit.sum(dim=1, keepdim=True)
170
-
171
- output = torch.einsum("ikmn,k->imn", [logit, bins]).unsqueeze(dim=1)
172
-
173
- else:
174
- if self.scale_up:
175
- output = self.sigmoid(self.conv_depth(feat)) * self.max_depth
176
- else:
177
- output = self.relu(self.conv_depth(feat)) + self.min_depth
178
- return output
179
-
180
- def losses(self, depth_pred, depth_gt):
181
- """Compute depth loss."""
182
- loss = dict()
183
- depth_pred = resize(
184
- input=depth_pred, size=depth_gt.shape[2:], mode="bilinear", align_corners=self.align_corners, warning=False
185
- )
186
- if not isinstance(self.loss_decode, nn.ModuleList):
187
- losses_decode = [self.loss_decode]
188
- else:
189
- losses_decode = self.loss_decode
190
- for loss_decode in losses_decode:
191
- if loss_decode.loss_name not in loss:
192
- loss[loss_decode.loss_name] = loss_decode(depth_pred, depth_gt)
193
- else:
194
- loss[loss_decode.loss_name] += loss_decode(depth_pred, depth_gt)
195
- return loss
196
-
197
- def log_images(self, img_path, depth_pred, depth_gt, img_meta):
198
- import numpy as np
199
-
200
- show_img = copy.deepcopy(img_path.detach().cpu().permute(1, 2, 0))
201
- show_img = show_img.numpy().astype(np.float32)
202
- show_img = _imdenormalize(
203
- show_img,
204
- img_meta["img_norm_cfg"]["mean"],
205
- img_meta["img_norm_cfg"]["std"],
206
- img_meta["img_norm_cfg"]["to_rgb"],
207
- )
208
- show_img = np.clip(show_img, 0, 255)
209
- show_img = show_img.astype(np.uint8)
210
- show_img = show_img[:, :, ::-1]
211
- show_img = show_img.transpose(0, 2, 1)
212
- show_img = show_img.transpose(1, 0, 2)
213
-
214
- depth_pred = depth_pred / torch.max(depth_pred)
215
- depth_gt = depth_gt / torch.max(depth_gt)
216
-
217
- depth_pred_color = copy.deepcopy(depth_pred.detach().cpu())
218
- depth_gt_color = copy.deepcopy(depth_gt.detach().cpu())
219
-
220
- return {"img_rgb": show_img, "img_depth_pred": depth_pred_color, "img_depth_gt": depth_gt_color}
221
-
222
-
223
- class BNHead(DepthBaseDecodeHead):
224
- """Just a batchnorm."""
225
-
226
- def __init__(self, input_transform="resize_concat", in_index=(0, 1, 2, 3), upsample=1, **kwargs):
227
- super().__init__(**kwargs)
228
- self.input_transform = input_transform
229
- self.in_index = in_index
230
- self.upsample = upsample
231
- # self.bn = nn.SyncBatchNorm(self.in_channels)
232
- if self.classify:
233
- self.conv_depth = nn.Conv2d(self.channels, self.n_bins, kernel_size=1, padding=0, stride=1)
234
- else:
235
- self.conv_depth = nn.Conv2d(self.channels, 1, kernel_size=1, padding=0, stride=1)
236
-
237
- def _transform_inputs(self, inputs):
238
- """Transform inputs for decoder.
239
- Args:
240
- inputs (list[Tensor]): List of multi-level img features.
241
- Returns:
242
- Tensor: The transformed inputs
243
- """
244
-
245
- if "concat" in self.input_transform:
246
- inputs = [inputs[i] for i in self.in_index]
247
- if "resize" in self.input_transform:
248
- inputs = [
249
- resize(
250
- input=x,
251
- size=[s * self.upsample for s in inputs[0].shape[2:]],
252
- mode="bilinear",
253
- align_corners=self.align_corners,
254
- )
255
- for x in inputs
256
- ]
257
- inputs = torch.cat(inputs, dim=1)
258
- elif self.input_transform == "multiple_select":
259
- inputs = [inputs[i] for i in self.in_index]
260
- else:
261
- inputs = inputs[self.in_index]
262
-
263
- return inputs
264
-
265
- def _forward_feature(self, inputs, img_metas=None, **kwargs):
266
- """Forward function for feature maps before classifying each pixel with
267
- ``self.cls_seg`` fc.
268
- Args:
269
- inputs (list[Tensor]): List of multi-level img features.
270
- Returns:
271
- feats (Tensor): A tensor of shape (batch_size, self.channels,
272
- H, W) which is feature map for last layer of decoder head.
273
- """
274
- # accept lists (for cls token)
275
- inputs = list(inputs)
276
- for i, x in enumerate(inputs):
277
- if len(x) == 2:
278
- x, cls_token = x[0], x[1]
279
- if len(x.shape) == 2:
280
- x = x[:, :, None, None]
281
- cls_token = cls_token[:, :, None, None].expand_as(x)
282
- inputs[i] = torch.cat((x, cls_token), 1)
283
- else:
284
- x = x[0]
285
- if len(x.shape) == 2:
286
- x = x[:, :, None, None]
287
- inputs[i] = x
288
- x = self._transform_inputs(inputs)
289
- # feats = self.bn(x)
290
- return x
291
-
292
- def forward(self, inputs, img_metas=None, **kwargs):
293
- """Forward function."""
294
- output = self._forward_feature(inputs, img_metas=img_metas, **kwargs)
295
- output = self.depth_pred(output)
296
- return output
297
-
298
-
299
- class ConvModule(nn.Module):
300
- """A conv block that bundles conv/norm/activation layers.
301
-
302
- This block simplifies the usage of convolution layers, which are commonly
303
- used with a norm layer (e.g., BatchNorm) and activation layer (e.g., ReLU).
304
- It is based upon three build methods: `build_conv_layer()`,
305
- `build_norm_layer()` and `build_activation_layer()`.
306
-
307
- Besides, we add some additional features in this module.
308
- 1. Automatically set `bias` of the conv layer.
309
- 2. Spectral norm is supported.
310
- 3. More padding modes are supported. Before PyTorch 1.5, nn.Conv2d only
311
- supports zero and circular padding, and we add "reflect" padding mode.
312
-
313
- Args:
314
- in_channels (int): Number of channels in the input feature map.
315
- Same as that in ``nn._ConvNd``.
316
- out_channels (int): Number of channels produced by the convolution.
317
- Same as that in ``nn._ConvNd``.
318
- kernel_size (int | tuple[int]): Size of the convolving kernel.
319
- Same as that in ``nn._ConvNd``.
320
- stride (int | tuple[int]): Stride of the convolution.
321
- Same as that in ``nn._ConvNd``.
322
- padding (int | tuple[int]): Zero-padding added to both sides of
323
- the input. Same as that in ``nn._ConvNd``.
324
- dilation (int | tuple[int]): Spacing between kernel elements.
325
- Same as that in ``nn._ConvNd``.
326
- groups (int): Number of blocked connections from input channels to
327
- output channels. Same as that in ``nn._ConvNd``.
328
- bias (bool | str): If specified as `auto`, it will be decided by the
329
- norm_layer. Bias will be set as True if `norm_layer` is None, otherwise
330
- False. Default: "auto".
331
- conv_layer (nn.Module): Convolution layer. Default: None,
332
- which means using conv2d.
333
- norm_layer (nn.Module): Normalization layer. Default: None.
334
- act_layer (nn.Module): Activation layer. Default: nn.ReLU.
335
- inplace (bool): Whether to use inplace mode for activation.
336
- Default: True.
337
- with_spectral_norm (bool): Whether use spectral norm in conv module.
338
- Default: False.
339
- padding_mode (str): If the `padding_mode` has not been supported by
340
- current `Conv2d` in PyTorch, we will use our own padding layer
341
- instead. Currently, we support ['zeros', 'circular'] with official
342
- implementation and ['reflect'] with our own implementation.
343
- Default: 'zeros'.
344
- order (tuple[str]): The order of conv/norm/activation layers. It is a
345
- sequence of "conv", "norm" and "act". Common examples are
346
- ("conv", "norm", "act") and ("act", "conv", "norm").
347
- Default: ('conv', 'norm', 'act').
348
- """
349
-
350
- _abbr_ = "conv_block"
351
-
352
- def __init__(
353
- self,
354
- in_channels,
355
- out_channels,
356
- kernel_size,
357
- stride=1,
358
- padding=0,
359
- dilation=1,
360
- groups=1,
361
- bias="auto",
362
- conv_layer=nn.Conv2d,
363
- norm_layer=None,
364
- act_layer=nn.ReLU,
365
- inplace=True,
366
- with_spectral_norm=False,
367
- padding_mode="zeros",
368
- order=("conv", "norm", "act"),
369
- ):
370
- super(ConvModule, self).__init__()
371
- official_padding_mode = ["zeros", "circular"]
372
- self.conv_layer = conv_layer
373
- self.norm_layer = norm_layer
374
- self.act_layer = act_layer
375
- self.inplace = inplace
376
- self.with_spectral_norm = with_spectral_norm
377
- self.with_explicit_padding = padding_mode not in official_padding_mode
378
- self.order = order
379
- assert isinstance(self.order, tuple) and len(self.order) == 3
380
- assert set(order) == set(["conv", "norm", "act"])
381
-
382
- self.with_norm = norm_layer is not None
383
- self.with_activation = act_layer is not None
384
- # if the conv layer is before a norm layer, bias is unnecessary.
385
- if bias == "auto":
386
- bias = not self.with_norm
387
- self.with_bias = bias
388
-
389
- if self.with_explicit_padding:
390
- if padding_mode == "zeros":
391
- padding_layer = nn.ZeroPad2d
392
- else:
393
- raise AssertionError(f"Unsupported padding mode: {padding_mode}")
394
- self.pad = padding_layer(padding)
395
-
396
- # reset padding to 0 for conv module
397
- conv_padding = 0 if self.with_explicit_padding else padding
398
- # build convolution layer
399
- self.conv = self.conv_layer(
400
- in_channels,
401
- out_channels,
402
- kernel_size,
403
- stride=stride,
404
- padding=conv_padding,
405
- dilation=dilation,
406
- groups=groups,
407
- bias=bias,
408
- )
409
- # export the attributes of self.conv to a higher level for convenience
410
- self.in_channels = self.conv.in_channels
411
- self.out_channels = self.conv.out_channels
412
- self.kernel_size = self.conv.kernel_size
413
- self.stride = self.conv.stride
414
- self.padding = padding
415
- self.dilation = self.conv.dilation
416
- self.transposed = self.conv.transposed
417
- self.output_padding = self.conv.output_padding
418
- self.groups = self.conv.groups
419
-
420
- if self.with_spectral_norm:
421
- self.conv = nn.utils.spectral_norm(self.conv)
422
-
423
- # build normalization layers
424
- if self.with_norm:
425
- # norm layer is after conv layer
426
- if order.index("norm") > order.index("conv"):
427
- norm_channels = out_channels
428
- else:
429
- norm_channels = in_channels
430
- norm = partial(norm_layer, num_features=norm_channels)
431
- self.add_module("norm", norm)
432
- if self.with_bias:
433
- from torch.nnModules.batchnorm import _BatchNorm
434
- from torch.nnModules.instancenorm import _InstanceNorm
435
-
436
- if isinstance(norm, (_BatchNorm, _InstanceNorm)):
437
- warnings.warn("Unnecessary conv bias before batch/instance norm")
438
- else:
439
- self.norm_name = None
440
-
441
- # build activation layer
442
- if self.with_activation:
443
- # nn.Tanh has no 'inplace' argument
444
- # (nn.Tanh, nn.PReLU, nn.Sigmoid, nn.HSigmoid, nn.Swish, nn.GELU)
445
- if not isinstance(act_layer, (nn.Tanh, nn.PReLU, nn.Sigmoid, nn.GELU)):
446
- act_layer = partial(act_layer, inplace=inplace)
447
- self.activate = act_layer()
448
-
449
- # Use msra init by default
450
- self.init_weights()
451
-
452
- @property
453
- def norm(self):
454
- if self.norm_name:
455
- return getattr(self, self.norm_name)
456
- else:
457
- return None
458
-
459
- def init_weights(self):
460
- # 1. It is mainly for customized conv layers with their own
461
- # initialization manners by calling their own ``init_weights()``,
462
- # and we do not want ConvModule to override the initialization.
463
- # 2. For customized conv layers without their own initialization
464
- # manners (that is, they don't have their own ``init_weights()``)
465
- # and PyTorch's conv layers, they will be initialized by
466
- # this method with default ``kaiming_init``.
467
- # Note: For PyTorch's conv layers, they will be overwritten by our
468
- # initialization implementation using default ``kaiming_init``.
469
- if not hasattr(self.conv, "init_weights"):
470
- if self.with_activation and isinstance(self.act_layer, nn.LeakyReLU):
471
- nonlinearity = "leaky_relu"
472
- a = 0.01 # XXX: default negative_slope
473
- else:
474
- nonlinearity = "relu"
475
- a = 0
476
- if hasattr(self.conv, "weight") and self.conv.weight is not None:
477
- nn.init.kaiming_normal_(self.conv.weight, a=a, mode="fan_out", nonlinearity=nonlinearity)
478
- if hasattr(self.conv, "bias") and self.conv.bias is not None:
479
- nn.init.constant_(self.conv.bias, 0)
480
- if self.with_norm:
481
- if hasattr(self.norm, "weight") and self.norm.weight is not None:
482
- nn.init.constant_(self.norm.weight, 1)
483
- if hasattr(self.norm, "bias") and self.norm.bias is not None:
484
- nn.init.constant_(self.norm.bias, 0)
485
-
486
- def forward(self, x, activate=True, norm=True):
487
- for layer in self.order:
488
- if layer == "conv":
489
- if self.with_explicit_padding:
490
- x = self.pad(x)
491
- x = self.conv(x)
492
- elif layer == "norm" and norm and self.with_norm:
493
- x = self.norm(x)
494
- elif layer == "act" and activate and self.with_activation:
495
- x = self.activate(x)
496
- return x
497
-
498
-
499
- class Interpolate(nn.Module):
500
- def __init__(self, scale_factor, mode, align_corners=False):
501
- super(Interpolate, self).__init__()
502
- self.interp = nn.functional.interpolate
503
- self.scale_factor = scale_factor
504
- self.mode = mode
505
- self.align_corners = align_corners
506
-
507
- def forward(self, x):
508
- x = self.interp(x, scale_factor=self.scale_factor, mode=self.mode, align_corners=self.align_corners)
509
- return x
510
-
511
-
512
- class HeadDepth(nn.Module):
513
- def __init__(self, features):
514
- super(HeadDepth, self).__init__()
515
- self.head = nn.Sequential(
516
- nn.Conv2d(features, features // 2, kernel_size=3, stride=1, padding=1),
517
- Interpolate(scale_factor=2, mode="bilinear", align_corners=True),
518
- nn.Conv2d(features // 2, 32, kernel_size=3, stride=1, padding=1),
519
- nn.ReLU(),
520
- nn.Conv2d(32, 1, kernel_size=1, stride=1, padding=0),
521
- )
522
-
523
- def forward(self, x):
524
- x = self.head(x)
525
- return x
526
-
527
-
528
- class ReassembleBlocks(nn.Module):
529
- """ViTPostProcessBlock, process cls_token in ViT backbone output and
530
- rearrange the feature vector to feature map.
531
- Args:
532
- in_channels (int): ViT feature channels. Default: 768.
533
- out_channels (List): output channels of each stage.
534
- Default: [96, 192, 384, 768].
535
- readout_type (str): Type of readout operation. Default: 'ignore'.
536
- patch_size (int): The patch size. Default: 16.
537
- """
538
-
539
- def __init__(self, in_channels=768, out_channels=[96, 192, 384, 768], readout_type="ignore", patch_size=16):
540
- super(ReassembleBlocks, self).__init__()
541
-
542
- assert readout_type in ["ignore", "add", "project"]
543
- self.readout_type = readout_type
544
- self.patch_size = patch_size
545
-
546
- self.projects = nn.ModuleList(
547
- [
548
- ConvModule(
549
- in_channels=in_channels,
550
- out_channels=out_channel,
551
- kernel_size=1,
552
- act_layer=None,
553
- )
554
- for out_channel in out_channels
555
- ]
556
- )
557
-
558
- self.resize_layers = nn.ModuleList(
559
- [
560
- nn.ConvTranspose2d(
561
- in_channels=out_channels[0], out_channels=out_channels[0], kernel_size=4, stride=4, padding=0
562
- ),
563
- nn.ConvTranspose2d(
564
- in_channels=out_channels[1], out_channels=out_channels[1], kernel_size=2, stride=2, padding=0
565
- ),
566
- nn.Identity(),
567
- nn.Conv2d(
568
- in_channels=out_channels[3], out_channels=out_channels[3], kernel_size=3, stride=2, padding=1
569
- ),
570
- ]
571
- )
572
- if self.readout_type == "project":
573
- self.readout_projects = nn.ModuleList()
574
- for _ in range(len(self.projects)):
575
- self.readout_projects.append(nn.Sequential(nn.Linear(2 * in_channels, in_channels), nn.GELU()))
576
-
577
- def forward(self, inputs):
578
- assert isinstance(inputs, list)
579
- out = []
580
- for i, x in enumerate(inputs):
581
- assert len(x) == 2
582
- x, cls_token = x[0], x[1]
583
- feature_shape = x.shape
584
- if self.readout_type == "project":
585
- x = x.flatten(2).permute((0, 2, 1))
586
- readout = cls_token.unsqueeze(1).expand_as(x)
587
- x = self.readout_projects[i](torch.cat((x, readout), -1))
588
- x = x.permute(0, 2, 1).reshape(feature_shape)
589
- elif self.readout_type == "add":
590
- x = x.flatten(2) + cls_token.unsqueeze(-1)
591
- x = x.reshape(feature_shape)
592
- else:
593
- pass
594
- x = self.projects[i](x)
595
- x = self.resize_layers[i](x)
596
- out.append(x)
597
- return out
598
-
599
-
600
- class PreActResidualConvUnit(nn.Module):
601
- """ResidualConvUnit, pre-activate residual unit.
602
- Args:
603
- in_channels (int): number of channels in the input feature map.
604
- act_layer (nn.Module): activation layer.
605
- norm_layer (nn.Module): norm layer.
606
- stride (int): stride of the first block. Default: 1
607
- dilation (int): dilation rate for convs layers. Default: 1.
608
- """
609
-
610
- def __init__(self, in_channels, act_layer, norm_layer, stride=1, dilation=1):
611
- super(PreActResidualConvUnit, self).__init__()
612
-
613
- self.conv1 = ConvModule(
614
- in_channels,
615
- in_channels,
616
- 3,
617
- stride=stride,
618
- padding=dilation,
619
- dilation=dilation,
620
- norm_layer=norm_layer,
621
- act_layer=act_layer,
622
- bias=False,
623
- order=("act", "conv", "norm"),
624
- )
625
-
626
- self.conv2 = ConvModule(
627
- in_channels,
628
- in_channels,
629
- 3,
630
- padding=1,
631
- norm_layer=norm_layer,
632
- act_layer=act_layer,
633
- bias=False,
634
- order=("act", "conv", "norm"),
635
- )
636
-
637
- def forward(self, inputs):
638
- inputs_ = inputs.clone()
639
- x = self.conv1(inputs)
640
- x = self.conv2(x)
641
- return x + inputs_
642
-
643
-
644
- class FeatureFusionBlock(nn.Module):
645
- """FeatureFusionBlock, merge feature map from different stages.
646
- Args:
647
- in_channels (int): Input channels.
648
- act_layer (nn.Module): activation layer for ResidualConvUnit.
649
- norm_layer (nn.Module): normalization layer.
650
- expand (bool): Whether expand the channels in post process block.
651
- Default: False.
652
- align_corners (bool): align_corner setting for bilinear upsample.
653
- Default: True.
654
- """
655
-
656
- def __init__(self, in_channels, act_layer, norm_layer, expand=False, align_corners=True):
657
- super(FeatureFusionBlock, self).__init__()
658
-
659
- self.in_channels = in_channels
660
- self.expand = expand
661
- self.align_corners = align_corners
662
-
663
- self.out_channels = in_channels
664
- if self.expand:
665
- self.out_channels = in_channels // 2
666
-
667
- self.project = ConvModule(self.in_channels, self.out_channels, kernel_size=1, act_layer=None, bias=True)
668
-
669
- self.res_conv_unit1 = PreActResidualConvUnit(
670
- in_channels=self.in_channels, act_layer=act_layer, norm_layer=norm_layer
671
- )
672
- self.res_conv_unit2 = PreActResidualConvUnit(
673
- in_channels=self.in_channels, act_layer=act_layer, norm_layer=norm_layer
674
- )
675
-
676
- def forward(self, *inputs):
677
- x = inputs[0]
678
- if len(inputs) == 2:
679
- if x.shape != inputs[1].shape:
680
- res = resize(inputs[1], size=(x.shape[2], x.shape[3]), mode="bilinear", align_corners=False)
681
- else:
682
- res = inputs[1]
683
- x = x + self.res_conv_unit1(res)
684
- x = self.res_conv_unit2(x)
685
- x = resize(x, scale_factor=2, mode="bilinear", align_corners=self.align_corners)
686
- x = self.project(x)
687
- return x
688
-
689
-
690
- class DPTHead(DepthBaseDecodeHead):
691
- """Vision Transformers for Dense Prediction.
692
- This head is implemented of `DPT <https://arxiv.org/abs/2103.13413>`_.
693
- Args:
694
- embed_dims (int): The embed dimension of the ViT backbone.
695
- Default: 768.
696
- post_process_channels (List): Out channels of post process conv
697
- layers. Default: [96, 192, 384, 768].
698
- readout_type (str): Type of readout operation. Default: 'ignore'.
699
- patch_size (int): The patch size. Default: 16.
700
- expand_channels (bool): Whether expand the channels in post process
701
- block. Default: False.
702
- """
703
-
704
- def __init__(
705
- self,
706
- embed_dims=768,
707
- post_process_channels=[96, 192, 384, 768],
708
- readout_type="ignore",
709
- patch_size=16,
710
- expand_channels=False,
711
- **kwargs,
712
- ):
713
- super(DPTHead, self).__init__(**kwargs)
714
-
715
- self.in_channels = self.in_channels
716
- self.expand_channels = expand_channels
717
- self.reassemble_blocks = ReassembleBlocks(embed_dims, post_process_channels, readout_type, patch_size)
718
-
719
- self.post_process_channels = [
720
- channel * math.pow(2, i) if expand_channels else channel for i, channel in enumerate(post_process_channels)
721
- ]
722
- self.convs = nn.ModuleList()
723
- for channel in self.post_process_channels:
724
- self.convs.append(ConvModule(channel, self.channels, kernel_size=3, padding=1, act_layer=None, bias=False))
725
- self.fusion_blocks = nn.ModuleList()
726
- for _ in range(len(self.convs)):
727
- self.fusion_blocks.append(FeatureFusionBlock(self.channels, self.act_layer, self.norm_layer))
728
- self.fusion_blocks[0].res_conv_unit1 = None
729
- self.project = ConvModule(self.channels, self.channels, kernel_size=3, padding=1, norm_layer=self.norm_layer)
730
- self.num_fusion_blocks = len(self.fusion_blocks)
731
- self.num_reassemble_blocks = len(self.reassemble_blocks.resize_layers)
732
- self.num_post_process_channels = len(self.post_process_channels)
733
- assert self.num_fusion_blocks == self.num_reassemble_blocks
734
- assert self.num_reassemble_blocks == self.num_post_process_channels
735
- self.conv_depth = HeadDepth(self.channels)
736
-
737
- def forward(self, inputs, img_metas):
738
- assert len(inputs) == self.num_reassemble_blocks
739
- x = [inp for inp in inputs]
740
- x = self.reassemble_blocks(x)
741
- x = [self.convs[i](feature) for i, feature in enumerate(x)]
742
- out = self.fusion_blocks[0](x[-1])
743
- for i in range(1, len(self.fusion_blocks)):
744
- out = self.fusion_blocks[i](out, x[-(i + 1)])
745
- out = self.project(out)
746
- out = self.depth_pred(out)
747
- return out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/depth/encoder_decoder.py DELETED
@@ -1,351 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from collections import OrderedDict
7
-
8
- import torch
9
- import torch.nn as nn
10
- import torch.nn.functional as F
11
-
12
- from .ops import resize
13
-
14
-
15
- def add_prefix(inputs, prefix):
16
- """Add prefix for dict.
17
-
18
- Args:
19
- inputs (dict): The input dict with str keys.
20
- prefix (str): The prefix to add.
21
-
22
- Returns:
23
-
24
- dict: The dict with keys updated with ``prefix``.
25
- """
26
-
27
- outputs = dict()
28
- for name, value in inputs.items():
29
- outputs[f"{prefix}.{name}"] = value
30
-
31
- return outputs
32
-
33
-
34
- class DepthEncoderDecoder(nn.Module):
35
- """Encoder Decoder depther.
36
-
37
- EncoderDecoder typically consists of backbone and decode_head.
38
- """
39
-
40
- def __init__(self, backbone, decode_head):
41
- super(DepthEncoderDecoder, self).__init__()
42
-
43
- self.backbone = backbone
44
- self.decode_head = decode_head
45
- self.align_corners = self.decode_head.align_corners
46
-
47
- def extract_feat(self, img):
48
- """Extract features from images."""
49
- return self.backbone(img)
50
-
51
- def encode_decode(self, img, img_metas, rescale=True, size=None):
52
- """Encode images with backbone and decode into a depth estimation
53
- map of the same size as input."""
54
- x = self.extract_feat(img)
55
- out = self._decode_head_forward_test(x, img_metas)
56
- # crop the pred depth to the certain range.
57
- out = torch.clamp(out, min=self.decode_head.min_depth, max=self.decode_head.max_depth)
58
- if rescale:
59
- if size is None:
60
- if img_metas is not None:
61
- size = img_metas[0]["ori_shape"][:2]
62
- else:
63
- size = img.shape[2:]
64
- out = resize(input=out, size=size, mode="bilinear", align_corners=self.align_corners)
65
- return out
66
-
67
- def _decode_head_forward_train(self, img, x, img_metas, depth_gt, **kwargs):
68
- """Run forward function and calculate loss for decode head in
69
- training."""
70
- losses = dict()
71
- loss_decode = self.decode_head.forward_train(img, x, img_metas, depth_gt, **kwargs)
72
- losses.update(add_prefix(loss_decode, "decode"))
73
- return losses
74
-
75
- def _decode_head_forward_test(self, x, img_metas):
76
- """Run forward function and calculate loss for decode head in
77
- inference."""
78
- depth_pred = self.decode_head.forward_test(x, img_metas)
79
- return depth_pred
80
-
81
- def forward_dummy(self, img):
82
- """Dummy forward function."""
83
- depth = self.encode_decode(img, None)
84
-
85
- return depth
86
-
87
- def forward_train(self, img, img_metas, depth_gt, **kwargs):
88
- """Forward function for training.
89
-
90
- Args:
91
- img (Tensor): Input images.
92
- img_metas (list[dict]): List of image info dict where each dict
93
- has: 'img_shape', 'scale_factor', 'flip', and may also contain
94
- 'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
95
- For details on the values of these keys see
96
- `depth/datasets/pipelines/formatting.py:Collect`.
97
- depth_gt (Tensor): Depth gt
98
- used if the architecture supports depth estimation task.
99
-
100
- Returns:
101
- dict[str, Tensor]: a dictionary of loss components
102
- """
103
-
104
- x = self.extract_feat(img)
105
-
106
- losses = dict()
107
-
108
- # the last of x saves the info from neck
109
- loss_decode = self._decode_head_forward_train(img, x, img_metas, depth_gt, **kwargs)
110
-
111
- losses.update(loss_decode)
112
-
113
- return losses
114
-
115
- def whole_inference(self, img, img_meta, rescale, size=None):
116
- """Inference with full image."""
117
- return self.encode_decode(img, img_meta, rescale, size=size)
118
-
119
- def slide_inference(self, img, img_meta, rescale, stride, crop_size):
120
- """Inference by sliding-window with overlap.
121
-
122
- If h_crop > h_img or w_crop > w_img, the small patch will be used to
123
- decode without padding.
124
- """
125
-
126
- h_stride, w_stride = stride
127
- h_crop, w_crop = crop_size
128
- batch_size, _, h_img, w_img = img.size()
129
- h_grids = max(h_img - h_crop + h_stride - 1, 0) // h_stride + 1
130
- w_grids = max(w_img - w_crop + w_stride - 1, 0) // w_stride + 1
131
- preds = img.new_zeros((batch_size, 1, h_img, w_img))
132
- count_mat = img.new_zeros((batch_size, 1, h_img, w_img))
133
- for h_idx in range(h_grids):
134
- for w_idx in range(w_grids):
135
- y1 = h_idx * h_stride
136
- x1 = w_idx * w_stride
137
- y2 = min(y1 + h_crop, h_img)
138
- x2 = min(x1 + w_crop, w_img)
139
- y1 = max(y2 - h_crop, 0)
140
- x1 = max(x2 - w_crop, 0)
141
- crop_img = img[:, :, y1:y2, x1:x2]
142
- depth_pred = self.encode_decode(crop_img, img_meta, rescale)
143
- preds += F.pad(depth_pred, (int(x1), int(preds.shape[3] - x2), int(y1), int(preds.shape[2] - y2)))
144
-
145
- count_mat[:, :, y1:y2, x1:x2] += 1
146
- assert (count_mat == 0).sum() == 0
147
- if torch.onnx.is_in_onnx_export():
148
- # cast count_mat to constant while exporting to ONNX
149
- count_mat = torch.from_numpy(count_mat.cpu().detach().numpy()).to(device=img.device)
150
- preds = preds / count_mat
151
- return preds
152
-
153
- def inference(self, img, img_meta, rescale, size=None, mode="whole"):
154
- """Inference with slide/whole style.
155
-
156
- Args:
157
- img (Tensor): The input image of shape (N, 3, H, W).
158
- img_meta (dict): Image info dict where each dict has: 'img_shape',
159
- 'scale_factor', 'flip', and may also contain
160
- 'filename', 'ori_shape', 'pad_shape', and 'img_norm_cfg'.
161
- For details on the values of these keys see
162
- `depth/datasets/pipelines/formatting.py:Collect`.
163
- rescale (bool): Whether rescale back to original shape.
164
-
165
- Returns:
166
- Tensor: The output depth map.
167
- """
168
-
169
- assert mode in ["slide", "whole"]
170
- ori_shape = img_meta[0]["ori_shape"]
171
- assert all(_["ori_shape"] == ori_shape for _ in img_meta)
172
- if mode == "slide":
173
- depth_pred = self.slide_inference(img, img_meta, rescale)
174
- else:
175
- depth_pred = self.whole_inference(img, img_meta, rescale, size=size)
176
- output = depth_pred
177
- flip = img_meta[0]["flip"]
178
- if flip:
179
- flip_direction = img_meta[0]["flip_direction"]
180
- assert flip_direction in ["horizontal", "vertical"]
181
- if flip_direction == "horizontal":
182
- output = output.flip(dims=(3,))
183
- elif flip_direction == "vertical":
184
- output = output.flip(dims=(2,))
185
-
186
- return output
187
-
188
- def simple_test(self, img, img_meta, rescale=True):
189
- """Simple test with single image."""
190
- depth_pred = self.inference(img, img_meta, rescale)
191
- if torch.onnx.is_in_onnx_export():
192
- # our inference backend only support 4D output
193
- depth_pred = depth_pred.unsqueeze(0)
194
- return depth_pred
195
- depth_pred = depth_pred.cpu().numpy()
196
- # unravel batch dim
197
- depth_pred = list(depth_pred)
198
- return depth_pred
199
-
200
- def aug_test(self, imgs, img_metas, rescale=True):
201
- """Test with augmentations.
202
-
203
- Only rescale=True is supported.
204
- """
205
- # aug_test rescale all imgs back to ori_shape for now
206
- assert rescale
207
- # to save memory, we get augmented depth logit inplace
208
- depth_pred = self.inference(imgs[0], img_metas[0], rescale)
209
- for i in range(1, len(imgs)):
210
- cur_depth_pred = self.inference(imgs[i], img_metas[i], rescale, size=depth_pred.shape[-2:])
211
- depth_pred += cur_depth_pred
212
- depth_pred /= len(imgs)
213
- depth_pred = depth_pred.cpu().numpy()
214
- # unravel batch dim
215
- depth_pred = list(depth_pred)
216
- return depth_pred
217
-
218
- def forward_test(self, imgs, img_metas, **kwargs):
219
- """
220
- Args:
221
- imgs (List[Tensor]): the outer list indicates test-time
222
- augmentations and inner Tensor should have a shape NxCxHxW,
223
- which contains all images in the batch.
224
- img_metas (List[List[dict]]): the outer list indicates test-time
225
- augs (multiscale, flip, etc.) and the inner list indicates
226
- images in a batch.
227
- """
228
- for var, name in [(imgs, "imgs"), (img_metas, "img_metas")]:
229
- if not isinstance(var, list):
230
- raise TypeError(f"{name} must be a list, but got " f"{type(var)}")
231
- num_augs = len(imgs)
232
- if num_augs != len(img_metas):
233
- raise ValueError(f"num of augmentations ({len(imgs)}) != " f"num of image meta ({len(img_metas)})")
234
- # all images in the same aug batch all of the same ori_shape and pad
235
- # shape
236
- for img_meta in img_metas:
237
- ori_shapes = [_["ori_shape"] for _ in img_meta]
238
- assert all(shape == ori_shapes[0] for shape in ori_shapes)
239
- img_shapes = [_["img_shape"] for _ in img_meta]
240
- assert all(shape == img_shapes[0] for shape in img_shapes)
241
- pad_shapes = [_["pad_shape"] for _ in img_meta]
242
- assert all(shape == pad_shapes[0] for shape in pad_shapes)
243
-
244
- if num_augs == 1:
245
- return self.simple_test(imgs[0], img_metas[0], **kwargs)
246
- else:
247
- return self.aug_test(imgs, img_metas, **kwargs)
248
-
249
- def forward(self, img, img_metas, return_loss=True, **kwargs):
250
- """Calls either :func:`forward_train` or :func:`forward_test` depending
251
- on whether ``return_loss`` is ``True``.
252
-
253
- Note this setting will change the expected inputs. When
254
- ``return_loss=True``, img and img_meta are single-nested (i.e. Tensor
255
- and List[dict]), and when ``resturn_loss=False``, img and img_meta
256
- should be double nested (i.e. List[Tensor], List[List[dict]]), with
257
- the outer list indicating test time augmentations.
258
- """
259
- if return_loss:
260
- return self.forward_train(img, img_metas, **kwargs)
261
- else:
262
- return self.forward_test(img, img_metas, **kwargs)
263
-
264
- def train_step(self, data_batch, optimizer, **kwargs):
265
- """The iteration step during training.
266
-
267
- This method defines an iteration step during training, except for the
268
- back propagation and optimizer updating, which are done in an optimizer
269
- hook. Note that in some complicated cases or models, the whole process
270
- including back propagation and optimizer updating is also defined in
271
- this method, such as GAN.
272
-
273
- Args:
274
- data (dict): The output of dataloader.
275
- optimizer (:obj:`torch.optim.Optimizer` | dict): The optimizer of
276
- runner is passed to ``train_step()``. This argument is unused
277
- and reserved.
278
-
279
- Returns:
280
- dict: It should contain at least 3 keys: ``loss``, ``log_vars``,
281
- ``num_samples``.
282
- ``loss`` is a tensor for back propagation, which can be a
283
- weighted sum of multiple losses.
284
- ``log_vars`` contains all the variables to be sent to the
285
- logger.
286
- ``num_samples`` indicates the batch size (when the model is
287
- DDP, it means the batch size on each GPU), which is used for
288
- averaging the logs.
289
- """
290
- losses = self(**data_batch)
291
-
292
- # split losses and images
293
- real_losses = {}
294
- log_imgs = {}
295
- for k, v in losses.items():
296
- if "img" in k:
297
- log_imgs[k] = v
298
- else:
299
- real_losses[k] = v
300
-
301
- loss, log_vars = self._parse_losses(real_losses)
302
-
303
- outputs = dict(loss=loss, log_vars=log_vars, num_samples=len(data_batch["img_metas"]), log_imgs=log_imgs)
304
-
305
- return outputs
306
-
307
- def val_step(self, data_batch, **kwargs):
308
- """The iteration step during validation.
309
-
310
- This method shares the same signature as :func:`train_step`, but used
311
- during val epochs. Note that the evaluation after training epochs is
312
- not implemented with this method, but an evaluation hook.
313
- """
314
- output = self(**data_batch, **kwargs)
315
- return output
316
-
317
- @staticmethod
318
- def _parse_losses(losses):
319
- import torch.distributed as dist
320
-
321
- """Parse the raw outputs (losses) of the network.
322
-
323
- Args:
324
- losses (dict): Raw output of the network, which usually contain
325
- losses and other necessary information.
326
-
327
- Returns:
328
- tuple[Tensor, dict]: (loss, log_vars), loss is the loss tensor
329
- which may be a weighted sum of all losses, log_vars contains
330
- all the variables to be sent to the logger.
331
- """
332
- log_vars = OrderedDict()
333
- for loss_name, loss_value in losses.items():
334
- if isinstance(loss_value, torch.Tensor):
335
- log_vars[loss_name] = loss_value.mean()
336
- elif isinstance(loss_value, list):
337
- log_vars[loss_name] = sum(_loss.mean() for _loss in loss_value)
338
- else:
339
- raise TypeError(f"{loss_name} is not a tensor or list of tensors")
340
-
341
- loss = sum(_value for _key, _value in log_vars.items() if "loss" in _key)
342
-
343
- log_vars["loss"] = loss
344
- for loss_name, loss_value in log_vars.items():
345
- # reduce loss when distributed training
346
- if dist.is_available() and dist.is_initialized():
347
- loss_value = loss_value.data.clone()
348
- dist.all_reduce(loss_value.div_(dist.get_world_size()))
349
- log_vars[loss_name] = loss_value.item()
350
-
351
- return loss, log_vars
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/depth/ops.py DELETED
@@ -1,28 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import warnings
7
-
8
- import torch.nn.functional as F
9
-
10
-
11
- def resize(input, size=None, scale_factor=None, mode="nearest", align_corners=None, warning=False):
12
- if warning:
13
- if size is not None and align_corners:
14
- input_h, input_w = tuple(int(x) for x in input.shape[2:])
15
- output_h, output_w = tuple(int(x) for x in size)
16
- if output_h > input_h or output_w > output_h:
17
- if (
18
- (output_h > 1 and output_w > 1 and input_h > 1 and input_w > 1)
19
- and (output_h - 1) % (input_h - 1)
20
- and (output_w - 1) % (input_w - 1)
21
- ):
22
- warnings.warn(
23
- f"When align_corners={align_corners}, "
24
- "the output would more aligned if "
25
- f"input size {(input_h, input_w)} is `x+1` and "
26
- f"out size {(output_h, output_w)} is `nx+1`"
27
- )
28
- return F.interpolate(input, size, scale_factor, mode, align_corners)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/depthers.py DELETED
@@ -1,246 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- from enum import Enum
7
- from functools import partial
8
- from typing import Optional, Tuple, Union
9
-
10
- import torch
11
-
12
- from .backbones import _make_dinov2_model
13
- from .depth import BNHead, DepthEncoderDecoder, DPTHead
14
- from .utils import _DINOV2_BASE_URL, _make_dinov2_model_name, CenterPadding
15
-
16
-
17
- class Weights(Enum):
18
- NYU = "NYU"
19
- KITTI = "KITTI"
20
-
21
-
22
- def _get_depth_range(pretrained: bool, weights: Weights = Weights.NYU) -> Tuple[float, float]:
23
- if not pretrained: # Default
24
- return (0.001, 10.0)
25
-
26
- # Pretrained, set according to the training dataset for the provided weights
27
- if weights == Weights.KITTI:
28
- return (0.001, 80.0)
29
-
30
- if weights == Weights.NYU:
31
- return (0.001, 10.0)
32
-
33
- return (0.001, 10.0)
34
-
35
-
36
- def _make_dinov2_linear_depth_head(
37
- *,
38
- embed_dim: int,
39
- layers: int,
40
- min_depth: float,
41
- max_depth: float,
42
- **kwargs,
43
- ):
44
- if layers not in (1, 4):
45
- raise AssertionError(f"Unsupported number of layers: {layers}")
46
-
47
- if layers == 1:
48
- in_index = [0]
49
- else:
50
- assert layers == 4
51
- in_index = [0, 1, 2, 3]
52
-
53
- return BNHead(
54
- classify=True,
55
- n_bins=256,
56
- bins_strategy="UD",
57
- norm_strategy="linear",
58
- upsample=4,
59
- in_channels=[embed_dim] * len(in_index),
60
- in_index=in_index,
61
- input_transform="resize_concat",
62
- channels=embed_dim * len(in_index) * 2,
63
- align_corners=False,
64
- min_depth=0.001,
65
- max_depth=80,
66
- loss_decode=(),
67
- )
68
-
69
-
70
- def _make_dinov2_linear_depther(
71
- *,
72
- arch_name: str = "vit_large",
73
- layers: int = 4,
74
- pretrained: bool = True,
75
- weights: Union[Weights, str] = Weights.NYU,
76
- depth_range: Optional[Tuple[float, float]] = None,
77
- **kwargs,
78
- ):
79
- if layers not in (1, 4):
80
- raise AssertionError(f"Unsupported number of layers: {layers}")
81
- if isinstance(weights, str):
82
- try:
83
- weights = Weights[weights]
84
- except KeyError:
85
- raise AssertionError(f"Unsupported weights: {weights}")
86
-
87
- if depth_range is None:
88
- depth_range = _get_depth_range(pretrained, weights)
89
- min_depth, max_depth = depth_range
90
-
91
- backbone = _make_dinov2_model(arch_name=arch_name, pretrained=pretrained, **kwargs)
92
-
93
- embed_dim = backbone.embed_dim
94
- patch_size = backbone.patch_size
95
- model_name = _make_dinov2_model_name(arch_name, patch_size)
96
- linear_depth_head = _make_dinov2_linear_depth_head(
97
- embed_dim=embed_dim,
98
- layers=layers,
99
- min_depth=min_depth,
100
- max_depth=max_depth,
101
- )
102
-
103
- layer_count = {
104
- "vit_small": 12,
105
- "vit_base": 12,
106
- "vit_large": 24,
107
- "vit_giant2": 40,
108
- }[arch_name]
109
-
110
- if layers == 4:
111
- out_index = {
112
- "vit_small": [2, 5, 8, 11],
113
- "vit_base": [2, 5, 8, 11],
114
- "vit_large": [4, 11, 17, 23],
115
- "vit_giant2": [9, 19, 29, 39],
116
- }[arch_name]
117
- else:
118
- assert layers == 1
119
- out_index = [layer_count - 1]
120
-
121
- model = DepthEncoderDecoder(backbone=backbone, decode_head=linear_depth_head)
122
- model.backbone.forward = partial(
123
- backbone.get_intermediate_layers,
124
- n=out_index,
125
- reshape=True,
126
- return_class_token=True,
127
- norm=False,
128
- )
129
- model.backbone.register_forward_pre_hook(lambda _, x: CenterPadding(patch_size)(x[0]))
130
-
131
- if pretrained:
132
- layers_str = str(layers) if layers == 4 else ""
133
- weights_str = weights.value.lower()
134
- url = _DINOV2_BASE_URL + f"/{model_name}/{model_name}_{weights_str}_linear{layers_str}_head.pth"
135
- checkpoint = torch.hub.load_state_dict_from_url(url, map_location="cpu")
136
- if "state_dict" in checkpoint:
137
- state_dict = checkpoint["state_dict"]
138
- model.load_state_dict(state_dict, strict=False)
139
-
140
- return model
141
-
142
-
143
- def dinov2_vits14_ld(*, layers: int = 4, pretrained: bool = True, weights: Union[Weights, str] = Weights.NYU, **kwargs):
144
- return _make_dinov2_linear_depther(
145
- arch_name="vit_small", layers=layers, pretrained=pretrained, weights=weights, **kwargs
146
- )
147
-
148
-
149
- def dinov2_vitb14_ld(*, layers: int = 4, pretrained: bool = True, weights: Union[Weights, str] = Weights.NYU, **kwargs):
150
- return _make_dinov2_linear_depther(
151
- arch_name="vit_base", layers=layers, pretrained=pretrained, weights=weights, **kwargs
152
- )
153
-
154
-
155
- def dinov2_vitl14_ld(*, layers: int = 4, pretrained: bool = True, weights: Union[Weights, str] = Weights.NYU, **kwargs):
156
- return _make_dinov2_linear_depther(
157
- arch_name="vit_large", layers=layers, pretrained=pretrained, weights=weights, **kwargs
158
- )
159
-
160
-
161
- def dinov2_vitg14_ld(*, layers: int = 4, pretrained: bool = True, weights: Union[Weights, str] = Weights.NYU, **kwargs):
162
- return _make_dinov2_linear_depther(
163
- arch_name="vit_giant2", layers=layers, ffn_layer="swiglufused", pretrained=pretrained, weights=weights, **kwargs
164
- )
165
-
166
-
167
- def _make_dinov2_dpt_depth_head(*, embed_dim: int, min_depth: float, max_depth: float):
168
- return DPTHead(
169
- in_channels=[embed_dim] * 4,
170
- channels=256,
171
- embed_dims=embed_dim,
172
- post_process_channels=[embed_dim // 2 ** (3 - i) for i in range(4)],
173
- readout_type="project",
174
- min_depth=min_depth,
175
- max_depth=max_depth,
176
- loss_decode=(),
177
- )
178
-
179
-
180
- def _make_dinov2_dpt_depther(
181
- *,
182
- arch_name: str = "vit_large",
183
- pretrained: bool = True,
184
- weights: Union[Weights, str] = Weights.NYU,
185
- depth_range: Optional[Tuple[float, float]] = None,
186
- **kwargs,
187
- ):
188
- if isinstance(weights, str):
189
- try:
190
- weights = Weights[weights]
191
- except KeyError:
192
- raise AssertionError(f"Unsupported weights: {weights}")
193
-
194
- if depth_range is None:
195
- depth_range = _get_depth_range(pretrained, weights)
196
- min_depth, max_depth = depth_range
197
-
198
- backbone = _make_dinov2_model(arch_name=arch_name, pretrained=pretrained, **kwargs)
199
-
200
- model_name = _make_dinov2_model_name(arch_name, backbone.patch_size)
201
- dpt_depth_head = _make_dinov2_dpt_depth_head(embed_dim=backbone.embed_dim, min_depth=min_depth, max_depth=max_depth)
202
-
203
- out_index = {
204
- "vit_small": [2, 5, 8, 11],
205
- "vit_base": [2, 5, 8, 11],
206
- "vit_large": [4, 11, 17, 23],
207
- "vit_giant2": [9, 19, 29, 39],
208
- }[arch_name]
209
-
210
- model = DepthEncoderDecoder(backbone=backbone, decode_head=dpt_depth_head)
211
- model.backbone.forward = partial(
212
- backbone.get_intermediate_layers,
213
- n=out_index,
214
- reshape=True,
215
- return_class_token=True,
216
- norm=False,
217
- )
218
- model.backbone.register_forward_pre_hook(lambda _, x: CenterPadding(backbone.patch_size)(x[0]))
219
-
220
- if pretrained:
221
- weights_str = weights.value.lower()
222
- url = _DINOV2_BASE_URL + f"/{model_name}/{model_name}_{weights_str}_dpt_head.pth"
223
- checkpoint = torch.hub.load_state_dict_from_url(url, map_location="cpu")
224
- if "state_dict" in checkpoint:
225
- state_dict = checkpoint["state_dict"]
226
- model.load_state_dict(state_dict, strict=False)
227
-
228
- return model
229
-
230
-
231
- def dinov2_vits14_dd(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.NYU, **kwargs):
232
- return _make_dinov2_dpt_depther(arch_name="vit_small", pretrained=pretrained, weights=weights, **kwargs)
233
-
234
-
235
- def dinov2_vitb14_dd(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.NYU, **kwargs):
236
- return _make_dinov2_dpt_depther(arch_name="vit_base", pretrained=pretrained, weights=weights, **kwargs)
237
-
238
-
239
- def dinov2_vitl14_dd(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.NYU, **kwargs):
240
- return _make_dinov2_dpt_depther(arch_name="vit_large", pretrained=pretrained, weights=weights, **kwargs)
241
-
242
-
243
- def dinov2_vitg14_dd(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.NYU, **kwargs):
244
- return _make_dinov2_dpt_depther(
245
- arch_name="vit_giant2", ffn_layer="swiglufused", pretrained=pretrained, weights=weights, **kwargs
246
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/dinotxt.py DELETED
@@ -1,77 +0,0 @@
1
- import torch
2
- import math
3
-
4
- from .backbones import dinov2_vitl14_reg
5
- from .utils import _DINOV2_BASE_URL
6
-
7
-
8
- def dinov2_vitl14_reg4_dinotxt_tet1280d20h24l():
9
- from .text.dinotxt_model import DinoTxtConfig, DinoTxt
10
- from .text.dinov2_wrapper import DINOv2Wrapper
11
- from .text.text_transformer import TextTransformer
12
-
13
- dinotxt_config = DinoTxtConfig(
14
- embed_dim=2048,
15
- vision_model_freeze_backbone=True,
16
- vision_model_train_img_size=224,
17
- vision_model_use_class_token=True,
18
- vision_model_use_patch_tokens=True,
19
- vision_model_num_head_blocks=2,
20
- vision_model_head_blocks_drop_path=0.3,
21
- vision_model_use_linear_projection=False,
22
- vision_model_patch_tokens_pooler_type="mean",
23
- vision_model_patch_token_layer=1, # which layer to take patch tokens from
24
- # 1 - last layer, 2 - second last layer, etc.
25
- text_model_freeze_backbone=False,
26
- text_model_num_head_blocks=0,
27
- text_model_head_blocks_is_causal=False,
28
- text_model_head_blocks_drop_prob=0.0,
29
- text_model_tokens_pooler_type="argmax",
30
- text_model_use_linear_projection=True,
31
- init_logit_scale=math.log(1 / 0.07),
32
- init_logit_bias=None,
33
- freeze_logit_scale=False,
34
- )
35
- vision_backbone = DINOv2Wrapper(dinov2_vitl14_reg())
36
- text_backbone = TextTransformer(
37
- context_length=77,
38
- vocab_size=49408,
39
- dim=1280,
40
- num_heads=20,
41
- num_layers=24,
42
- ffn_ratio=4,
43
- is_causal=True,
44
- ls_init_value=None,
45
- dropout_prob=0.0,
46
- )
47
- model = DinoTxt(dinotxt_config, vision_backbone, text_backbone)
48
- model.init_weights()
49
- model.visual_model.backbone = vision_backbone
50
- model.eval()
51
-
52
- visual_model_head_state_dict = torch.hub.load_state_dict_from_url(
53
- _DINOV2_BASE_URL + "/dinov2_vitl14/dinov2_vitl14_reg4_dinotxt_tet1280d20h24l_vision_head.pth",
54
- map_location="cpu",
55
- )
56
- text_model_state_dict = torch.hub.load_state_dict_from_url(
57
- _DINOV2_BASE_URL + "/dinov2_vitl14/dinov2_vitl14_reg4_dinotxt_tet1280d20h24l_text_encoder.pth",
58
- map_location="cpu",
59
- )
60
- model.visual_model.head.load_state_dict(visual_model_head_state_dict, strict=True)
61
- model.text_model.load_state_dict(text_model_state_dict, strict=True)
62
- return model
63
-
64
-
65
- def get_tokenizer():
66
- from .text.tokenizer import Tokenizer
67
- import requests
68
- from io import BytesIO
69
-
70
- url = _DINOV2_BASE_URL + "/thirdparty/bpe_simple_vocab_16e6.txt.gz"
71
- try:
72
- response = requests.get(url)
73
- response.raise_for_status()
74
- file_buf = BytesIO(response.content)
75
- return Tokenizer(vocab_path=file_buf)
76
- except Exception as e:
77
- raise FileNotFoundError(f"Failed to download file from url {url} with error last: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/text/dinotxt_model.py DELETED
@@ -1,130 +0,0 @@
1
- import math
2
- from dataclasses import dataclass
3
- from typing import Optional, Tuple
4
-
5
- import torch
6
- import torch.nn.functional as F
7
- from torch import nn, Tensor
8
-
9
- from .vision_tower import VisionTower
10
- from .text_tower import TextTower
11
-
12
-
13
- @dataclass
14
- class DinoTxtConfig:
15
- embed_dim: int
16
- vision_model_freeze_backbone: bool = True
17
- vision_model_train_img_size: int = 224
18
- vision_model_use_class_token: bool = True
19
- vision_model_use_patch_tokens: bool = False
20
- vision_model_num_head_blocks: int = 0
21
- vision_model_head_blocks_drop_path: float = 0.3
22
- vision_model_use_linear_projection: bool = False
23
- vision_model_patch_tokens_pooler_type: str = "mean"
24
- vision_model_patch_token_layer: int = 1 # which layer to take patch tokens from
25
- # 1 - last layer, 2 - second last layer, etc.
26
- text_model_freeze_backbone: bool = False
27
- text_model_num_head_blocks: int = 0
28
- text_model_head_blocks_is_causal: bool = False
29
- text_model_head_blocks_drop_prob: float = 0.0
30
- text_model_tokens_pooler_type: str = "first"
31
- text_model_use_linear_projection: bool = False
32
- init_logit_scale: float = math.log(1 / 0.07)
33
- init_logit_bias: Optional[float] = None
34
- freeze_logit_scale: bool = False
35
-
36
-
37
- class DinoTxt(nn.Module):
38
- def __init__(
39
- self,
40
- model_config: DinoTxtConfig,
41
- vision_backbone: nn.Module,
42
- text_backbone: nn.Module,
43
- ):
44
- super().__init__()
45
- self.model_config = model_config
46
- self.visual_model = VisionTower(
47
- vision_backbone,
48
- model_config.vision_model_freeze_backbone,
49
- model_config.embed_dim,
50
- model_config.vision_model_num_head_blocks,
51
- model_config.vision_model_head_blocks_drop_path,
52
- model_config.vision_model_use_class_token,
53
- model_config.vision_model_use_patch_tokens,
54
- model_config.vision_model_patch_token_layer,
55
- model_config.vision_model_patch_tokens_pooler_type,
56
- model_config.vision_model_use_linear_projection,
57
- )
58
- self.text_model = TextTower(
59
- text_backbone,
60
- model_config.text_model_freeze_backbone,
61
- model_config.embed_dim,
62
- model_config.text_model_num_head_blocks,
63
- model_config.text_model_head_blocks_is_causal,
64
- model_config.text_model_head_blocks_drop_prob,
65
- model_config.text_model_tokens_pooler_type,
66
- model_config.text_model_use_linear_projection,
67
- )
68
- self.logit_scale = nn.Parameter(torch.ones(1) * model_config.init_logit_scale)
69
- if model_config.freeze_logit_scale:
70
- self.logit_scale.requires_grad = False
71
-
72
- def init_weights(self):
73
- self.visual_model.init_weights()
74
- self.text_model.init_weights()
75
-
76
- def get_visual_class_and_patch_tokens(self, image: Tensor) -> Tuple[Tensor, Tensor]:
77
- return self.visual_model.get_class_and_patch_tokens(image)
78
-
79
- def encode_image(
80
- self,
81
- image: Tensor,
82
- normalize: bool = False,
83
- ) -> Tensor:
84
- """
85
- Encode an image into a vector descriptor containing both global and local features.
86
-
87
- Args:
88
- image (Tensor): Tensor of shape `(batch_size, rgb, height, width)`, normalized using ImageNet mean and std.
89
- normalize (bool, optional): Whether to normalize the output vectors. Default is False.
90
- Image features should always be normalized before comparing them with text features:
91
- Returns:
92
- Tensor: Tensor of shape `(batch_size, embed_dim)` containing the image features.
93
- The first half of the vector corresponds to the global features (class token),
94
- and the second half corresponds to the pooled patch features.
95
- """
96
- features = self.visual_model(image)
97
- return F.normalize(features, dim=-1) if normalize else features
98
-
99
- def encode_text(self, text: Tensor, normalize: bool = False) -> Tensor:
100
- """
101
- Encode a text input into a vector descriptor.
102
-
103
- Args:
104
- text (Tensor): Tensor of shape `(batch_size, seq_len)` containing token indices.
105
- normalize (bool, optional): Whether to normalize the output vectors. Default is False.
106
- Text features should be normalized before comparing them with image features:
107
- Returns:
108
- Tensor: Tensor of shape `(batch_size, embed_dim)` containing the text features.
109
- As a consequence of the training procedure, assume that the first half of the tensor corresponds
110
- to global image features and the second half to pooled patch features.
111
- """
112
- features = self.text_model(text)
113
- return F.normalize(features, dim=-1) if normalize else features
114
-
115
- def get_logits(self, image: Tensor, text: Tensor) -> Tuple[Tensor, Tensor]:
116
- text_features = self.encode_text(text, normalize=True)
117
- image_features = self.encode_image(image, normalize=True)
118
- image_logits = self.logit_scale.exp() * image_features @ text_features.T
119
- text_logits = image_logits.T
120
- return image_logits, text_logits
121
-
122
- def forward(
123
- self,
124
- image: Tensor,
125
- text: Tensor,
126
- ) -> Tuple[Tensor, Tensor, Tensor]:
127
-
128
- text_features = self.encode_text(text, normalize=True)
129
- image_features = self.encode_image(image, normalize=True)
130
- return image_features, text_features, self.logit_scale.exp()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/text/dinov2_wrapper.py DELETED
@@ -1,59 +0,0 @@
1
- from typing import Sequence
2
-
3
- import torch
4
-
5
-
6
- class DINOv2Wrapper(torch.nn.Module):
7
- def __init__(self, model):
8
- super().__init__()
9
- self.model = model
10
- self.embed_dim = model.embed_dim
11
- self.num_heads = model.num_heads
12
- self.num_register_tokens = model.num_register_tokens
13
-
14
- # Same as the original forward, but assert is_training and rename x_norm_regtokens -> x_storage_tokens
15
- def forward(self, img, is_training: bool):
16
- assert is_training
17
- H, W = img.shape[-2:]
18
- P = self.model.patch_size
19
- x_dict = self.model(img, is_training=True)
20
- x_dict["h"] = h = H // P
21
- x_dict["w"] = w = W // P
22
- assert x_dict["x_norm_patchtokens"].shape[-2] == h * w
23
- return x_dict
24
-
25
- # Same as the original get_intermediate_layers, but allow returining extra tokens (registers)
26
- def get_intermediate_layers(
27
- self,
28
- x: torch.Tensor,
29
- n: int | Sequence[int] = 1, # Layers or n last layers to take
30
- reshape: bool = False,
31
- return_class_token: bool = False,
32
- return_register_tokens: bool = False,
33
- norm=True,
34
- ) -> tuple[torch.Tensor] | tuple[tuple[torch.Tensor, ...], ...]:
35
- if self.model.chunked_blocks:
36
- outputs = self.model._get_intermediate_layers_chunked(x, n)
37
- else:
38
- outputs = self.model._get_intermediate_layers_not_chunked(x, n)
39
- if norm:
40
- outputs = [self.model.norm(out) for out in outputs]
41
- class_tokens = [out[:, 0] for out in outputs]
42
- register_tokens = [out[:, 1 : 1 + self.model.num_register_tokens] for out in outputs]
43
- outputs = [out[:, 1 + self.model.num_register_tokens :] for out in outputs]
44
- if reshape:
45
- B, _, h, w = x.shape
46
- outputs = [
47
- out.reshape(B, h // self.model.patch_size, w // self.model.patch_size, -1)
48
- .permute(0, 3, 1, 2)
49
- .contiguous()
50
- for out in outputs
51
- ]
52
-
53
- if not return_class_token and not return_register_tokens:
54
- return tuple(outputs)
55
- if return_class_token and not return_register_tokens:
56
- return tuple(zip(outputs, class_tokens))
57
- if not return_class_token and return_register_tokens:
58
- return tuple(zip(outputs, register_tokens))
59
- return tuple(zip(outputs, class_tokens, register_tokens))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/text/text_tower.py DELETED
@@ -1,99 +0,0 @@
1
- import torch
2
- from torch import nn, Tensor
3
-
4
- from dinov2.layers import (
5
- CausalAttentionBlock,
6
- )
7
-
8
-
9
- class TextHead(nn.Module):
10
- def __init__(
11
- self,
12
- input_dim: int,
13
- embed_dim: int,
14
- num_heads: int,
15
- num_blocks: int,
16
- block_drop_prob: float,
17
- is_causal: bool,
18
- use_linear_projection: bool,
19
- ):
20
- super().__init__()
21
- block_list = [nn.Identity()]
22
- self.ln_final = nn.Identity()
23
- if num_blocks > 0:
24
- block_list = [
25
- CausalAttentionBlock(
26
- dim=input_dim,
27
- num_heads=num_heads,
28
- is_causal=is_causal,
29
- dropout_prob=block_drop_prob,
30
- )
31
- for _ in range(num_blocks)
32
- ]
33
- self.ln_final = nn.LayerNorm(input_dim)
34
- self.block_list = nn.ModuleList(block_list)
35
- self.num_blocks = num_blocks
36
- self.linear_projection = nn.Identity()
37
- if input_dim != embed_dim or use_linear_projection:
38
- self.linear_projection = nn.Linear(input_dim, embed_dim, bias=False)
39
-
40
- def init_weights(self):
41
- if self.num_blocks > 0:
42
- for i in range(self.num_blocks):
43
- self.block_list[i].init_weights()
44
- self.ln_final.reset_parameters()
45
- if isinstance(self.linear_projection, nn.Linear):
46
- nn.init.normal_(self.linear_projection.weight, std=self.linear_projection.in_features**-0.5)
47
-
48
- def forward(self, text_tokens: Tensor) -> Tensor:
49
- for block in self.block_list:
50
- text_tokens = block(text_tokens)
51
- text_tokens = self.ln_final(text_tokens)
52
- return self.linear_projection(text_tokens)
53
-
54
-
55
- class TextTower(nn.Module):
56
- def __init__(
57
- self,
58
- backbone: nn.Module,
59
- freeze_backbone: bool,
60
- embed_dim: int,
61
- num_head_blocks: int,
62
- head_blocks_is_causal: bool,
63
- head_blocks_block_drop_prob: float,
64
- tokens_pooler_type: str,
65
- use_linear_projection: bool,
66
- ):
67
- super().__init__()
68
- self.backbone = backbone
69
- self.freeze_backbone = freeze_backbone
70
- backbone_out_dim = backbone.embed_dim
71
- self.backbone = backbone
72
- self.head = TextHead(
73
- backbone_out_dim,
74
- embed_dim,
75
- self.backbone.num_heads,
76
- num_head_blocks,
77
- head_blocks_block_drop_prob,
78
- head_blocks_is_causal,
79
- use_linear_projection,
80
- )
81
- self.tokens_pooler_type = tokens_pooler_type
82
-
83
- def init_weights(self):
84
- self.backbone.init_weights()
85
- self.head.init_weights()
86
-
87
- def forward(self, token_indices: Tensor) -> Tensor:
88
- text_tokens = self.backbone(token_indices)
89
- text_tokens = self.head(text_tokens)
90
- if self.tokens_pooler_type == "first":
91
- features = text_tokens[:, 0]
92
- elif self.tokens_pooler_type == "last":
93
- features = text_tokens[:, -1]
94
- elif self.tokens_pooler_type == "argmax":
95
- assert token_indices is not None
96
- features = text_tokens[torch.arange(text_tokens.shape[0]), token_indices.argmax(dim=-1)]
97
- else:
98
- raise ValueError(f"Unknown text tokens pooler type: {self.pooler_type}")
99
- return features
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/text/text_transformer.py DELETED
@@ -1,67 +0,0 @@
1
- from typing import Callable, Optional, Tuple
2
-
3
- import torch
4
- from torch import nn, Tensor
5
-
6
-
7
- from dinov2.layers import CausalAttentionBlock
8
-
9
-
10
- class TextTransformer(nn.Module):
11
- def __init__(
12
- self,
13
- context_length: int,
14
- vocab_size: int,
15
- dim: int,
16
- num_heads: int,
17
- num_layers: int,
18
- ffn_ratio: float,
19
- is_causal: bool,
20
- ls_init_value: Optional[float] = None,
21
- act_layer: Callable = nn.GELU,
22
- norm_layer: Callable = nn.LayerNorm,
23
- dropout_prob: float = 0.0,
24
- ):
25
- super().__init__()
26
- self.vocab_size = vocab_size
27
- self.embed_dim = dim
28
- self.num_heads = num_heads
29
-
30
- self.token_embedding = nn.Embedding(vocab_size, dim)
31
- self.positional_embedding = nn.Parameter(torch.empty(context_length, dim))
32
- self.dropout = nn.Dropout(dropout_prob)
33
- self.num_layers = num_layers
34
- block_list = [
35
- CausalAttentionBlock(
36
- dim=dim,
37
- num_heads=num_heads,
38
- ffn_ratio=ffn_ratio,
39
- ls_init_value=ls_init_value,
40
- is_causal=is_causal,
41
- act_layer=act_layer,
42
- norm_layer=norm_layer,
43
- dropout_prob=dropout_prob,
44
- )
45
- for _ in range(num_layers)
46
- ]
47
- self.blocks = nn.ModuleList(block_list)
48
- self.ln_final = norm_layer(dim)
49
-
50
- def init_weights(self):
51
- nn.init.normal_(self.token_embedding.weight, std=0.02)
52
- nn.init.normal_(self.positional_embedding, std=0.01)
53
- init_attn_std = self.embed_dim**-0.5
54
- init_proj_std = (self.embed_dim**-0.5) * ((2 * self.num_layers) ** -0.5)
55
- init_fc_std = (2 * self.embed_dim) ** -0.5
56
- for block in self.blocks:
57
- block.init_weights(init_attn_std, init_proj_std, init_fc_std)
58
- self.ln_final.reset_parameters()
59
-
60
- def forward(self, token_indices: Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
61
- _, N = token_indices.size()
62
- x = self.token_embedding(token_indices) + self.positional_embedding[:N]
63
- x = self.dropout(x)
64
- for block in self.blocks:
65
- x = block(x)
66
- x = self.ln_final(x)
67
- return x
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/text/tokenizer.py DELETED
@@ -1,40 +0,0 @@
1
- import torch
2
- from typing import List, Union
3
-
4
-
5
- from dinov2.thirdparty.CLIP.clip.simple_tokenizer import SimpleTokenizer
6
-
7
-
8
- class Tokenizer(SimpleTokenizer):
9
- def __init__(self, vocab_path: str):
10
- SimpleTokenizer.__init__(self, bpe_path=vocab_path)
11
-
12
- def tokenize(self, texts: Union[str, List[str]], context_length: int = 77) -> torch.LongTensor:
13
- """
14
- Returns the tokenized representation of given input string(s)
15
-
16
- Parameters
17
- ----------
18
- texts : Union[str, List[str]]
19
- An input string or a list of input strings to tokenize
20
- context_length : int
21
- The context length to use; all CLIP models use 77 as the context length
22
-
23
- Returns
24
- -------
25
- A two-dimensional tensor containing the resulting tokens, shape = [number of input strings, context_length]
26
- """
27
- if isinstance(texts, str):
28
- texts = [texts]
29
- sot_token = self.encoder["<|startoftext|>"]
30
- eot_token = self.encoder["<|endoftext|>"]
31
- all_tokens = [[sot_token] + self.encode(text) + [eot_token] for text in texts]
32
- result = torch.zeros(len(all_tokens), context_length, dtype=torch.long)
33
-
34
- for i, tokens in enumerate(all_tokens):
35
- if len(tokens) > context_length:
36
- tokens = tokens[:context_length] # Truncate
37
- tokens[-1] = eot_token
38
- result[i, : len(tokens)] = torch.tensor(tokens)
39
-
40
- return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/text/vision_tower.py DELETED
@@ -1,174 +0,0 @@
1
- from functools import partial
2
- from typing import Callable, Tuple
3
-
4
- import torch
5
- from torch import nn, Tensor
6
-
7
- from dinov2.layers import (
8
- LayerScale,
9
- NestedTensorBlock as AttentionBlock,
10
- SwiGLUFFNAligned as SwiGLUFFN,
11
- )
12
-
13
-
14
- def init_weights_vit_timm(module: nn.Module, name: str = ""):
15
- """ViT weight initialization, original timm impl (for reproducibility)"""
16
- if isinstance(module, nn.Linear):
17
- nn.init.trunc_normal_(module.weight, std=0.02)
18
- if module.bias is not None:
19
- nn.init.zeros_(module.bias)
20
- if isinstance(module, nn.LayerNorm):
21
- module.reset_parameters()
22
- if isinstance(module, LayerScale):
23
- module.reset_parameters()
24
- if isinstance(module, nn.Conv2d):
25
- module.reset_parameters()
26
-
27
-
28
- def named_apply(fn: Callable, module: nn.Module, name="", depth_first=True, include_root=False) -> nn.Module:
29
- if not depth_first and include_root:
30
- fn(module=module, name=name)
31
- for child_name, child_module in module.named_children():
32
- child_name = ".".join((name, child_name)) if name else child_name
33
- named_apply(
34
- fn=fn,
35
- module=child_module,
36
- name=child_name,
37
- depth_first=depth_first,
38
- include_root=True,
39
- )
40
- if depth_first and include_root:
41
- fn(module=module, name=name)
42
- return module
43
-
44
-
45
- class VisionHead(nn.Module):
46
- def __init__(
47
- self,
48
- input_dim: int,
49
- embed_dim: int,
50
- num_heads: int,
51
- num_blocks: int,
52
- blocks_drop_path: float,
53
- use_class_token: bool,
54
- use_patch_tokens: bool,
55
- use_linear_projection: bool,
56
- ):
57
- super().__init__()
58
- block_list = [nn.Identity()]
59
- self.ln_final = nn.Identity()
60
- if num_blocks > 0:
61
- block_list = [
62
- AttentionBlock(
63
- input_dim,
64
- num_heads,
65
- ffn_layer=partial(SwiGLUFFN, align_to=64),
66
- init_values=1e-5,
67
- drop_path=blocks_drop_path,
68
- )
69
- for _ in range(num_blocks)
70
- ]
71
- self.ln_final = nn.LayerNorm(input_dim)
72
- self.block_list = nn.ModuleList(block_list)
73
- self.num_blocks = num_blocks
74
- multiplier = 2 if use_class_token and use_patch_tokens else 1
75
- self.linear_projection = nn.Identity()
76
- if multiplier * input_dim != embed_dim or use_linear_projection:
77
- assert embed_dim % multiplier == 0, f"Expects {embed_dim} to be divisible by {multiplier}"
78
- self.linear_projection = nn.Linear(input_dim, embed_dim // multiplier, bias=False)
79
-
80
- def init_weights(self):
81
- if self.num_blocks > 0:
82
- for i in range(self.num_blocks):
83
- block = self.block_list[i]
84
- named_apply(init_weights_vit_timm, block)
85
- self.ln_final.reset_parameters()
86
- if isinstance(self.linear_projection, nn.Linear):
87
- nn.init.normal_(self.linear_projection.weight, std=self.linear_projection.in_features**-0.5)
88
-
89
- def forward(self, image_tokens: Tensor) -> Tensor:
90
- for block in self.block_list:
91
- image_tokens = block(image_tokens)
92
- image_tokens = self.ln_final(image_tokens)
93
- return self.linear_projection(image_tokens)
94
-
95
-
96
- class VisionTower(nn.Module):
97
- def __init__(
98
- self,
99
- backbone: nn.Module,
100
- freeze_backbone: bool,
101
- embed_dim: int,
102
- num_head_blocks: int,
103
- head_blocks_block_drop_path: float,
104
- use_class_token: bool,
105
- use_patch_tokens: bool,
106
- patch_token_layer: int,
107
- patch_tokens_pooler_type: str,
108
- use_linear_projection: bool,
109
- ):
110
- super().__init__()
111
- self.backbone = backbone
112
- self.freeze_backbone = freeze_backbone
113
- self.use_class_token = use_class_token
114
- self.use_patch_tokens = use_patch_tokens
115
- self.patch_token_layer = patch_token_layer
116
- self.patch_tokens_pooler_type = patch_tokens_pooler_type
117
- self.num_register_tokens = 0
118
- if hasattr(self.backbone, "num_register_tokens"):
119
- self.num_register_tokens = self.backbone.num_register_tokens
120
- elif hasattr(self.backbone, "n_storage_tokens"):
121
- self.num_register_tokens = self.backbone.n_storage_tokens
122
- backbone_out_dim = self.backbone.embed_dim
123
- self.head = VisionHead(
124
- backbone_out_dim,
125
- embed_dim,
126
- self.backbone.num_heads,
127
- num_head_blocks,
128
- head_blocks_block_drop_path,
129
- use_class_token,
130
- use_patch_tokens,
131
- use_linear_projection,
132
- )
133
-
134
- def init_weights(self):
135
- if not self.freeze_backbone:
136
- self.backbone.init_weights()
137
- self.head.init_weights()
138
-
139
- def get_backbone_features(self, images: Tensor) -> Tuple[Tensor, Tensor, Tensor]:
140
- tokens = self.backbone.get_intermediate_layers(
141
- images,
142
- n=self.patch_token_layer,
143
- return_class_token=True,
144
- return_register_tokens=True,
145
- )
146
- class_token = tokens[-1][1]
147
- patch_tokens = tokens[0][0]
148
- register_tokens = tokens[0][2]
149
- return class_token, patch_tokens, register_tokens
150
-
151
- def get_class_and_patch_tokens(self, images: Tensor) -> Tuple[Tensor, Tensor]:
152
- class_token, patch_tokens, register_tokens = self.get_backbone_features(images)
153
- image_tokens = self.head(torch.cat([class_token.unsqueeze(1), register_tokens, patch_tokens], dim=1))
154
- class_token, patch_tokens = image_tokens[:, 0], image_tokens[:, self.num_register_tokens + 1 :]
155
- return class_token, patch_tokens
156
-
157
- def forward(self, images: Tensor) -> Tensor:
158
- class_token, patch_tokens = self.get_class_and_patch_tokens(images)
159
- features = []
160
- if self.use_class_token:
161
- features.append(class_token)
162
- if self.use_patch_tokens:
163
- if self.patch_tokens_pooler_type == "mean":
164
- features.append(torch.mean(patch_tokens, dim=1))
165
- elif self.patch_tokens_pooler_type == "max":
166
- features.append(torch.max(patch_tokens, dim=1).values)
167
- elif self.patch_tokens_pooler_type == "gem":
168
- power = 3
169
- eps = 1e-6
170
- patch_tokens_power = patch_tokens.clamp(min=eps).pow(power)
171
- features.append(torch.mean(patch_tokens_power, dim=1).pow(1 / power))
172
- else:
173
- raise ValueError(f"Unknown patch tokens pooler type: {self.patch_tokens_pooler_type}")
174
- return torch.cat(features, dim=-1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/utils.py DELETED
@@ -1,39 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the Apache License, Version 2.0
4
- # found in the LICENSE file in the root directory of this source tree.
5
-
6
- import itertools
7
- import math
8
-
9
- import torch
10
- import torch.nn as nn
11
- import torch.nn.functional as F
12
-
13
-
14
- _DINOV2_BASE_URL = "https://dl.fbaipublicfiles.com/dinov2"
15
-
16
-
17
- def _make_dinov2_model_name(arch_name: str, patch_size: int, num_register_tokens: int = 0) -> str:
18
- compact_arch_name = arch_name.replace("_", "")[:4]
19
- registers_suffix = f"_reg{num_register_tokens}" if num_register_tokens else ""
20
- return f"dinov2_{compact_arch_name}{patch_size}{registers_suffix}"
21
-
22
-
23
- class CenterPadding(nn.Module):
24
- def __init__(self, multiple):
25
- super().__init__()
26
- self.multiple = multiple
27
-
28
- def _get_pad(self, size):
29
- new_size = math.ceil(size / self.multiple) * self.multiple
30
- pad_size = new_size - size
31
- pad_size_left = pad_size // 2
32
- pad_size_right = pad_size - pad_size_left
33
- return pad_size_left, pad_size_right
34
-
35
- @torch.inference_mode()
36
- def forward(self, x):
37
- pads = list(itertools.chain.from_iterable(self._get_pad(m) for m in x.shape[:1:-1]))
38
- output = F.pad(x, pads)
39
- return output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/hub/xray_dino/backbones.py DELETED
@@ -1,28 +0,0 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
- #
3
- # This source code is licensed under the licence
4
- # found in the LICENSE_XRAY_DINO_MODEL file in the root directory of this source tree.
5
-
6
- from typing import Union
7
-
8
- from ..backbones import Weights, _make_dinov2_model
9
-
10
-
11
- def xray_dino_vitl16(*, pretrained: bool = True, weights: Union[Weights, str] = Weights.XRAY_DINO, **kwargs):
12
- """
13
- XRay-DINO ViT-L/16 model (optionally) pretrained on the XRay-DINO dataset.
14
- """
15
- return _make_dinov2_model(
16
- arch_name="vit_large",
17
- patch_size=16,
18
- img_size=512,
19
- num_register_tokens=0,
20
- interpolate_antialias=False,
21
- interpolate_offset=0.1,
22
- block_chunks=4,
23
- pretrained=pretrained,
24
- weights=weights,
25
- hash="ad31c2b0",
26
- check_hash=True,
27
- **kwargs,
28
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
OpenPath/dinov2/loss/gram_loss.py CHANGED
@@ -1,7 +1,13 @@
1
- # Copyright (c) Meta Platforms, Inc. and affiliates.
2
  #
3
- # This software may be used and distributed in accordance with
4
- # the terms of the DINOv3 License Agreement.
 
 
 
 
 
 
5
 
6
  import torch
7
  import torch.nn as nn
@@ -9,76 +15,54 @@ import torch.nn.functional as F
9
 
10
 
11
  class GramLoss(nn.Module):
12
- """Implementation of the gram loss"""
13
-
14
- def __init__(
15
- self,
16
- apply_norm=True,
17
- img_level=True,
18
- remove_neg=True,
19
- remove_only_teacher_neg=False,
20
- ):
 
 
 
 
 
 
 
 
21
  super().__init__()
22
-
23
- # Loss
24
- self.mse_loss = torch.nn.MSELoss()
25
-
26
- # Parameters
27
  self.apply_norm = apply_norm
 
28
  self.remove_neg = remove_neg
29
  self.remove_only_teacher_neg = remove_only_teacher_neg
30
 
31
- if self.remove_neg or self.remove_only_teacher_neg:
32
- assert self.remove_neg != self.remove_only_teacher_neg
33
-
34
- def forward(self, output_feats, target_feats, img_level=True):
35
- """Compute the MSE loss between the gram matrix of the input and target features.
36
-
37
- Args:
38
- output_feats: Pytorch tensor (B, N, dim) or (B*N, dim) if img_level == False
39
- target_feats: Pytorch tensor (B, N, dim) or (B*N, dim) if img_level == False
40
- img_level: bool, if true gram computed at the image level only else over the entire batch
41
- Returns:
42
- loss: scalar
43
- """
44
 
45
- # Dimensions of the tensor should be (B, N, dim)
46
- if img_level:
47
- assert len(target_feats.shape) == 3 and len(output_feats.shape) == 3
48
 
49
- # Float casting
50
- output_feats = output_feats.float()
51
- target_feats = target_feats.float()
52
-
53
- # SSL correlation
54
- if self.apply_norm:
55
- target_feats = F.normalize(target_feats, dim=-1)
56
-
57
- if not img_level and len(target_feats.shape) == 3:
58
- # Flatten (B, N, D) into (B*N, D)
59
- target_feats = target_feats.flatten(0, 1)
60
-
61
- # Compute similarities
62
- target_sim = torch.matmul(target_feats, target_feats.transpose(-1, -2))
63
-
64
- # Patch correlation
65
  if self.apply_norm:
66
- output_feats = F.normalize(output_feats, dim=-1)
 
67
 
68
- if not img_level and len(output_feats.shape) == 3:
69
- # Flatten (B, N, D) into (B*N, D)
70
- output_feats = output_feats.flatten(0, 1)
 
71
 
72
- # Compute similarities
73
- student_sim = torch.matmul(output_feats, output_feats.transpose(-1, -2))
74
 
75
  if self.remove_neg:
76
- target_sim[target_sim < 0] = 0.0
77
- student_sim[student_sim < 0] = 0.0
78
-
79
- elif self.remove_only_teacher_neg:
80
- # Remove only the negative sim values of the teacher
81
- target_sim[target_sim < 0] = 0.0
82
- student_sim[(student_sim < 0) & (target_sim < 0)] = 0.0
83
 
84
- return self.mse_loss(student_sim, target_sim)
 
1
+ # Copyright (c) 2026 OpenPath authors.
2
  #
3
+ # This source code is licensed under the Apache License, Version 2.0
4
+ # found in the LICENSE file in the root directory of this source tree.
5
+ #
6
+ # Clean-room re-implementation of the gram-anchoring loss (technique from DINOv3,
7
+ # Siméoni et al., 2025). Written from the mathematical description only — the MSE
8
+ # between the (optionally L2-normalized) patch-token Gram / self-similarity matrices
9
+ # of the student and a frozen anchor — with no reference to the original DINOv3
10
+ # source, so this file carries the same Apache-2.0 license as the rest of the fork.
11
 
12
  import torch
13
  import torch.nn as nn
 
15
 
16
 
17
  class GramLoss(nn.Module):
18
+ """Gram-anchoring loss.
19
+
20
+ For patch-token features from the student and a frozen anchor, build each model's
21
+ per-image Gram matrix (token-by-token self-similarity) and penalize their squared
22
+ difference. This anchors the student's *relational* (dense/patch) structure to the
23
+ anchor's while DINO/iBOT keep optimizing the global CLS representation.
24
+
25
+ Args:
26
+ apply_norm: L2-normalize tokens along the feature dim before the Gram product,
27
+ so each Gram entry is a cosine similarity in [-1, 1].
28
+ img_level: build one Gram per image, shape (B, N, N) (default path).
29
+ remove_neg: clamp negative similarities to 0 before the comparison.
30
+ remove_only_teacher_neg: when clamping, clamp the anchor's Gram only.
31
+ """
32
+
33
+ def __init__(self, apply_norm=True, img_level=True, remove_neg=True,
34
+ remove_only_teacher_neg=False):
35
  super().__init__()
 
 
 
 
 
36
  self.apply_norm = apply_norm
37
+ self.img_level = img_level
38
  self.remove_neg = remove_neg
39
  self.remove_only_teacher_neg = remove_only_teacher_neg
40
 
41
+ @staticmethod
42
+ def _gram(x):
43
+ # x: (..., N, D) -> (..., N, N) self-similarity
44
+ return x @ x.transpose(-1, -2)
 
 
 
 
 
 
 
 
 
45
 
46
+ def forward(self, student_tokens, teacher_tokens, img_level=None):
47
+ img_level = self.img_level if img_level is None else img_level
 
48
 
49
+ xs = student_tokens.float()
50
+ xt = teacher_tokens.float()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  if self.apply_norm:
52
+ xs = F.normalize(xs, dim=-1)
53
+ xt = F.normalize(xt, dim=-1)
54
 
55
+ if not img_level:
56
+ # collapse the batch into one token set (rarely used)
57
+ xs = xs.reshape(-1, xs.shape[-1])
58
+ xt = xt.reshape(-1, xt.shape[-1])
59
 
60
+ gs = self._gram(xs)
61
+ gt = self._gram(xt)
62
 
63
  if self.remove_neg:
64
+ gt = gt.clamp(min=0.0)
65
+ if not self.remove_only_teacher_neg:
66
+ gs = gs.clamp(min=0.0)
 
 
 
 
67
 
68
+ return F.mse_loss(gs, gt)
OpenPath/dinov2/train/ssl_meta_arch.py CHANGED
@@ -123,7 +123,7 @@ class SSLMetaArch(nn.Module):
123
  for p in self.teacher.parameters():
124
  p.requires_grad = False
125
 
126
- # ---- Gram anchoring (DINOv3 기법 이식) ----
127
  # 얼린 앵커(=좋은 peak 체크포인트)의 patch 구조에 student를 붙잡아 학습 후반 degradation 방지.
128
  self.do_gram = bool(getattr(cfg, "gram", None)) and bool(cfg.gram.use_loss)
129
  if self.do_gram:
 
123
  for p in self.teacher.parameters():
124
  p.requires_grad = False
125
 
126
+ # ---- Gram anchoring (technique from DINOv3; loss re-implemented clean-room, Apache-2.0) ----
127
  # 얼린 앵커(=좋은 peak 체크포인트)의 patch 구조에 student를 붙잡아 학습 후반 degradation 방지.
128
  self.do_gram = bool(getattr(cfg, "gram", None)) and bool(cfg.gram.use_loss)
129
  if self.do_gram:
README.md CHANGED
@@ -29,7 +29,7 @@ The corpus and checkpoints are hosted separately (see below).
29
  > See [Evaluation](#evaluation).
30
 
31
  - **Encoder:** ViT-g/14 (reg4), 1536-dim CLS embedding
32
- - **Objective:** DINO + iBOT + KDE (DINOv2) with **gram anchoring** ported from DINOv3
33
  - **Data:** public pathology WSIs only (TCGA, TCIA, GTEx, CAMELYON, ACROBAT, SurGen, …), re-tiled at native 40×
34
  - **Warm start:** Meta DINOv2 ViT-g/14-reg
35
  - **Training:** FSDP (SHARD_GRAD_OP), bf16, flat learning-rate schedule, 40× B200 (multi-node)
@@ -228,5 +228,12 @@ of the Republic of Korea, Ministry of Science and ICT; and the National Research
228
 
229
  ## License
230
 
231
- Code: derived from the OpenMidnight / DINOv2 fork see `OpenPath/LICENSE`.
232
- Training data: public pathology datasets under CC-BY / CC0 / NIH-open terms (redistributable).
 
 
 
 
 
 
 
 
29
  > See [Evaluation](#evaluation).
30
 
31
  - **Encoder:** ViT-g/14 (reg4), 1536-dim CLS embedding
32
+ - **Objective:** DINO + iBOT + KDE (DINOv2) with **gram anchoring** (technique from DINOv3, re-implemented)
33
  - **Data:** public pathology WSIs only (TCGA, TCIA, GTEx, CAMELYON, ACROBAT, SurGen, …), re-tiled at native 40×
34
  - **Warm start:** Meta DINOv2 ViT-g/14-reg
35
  - **Training:** FSDP (SHARD_GRAD_OP), bf16, flat learning-rate schedule, 40× B200 (multi-node)
 
228
 
229
  ## License
230
 
231
+ **Code Apache-2.0.** This repository is a fork of **DINOv2 / OpenMidnight** (both Apache-2.0); see
232
+ `OpenPath/LICENSE`. The gram-anchoring loss (`OpenPath/dinov2/loss/gram_loss.py`) is a **clean-room
233
+ re-implementation** of the DINOv3 technique — written from its mathematical description and verified
234
+ to be numerically equivalent — so it is Apache-2.0 as well, and the codebase contains **no
235
+ non-commercial (DINOv3-licensed) code**.
236
+
237
+ **Weights — Apache-2.0** (warm-started from Meta DINOv2 ViT-g/14-reg, itself Apache-2.0).
238
+
239
+ **Training data:** public pathology datasets under CC-BY / CC0 / NIH-open terms (redistributable).