ffgs 0.1.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ffgs/__init__.py +39 -0
- ffgs/dynamic.py +239 -0
- ffgs/geometry.py +418 -0
- ffgs/image.py +204 -0
- ffgs/models/__init__.py +2 -0
- ffgs/models/anysplat/LICENSE +1120 -0
- ffgs/models/anysplat/__init__.py +49 -0
- ffgs/models/anysplat/encoder.py +372 -0
- ffgs/models/anysplat/evaluation.py +98 -0
- ffgs/models/anysplat/gaussian_adapter.py +96 -0
- ffgs/models/anysplat/gs_head.py +166 -0
- ffgs/models/anysplat/modeling.py +95 -0
- ffgs/models/anysplat/processing.py +249 -0
- ffgs/models/anysplat/vggt/__init__.py +0 -0
- ffgs/models/anysplat/vggt/heads/__init__.py +0 -0
- ffgs/models/anysplat/vggt/heads/camera_head.py +181 -0
- ffgs/models/anysplat/vggt/heads/dpt_head.py +612 -0
- ffgs/models/anysplat/vggt/heads/head_act.py +134 -0
- ffgs/models/anysplat/vggt/heads/utils.py +126 -0
- ffgs/models/anysplat/vggt/layers/__init__.py +28 -0
- ffgs/models/anysplat/vggt/layers/attention.py +92 -0
- ffgs/models/anysplat/vggt/layers/block.py +157 -0
- ffgs/models/anysplat/vggt/layers/drop_path.py +42 -0
- ffgs/models/anysplat/vggt/layers/layer_scale.py +32 -0
- ffgs/models/anysplat/vggt/layers/mlp.py +45 -0
- ffgs/models/anysplat/vggt/layers/patch_embed.py +106 -0
- ffgs/models/anysplat/vggt/layers/rope.py +211 -0
- ffgs/models/anysplat/vggt/layers/swiglu_ffn.py +77 -0
- ffgs/models/anysplat/vggt/layers/vision_transformer.py +463 -0
- ffgs/models/anysplat/vggt/models/__init__.py +0 -0
- ffgs/models/anysplat/vggt/models/aggregator.py +398 -0
- ffgs/models/anysplat/vggt/utils/__init__.py +0 -0
- ffgs/models/anysplat/vggt/utils/geometry.py +230 -0
- ffgs/models/anysplat/vggt/utils/pose_enc.py +144 -0
- ffgs/models/anysplat/vggt/utils/rotation.py +148 -0
- ffgs/models/tokengs/__init__.py +43 -0
- ffgs/models/tokengs/activations.py +135 -0
- ffgs/models/tokengs/attention.py +189 -0
- ffgs/models/tokengs/enc_dec.py +323 -0
- ffgs/models/tokengs/modeling.py +173 -0
- ffgs/models/tokengs/processing.py +339 -0
- ffgs/pipeline.py +341 -0
- ffgs/processor.py +236 -0
- ffgs/registry.py +132 -0
- ffgs/render.py +111 -0
- ffgs/types.py +291 -0
- ffgs/zoo/__init__.py +234 -0
- ffgs/zoo/anysplat/default.json +34 -0
- ffgs/zoo/tokengs/dl3dv-2v.json +29 -0
- ffgs/zoo/tokengs/dl3dv-4v.json +29 -0
- ffgs/zoo/tokengs/dl3dv-6v.json +29 -0
- ffgs/zoo/tokengs/dl3dv-base.json +29 -0
- ffgs/zoo/tokengs/dl3dv-latent-2v-lpips.json +48 -0
- ffgs/zoo/tokengs/dl3dv-latent-2v-ssim.json +48 -0
- ffgs/zoo/tokengs/dl3dv-latent-4v-lpips.json +48 -0
- ffgs/zoo/tokengs/dl3dv-latent-4v-ssim.json +48 -0
- ffgs/zoo/tokengs/dl3dv-latent-6v-lpips.json +48 -0
- ffgs/zoo/tokengs/dl3dv-latent-6v-ssim.json +48 -0
- ffgs-0.1.0.dev0.dist-info/METADATA +320 -0
- ffgs-0.1.0.dev0.dist-info/RECORD +63 -0
- ffgs-0.1.0.dev0.dist-info/WHEEL +4 -0
- ffgs-0.1.0.dev0.dist-info/licenses/LICENSE +237 -0
- ffgs-0.1.0.dev0.dist-info/licenses/src/ffgs/models/anysplat/LICENSE +1120 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
#
|
|
4
|
+
# This source code is licensed under the license found in the
|
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
|
6
|
+
#
|
|
7
|
+
# Modified by the libgs contributors, 2026, from
|
|
8
|
+
# src/model/encoder/vggt/heads/dpt_head.py of https://github.com/InternRobotics/AnySplat
|
|
9
|
+
# (5f5e208): formatted with black, unused imports removed.
|
|
10
|
+
# "The license found in the LICENSE file" above is the one VGGT's code
|
|
11
|
+
# had when AnySplat vendored it (2025-06-30): CC BY-NC 4.0, non-commercial
|
|
12
|
+
# (facebookresearch/vggt c6bf698); see LICENSE (this directory).
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Inspired by https://github.com/DepthAnything/Depth-Anything-V2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from typing import List, Tuple, Union
|
|
19
|
+
|
|
20
|
+
import torch
|
|
21
|
+
import torch.nn as nn
|
|
22
|
+
|
|
23
|
+
from .head_act import activate_head
|
|
24
|
+
from .utils import create_uv_grid, position_grid_to_embed
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class DPTHead(nn.Module):
|
|
28
|
+
"""
|
|
29
|
+
DPT Head for dense prediction tasks.
|
|
30
|
+
|
|
31
|
+
This implementation follows the architecture described in "Vision Transformers for Dense Prediction"
|
|
32
|
+
(https://arxiv.org/abs/2103.13413). The DPT head processes features from a vision transformer
|
|
33
|
+
backbone and produces dense predictions by fusing multi-scale features.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
dim_in (int): Input dimension (channels).
|
|
37
|
+
patch_size (int, optional): Patch size. Default is 14.
|
|
38
|
+
output_dim (int, optional): Number of output channels. Default is 4.
|
|
39
|
+
activation (str, optional): Activation type. Default is "inv_log".
|
|
40
|
+
conf_activation (str, optional): Confidence activation type. Default is "expp1".
|
|
41
|
+
features (int, optional): Feature channels for intermediate representations. Default is 256.
|
|
42
|
+
out_channels (List[int], optional): Output channels for each intermediate layer.
|
|
43
|
+
intermediate_layer_idx (List[int], optional): Indices of layers from aggregated tokens used for DPT.
|
|
44
|
+
pos_embed (bool, optional): Whether to use positional embedding. Default is True.
|
|
45
|
+
feature_only (bool, optional): If True, return features only without the last several layers and activation head. Default is False.
|
|
46
|
+
down_ratio (int, optional): Downscaling factor for the output resolution. Default is 1.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
dim_in: int,
|
|
52
|
+
patch_size: int = 14,
|
|
53
|
+
output_dim: int = 4,
|
|
54
|
+
activation: str = "inv_log",
|
|
55
|
+
conf_activation: str = "expp1",
|
|
56
|
+
features: int = 256,
|
|
57
|
+
out_channels: List[int] = [256, 512, 1024, 1024],
|
|
58
|
+
intermediate_layer_idx: List[int] = [4, 11, 17, 23],
|
|
59
|
+
pos_embed: bool = True,
|
|
60
|
+
feature_only: bool = False,
|
|
61
|
+
down_ratio: int = 1,
|
|
62
|
+
) -> None:
|
|
63
|
+
super(DPTHead, self).__init__()
|
|
64
|
+
self.patch_size = patch_size
|
|
65
|
+
self.activation = activation
|
|
66
|
+
self.conf_activation = conf_activation
|
|
67
|
+
self.pos_embed = pos_embed
|
|
68
|
+
self.feature_only = feature_only
|
|
69
|
+
self.down_ratio = down_ratio
|
|
70
|
+
self.intermediate_layer_idx = intermediate_layer_idx
|
|
71
|
+
|
|
72
|
+
self.norm = nn.LayerNorm(dim_in)
|
|
73
|
+
|
|
74
|
+
# Projection layers for each output channel from tokens.
|
|
75
|
+
self.projects = nn.ModuleList(
|
|
76
|
+
[
|
|
77
|
+
nn.Conv2d(
|
|
78
|
+
in_channels=dim_in,
|
|
79
|
+
out_channels=oc,
|
|
80
|
+
kernel_size=1,
|
|
81
|
+
stride=1,
|
|
82
|
+
padding=0,
|
|
83
|
+
)
|
|
84
|
+
for oc in out_channels
|
|
85
|
+
]
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Resize layers for upsampling feature maps.
|
|
89
|
+
self.resize_layers = nn.ModuleList(
|
|
90
|
+
[
|
|
91
|
+
nn.ConvTranspose2d(
|
|
92
|
+
in_channels=out_channels[0],
|
|
93
|
+
out_channels=out_channels[0],
|
|
94
|
+
kernel_size=4,
|
|
95
|
+
stride=4,
|
|
96
|
+
padding=0,
|
|
97
|
+
),
|
|
98
|
+
nn.ConvTranspose2d(
|
|
99
|
+
in_channels=out_channels[1],
|
|
100
|
+
out_channels=out_channels[1],
|
|
101
|
+
kernel_size=2,
|
|
102
|
+
stride=2,
|
|
103
|
+
padding=0,
|
|
104
|
+
),
|
|
105
|
+
nn.Identity(),
|
|
106
|
+
nn.Conv2d(
|
|
107
|
+
in_channels=out_channels[3],
|
|
108
|
+
out_channels=out_channels[3],
|
|
109
|
+
kernel_size=3,
|
|
110
|
+
stride=2,
|
|
111
|
+
padding=1,
|
|
112
|
+
),
|
|
113
|
+
]
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
self.scratch = _make_scratch(
|
|
117
|
+
out_channels,
|
|
118
|
+
features,
|
|
119
|
+
expand=False,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Attach additional modules to scratch.
|
|
123
|
+
self.scratch.stem_transpose = None
|
|
124
|
+
self.scratch.refinenet1 = _make_fusion_block(features)
|
|
125
|
+
self.scratch.refinenet2 = _make_fusion_block(features)
|
|
126
|
+
self.scratch.refinenet3 = _make_fusion_block(features)
|
|
127
|
+
self.scratch.refinenet4 = _make_fusion_block(features, has_residual=False)
|
|
128
|
+
|
|
129
|
+
head_features_1 = features
|
|
130
|
+
head_features_2 = 32
|
|
131
|
+
|
|
132
|
+
if feature_only:
|
|
133
|
+
self.scratch.output_conv1 = nn.Conv2d(
|
|
134
|
+
head_features_1, head_features_1, kernel_size=3, stride=1, padding=1
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
self.scratch.output_conv1 = nn.Conv2d(
|
|
138
|
+
head_features_1,
|
|
139
|
+
head_features_1 // 2,
|
|
140
|
+
kernel_size=3,
|
|
141
|
+
stride=1,
|
|
142
|
+
padding=1,
|
|
143
|
+
)
|
|
144
|
+
conv2_in_channels = head_features_1 // 2
|
|
145
|
+
|
|
146
|
+
self.scratch.output_conv2 = nn.Sequential(
|
|
147
|
+
nn.Conv2d(
|
|
148
|
+
conv2_in_channels,
|
|
149
|
+
head_features_2,
|
|
150
|
+
kernel_size=3,
|
|
151
|
+
stride=1,
|
|
152
|
+
padding=1,
|
|
153
|
+
),
|
|
154
|
+
nn.ReLU(inplace=True),
|
|
155
|
+
nn.Conv2d(
|
|
156
|
+
head_features_2, output_dim, kernel_size=1, stride=1, padding=0
|
|
157
|
+
),
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def forward(
|
|
161
|
+
self,
|
|
162
|
+
aggregated_tokens_list: List[torch.Tensor],
|
|
163
|
+
images: torch.Tensor,
|
|
164
|
+
patch_start_idx: int,
|
|
165
|
+
frames_chunk_size: int = 8,
|
|
166
|
+
) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
|
|
167
|
+
"""
|
|
168
|
+
Forward pass through the DPT head, supports processing by chunking frames.
|
|
169
|
+
Args:
|
|
170
|
+
aggregated_tokens_list (List[Tensor]): List of token tensors from different transformer layers.
|
|
171
|
+
images (Tensor): Input images with shape [B, S, 3, H, W], in range [0, 1].
|
|
172
|
+
patch_start_idx (int): Starting index for patch tokens in the token sequence.
|
|
173
|
+
Used to separate patch tokens from other tokens (e.g., camera or register tokens).
|
|
174
|
+
frames_chunk_size (int, optional): Number of frames to process in each chunk.
|
|
175
|
+
If None or larger than S, all frames are processed at once. Default: 8.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Tensor or Tuple[Tensor, Tensor]:
|
|
179
|
+
- If feature_only=True: Feature maps with shape [B, S, C, H, W]
|
|
180
|
+
- Otherwise: Tuple of (predictions, confidence) both with shape [B, S, 1, H, W]
|
|
181
|
+
"""
|
|
182
|
+
B, S, _, H, W = images.shape
|
|
183
|
+
|
|
184
|
+
# If frames_chunk_size is not specified or greater than S, process all frames at once
|
|
185
|
+
if frames_chunk_size is None or frames_chunk_size >= S:
|
|
186
|
+
return self._forward_impl(aggregated_tokens_list, images, patch_start_idx)
|
|
187
|
+
|
|
188
|
+
# Otherwise, process frames in chunks to manage memory usage
|
|
189
|
+
assert frames_chunk_size > 0
|
|
190
|
+
|
|
191
|
+
# Process frames in batches
|
|
192
|
+
all_preds = []
|
|
193
|
+
all_conf = []
|
|
194
|
+
|
|
195
|
+
for frames_start_idx in range(0, S, frames_chunk_size):
|
|
196
|
+
frames_end_idx = min(frames_start_idx + frames_chunk_size, S)
|
|
197
|
+
|
|
198
|
+
# Process batch of frames
|
|
199
|
+
if self.feature_only:
|
|
200
|
+
chunk_output = self._forward_impl(
|
|
201
|
+
aggregated_tokens_list,
|
|
202
|
+
images,
|
|
203
|
+
patch_start_idx,
|
|
204
|
+
frames_start_idx,
|
|
205
|
+
frames_end_idx,
|
|
206
|
+
)
|
|
207
|
+
all_preds.append(chunk_output)
|
|
208
|
+
else:
|
|
209
|
+
chunk_preds, chunk_conf = self._forward_impl(
|
|
210
|
+
aggregated_tokens_list,
|
|
211
|
+
images,
|
|
212
|
+
patch_start_idx,
|
|
213
|
+
frames_start_idx,
|
|
214
|
+
frames_end_idx,
|
|
215
|
+
)
|
|
216
|
+
all_preds.append(chunk_preds)
|
|
217
|
+
all_conf.append(chunk_conf)
|
|
218
|
+
|
|
219
|
+
# Concatenate results along the sequence dimension
|
|
220
|
+
if self.feature_only:
|
|
221
|
+
return torch.cat(all_preds, dim=1)
|
|
222
|
+
else:
|
|
223
|
+
return torch.cat(all_preds, dim=1), torch.cat(all_conf, dim=1)
|
|
224
|
+
|
|
225
|
+
def _forward_impl(
|
|
226
|
+
self,
|
|
227
|
+
aggregated_tokens_list: List[torch.Tensor],
|
|
228
|
+
images: torch.Tensor,
|
|
229
|
+
patch_start_idx: int,
|
|
230
|
+
frames_start_idx: int = None,
|
|
231
|
+
frames_end_idx: int = None,
|
|
232
|
+
) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
|
|
233
|
+
"""
|
|
234
|
+
Implementation of the forward pass through the DPT head.
|
|
235
|
+
|
|
236
|
+
This method processes a specific chunk of frames from the sequence.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
aggregated_tokens_list (List[Tensor]): List of token tensors from different transformer layers.
|
|
240
|
+
images (Tensor): Input images with shape [B, S, 3, H, W].
|
|
241
|
+
patch_start_idx (int): Starting index for patch tokens.
|
|
242
|
+
frames_start_idx (int, optional): Starting index for frames to process.
|
|
243
|
+
frames_end_idx (int, optional): Ending index for frames to process.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
Tensor or Tuple[Tensor, Tensor]: Feature maps or (predictions, confidence).
|
|
247
|
+
"""
|
|
248
|
+
if frames_start_idx is not None and frames_end_idx is not None:
|
|
249
|
+
images = images[:, frames_start_idx:frames_end_idx]
|
|
250
|
+
|
|
251
|
+
B, S, _, H, W = images.shape
|
|
252
|
+
|
|
253
|
+
patch_h, patch_w = H // self.patch_size, W // self.patch_size
|
|
254
|
+
|
|
255
|
+
out = []
|
|
256
|
+
dpt_idx = 0
|
|
257
|
+
|
|
258
|
+
for layer_idx in self.intermediate_layer_idx:
|
|
259
|
+
# x = aggregated_tokens_list[layer_idx][:, :, patch_start_idx:]
|
|
260
|
+
if len(aggregated_tokens_list) > 10:
|
|
261
|
+
x = aggregated_tokens_list[layer_idx][:, :, patch_start_idx:]
|
|
262
|
+
else:
|
|
263
|
+
list_idx = self.intermediate_layer_idx.index(layer_idx)
|
|
264
|
+
x = aggregated_tokens_list[list_idx][:, :, patch_start_idx:]
|
|
265
|
+
|
|
266
|
+
# Select frames if processing a chunk
|
|
267
|
+
if frames_start_idx is not None and frames_end_idx is not None:
|
|
268
|
+
x = x[:, frames_start_idx:frames_end_idx].contiguous()
|
|
269
|
+
|
|
270
|
+
x = x.view(B * S, -1, x.shape[-1])
|
|
271
|
+
x = self.norm(x)
|
|
272
|
+
|
|
273
|
+
x = x.permute(0, 2, 1).reshape((x.shape[0], x.shape[-1], patch_h, patch_w))
|
|
274
|
+
|
|
275
|
+
x = self.projects[dpt_idx](x)
|
|
276
|
+
if self.pos_embed:
|
|
277
|
+
x = self._apply_pos_embed(x, W, H)
|
|
278
|
+
|
|
279
|
+
x = self.resize_layers[dpt_idx](x)
|
|
280
|
+
|
|
281
|
+
out.append(x)
|
|
282
|
+
dpt_idx += 1
|
|
283
|
+
|
|
284
|
+
# Fuse features from multiple layers.
|
|
285
|
+
out = self.scratch_forward(out)
|
|
286
|
+
# Interpolate fused output to match target image resolution.
|
|
287
|
+
out = custom_interpolate(
|
|
288
|
+
out,
|
|
289
|
+
(
|
|
290
|
+
int(patch_h * self.patch_size / self.down_ratio),
|
|
291
|
+
int(patch_w * self.patch_size / self.down_ratio),
|
|
292
|
+
),
|
|
293
|
+
mode="bilinear",
|
|
294
|
+
align_corners=True,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if self.pos_embed:
|
|
298
|
+
out = self._apply_pos_embed(out, W, H)
|
|
299
|
+
|
|
300
|
+
if self.feature_only:
|
|
301
|
+
return out.view(B, S, *out.shape[1:])
|
|
302
|
+
|
|
303
|
+
out = self.scratch.output_conv2(out)
|
|
304
|
+
preds, conf = activate_head(
|
|
305
|
+
out, activation=self.activation, conf_activation=self.conf_activation
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
preds = preds.view(B, S, *preds.shape[1:])
|
|
309
|
+
conf = conf.view(B, S, *conf.shape[1:])
|
|
310
|
+
return preds, conf
|
|
311
|
+
|
|
312
|
+
def _apply_pos_embed(
|
|
313
|
+
self, x: torch.Tensor, W: int, H: int, ratio: float = 0.1
|
|
314
|
+
) -> torch.Tensor:
|
|
315
|
+
"""
|
|
316
|
+
Apply positional embedding to tensor x.
|
|
317
|
+
"""
|
|
318
|
+
patch_w = x.shape[-1]
|
|
319
|
+
patch_h = x.shape[-2]
|
|
320
|
+
pos_embed = create_uv_grid(
|
|
321
|
+
patch_w, patch_h, aspect_ratio=W / H, dtype=x.dtype, device=x.device
|
|
322
|
+
)
|
|
323
|
+
pos_embed = position_grid_to_embed(pos_embed, x.shape[1])
|
|
324
|
+
pos_embed = pos_embed * ratio
|
|
325
|
+
pos_embed = pos_embed.permute(2, 0, 1)[None].expand(x.shape[0], -1, -1, -1)
|
|
326
|
+
return x + pos_embed
|
|
327
|
+
|
|
328
|
+
def scratch_forward(self, features: List[torch.Tensor]) -> torch.Tensor:
|
|
329
|
+
"""
|
|
330
|
+
Forward pass through the fusion blocks.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
features (List[Tensor]): List of feature maps from different layers.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Tensor: Fused feature map.
|
|
337
|
+
"""
|
|
338
|
+
layer_1, layer_2, layer_3, layer_4 = features
|
|
339
|
+
|
|
340
|
+
layer_1_rn = self.scratch.layer1_rn(layer_1)
|
|
341
|
+
layer_2_rn = self.scratch.layer2_rn(layer_2)
|
|
342
|
+
layer_3_rn = self.scratch.layer3_rn(layer_3)
|
|
343
|
+
layer_4_rn = self.scratch.layer4_rn(layer_4)
|
|
344
|
+
|
|
345
|
+
out = self.scratch.refinenet4(layer_4_rn, size=layer_3_rn.shape[2:])
|
|
346
|
+
del layer_4_rn, layer_4
|
|
347
|
+
|
|
348
|
+
out = self.scratch.refinenet3(out, layer_3_rn, size=layer_2_rn.shape[2:])
|
|
349
|
+
del layer_3_rn, layer_3
|
|
350
|
+
|
|
351
|
+
out = self.scratch.refinenet2(out, layer_2_rn, size=layer_1_rn.shape[2:])
|
|
352
|
+
del layer_2_rn, layer_2
|
|
353
|
+
|
|
354
|
+
out = self.scratch.refinenet1(out, layer_1_rn)
|
|
355
|
+
del layer_1_rn, layer_1
|
|
356
|
+
|
|
357
|
+
out = self.scratch.output_conv1(out)
|
|
358
|
+
return out
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
################################################################################
|
|
362
|
+
# Modules
|
|
363
|
+
################################################################################
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _make_fusion_block(
|
|
367
|
+
features: int, size: int = None, has_residual: bool = True, groups: int = 1
|
|
368
|
+
) -> nn.Module:
|
|
369
|
+
return FeatureFusionBlock(
|
|
370
|
+
features,
|
|
371
|
+
nn.ReLU(inplace=True),
|
|
372
|
+
deconv=False,
|
|
373
|
+
bn=False,
|
|
374
|
+
expand=False,
|
|
375
|
+
align_corners=True,
|
|
376
|
+
size=size,
|
|
377
|
+
has_residual=has_residual,
|
|
378
|
+
groups=groups,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _make_scratch(
|
|
383
|
+
in_shape: List[int], out_shape: int, groups: int = 1, expand: bool = False
|
|
384
|
+
) -> nn.Module:
|
|
385
|
+
scratch = nn.Module()
|
|
386
|
+
out_shape1 = out_shape
|
|
387
|
+
out_shape2 = out_shape
|
|
388
|
+
out_shape3 = out_shape
|
|
389
|
+
if len(in_shape) >= 4:
|
|
390
|
+
out_shape4 = out_shape
|
|
391
|
+
|
|
392
|
+
if expand:
|
|
393
|
+
out_shape1 = out_shape
|
|
394
|
+
out_shape2 = out_shape * 2
|
|
395
|
+
out_shape3 = out_shape * 4
|
|
396
|
+
if len(in_shape) >= 4:
|
|
397
|
+
out_shape4 = out_shape * 8
|
|
398
|
+
|
|
399
|
+
scratch.layer1_rn = nn.Conv2d(
|
|
400
|
+
in_shape[0],
|
|
401
|
+
out_shape1,
|
|
402
|
+
kernel_size=3,
|
|
403
|
+
stride=1,
|
|
404
|
+
padding=1,
|
|
405
|
+
bias=False,
|
|
406
|
+
groups=groups,
|
|
407
|
+
)
|
|
408
|
+
scratch.layer2_rn = nn.Conv2d(
|
|
409
|
+
in_shape[1],
|
|
410
|
+
out_shape2,
|
|
411
|
+
kernel_size=3,
|
|
412
|
+
stride=1,
|
|
413
|
+
padding=1,
|
|
414
|
+
bias=False,
|
|
415
|
+
groups=groups,
|
|
416
|
+
)
|
|
417
|
+
scratch.layer3_rn = nn.Conv2d(
|
|
418
|
+
in_shape[2],
|
|
419
|
+
out_shape3,
|
|
420
|
+
kernel_size=3,
|
|
421
|
+
stride=1,
|
|
422
|
+
padding=1,
|
|
423
|
+
bias=False,
|
|
424
|
+
groups=groups,
|
|
425
|
+
)
|
|
426
|
+
if len(in_shape) >= 4:
|
|
427
|
+
scratch.layer4_rn = nn.Conv2d(
|
|
428
|
+
in_shape[3],
|
|
429
|
+
out_shape4,
|
|
430
|
+
kernel_size=3,
|
|
431
|
+
stride=1,
|
|
432
|
+
padding=1,
|
|
433
|
+
bias=False,
|
|
434
|
+
groups=groups,
|
|
435
|
+
)
|
|
436
|
+
return scratch
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class ResidualConvUnit(nn.Module):
|
|
440
|
+
"""Residual convolution module."""
|
|
441
|
+
|
|
442
|
+
def __init__(self, features, activation, bn, groups=1):
|
|
443
|
+
"""Init.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
features (int): number of features
|
|
447
|
+
"""
|
|
448
|
+
super().__init__()
|
|
449
|
+
|
|
450
|
+
self.bn = bn
|
|
451
|
+
self.groups = groups
|
|
452
|
+
self.conv1 = nn.Conv2d(
|
|
453
|
+
features,
|
|
454
|
+
features,
|
|
455
|
+
kernel_size=3,
|
|
456
|
+
stride=1,
|
|
457
|
+
padding=1,
|
|
458
|
+
bias=True,
|
|
459
|
+
groups=self.groups,
|
|
460
|
+
)
|
|
461
|
+
self.conv2 = nn.Conv2d(
|
|
462
|
+
features,
|
|
463
|
+
features,
|
|
464
|
+
kernel_size=3,
|
|
465
|
+
stride=1,
|
|
466
|
+
padding=1,
|
|
467
|
+
bias=True,
|
|
468
|
+
groups=self.groups,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
self.norm1 = None
|
|
472
|
+
self.norm2 = None
|
|
473
|
+
|
|
474
|
+
self.activation = activation
|
|
475
|
+
self.skip_add = nn.quantized.FloatFunctional()
|
|
476
|
+
|
|
477
|
+
def forward(self, x):
|
|
478
|
+
"""Forward pass.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
x (tensor): input
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
tensor: output
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
out = self.activation(x)
|
|
488
|
+
out = self.conv1(out)
|
|
489
|
+
if self.norm1 is not None:
|
|
490
|
+
out = self.norm1(out)
|
|
491
|
+
|
|
492
|
+
out = self.activation(out)
|
|
493
|
+
out = self.conv2(out)
|
|
494
|
+
if self.norm2 is not None:
|
|
495
|
+
out = self.norm2(out)
|
|
496
|
+
|
|
497
|
+
return self.skip_add.add(out, x)
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
class FeatureFusionBlock(nn.Module):
|
|
501
|
+
"""Feature fusion block."""
|
|
502
|
+
|
|
503
|
+
def __init__(
|
|
504
|
+
self,
|
|
505
|
+
features,
|
|
506
|
+
activation,
|
|
507
|
+
deconv=False,
|
|
508
|
+
bn=False,
|
|
509
|
+
expand=False,
|
|
510
|
+
align_corners=True,
|
|
511
|
+
size=None,
|
|
512
|
+
has_residual=True,
|
|
513
|
+
groups=1,
|
|
514
|
+
):
|
|
515
|
+
"""Init.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
features (int): number of features
|
|
519
|
+
"""
|
|
520
|
+
super(FeatureFusionBlock, self).__init__()
|
|
521
|
+
|
|
522
|
+
self.deconv = deconv
|
|
523
|
+
self.align_corners = align_corners
|
|
524
|
+
self.groups = groups
|
|
525
|
+
self.expand = expand
|
|
526
|
+
out_features = features
|
|
527
|
+
if self.expand == True: # noqa: E712 (upstream)
|
|
528
|
+
out_features = features // 2
|
|
529
|
+
|
|
530
|
+
self.out_conv = nn.Conv2d(
|
|
531
|
+
features,
|
|
532
|
+
out_features,
|
|
533
|
+
kernel_size=1,
|
|
534
|
+
stride=1,
|
|
535
|
+
padding=0,
|
|
536
|
+
bias=True,
|
|
537
|
+
groups=self.groups,
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
if has_residual:
|
|
541
|
+
self.resConfUnit1 = ResidualConvUnit(
|
|
542
|
+
features, activation, bn, groups=self.groups
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
self.has_residual = has_residual
|
|
546
|
+
self.resConfUnit2 = ResidualConvUnit(
|
|
547
|
+
features, activation, bn, groups=self.groups
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
self.skip_add = nn.quantized.FloatFunctional()
|
|
551
|
+
self.size = size
|
|
552
|
+
|
|
553
|
+
def forward(self, *xs, size=None):
|
|
554
|
+
"""Forward pass.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
tensor: output
|
|
558
|
+
"""
|
|
559
|
+
output = xs[0]
|
|
560
|
+
|
|
561
|
+
if self.has_residual:
|
|
562
|
+
res = self.resConfUnit1(xs[1])
|
|
563
|
+
output = self.skip_add.add(output, res)
|
|
564
|
+
|
|
565
|
+
output = self.resConfUnit2(output)
|
|
566
|
+
|
|
567
|
+
if (size is None) and (self.size is None):
|
|
568
|
+
modifier = {"scale_factor": 2}
|
|
569
|
+
elif size is None:
|
|
570
|
+
modifier = {"size": self.size}
|
|
571
|
+
else:
|
|
572
|
+
modifier = {"size": size}
|
|
573
|
+
|
|
574
|
+
output = custom_interpolate(
|
|
575
|
+
output, **modifier, mode="bilinear", align_corners=self.align_corners
|
|
576
|
+
)
|
|
577
|
+
output = self.out_conv(output)
|
|
578
|
+
|
|
579
|
+
return output
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def custom_interpolate(
|
|
583
|
+
x: torch.Tensor,
|
|
584
|
+
size: Tuple[int, int] = None,
|
|
585
|
+
scale_factor: float = None,
|
|
586
|
+
mode: str = "bilinear",
|
|
587
|
+
align_corners: bool = True,
|
|
588
|
+
) -> torch.Tensor:
|
|
589
|
+
"""
|
|
590
|
+
Custom interpolate to avoid INT_MAX issues in nn.functional.interpolate.
|
|
591
|
+
"""
|
|
592
|
+
if size is None:
|
|
593
|
+
size = (int(x.shape[-2] * scale_factor), int(x.shape[-1] * scale_factor))
|
|
594
|
+
|
|
595
|
+
INT_MAX = 1610612736
|
|
596
|
+
|
|
597
|
+
input_elements = size[0] * size[1] * x.shape[0] * x.shape[1]
|
|
598
|
+
|
|
599
|
+
if input_elements > INT_MAX:
|
|
600
|
+
chunks = torch.chunk(x, chunks=(input_elements // INT_MAX) + 1, dim=0)
|
|
601
|
+
interpolated_chunks = [
|
|
602
|
+
nn.functional.interpolate(
|
|
603
|
+
chunk, size=size, mode=mode, align_corners=align_corners
|
|
604
|
+
)
|
|
605
|
+
for chunk in chunks
|
|
606
|
+
]
|
|
607
|
+
x = torch.cat(interpolated_chunks, dim=0)
|
|
608
|
+
return x.contiguous()
|
|
609
|
+
else:
|
|
610
|
+
return nn.functional.interpolate(
|
|
611
|
+
x, size=size, mode=mode, align_corners=align_corners
|
|
612
|
+
)
|