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
ffgs/image.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Adapted from the crop_shim of pixelSplat (https://github.com/dcharatan/pixelsplat):
|
|
2
|
+
# Copyright (c) 2023 David Charatan, Sizhe Li, Andrea Tagliasacchi, and Vincent Sitzmann
|
|
3
|
+
# SPDX-License-Identifier: MIT (see the pixelSplat section at the end of LICENSE)
|
|
4
|
+
|
|
5
|
+
"""Fitting input images to a model's input shape.
|
|
6
|
+
|
|
7
|
+
The default (`crop_mode="crop"`) is the rule most feed-forward 3DGS models are
|
|
8
|
+
trained with: scale to cover the shape, crop the middle, keep the normalised
|
|
9
|
+
principal point. `tests/test_image.py` pins it bit for bit to frozen reference
|
|
10
|
+
outputs (`tests/fixtures/`). `"pad"` scales to fit inside and pads instead
|
|
11
|
+
(nothing is cut off); `"none"` takes inputs already at the model's shape as they are.
|
|
12
|
+
|
|
13
|
+
Intrinsics are the normalised 3x3 (fx / W, cx / W, fy / H, cy / H). The centre crop
|
|
14
|
+
scales fx / fy and leaves the normalised principal point where it is -- the rule the
|
|
15
|
+
models were trained with, which assumes a centred principal point. Padding moves the
|
|
16
|
+
principal point with the content.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
import torch
|
|
25
|
+
import torchvision.transforms.functional as F
|
|
26
|
+
from PIL import Image
|
|
27
|
+
|
|
28
|
+
RESIZE_MODES = ("lanczos", "bilinear")
|
|
29
|
+
CROP_MODES = ("crop", "pad", "none")
|
|
30
|
+
|
|
31
|
+
# (images [..., 3, h, w], (h_out, w_out)) -> images [..., 3, h_out, w_out]
|
|
32
|
+
Resize = Callable[[torch.Tensor, tuple[int, int]], torch.Tensor]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def rescale(
|
|
36
|
+
images: torch.Tensor,
|
|
37
|
+
shape: tuple[int, int],
|
|
38
|
+
resize_mode: str = "lanczos",
|
|
39
|
+
) -> torch.Tensor:
|
|
40
|
+
"""[..., 3, h_in, w_in] float in [0, 1] -> [..., 3, h_out, w_out]."""
|
|
41
|
+
*batch, c, h, w = images.shape
|
|
42
|
+
images = images.reshape(-1, c, h, w)
|
|
43
|
+
h_out, w_out = shape
|
|
44
|
+
|
|
45
|
+
if resize_mode == "bilinear":
|
|
46
|
+
images_new = F.resize(
|
|
47
|
+
images,
|
|
48
|
+
(h_out, w_out),
|
|
49
|
+
interpolation=F.InterpolationMode.BILINEAR,
|
|
50
|
+
antialias=True,
|
|
51
|
+
)
|
|
52
|
+
elif resize_mode == "lanczos":
|
|
53
|
+
# PIL takes uint8 only, so this path quantises to 8 bit before resampling.
|
|
54
|
+
device, dtype = images.device, images.dtype
|
|
55
|
+
imgs_np = (images * 255).clip(min=0, max=255).to(torch.uint8)
|
|
56
|
+
imgs_np = imgs_np.permute(0, 2, 3, 1).contiguous().cpu().numpy()
|
|
57
|
+
rescaled_list = [
|
|
58
|
+
np.array(Image.fromarray(img).resize((w_out, h_out), Image.LANCZOS))
|
|
59
|
+
for img in imgs_np
|
|
60
|
+
]
|
|
61
|
+
images_new = torch.from_numpy(np.stack(rescaled_list) / 255.0)
|
|
62
|
+
images_new = images_new.to(dtype=dtype, device=device).permute(0, 3, 1, 2)
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"Unknown resize_mode {resize_mode!r}; expected one of {RESIZE_MODES}."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return images_new.reshape(*batch, c, h_out, w_out)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def center_crop(
|
|
72
|
+
images: torch.Tensor,
|
|
73
|
+
intrinsics: torch.Tensor,
|
|
74
|
+
shape: tuple[int, int],
|
|
75
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
76
|
+
*_, h_in, w_in = images.shape
|
|
77
|
+
h_out, w_out = shape
|
|
78
|
+
|
|
79
|
+
# Odd size differences induce half-pixel misalignments, as in training.
|
|
80
|
+
row = (h_in - h_out) // 2
|
|
81
|
+
col = (w_in - w_out) // 2
|
|
82
|
+
images = images[..., :, row : row + h_out, col : col + w_out]
|
|
83
|
+
|
|
84
|
+
intrinsics = intrinsics.clone()
|
|
85
|
+
intrinsics[..., 0, 0] *= w_in / w_out # fx
|
|
86
|
+
intrinsics[..., 1, 1] *= h_in / h_out # fy
|
|
87
|
+
return images, intrinsics
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def scaled_shape(
|
|
91
|
+
image_shape: tuple[int, int], shape: tuple[int, int], crop_mode: str
|
|
92
|
+
) -> tuple[int, int]:
|
|
93
|
+
"""The size an image is resized to before the crop / pad to `shape`."""
|
|
94
|
+
h_in, w_in = image_shape
|
|
95
|
+
h_out, w_out = shape
|
|
96
|
+
if crop_mode == "none":
|
|
97
|
+
return h_in, w_in
|
|
98
|
+
pick = max if crop_mode == "crop" else min
|
|
99
|
+
scale_factor = pick(h_out / h_in, w_out / w_in)
|
|
100
|
+
h_scaled = round(h_in * scale_factor)
|
|
101
|
+
w_scaled = round(w_in * scale_factor)
|
|
102
|
+
assert h_scaled == h_out or w_scaled == w_out
|
|
103
|
+
return h_scaled, w_scaled
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def rescale_and_crop(
|
|
107
|
+
images: torch.Tensor,
|
|
108
|
+
intrinsics: torch.Tensor,
|
|
109
|
+
shape: tuple[int, int],
|
|
110
|
+
resize_mode: str = "lanczos",
|
|
111
|
+
resize: Resize | None = None,
|
|
112
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
113
|
+
"""Scale so the image covers `shape`, then crop the middle.
|
|
114
|
+
|
|
115
|
+
`resize` replaces the built-in `resize_mode` kernels when given.
|
|
116
|
+
"""
|
|
117
|
+
scaled = scaled_shape(tuple(images.shape[-2:]), shape, "crop")
|
|
118
|
+
if resize is None:
|
|
119
|
+
images = rescale(images, scaled, resize_mode)
|
|
120
|
+
else:
|
|
121
|
+
images = resize(images, scaled)
|
|
122
|
+
return center_crop(images, intrinsics, shape)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def rescale_and_pad(
|
|
126
|
+
images: torch.Tensor,
|
|
127
|
+
intrinsics: torch.Tensor,
|
|
128
|
+
shape: tuple[int, int],
|
|
129
|
+
resize_mode: str = "lanczos",
|
|
130
|
+
resize: Resize | None = None,
|
|
131
|
+
pad_value: float = 0.0,
|
|
132
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
133
|
+
"""Scale so the image fits inside `shape`, then pad around it (centred)."""
|
|
134
|
+
image_shape = tuple(images.shape[-2:])
|
|
135
|
+
h_scaled, w_scaled = scaled_shape(image_shape, shape, "pad")
|
|
136
|
+
if resize is None:
|
|
137
|
+
images = rescale(images, (h_scaled, w_scaled), resize_mode)
|
|
138
|
+
else:
|
|
139
|
+
images = resize(images, (h_scaled, w_scaled))
|
|
140
|
+
h_out, w_out = shape
|
|
141
|
+
top, left = (h_out - h_scaled) // 2, (w_out - w_scaled) // 2
|
|
142
|
+
padded = images.new_full((*images.shape[:-2], h_out, w_out), pad_value)
|
|
143
|
+
padded[..., top : top + h_scaled, left : left + w_scaled] = images
|
|
144
|
+
return padded, fit_intrinsics(intrinsics, image_shape, shape, "pad")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def fit_intrinsics(
|
|
148
|
+
intrinsics: torch.Tensor,
|
|
149
|
+
image_shape: tuple[int, int],
|
|
150
|
+
shape: tuple[int, int],
|
|
151
|
+
crop_mode: str = "crop",
|
|
152
|
+
) -> torch.Tensor:
|
|
153
|
+
"""Normalised K for an `image_shape` image after `fit_images` to `shape`."""
|
|
154
|
+
check_crop_mode(crop_mode)
|
|
155
|
+
h_out, w_out = shape
|
|
156
|
+
if crop_mode == "none":
|
|
157
|
+
check_shape(image_shape, shape)
|
|
158
|
+
return intrinsics.clone()
|
|
159
|
+
h_scaled, w_scaled = scaled_shape(image_shape, shape, crop_mode)
|
|
160
|
+
k = intrinsics.clone()
|
|
161
|
+
k[..., 0, 0] *= w_scaled / w_out
|
|
162
|
+
k[..., 1, 1] *= h_scaled / h_out
|
|
163
|
+
if crop_mode == "pad":
|
|
164
|
+
top, left = (h_out - h_scaled) // 2, (w_out - w_scaled) // 2
|
|
165
|
+
k[..., 0, 2] = (k[..., 0, 2] * w_scaled + left) / w_out
|
|
166
|
+
k[..., 1, 2] = (k[..., 1, 2] * h_scaled + top) / h_out
|
|
167
|
+
return k
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def fit_images(
|
|
171
|
+
images: torch.Tensor,
|
|
172
|
+
intrinsics: torch.Tensor,
|
|
173
|
+
shape: tuple[int, int],
|
|
174
|
+
resize_mode: str = "lanczos",
|
|
175
|
+
crop_mode: str = "crop",
|
|
176
|
+
resize: Resize | None = None,
|
|
177
|
+
pad_value: float = 0.0,
|
|
178
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
179
|
+
"""[..., 3, h, w] images + normalised K -> `shape` by `crop_mode`."""
|
|
180
|
+
check_crop_mode(crop_mode)
|
|
181
|
+
shape = tuple(shape)
|
|
182
|
+
if crop_mode == "crop":
|
|
183
|
+
return rescale_and_crop(images, intrinsics, shape, resize_mode, resize)
|
|
184
|
+
if crop_mode == "pad":
|
|
185
|
+
return rescale_and_pad(
|
|
186
|
+
images, intrinsics, shape, resize_mode, resize, pad_value
|
|
187
|
+
)
|
|
188
|
+
check_shape(tuple(images.shape[-2:]), shape)
|
|
189
|
+
return images, intrinsics
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def check_crop_mode(crop_mode: str) -> None:
|
|
193
|
+
if crop_mode not in CROP_MODES:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"Unknown crop_mode {crop_mode!r}; expected one of {CROP_MODES}."
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def check_shape(image_shape: tuple[int, int], shape: tuple[int, int]) -> None:
|
|
200
|
+
if tuple(image_shape) != tuple(shape):
|
|
201
|
+
raise ValueError(
|
|
202
|
+
f'crop_mode="none" needs inputs at the model shape {tuple(shape)}, '
|
|
203
|
+
f"got {tuple(image_shape)}"
|
|
204
|
+
)
|
ffgs/models/__init__.py
ADDED