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,323 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Modified by the libgs contributors, 2026, from tokengs/models/enc_dec.py of
|
|
17
|
+
# https://github.com/nv-tlabs/TokenGS (b16269c): inference-only subset. Takes a
|
|
18
|
+
# `TokenGSConfig` instead of the training `Options`; einops replaced by the
|
|
19
|
+
# equivalent reshape / permute; dynamic Gaussian tokens (block-causal
|
|
20
|
+
# FlexAttention) removed. Parameter names are unchanged.
|
|
21
|
+
|
|
22
|
+
"""TokenGS encoder-decoder backbone: ViT encoder, optional latent bottleneck, and a
|
|
23
|
+
decoder whose Gaussian tokens cross-attend to the encoder output."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
import torch
|
|
30
|
+
import torch.nn as nn
|
|
31
|
+
import torch.nn.functional as F
|
|
32
|
+
|
|
33
|
+
from .attention import Attention, Block, LayerScale, Mlp
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class TokenGSConfig:
|
|
38
|
+
"""Architecture of a TokenGS checkpoint (the model part of the upstream
|
|
39
|
+
`Options`, same names and defaults)."""
|
|
40
|
+
|
|
41
|
+
patch_size: int = 8
|
|
42
|
+
dec_patch_size: int | None = None
|
|
43
|
+
enc_depth: int = 3
|
|
44
|
+
dec_depth: int = 12
|
|
45
|
+
enc_embed_dim: int = 1024
|
|
46
|
+
enc_num_heads: int = 16
|
|
47
|
+
mlp_ratio: float = 4
|
|
48
|
+
clip_head_readout_std: float = 0.002
|
|
49
|
+
clip_head_z_init: float | None = None
|
|
50
|
+
dec_init_values: float | None = None
|
|
51
|
+
gaussian_scale_cap: float = 0.075
|
|
52
|
+
opacity_bias: float = 2.0
|
|
53
|
+
gaussian_z_offset: float = 1.0
|
|
54
|
+
num_gs_tokens: int = 1024
|
|
55
|
+
token_dim: int = 1024
|
|
56
|
+
gs_token_std: float = 1e-2
|
|
57
|
+
use_multiscale_encoder: bool = False
|
|
58
|
+
multiscale_encoder_layers: tuple[int, ...] = (5, 7, 9, 11)
|
|
59
|
+
use_latent_bottleneck: bool = False
|
|
60
|
+
num_latents: int = 4096
|
|
61
|
+
latent_cross_attn_depth: int = 12
|
|
62
|
+
|
|
63
|
+
def __post_init__(self) -> None:
|
|
64
|
+
if self.dec_patch_size is None:
|
|
65
|
+
self.dec_patch_size = self.patch_size
|
|
66
|
+
self.multiscale_encoder_layers = tuple(self.multiscale_encoder_layers)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _split_kv(x: torch.Tensor, num_heads: int) -> tuple[torch.Tensor, torch.Tensor]:
|
|
70
|
+
""" "b n (kv h c) -> kv b h n c", unbound into (k, v)."""
|
|
71
|
+
b, n, _ = x.shape
|
|
72
|
+
x = x.reshape(b, n, 2, num_heads, -1).permute(2, 0, 3, 1, 4)
|
|
73
|
+
return x[0], x[1]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class DecoderBlock(nn.Module):
|
|
77
|
+
"""
|
|
78
|
+
Decoder block for encoder-decoder architecture.
|
|
79
|
+
Works like a transformer decoder layer except the keys/values are provided by
|
|
80
|
+
the encoder (already normalized).
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
class SelfAttnBlock(nn.Module):
|
|
84
|
+
def __init__(self, dim: int, num_heads: int, qkv_bias: bool, qk_norm: bool):
|
|
85
|
+
super().__init__()
|
|
86
|
+
self.norm = nn.LayerNorm(dim)
|
|
87
|
+
self.gs_self_attn = Attention(
|
|
88
|
+
dim, num_heads, qkv_bias=qkv_bias, qk_norm=qk_norm
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def forward(self, gs_tokens: torch.Tensor) -> torch.Tensor:
|
|
92
|
+
queries_normed = self.norm(gs_tokens)
|
|
93
|
+
return self.gs_self_attn(queries_normed)
|
|
94
|
+
|
|
95
|
+
class CrossAttnBlock(nn.Module):
|
|
96
|
+
def __init__(self, dim: int, num_heads: int, qkv_bias: bool, q_norm: bool):
|
|
97
|
+
super().__init__()
|
|
98
|
+
self.num_heads = num_heads
|
|
99
|
+
self.gs_token_norm = nn.LayerNorm(dim)
|
|
100
|
+
self.q_norm = nn.LayerNorm(dim // num_heads) if q_norm else nn.Identity()
|
|
101
|
+
self.q_proj = nn.Linear(dim, dim, bias=qkv_bias)
|
|
102
|
+
self.out_proj = nn.Linear(dim, dim)
|
|
103
|
+
|
|
104
|
+
def forward(
|
|
105
|
+
self, gs_tokens: torch.Tensor, keys: torch.Tensor, values: torch.Tensor
|
|
106
|
+
) -> torch.Tensor:
|
|
107
|
+
gs_tokens_normed = self.gs_token_norm(gs_tokens)
|
|
108
|
+
q = self.q_proj(gs_tokens_normed)
|
|
109
|
+
b, n, _ = q.shape
|
|
110
|
+
# "b n (h d) -> b h n d"
|
|
111
|
+
q = q.reshape(b, n, self.num_heads, -1).permute(0, 2, 1, 3)
|
|
112
|
+
q = self.q_norm(q)
|
|
113
|
+
|
|
114
|
+
cross_attn_output = F.scaled_dot_product_attention(q, keys, values)
|
|
115
|
+
# "b h n d -> b n (h d)"
|
|
116
|
+
cross_attn_output = cross_attn_output.permute(0, 2, 1, 3).reshape(b, n, -1)
|
|
117
|
+
return self.out_proj(cross_attn_output)
|
|
118
|
+
|
|
119
|
+
class MlpBlock(nn.Module):
|
|
120
|
+
def __init__(self, dim: int, mlp_ratio: float, ffn_bias: bool):
|
|
121
|
+
super().__init__()
|
|
122
|
+
self.norm = nn.LayerNorm(dim)
|
|
123
|
+
self.mlp = Mlp(dim, int(dim * mlp_ratio), dim, bias=ffn_bias)
|
|
124
|
+
|
|
125
|
+
def forward(self, gs_tokens: torch.Tensor) -> torch.Tensor:
|
|
126
|
+
return self.mlp(self.norm(gs_tokens))
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
dim: int,
|
|
131
|
+
num_heads: int,
|
|
132
|
+
mlp_ratio: float,
|
|
133
|
+
qkv_bias: bool,
|
|
134
|
+
ffn_bias: bool,
|
|
135
|
+
qk_norm: bool,
|
|
136
|
+
init_values: float | None = None,
|
|
137
|
+
):
|
|
138
|
+
super().__init__()
|
|
139
|
+
|
|
140
|
+
def make_scale() -> nn.Module:
|
|
141
|
+
return (
|
|
142
|
+
LayerScale(dim, init_values=init_values)
|
|
143
|
+
if init_values
|
|
144
|
+
else nn.Identity()
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
self.gs_self_attn = DecoderBlock.SelfAttnBlock(
|
|
148
|
+
dim, num_heads, qkv_bias, qk_norm=qk_norm
|
|
149
|
+
)
|
|
150
|
+
self.gs_self_attn_scale = make_scale()
|
|
151
|
+
|
|
152
|
+
self.gs_cross_attn = DecoderBlock.CrossAttnBlock(
|
|
153
|
+
dim, num_heads, qkv_bias, q_norm=qk_norm
|
|
154
|
+
)
|
|
155
|
+
self.gs_cross_attn_scale = make_scale()
|
|
156
|
+
|
|
157
|
+
self.mlp = DecoderBlock.MlpBlock(dim, mlp_ratio, ffn_bias)
|
|
158
|
+
self.mlp_scale = make_scale()
|
|
159
|
+
|
|
160
|
+
def forward(
|
|
161
|
+
self, gs_tokens: torch.Tensor, keys: torch.Tensor, values: torch.Tensor
|
|
162
|
+
) -> torch.Tensor:
|
|
163
|
+
gs_tokens = gs_tokens + self.gs_cross_attn_scale(
|
|
164
|
+
self.gs_cross_attn(gs_tokens, keys, values)
|
|
165
|
+
)
|
|
166
|
+
gs_tokens = gs_tokens + self.gs_self_attn_scale(self.gs_self_attn(gs_tokens))
|
|
167
|
+
gs_tokens = gs_tokens + self.mlp_scale(self.mlp(gs_tokens))
|
|
168
|
+
|
|
169
|
+
return gs_tokens
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class EncDecBackbone(nn.Module):
|
|
173
|
+
"""
|
|
174
|
+
An encoder-decoder backbone for the EncDec architecture.
|
|
175
|
+
|
|
176
|
+
Encoder: a stack of ViT blocks which produce a latent representation. This is
|
|
177
|
+
followed by a key-value projection to produce a key and value for the decoder.
|
|
178
|
+
|
|
179
|
+
Decoder: a stack of transformer decoder layers which attend from GS tokens to
|
|
180
|
+
the encoder output and among themselves.
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
def __init__(self, opt: TokenGSConfig):
|
|
184
|
+
super().__init__()
|
|
185
|
+
|
|
186
|
+
self.opt = opt
|
|
187
|
+
|
|
188
|
+
self.encoder = nn.Sequential(
|
|
189
|
+
*[
|
|
190
|
+
Block(
|
|
191
|
+
self.opt.enc_embed_dim,
|
|
192
|
+
self.opt.enc_num_heads,
|
|
193
|
+
self.opt.mlp_ratio,
|
|
194
|
+
qkv_bias=True,
|
|
195
|
+
proj_bias=True,
|
|
196
|
+
ffn_bias=True,
|
|
197
|
+
init_values=0.01,
|
|
198
|
+
qk_norm=True,
|
|
199
|
+
)
|
|
200
|
+
for _ in range(self.opt.enc_depth)
|
|
201
|
+
]
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
self.use_multiscale = self.opt.use_multiscale_encoder
|
|
205
|
+
self.use_latent_bottleneck = self.opt.use_latent_bottleneck
|
|
206
|
+
|
|
207
|
+
if self.use_multiscale:
|
|
208
|
+
self.multiscale_layers = self.opt.multiscale_encoder_layers
|
|
209
|
+
self.multiscale_norms = nn.ModuleList(
|
|
210
|
+
[nn.LayerNorm(self.opt.enc_embed_dim) for _ in self.multiscale_layers]
|
|
211
|
+
)
|
|
212
|
+
encoder_feature_dim = self.opt.enc_embed_dim * len(self.multiscale_layers)
|
|
213
|
+
else:
|
|
214
|
+
self.encoder_norm = nn.LayerNorm(self.opt.enc_embed_dim)
|
|
215
|
+
encoder_feature_dim = self.opt.enc_embed_dim
|
|
216
|
+
|
|
217
|
+
if not self.use_latent_bottleneck:
|
|
218
|
+
self.kv_proj = nn.Linear(
|
|
219
|
+
encoder_feature_dim, self.opt.enc_embed_dim * 2, bias=True
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
self.k_proj_norm = nn.LayerNorm(
|
|
223
|
+
self.opt.enc_embed_dim // self.opt.enc_num_heads
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if self.use_latent_bottleneck:
|
|
227
|
+
self.latents = nn.Parameter(
|
|
228
|
+
torch.randn(self.opt.num_latents, self.opt.enc_embed_dim) * 0.02
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
self.latent_feature_kv_proj = nn.Linear(
|
|
232
|
+
encoder_feature_dim, self.opt.enc_embed_dim * 2, bias=True
|
|
233
|
+
)
|
|
234
|
+
self.latent_feature_k_norm = nn.LayerNorm(
|
|
235
|
+
self.opt.enc_embed_dim // self.opt.enc_num_heads
|
|
236
|
+
)
|
|
237
|
+
self.latent_blocks = nn.ModuleList(
|
|
238
|
+
[
|
|
239
|
+
DecoderBlock(
|
|
240
|
+
self.opt.enc_embed_dim,
|
|
241
|
+
self.opt.enc_num_heads,
|
|
242
|
+
self.opt.mlp_ratio,
|
|
243
|
+
qkv_bias=True,
|
|
244
|
+
ffn_bias=True,
|
|
245
|
+
init_values=0.01,
|
|
246
|
+
qk_norm=True,
|
|
247
|
+
)
|
|
248
|
+
for _ in range(self.opt.latent_cross_attn_depth)
|
|
249
|
+
]
|
|
250
|
+
)
|
|
251
|
+
self.latent_kv_proj = nn.Linear(
|
|
252
|
+
self.opt.enc_embed_dim, self.opt.enc_embed_dim * 2, bias=True
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
self.decoder_blocks = nn.ModuleList(
|
|
256
|
+
[
|
|
257
|
+
DecoderBlock(
|
|
258
|
+
self.opt.enc_embed_dim,
|
|
259
|
+
self.opt.enc_num_heads,
|
|
260
|
+
self.opt.mlp_ratio,
|
|
261
|
+
qkv_bias=True,
|
|
262
|
+
ffn_bias=True,
|
|
263
|
+
init_values=(
|
|
264
|
+
self.opt.dec_init_values
|
|
265
|
+
if self.opt.dec_init_values is not None
|
|
266
|
+
else 5e-3 * self.opt.gs_token_std
|
|
267
|
+
),
|
|
268
|
+
qk_norm=True,
|
|
269
|
+
)
|
|
270
|
+
for _ in range(self.opt.dec_depth)
|
|
271
|
+
]
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def _encode_features(self, image_features: torch.Tensor) -> torch.Tensor:
|
|
275
|
+
if self.use_multiscale:
|
|
276
|
+
x = image_features
|
|
277
|
+
features = []
|
|
278
|
+
for i, block in enumerate(self.encoder):
|
|
279
|
+
x = block(x)
|
|
280
|
+
if i in self.multiscale_layers:
|
|
281
|
+
features.append(self.multiscale_norms[len(features)](x))
|
|
282
|
+
return torch.cat(features, dim=-1)
|
|
283
|
+
image_features = self.encoder(image_features)
|
|
284
|
+
return self.encoder_norm(image_features)
|
|
285
|
+
|
|
286
|
+
def _features_to_kv(
|
|
287
|
+
self, image_features: torch.Tensor
|
|
288
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
289
|
+
image_feature_keys, image_feature_values = _split_kv(
|
|
290
|
+
self.kv_proj(image_features), self.opt.enc_num_heads
|
|
291
|
+
)
|
|
292
|
+
image_feature_keys = self.k_proj_norm(image_feature_keys)
|
|
293
|
+
return image_feature_keys, image_feature_values
|
|
294
|
+
|
|
295
|
+
def _features_to_scene_latents(self, image_features: torch.Tensor) -> torch.Tensor:
|
|
296
|
+
feat_keys, feat_values = _split_kv(
|
|
297
|
+
self.latent_feature_kv_proj(image_features), self.opt.enc_num_heads
|
|
298
|
+
)
|
|
299
|
+
feat_keys = self.latent_feature_k_norm(feat_keys)
|
|
300
|
+
|
|
301
|
+
latents = self.latents.unsqueeze(0).expand(image_features.shape[0], -1, -1)
|
|
302
|
+
for block in self.latent_blocks:
|
|
303
|
+
latents = block(gs_tokens=latents, keys=feat_keys, values=feat_values)
|
|
304
|
+
|
|
305
|
+
return latents
|
|
306
|
+
|
|
307
|
+
def _latents_to_kv(
|
|
308
|
+
self, latents: torch.Tensor
|
|
309
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
310
|
+
latent_keys, latent_values = _split_kv(
|
|
311
|
+
self.latent_kv_proj(latents), self.opt.enc_num_heads
|
|
312
|
+
)
|
|
313
|
+
latent_keys = self.k_proj_norm(latent_keys)
|
|
314
|
+
return latent_keys, latent_values
|
|
315
|
+
|
|
316
|
+
def _encode_to_kv(
|
|
317
|
+
self, image_features: torch.Tensor
|
|
318
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
319
|
+
image_features = self._encode_features(image_features)
|
|
320
|
+
if self.use_latent_bottleneck:
|
|
321
|
+
latents = self._features_to_scene_latents(image_features)
|
|
322
|
+
return self._latents_to_kv(latents)
|
|
323
|
+
return self._features_to_kv(image_features)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Modified by the libgs contributors, 2026, from tokengs/models/tokengs.py of
|
|
17
|
+
# https://github.com/nv-tlabs/TokenGS (b16269c): the feed-forward inference path
|
|
18
|
+
# (`forward_reconstruction`) as a `PyTorchModelHubMixin` module built from keyword
|
|
19
|
+
# arguments. Removed the renderer, losses, LPIPS, test-time tuning, and time /
|
|
20
|
+
# dynamic-token conditioning; einops replaced by the equivalent reshape / permute.
|
|
21
|
+
# Parameter names are unchanged, so upstream checkpoints load as they are.
|
|
22
|
+
|
|
23
|
+
"""The TokenGS network: input views -> Gaussians in the model frame."""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import fields
|
|
28
|
+
from functools import partial
|
|
29
|
+
|
|
30
|
+
import torch
|
|
31
|
+
import torch.nn as nn
|
|
32
|
+
from huggingface_hub import PyTorchModelHubMixin
|
|
33
|
+
|
|
34
|
+
from .activations import ClipActivationHead
|
|
35
|
+
from .attention import PatchEmbed
|
|
36
|
+
from .enc_dec import EncDecBackbone, TokenGSConfig
|
|
37
|
+
|
|
38
|
+
_CONFIG_FIELDS = frozenset(f.name for f in fields(TokenGSConfig))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TokenGS(nn.Module, PyTorchModelHubMixin):
|
|
42
|
+
"""
|
|
43
|
+
TokenGS model with encoder-decoder architecture: learnable Gaussian tokens
|
|
44
|
+
cross-attend to the encoded input views and decode to 3D Gaussians.
|
|
45
|
+
|
|
46
|
+
Input (from `TokenGSProcessor`): {"images": [B, V, 3, H, W] ImageNet-normalised
|
|
47
|
+
RGB, "plucker": [B, V, 6, H, W]}. Output: Gaussians [B, N, 14] in the model
|
|
48
|
+
frame -- xyz, opacity, scale, rotation (wxyz), rgb -- with
|
|
49
|
+
N = num_gs_tokens * dec_patch_size ** 2. The arguments are those of
|
|
50
|
+
`TokenGSConfig`.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
patch_size: int = 8,
|
|
56
|
+
dec_patch_size: int | None = None,
|
|
57
|
+
enc_depth: int = 3,
|
|
58
|
+
dec_depth: int = 12,
|
|
59
|
+
enc_embed_dim: int = 1024,
|
|
60
|
+
enc_num_heads: int = 16,
|
|
61
|
+
mlp_ratio: float = 4,
|
|
62
|
+
clip_head_readout_std: float = 0.002,
|
|
63
|
+
clip_head_z_init: float | None = None,
|
|
64
|
+
dec_init_values: float | None = None,
|
|
65
|
+
gaussian_scale_cap: float = 0.075,
|
|
66
|
+
opacity_bias: float = 2.0,
|
|
67
|
+
gaussian_z_offset: float = 1.0,
|
|
68
|
+
num_gs_tokens: int = 1024,
|
|
69
|
+
token_dim: int = 1024,
|
|
70
|
+
gs_token_std: float = 1e-2,
|
|
71
|
+
use_multiscale_encoder: bool = False,
|
|
72
|
+
multiscale_encoder_layers: tuple[int, ...] | list[int] = (5, 7, 9, 11),
|
|
73
|
+
use_latent_bottleneck: bool = False,
|
|
74
|
+
num_latents: int = 4096,
|
|
75
|
+
latent_cross_attn_depth: int = 12,
|
|
76
|
+
):
|
|
77
|
+
arguments = {k: v for k, v in locals().items() if k in _CONFIG_FIELDS}
|
|
78
|
+
super().__init__()
|
|
79
|
+
self.opt = TokenGSConfig(**arguments)
|
|
80
|
+
|
|
81
|
+
norm_layer_factory = partial(nn.LayerNorm, bias=True)
|
|
82
|
+
|
|
83
|
+
self.patch_embed = PatchEmbed(
|
|
84
|
+
patch_size=self.opt.patch_size,
|
|
85
|
+
in_chans=3,
|
|
86
|
+
embed_dim=self.opt.enc_embed_dim,
|
|
87
|
+
norm_layer=norm_layer_factory,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self.patch_plucker_embed = PatchEmbed(
|
|
91
|
+
patch_size=self.opt.patch_size,
|
|
92
|
+
in_chans=6,
|
|
93
|
+
embed_dim=self.opt.enc_embed_dim,
|
|
94
|
+
norm_layer=norm_layer_factory,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Encoder-decoder architecture
|
|
98
|
+
self.enc_dec_backbone = EncDecBackbone(self.opt)
|
|
99
|
+
|
|
100
|
+
# Activation head (always clip)
|
|
101
|
+
self.activation_head = ClipActivationHead(self.opt)
|
|
102
|
+
|
|
103
|
+
# Learnable GS tokens
|
|
104
|
+
self.gs_tokens = nn.Parameter(
|
|
105
|
+
self.opt.gs_token_std
|
|
106
|
+
* torch.randn(self.opt.num_gs_tokens, self.opt.token_dim)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def _embed_encoder_input(
|
|
110
|
+
self, images_rgb: torch.Tensor, plucker: torch.Tensor
|
|
111
|
+
) -> torch.Tensor:
|
|
112
|
+
B, V, _, H, W = images_rgb.shape
|
|
113
|
+
height = int(H // self.opt.patch_size)
|
|
114
|
+
width = int(W // self.opt.patch_size)
|
|
115
|
+
|
|
116
|
+
assert (
|
|
117
|
+
height * self.opt.patch_size == H
|
|
118
|
+
), f"H={H} must be divisible by patch_size={self.opt.patch_size}"
|
|
119
|
+
assert (
|
|
120
|
+
width * self.opt.patch_size == W
|
|
121
|
+
), f"W={W} must be divisible by patch_size={self.opt.patch_size}"
|
|
122
|
+
|
|
123
|
+
# Reshape for tokenization
|
|
124
|
+
images_rgb_reshaped = images_rgb.reshape(B * V, 3, H, W)
|
|
125
|
+
plucker_reshaped = plucker.reshape(B * V, 6, H, W)
|
|
126
|
+
|
|
127
|
+
# Embed RGB patches
|
|
128
|
+
x = self.patch_embed(images_rgb_reshaped)
|
|
129
|
+
# Add Plucker embeddings
|
|
130
|
+
x_plucker_emb = self.patch_plucker_embed(plucker_reshaped)
|
|
131
|
+
x = x + x_plucker_emb # B*V, N, C
|
|
132
|
+
|
|
133
|
+
# "(b v) n c -> b (v n) c": one sequence per batch
|
|
134
|
+
return x.reshape(B, V * x.shape[1], x.shape[2])
|
|
135
|
+
|
|
136
|
+
def forward_encoder(
|
|
137
|
+
self, images_rgb: torch.Tensor, plucker: torch.Tensor
|
|
138
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
139
|
+
"""Encode input views into keys and values for the decoder's
|
|
140
|
+
cross-attention, each [B, heads, tokens, head_dim]."""
|
|
141
|
+
x = self._embed_encoder_input(images_rgb, plucker)
|
|
142
|
+
return self.enc_dec_backbone._encode_to_kv(x)
|
|
143
|
+
|
|
144
|
+
def get_gs_tokens(self, batch_size: int) -> torch.Tensor:
|
|
145
|
+
"""Initial GS tokens [B, num_gs_tokens, C]."""
|
|
146
|
+
return self.gs_tokens.unsqueeze(0).expand(batch_size, -1, -1).clone()
|
|
147
|
+
|
|
148
|
+
def forward_decoder(
|
|
149
|
+
self,
|
|
150
|
+
keys: torch.Tensor,
|
|
151
|
+
values: torch.Tensor,
|
|
152
|
+
gs_tokens: torch.Tensor | None = None,
|
|
153
|
+
) -> torch.Tensor:
|
|
154
|
+
"""Keys / values -> Gaussians [B, N, 14]."""
|
|
155
|
+
if gs_tokens is None:
|
|
156
|
+
gs_tokens = self.get_gs_tokens(batch_size=keys.shape[0])
|
|
157
|
+
|
|
158
|
+
for layer in self.enc_dec_backbone.decoder_blocks:
|
|
159
|
+
gs_tokens = layer(gs_tokens=gs_tokens, keys=keys, values=values)
|
|
160
|
+
|
|
161
|
+
# Convert to Gaussians
|
|
162
|
+
gaussians = self.activation_head(gs_tokens)
|
|
163
|
+
|
|
164
|
+
# give gaussians an offset to the z axis so it is visible when initialized
|
|
165
|
+
gaussians[..., 2] = gaussians[..., 2] + self.opt.gaussian_z_offset
|
|
166
|
+
|
|
167
|
+
return gaussians
|
|
168
|
+
|
|
169
|
+
def forward(self, model_input: dict[str, torch.Tensor]) -> torch.Tensor:
|
|
170
|
+
keys, values = self.forward_encoder(
|
|
171
|
+
model_input["images"], model_input["plucker"]
|
|
172
|
+
)
|
|
173
|
+
return self.forward_decoder(keys, values)
|