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.
Files changed (63) hide show
  1. ffgs/__init__.py +39 -0
  2. ffgs/dynamic.py +239 -0
  3. ffgs/geometry.py +418 -0
  4. ffgs/image.py +204 -0
  5. ffgs/models/__init__.py +2 -0
  6. ffgs/models/anysplat/LICENSE +1120 -0
  7. ffgs/models/anysplat/__init__.py +49 -0
  8. ffgs/models/anysplat/encoder.py +372 -0
  9. ffgs/models/anysplat/evaluation.py +98 -0
  10. ffgs/models/anysplat/gaussian_adapter.py +96 -0
  11. ffgs/models/anysplat/gs_head.py +166 -0
  12. ffgs/models/anysplat/modeling.py +95 -0
  13. ffgs/models/anysplat/processing.py +249 -0
  14. ffgs/models/anysplat/vggt/__init__.py +0 -0
  15. ffgs/models/anysplat/vggt/heads/__init__.py +0 -0
  16. ffgs/models/anysplat/vggt/heads/camera_head.py +181 -0
  17. ffgs/models/anysplat/vggt/heads/dpt_head.py +612 -0
  18. ffgs/models/anysplat/vggt/heads/head_act.py +134 -0
  19. ffgs/models/anysplat/vggt/heads/utils.py +126 -0
  20. ffgs/models/anysplat/vggt/layers/__init__.py +28 -0
  21. ffgs/models/anysplat/vggt/layers/attention.py +92 -0
  22. ffgs/models/anysplat/vggt/layers/block.py +157 -0
  23. ffgs/models/anysplat/vggt/layers/drop_path.py +42 -0
  24. ffgs/models/anysplat/vggt/layers/layer_scale.py +32 -0
  25. ffgs/models/anysplat/vggt/layers/mlp.py +45 -0
  26. ffgs/models/anysplat/vggt/layers/patch_embed.py +106 -0
  27. ffgs/models/anysplat/vggt/layers/rope.py +211 -0
  28. ffgs/models/anysplat/vggt/layers/swiglu_ffn.py +77 -0
  29. ffgs/models/anysplat/vggt/layers/vision_transformer.py +463 -0
  30. ffgs/models/anysplat/vggt/models/__init__.py +0 -0
  31. ffgs/models/anysplat/vggt/models/aggregator.py +398 -0
  32. ffgs/models/anysplat/vggt/utils/__init__.py +0 -0
  33. ffgs/models/anysplat/vggt/utils/geometry.py +230 -0
  34. ffgs/models/anysplat/vggt/utils/pose_enc.py +144 -0
  35. ffgs/models/anysplat/vggt/utils/rotation.py +148 -0
  36. ffgs/models/tokengs/__init__.py +43 -0
  37. ffgs/models/tokengs/activations.py +135 -0
  38. ffgs/models/tokengs/attention.py +189 -0
  39. ffgs/models/tokengs/enc_dec.py +323 -0
  40. ffgs/models/tokengs/modeling.py +173 -0
  41. ffgs/models/tokengs/processing.py +339 -0
  42. ffgs/pipeline.py +341 -0
  43. ffgs/processor.py +236 -0
  44. ffgs/registry.py +132 -0
  45. ffgs/render.py +111 -0
  46. ffgs/types.py +291 -0
  47. ffgs/zoo/__init__.py +234 -0
  48. ffgs/zoo/anysplat/default.json +34 -0
  49. ffgs/zoo/tokengs/dl3dv-2v.json +29 -0
  50. ffgs/zoo/tokengs/dl3dv-4v.json +29 -0
  51. ffgs/zoo/tokengs/dl3dv-6v.json +29 -0
  52. ffgs/zoo/tokengs/dl3dv-base.json +29 -0
  53. ffgs/zoo/tokengs/dl3dv-latent-2v-lpips.json +48 -0
  54. ffgs/zoo/tokengs/dl3dv-latent-2v-ssim.json +48 -0
  55. ffgs/zoo/tokengs/dl3dv-latent-4v-lpips.json +48 -0
  56. ffgs/zoo/tokengs/dl3dv-latent-4v-ssim.json +48 -0
  57. ffgs/zoo/tokengs/dl3dv-latent-6v-lpips.json +48 -0
  58. ffgs/zoo/tokengs/dl3dv-latent-6v-ssim.json +48 -0
  59. ffgs-0.1.0.dev0.dist-info/METADATA +320 -0
  60. ffgs-0.1.0.dev0.dist-info/RECORD +63 -0
  61. ffgs-0.1.0.dev0.dist-info/WHEEL +4 -0
  62. ffgs-0.1.0.dev0.dist-info/licenses/LICENSE +237 -0
  63. ffgs-0.1.0.dev0.dist-info/licenses/src/ffgs/models/anysplat/LICENSE +1120 -0
@@ -0,0 +1,144 @@
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/utils/pose_enc.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
+ import torch
15
+
16
+ from .rotation import mat_to_quat, quat_to_mat
17
+
18
+
19
+ def extri_intri_to_pose_encoding(
20
+ extrinsics,
21
+ intrinsics,
22
+ image_size_hw=None, # e.g., (256, 512)
23
+ pose_encoding_type="absT_quaR_FoV",
24
+ ):
25
+ """Convert camera extrinsics and intrinsics to a compact pose encoding.
26
+
27
+ This function transforms camera parameters into a unified pose encoding format,
28
+ which can be used for various downstream tasks like pose prediction or representation.
29
+
30
+ Args:
31
+ extrinsics (torch.Tensor): Camera extrinsic parameters with shape BxSx3x4,
32
+ where B is batch size and S is sequence length.
33
+ In OpenCV coordinate system (x-right, y-down, z-forward), representing camera from world transformation.
34
+ The format is [R|t] where R is a 3x3 rotation matrix and t is a 3x1 translation vector.
35
+ intrinsics (torch.Tensor): Camera intrinsic parameters with shape BxSx3x3.
36
+ Defined in pixels, with format:
37
+ [[fx, 0, cx],
38
+ [0, fy, cy],
39
+ [0, 0, 1]]
40
+ where fx, fy are focal lengths and (cx, cy) is the principal point
41
+ image_size_hw (tuple): Tuple of (height, width) of the image in pixels.
42
+ Required for computing field of view values. For example: (256, 512).
43
+ pose_encoding_type (str): Type of pose encoding to use. Currently only
44
+ supports "absT_quaR_FoV" (absolute translation, quaternion rotation, field of view).
45
+
46
+ Returns:
47
+ torch.Tensor: Encoded camera pose parameters with shape BxSx9.
48
+ For "absT_quaR_FoV" type, the 9 dimensions are:
49
+ - [:3] = absolute translation vector T (3D)
50
+ - [3:7] = rotation as quaternion quat (4D)
51
+ - [7:] = field of view (2D)
52
+ """
53
+
54
+ # extrinsics: BxSx3x4
55
+ # intrinsics: BxSx3x3
56
+
57
+ if pose_encoding_type == "absT_quaR_FoV":
58
+ R = extrinsics[:, :, :3, :3] # BxSx3x3
59
+ T = extrinsics[:, :, :3, 3] # BxSx3
60
+
61
+ quat = mat_to_quat(R)
62
+ # Note the order of h and w here
63
+ H, W = image_size_hw
64
+ fov_h = 2 * torch.atan((H / 2) / intrinsics[..., 1, 1])
65
+ fov_w = 2 * torch.atan((W / 2) / intrinsics[..., 0, 0])
66
+ pose_encoding = torch.cat(
67
+ [T, quat, fov_h[..., None], fov_w[..., None]], dim=-1
68
+ ).float()
69
+ else:
70
+ raise NotImplementedError
71
+
72
+ return pose_encoding
73
+
74
+
75
+ def pose_encoding_to_extri_intri(
76
+ pose_encoding,
77
+ image_size_hw=None, # e.g., (256, 512)
78
+ pose_encoding_type="absT_quaR_FoV",
79
+ build_intrinsics=True,
80
+ ):
81
+ """Convert a pose encoding back to camera extrinsics and intrinsics.
82
+
83
+ This function performs the inverse operation of extri_intri_to_pose_encoding,
84
+ reconstructing the full camera parameters from the compact encoding.
85
+
86
+ Args:
87
+ pose_encoding (torch.Tensor): Encoded camera pose parameters with shape BxSx9,
88
+ where B is batch size and S is sequence length.
89
+ For "absT_quaR_FoV" type, the 9 dimensions are:
90
+ - [:3] = absolute translation vector T (3D)
91
+ - [3:7] = rotation as quaternion quat (4D)
92
+ - [7:] = field of view (2D)
93
+ image_size_hw (tuple): Tuple of (height, width) of the image in pixels.
94
+ Required for reconstructing intrinsics from field of view values.
95
+ For example: (256, 512).
96
+ pose_encoding_type (str): Type of pose encoding used. Currently only
97
+ supports "absT_quaR_FoV" (absolute translation, quaternion rotation, field of view).
98
+ build_intrinsics (bool): Whether to reconstruct the intrinsics matrix.
99
+ If False, only extrinsics are returned and intrinsics will be None.
100
+
101
+ Returns:
102
+ tuple: (extrinsics, intrinsics)
103
+ - extrinsics (torch.Tensor): Camera extrinsic parameters with shape BxSx3x4.
104
+ In OpenCV coordinate system (x-right, y-down, z-forward), representing camera from world
105
+ transformation. The format is [R|t] where R is a 3x3 rotation matrix and t is
106
+ a 3x1 translation vector.
107
+ - intrinsics (torch.Tensor or None): Camera intrinsic parameters with shape BxSx3x3,
108
+ or None if build_intrinsics is False. Defined in pixels, with format:
109
+ [[fx, 0, cx],
110
+ [0, fy, cy],
111
+ [0, 0, 1]]
112
+ where fx, fy are focal lengths and (cx, cy) is the principal point,
113
+ assumed to be at the center of the image (W/2, H/2).
114
+ """
115
+
116
+ intrinsics = None
117
+
118
+ if pose_encoding_type == "absT_quaR_FoV":
119
+ T = pose_encoding[..., :3]
120
+ quat = pose_encoding[..., 3:7]
121
+ fov_h = pose_encoding[..., 7]
122
+ fov_w = pose_encoding[..., 8]
123
+
124
+ R = quat_to_mat(quat)
125
+ extrinsics = torch.cat([R, T[..., None]], dim=-1)
126
+
127
+ if build_intrinsics:
128
+ H, W = image_size_hw
129
+ fy = (H / 2.0) / (torch.tan(fov_h / 2.0) + 1e-3)
130
+ fx = (W / 2.0) / (torch.tan(fov_w / 2.0) + 1e-3)
131
+ intrinsics = torch.zeros(
132
+ pose_encoding.shape[:2] + (3, 3),
133
+ device=pose_encoding.device,
134
+ dtype=pose_encoding.dtype,
135
+ )
136
+ intrinsics[..., 0, 0] = fx
137
+ intrinsics[..., 1, 1] = fy
138
+ intrinsics[..., 0, 2] = W / 2
139
+ intrinsics[..., 1, 2] = H / 2
140
+ intrinsics[..., 2, 2] = 1.0 # Set the homogeneous coordinate to 1
141
+ else:
142
+ raise NotImplementedError
143
+
144
+ return extrinsics, intrinsics
@@ -0,0 +1,148 @@
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/utils/rotation.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
+ # Modified from PyTorch3D, https://github.com/facebookresearch/pytorch3d
15
+
16
+ import torch
17
+ import torch.nn.functional as F
18
+
19
+
20
+ def quat_to_mat(quaternions: torch.Tensor) -> torch.Tensor:
21
+ """
22
+ Quaternion Order: XYZW or say ijkr, scalar-last
23
+
24
+ Convert rotations given as quaternions to rotation matrices.
25
+ Args:
26
+ quaternions: quaternions with real part last,
27
+ as tensor of shape (..., 4).
28
+
29
+ Returns:
30
+ Rotation matrices as tensor of shape (..., 3, 3).
31
+ """
32
+ i, j, k, r = torch.unbind(quaternions, -1)
33
+ # pyre-fixme[58]: `/` is not supported for operand types `float` and `Tensor`.
34
+ two_s = 2.0 / (quaternions * quaternions).sum(-1)
35
+
36
+ o = torch.stack(
37
+ (
38
+ 1 - two_s * (j * j + k * k),
39
+ two_s * (i * j - k * r),
40
+ two_s * (i * k + j * r),
41
+ two_s * (i * j + k * r),
42
+ 1 - two_s * (i * i + k * k),
43
+ two_s * (j * k - i * r),
44
+ two_s * (i * k - j * r),
45
+ two_s * (j * k + i * r),
46
+ 1 - two_s * (i * i + j * j),
47
+ ),
48
+ -1,
49
+ )
50
+ return o.reshape(quaternions.shape[:-1] + (3, 3))
51
+
52
+
53
+ def mat_to_quat(matrix: torch.Tensor) -> torch.Tensor:
54
+ """
55
+ Convert rotations given as rotation matrices to quaternions.
56
+
57
+ Args:
58
+ matrix: Rotation matrices as tensor of shape (..., 3, 3).
59
+
60
+ Returns:
61
+ quaternions with real part last, as tensor of shape (..., 4).
62
+ Quaternion Order: XYZW or say ijkr, scalar-last
63
+ """
64
+ if matrix.size(-1) != 3 or matrix.size(-2) != 3:
65
+ raise ValueError(f"Invalid rotation matrix shape {matrix.shape}.")
66
+
67
+ batch_dim = matrix.shape[:-2]
68
+ m00, m01, m02, m10, m11, m12, m20, m21, m22 = torch.unbind(
69
+ matrix.reshape(batch_dim + (9,)), dim=-1
70
+ )
71
+
72
+ q_abs = _sqrt_positive_part(
73
+ torch.stack(
74
+ [
75
+ 1.0 + m00 + m11 + m22,
76
+ 1.0 + m00 - m11 - m22,
77
+ 1.0 - m00 + m11 - m22,
78
+ 1.0 - m00 - m11 + m22,
79
+ ],
80
+ dim=-1,
81
+ )
82
+ )
83
+
84
+ # we produce the desired quaternion multiplied by each of r, i, j, k
85
+ quat_by_rijk = torch.stack(
86
+ [
87
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
88
+ # `int`.
89
+ torch.stack([q_abs[..., 0] ** 2, m21 - m12, m02 - m20, m10 - m01], dim=-1),
90
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
91
+ # `int`.
92
+ torch.stack([m21 - m12, q_abs[..., 1] ** 2, m10 + m01, m02 + m20], dim=-1),
93
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
94
+ # `int`.
95
+ torch.stack([m02 - m20, m10 + m01, q_abs[..., 2] ** 2, m12 + m21], dim=-1),
96
+ # pyre-fixme[58]: `**` is not supported for operand types `Tensor` and
97
+ # `int`.
98
+ torch.stack([m10 - m01, m20 + m02, m21 + m12, q_abs[..., 3] ** 2], dim=-1),
99
+ ],
100
+ dim=-2,
101
+ )
102
+
103
+ # We floor here at 0.1 but the exact level is not important; if q_abs is small,
104
+ # the candidate won't be picked.
105
+ flr = torch.tensor(0.1).to(dtype=q_abs.dtype, device=q_abs.device)
106
+ quat_candidates = quat_by_rijk / (2.0 * q_abs[..., None].max(flr))
107
+
108
+ # if not for numerical problems, quat_candidates[i] should be same (up to a sign),
109
+ # forall i; we pick the best-conditioned one (with the largest denominator)
110
+ out = quat_candidates[
111
+ F.one_hot(q_abs.argmax(dim=-1), num_classes=4) > 0.5, :
112
+ ].reshape(batch_dim + (4,))
113
+
114
+ # Convert from rijk to ijkr
115
+ out = out[..., [1, 2, 3, 0]]
116
+
117
+ out = standardize_quaternion(out)
118
+
119
+ return out
120
+
121
+
122
+ def _sqrt_positive_part(x: torch.Tensor) -> torch.Tensor:
123
+ """
124
+ Returns torch.sqrt(torch.max(0, x))
125
+ but with a zero subgradient where x is 0.
126
+ """
127
+ ret = torch.zeros_like(x)
128
+ positive_mask = x > 0
129
+ if torch.is_grad_enabled():
130
+ ret[positive_mask] = torch.sqrt(x[positive_mask])
131
+ else:
132
+ ret = torch.where(positive_mask, torch.sqrt(x), ret)
133
+ return ret
134
+
135
+
136
+ def standardize_quaternion(quaternions: torch.Tensor) -> torch.Tensor:
137
+ """
138
+ Convert a unit quaternion to a standard form: one in which the real
139
+ part is non negative.
140
+
141
+ Args:
142
+ quaternions: Quaternions with real part last,
143
+ as tensor of shape (..., 4).
144
+
145
+ Returns:
146
+ Standardized quaternions as tensor of shape (..., 4).
147
+ """
148
+ return torch.where(quaternions[..., 3:4] < 0, -quaternions, quaternions)
@@ -0,0 +1,43 @@
1
+ """TokenGS (Ren, Tyszkiewicz, Huang, Gojcic; CVPR 2026), vendored from the official
2
+ Apache-2.0 code at https://github.com/nv-tlabs/TokenGS (b16269c).
3
+
4
+ `model_type` "tokengs". Imported on first use: `import ffgs` does not import it.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from ...registry import ModelSpec, register_model
12
+ from .modeling import TokenGS
13
+ from .processing import TokenGSProcessor, TokenGSProcessorConfig
14
+
15
+ MODEL_TYPE = "tokengs"
16
+
17
+
18
+ def convert_state_dict(checkpoint: dict[str, Any]) -> dict[str, Any]:
19
+ """An official checkpoint (the upstream module's state dict) -> `TokenGS`.
20
+
21
+ The parameter names are the upstream ones; only the LPIPS training loss that
22
+ upstream checkpoints may carry is dropped, as the upstream evaluation does.
23
+ """
24
+ return {k: v for k, v in checkpoint.items() if "lpips_loss" not in k}
25
+
26
+
27
+ SPEC = register_model(
28
+ ModelSpec(
29
+ model_type=MODEL_TYPE,
30
+ model_cls=TokenGS,
31
+ processor_cls=TokenGSProcessor,
32
+ convert_state_dict=convert_state_dict,
33
+ )
34
+ )
35
+
36
+ __all__ = [
37
+ "MODEL_TYPE",
38
+ "SPEC",
39
+ "TokenGS",
40
+ "TokenGSProcessor",
41
+ "TokenGSProcessorConfig",
42
+ "convert_state_dict",
43
+ ]
@@ -0,0 +1,135 @@
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/activations.py of
17
+ # https://github.com/nv-tlabs/TokenGS (b16269c): the clip head only, taking a
18
+ # `TokenGSConfig`; einops replaced by the equivalent reshape / permute; the unused
19
+ # ray arguments and weight-decay flags removed. Parameter names are unchanged.
20
+
21
+ """Gaussian tokens -> activated Gaussian parameters."""
22
+
23
+ from __future__ import annotations
24
+
25
+ import math
26
+
27
+ import torch
28
+ import torch.nn as nn
29
+ import torch.nn.functional as F
30
+ from torch.nn.init import trunc_normal_
31
+
32
+ from .enc_dec import TokenGSConfig
33
+
34
+
35
+ class ClipActivationHead(nn.Module):
36
+ """
37
+ Clip-based activation head that uses exponential activations with hard
38
+ clipping. Uses a single deconv (linear) layer for all Gaussian components.
39
+ """
40
+
41
+ def __init__(self, opt: TokenGSConfig):
42
+ super().__init__()
43
+ self.opt = opt
44
+
45
+ self.num_gaussians_per_token = self.opt.dec_patch_size**2
46
+
47
+ scale_cap = self.opt.gaussian_scale_cap
48
+ self.scale_shift = 1 - math.log(scale_cap)
49
+ self.scale_cap = scale_cap
50
+
51
+ self.output_dims = 3 + 1 + 3 + 4 + 3 # x, y, z, opacity, scale, rotation, rgb
52
+
53
+ # Single linear layer (deconv) for all components
54
+ self.deconv = nn.Linear(
55
+ self.opt.enc_embed_dim,
56
+ self.output_dims * self.opt.dec_patch_size * self.opt.dec_patch_size,
57
+ bias=True,
58
+ )
59
+
60
+ self._init_weights(self.deconv)
61
+ if self.opt.clip_head_z_init is not None:
62
+ bias_val = math.log(1.0 + self.opt.clip_head_z_init)
63
+ with torch.no_grad():
64
+ self.deconv.bias.view(
65
+ self.opt.dec_patch_size * self.opt.dec_patch_size,
66
+ self.output_dims,
67
+ )[:, 2].fill_(bias_val)
68
+
69
+ def _init_weights(self, m):
70
+ """Initialize deconv weights"""
71
+ if isinstance(m, nn.Linear):
72
+ trunc_normal_(m.weight, std=self.opt.clip_head_readout_std)
73
+ if m.bias is not None:
74
+ m.bias.data.zero_()
75
+
76
+ def pos_act(self, x: torch.Tensor) -> torch.Tensor:
77
+ """Position activation without scaling factor (for xyz prediction)"""
78
+ pos = torch.sign(x) * (torch.expm1(torch.abs(x))) # inverse log transform
79
+ return pos
80
+
81
+ def scale_act(self, x: torch.Tensor) -> torch.Tensor:
82
+ """Scale activation with exponential and hard clipping"""
83
+ x = x - self.scale_shift
84
+ return torch.minimum(
85
+ torch.exp(x),
86
+ torch.tensor(self.scale_cap, device=x.device, dtype=x.dtype),
87
+ )
88
+
89
+ def opacity_act(self, x: torch.Tensor) -> torch.Tensor:
90
+ """Opacity activation with hard clipping"""
91
+ return torch.sigmoid(x - self.opt.opacity_bias)
92
+
93
+ def rot_act(self, x: torch.Tensor) -> torch.Tensor:
94
+ """Rotation normalization"""
95
+ return F.normalize(x, dim=-1)
96
+
97
+ def rgb_act(self, x: torch.Tensor) -> torch.Tensor:
98
+ """RGB activation (always tanh for clip-based head)"""
99
+ return 0.5 * torch.tanh(x) + 0.5
100
+
101
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
102
+ """
103
+ Convert tokens to Gaussians using a single deconv layer.
104
+
105
+ Args:
106
+ x: Input tokens [B, N, C]
107
+
108
+ Returns:
109
+ Gaussians [B, N * num_gaussians_per_token, 14]: xyz, opacity, scale,
110
+ rotation (wxyz), rgb
111
+ """
112
+ B = x.shape[0]
113
+
114
+ x = self.deconv(x) # [B, N, output_dims * P * P]
115
+ # "b n (p c) -> b c (n p)"
116
+ n = x.shape[1]
117
+ p = self.opt.dec_patch_size**2
118
+ x = x.reshape(B, n, p, -1).permute(0, 3, 1, 2).reshape(B, -1, n * p)
119
+
120
+ x = x.reshape(B, self.output_dims, -1) # B, output_dims, N * P * P
121
+ x = x.permute(0, 2, 1).contiguous() # B, N * P * P, output_dims
122
+
123
+ pos, rgb, scaling, rotation, opacity = x.split([3, 3, 3, 4, 1], dim=-1)
124
+ pos = self.pos_act(pos)
125
+
126
+ opacity = self.opacity_act(opacity)
127
+ scale = self.scale_act(scaling)
128
+ rotation = self.rot_act(rotation)
129
+ rgbs = self.rgb_act(rgb)
130
+
131
+ gaussians = torch.cat(
132
+ [pos, opacity, scale, rotation, rgbs], dim=-1
133
+ ) # [B, N, 14]
134
+
135
+ return gaussians
@@ -0,0 +1,189 @@
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/attention.py of
17
+ # https://github.com/nv-tlabs/TokenGS (b16269c): inference-only subset. Removed
18
+ # FlexAttention (and the global torch._dynamo settings made on import), RoPE and
19
+ # stochastic depth; parameter names are unchanged.
20
+
21
+ """Transformer building blocks of TokenGS."""
22
+
23
+ from __future__ import annotations
24
+
25
+ from collections.abc import Callable
26
+
27
+ import torch
28
+ import torch.nn.functional as F
29
+ from torch import Tensor, nn
30
+
31
+
32
+ class Mlp(nn.Module):
33
+ def __init__(
34
+ self,
35
+ in_features: int,
36
+ hidden_features: int | None = None,
37
+ out_features: int | None = None,
38
+ act_layer: Callable[..., nn.Module] = nn.GELU,
39
+ bias: bool = True,
40
+ ) -> None:
41
+ super().__init__()
42
+ out_features = out_features or in_features
43
+ hidden_features = hidden_features or in_features
44
+ self.fc1 = nn.Linear(in_features, hidden_features, bias=bias)
45
+ self.act = act_layer()
46
+ self.fc2 = nn.Linear(hidden_features, out_features, bias=bias)
47
+
48
+ def forward(self, x: Tensor) -> Tensor:
49
+ x = self.fc1(x)
50
+ x = self.act(x)
51
+ x = self.fc2(x)
52
+ return x
53
+
54
+
55
+ class Attention(nn.Module):
56
+ def __init__(
57
+ self,
58
+ dim: int,
59
+ num_heads: int = 8,
60
+ qkv_bias: bool = True,
61
+ proj_bias: bool = True,
62
+ norm_layer: Callable[..., nn.Module] = nn.LayerNorm,
63
+ qk_norm: bool = False,
64
+ ) -> None:
65
+ super().__init__()
66
+ assert dim % num_heads == 0, "dim should be divisible by num_heads"
67
+ self.num_heads = num_heads
68
+ self.head_dim = dim // num_heads
69
+
70
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
71
+ self.q_norm = norm_layer(self.head_dim) if qk_norm else nn.Identity()
72
+ self.k_norm = norm_layer(self.head_dim) if qk_norm else nn.Identity()
73
+ self.proj = nn.Linear(dim, dim, bias=proj_bias)
74
+
75
+ def forward(self, x: Tensor) -> Tensor:
76
+ B, N, C = x.shape
77
+ qkv = (
78
+ self.qkv(x)
79
+ .reshape(B, N, 3, self.num_heads, self.head_dim)
80
+ .permute(2, 0, 3, 1, 4)
81
+ )
82
+ q, k, v = qkv.unbind(0)
83
+ q, k = self.q_norm(q), self.k_norm(k)
84
+
85
+ x = F.scaled_dot_product_attention(q, k, v)
86
+
87
+ x = x.transpose(1, 2).reshape(B, N, C)
88
+ x = self.proj(x)
89
+ return x
90
+
91
+
92
+ class LayerScale(nn.Module):
93
+ def __init__(self, dim: int, init_values: float | Tensor = 1e-5) -> None:
94
+ super().__init__()
95
+ self.gamma = nn.Parameter(init_values * torch.ones(dim))
96
+
97
+ def forward(self, x: Tensor) -> Tensor:
98
+ return x * self.gamma
99
+
100
+
101
+ class Block(nn.Module):
102
+ def __init__(
103
+ self,
104
+ dim: int,
105
+ num_heads: int,
106
+ mlp_ratio: float = 4.0,
107
+ qkv_bias: bool = True,
108
+ proj_bias: bool = True,
109
+ ffn_bias: bool = True,
110
+ init_values: float | Tensor | None = None,
111
+ act_layer: Callable[..., nn.Module] = nn.GELU,
112
+ norm_layer: Callable[..., nn.Module] = nn.LayerNorm,
113
+ qk_norm: bool = False,
114
+ ) -> None:
115
+ super().__init__()
116
+
117
+ self.norm1 = norm_layer(dim)
118
+ self.attn = Attention(
119
+ dim,
120
+ num_heads=num_heads,
121
+ qkv_bias=qkv_bias,
122
+ proj_bias=proj_bias,
123
+ qk_norm=qk_norm,
124
+ )
125
+ self.ls1 = (
126
+ LayerScale(dim, init_values=init_values) if init_values else nn.Identity()
127
+ )
128
+
129
+ self.norm2 = norm_layer(dim)
130
+ self.mlp = Mlp(
131
+ in_features=dim,
132
+ hidden_features=int(dim * mlp_ratio),
133
+ act_layer=act_layer,
134
+ bias=ffn_bias,
135
+ )
136
+ self.ls2 = (
137
+ LayerScale(dim, init_values=init_values) if init_values else nn.Identity()
138
+ )
139
+
140
+ def forward(self, x: Tensor) -> Tensor:
141
+ x = x + self.ls1(self.attn(self.norm1(x)))
142
+ x = x + self.ls2(self.mlp(self.norm2(x)))
143
+ return x
144
+
145
+
146
+ def make_2tuple(x: int | tuple[int, int]) -> tuple[int, int]:
147
+ if isinstance(x, tuple):
148
+ assert len(x) == 2
149
+ return x
150
+ assert isinstance(x, int)
151
+ return (x, x)
152
+
153
+
154
+ class PatchEmbed(nn.Module):
155
+ """2D image to patch embedding: (B, C, H, W) -> (B, N, D)."""
156
+
157
+ def __init__(
158
+ self,
159
+ patch_size: int | tuple[int, int] = 16,
160
+ in_chans: int = 3,
161
+ embed_dim: int = 768,
162
+ norm_layer: Callable[..., nn.Module] | None = None,
163
+ ) -> None:
164
+ super().__init__()
165
+ patch_HW = make_2tuple(patch_size)
166
+ self.patch_size = patch_HW
167
+ self.in_chans = in_chans
168
+ self.embed_dim = embed_dim
169
+
170
+ self.proj = nn.Conv2d(
171
+ in_chans, embed_dim, kernel_size=patch_HW, stride=patch_HW
172
+ )
173
+ self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()
174
+
175
+ def forward(self, x: Tensor) -> Tensor:
176
+ _, _, H, W = x.shape
177
+ patch_H, patch_W = self.patch_size
178
+
179
+ assert (
180
+ H % patch_H == 0
181
+ ), f"Input image height {H} is not a multiple of patch height {patch_H}"
182
+ assert (
183
+ W % patch_W == 0
184
+ ), f"Input image width {W} is not a multiple of patch width: {patch_W}"
185
+
186
+ x = self.proj(x)
187
+ x = x.flatten(2).transpose(1, 2)
188
+ x = self.norm(x)
189
+ return x