multimodalart HF Staff commited on
Commit
1be9844
·
verified ·
1 Parent(s): 7767094

Delete files codecsplat_pipeline.py LICENSE-CodecSplat with huggingface_hub

Browse files
Files changed (2) hide show
  1. LICENSE-CodecSplat +0 -21
  2. codecsplat_pipeline.py +0 -338
LICENSE-CodecSplat DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Pengpeng Yu
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
codecsplat_pipeline.py DELETED
@@ -1,338 +0,0 @@
1
- """CodecSplat inference helpers for the Space.
2
-
3
- Everything here mirrors the authors' evaluation path
4
- (`scripts/test_dl3dv_view8_256x448.sh` -> `src/main.py` -> `ModelWrapper.test_step`):
5
- the encoder runs the *complete* learned-codec path (encode context -> entropy-code the
6
- 2D latent features into a real bitstream -> decode the bitstream -> predict Gaussians),
7
- and the gsplat decoder renders novel views, all under bf16 autocast like
8
- `trainer.precision=bf16-mixed`.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import os
14
- from pathlib import Path
15
- from types import SimpleNamespace
16
-
17
- import numpy as np
18
- import torch
19
- from dacite import Config as DaciteConfig, from_dict
20
- from einops import rearrange, repeat
21
- from omegaconf import OmegaConf
22
- from plyfile import PlyData, PlyElement
23
- from scipy.spatial.transform import Rotation as R
24
- from scipy.spatial.transform import Slerp
25
-
26
- from src.dataset.shims.crop_shim import apply_crop_shim
27
- from src.dataset.shims.patch_shim import apply_patch_shim
28
- from src.misc.sh_rotation import rotate_sh
29
- from src.model.encoder import get_encoder
30
- from src.model.encoder.encoder_codecsplat import EncoderCodecSplatCfg
31
-
32
- APP_DIR = Path(__file__).parent.resolve()
33
-
34
- # ---- authors' DL3DV 8-view 256x448 evaluation preset ---------------------------------
35
- IMAGE_SHAPE = (256, 448)
36
- NEAR, FAR = 0.5, 200.0 # config/experiment/dl3dv.yaml
37
- ENCODER_OVERRIDES = {
38
- "num_depth_candidates": 128,
39
- "shim_patch_size": 16,
40
- "num_scales": 2,
41
- "upsample_factor": 4,
42
- "lowest_feature_resolution": 8,
43
- "monodepth_vit_type": "vitb",
44
- "enable_feature_codec": True,
45
- "enable_depth_guided_mv_refiner": True,
46
- "enable_post_vae_mv_refiner": True,
47
- "codec_lmb_range": [16.0, 1024.0],
48
- "supervise_intermediate_depth": False,
49
- }
50
- DECODER_OVERRIDES = {
51
- "use_covariances": True,
52
- "rasterize_mode": "antialiased",
53
- "eps2d": 0.1,
54
- "packed": True,
55
- "scale_invariant": False,
56
- }
57
-
58
-
59
- def build_encoder(checkpoint_path: str):
60
- enc_cfg = OmegaConf.load(APP_DIR / "config/model/encoder/codecsplat.yaml")
61
- enc_cfg = OmegaConf.merge(enc_cfg, ENCODER_OVERRIDES)
62
- enc_cfg = from_dict(
63
- EncoderCodecSplatCfg,
64
- OmegaConf.to_container(enc_cfg, resolve=True),
65
- config=DaciteConfig(cast=[tuple], strict=False),
66
- )
67
- encoder = get_encoder(enc_cfg)
68
-
69
- ckpt = torch.load(checkpoint_path, map_location="cpu", weights_only=False)
70
- state = ckpt.get("state_dict", ckpt)
71
- state = {k[len("encoder."):]: v for k, v in state.items() if k.startswith("encoder.")}
72
- encoder.load_state_dict(state, strict=True)
73
- # eval() also runs `compress_mode` -> builds the entropy-coder CDF tables (on CPU).
74
- encoder.eval()
75
- return encoder
76
-
77
-
78
- def build_decoder():
79
- """The authors' `model/decoder=splatting_cuda` decoder (Inria diff-gaussian-rasterization).
80
-
81
- The evaluation scripts use the gsplat decoder; on ZeroGPU we use the repo's other
82
- supported decoder because a prebuilt sm_120 wheel exists for it (gsplat would have to
83
- be JIT-compiled against a CUDA toolkit at startup).
84
- """
85
- from src.model.decoder.decoder_splatting_cuda import (
86
- DecoderSplattingCUDA,
87
- DecoderSplattingCUDACfg,
88
- )
89
-
90
- decoder = DecoderSplattingCUDA(
91
- DecoderSplattingCUDACfg(name="splatting_cuda"),
92
- SimpleNamespace(background_color=[0.0, 0.0, 0.0]),
93
- )
94
- return decoder.eval()
95
-
96
-
97
- # ---- view selection ------------------------------------------------------------------
98
- def farthest_point_sample(xyz: np.ndarray, npoint: int) -> np.ndarray:
99
- n = len(xyz)
100
- if npoint >= n:
101
- return np.arange(n)
102
- dist = np.full(n, 1e10)
103
- farthest = int(np.argmax(((xyz - xyz.mean(0)) ** 2).sum(-1)))
104
- out = []
105
- for _ in range(npoint):
106
- out.append(farthest)
107
- d = ((xyz - xyz[farthest]) ** 2).sum(-1)
108
- dist = np.minimum(dist, d)
109
- farthest = int(np.argmax(dist))
110
- return np.sort(np.array(out))
111
-
112
-
113
- def select_views(c2w: np.ndarray, num_context: int = 8, num_target: int = 8):
114
- n = len(c2w)
115
- ctx = farthest_point_sample(c2w[:, :3, 3], min(num_context, n))
116
- rest = np.setdiff1d(np.arange(n), ctx)
117
- if len(rest) > num_target:
118
- rest = rest[np.linspace(0, len(rest) - 1, num_target).astype(int)]
119
- return ctx, rest
120
-
121
-
122
- # ---- batch construction (same conventions as the dataset loaders) --------------------
123
- def _load_images(paths):
124
- from PIL import Image
125
-
126
- imgs = []
127
- for p in paths:
128
- im = np.asarray(Image.open(p).convert("RGB"), dtype=np.float32) / 255.0
129
- imgs.append(torch.from_numpy(im).permute(2, 0, 1))
130
- return torch.stack(imgs)
131
-
132
-
133
- def image_shape_for(h: int, w: int) -> tuple[int, int]:
134
- # The checkpoint is trained at 256x448 (landscape). Portrait inputs use 448x256.
135
- return IMAGE_SHAPE if w >= h else (IMAGE_SHAPE[1], IMAGE_SHAPE[0])
136
-
137
-
138
- def build_example(image_paths, c2w, K, ctx_idx, tgt_idx):
139
- """Build a single (un-batched) example, apply the authors' crop + patch shims."""
140
- images = _load_images(image_paths) # [N, 3, H, W]
141
- shape = image_shape_for(images.shape[-2], images.shape[-1])
142
- c2w = torch.as_tensor(c2w, dtype=torch.float32)
143
- K = torch.as_tensor(K, dtype=torch.float32)
144
-
145
- # Express all poses relative to the middle context camera (rigid change of frame).
146
- pivot = c2w[ctx_idx[len(ctx_idx) // 2]]
147
- c2w = torch.linalg.inv(pivot)[None] @ c2w
148
-
149
- def views(idx):
150
- idx = torch.as_tensor(np.asarray(idx), dtype=torch.long)
151
- return {
152
- "extrinsics": c2w[idx],
153
- "intrinsics": K[idx],
154
- "image": images[idx],
155
- "near": torch.full((len(idx),), NEAR),
156
- "far": torch.full((len(idx),), FAR),
157
- "index": idx,
158
- }
159
-
160
- example = {"context": views(ctx_idx), "target": views(tgt_idx), "scene": "user_scene"}
161
- example = apply_crop_shim(example, shape)
162
- batch = {
163
- "context": {k: v[None] for k, v in example["context"].items()},
164
- "target": {k: v[None] for k, v in example["target"].items()},
165
- "scene": ["user_scene"],
166
- }
167
- # shim_patch_size * downscale_factor = 16 * 4
168
- return apply_patch_shim(batch, patch_size=64)
169
-
170
-
171
- def to_device(batch, device):
172
- out = {}
173
- for k, v in batch.items():
174
- if isinstance(v, dict):
175
- out[k] = {kk: vv.to(device) for kk, vv in v.items()}
176
- else:
177
- out[k] = v
178
- return out
179
-
180
-
181
- # ---- smooth fly-through trajectory through the context cameras ----------------------
182
- def order_cameras(c2w: torch.Tensor) -> list[int]:
183
- """Greedy nearest-neighbour ordering of cameras (start from an extreme one)."""
184
- pos = c2w[:, :3, 3].cpu().numpy()
185
- n = len(pos)
186
- if n <= 2:
187
- return list(range(n))
188
- start = int(np.argmax(((pos - pos.mean(0)) ** 2).sum(-1)))
189
- order, left = [start], set(range(n)) - {start}
190
- while left:
191
- last = pos[order[-1]]
192
- nxt = min(left, key=lambda i: float(((pos[i] - last) ** 2).sum()))
193
- order.append(nxt)
194
- left.remove(nxt)
195
- return order
196
-
197
-
198
- def interpolate_trajectory(c2w: torch.Tensor, K: torch.Tensor, frames_per_segment: int = 12):
199
- order = order_cameras(c2w)
200
- c2w = c2w[order].cpu().double().numpy()
201
- K = K[order].cpu()
202
- poses, intrs = [], []
203
- for i in range(len(order) - 1):
204
- rots = R.from_matrix(c2w[i : i + 2, :3, :3])
205
- slerp = Slerp([0.0, 1.0], rots)
206
- for t in np.linspace(0, 1, frames_per_segment, endpoint=False):
207
- m = np.eye(4)
208
- m[:3, :3] = slerp([t]).as_matrix()[0]
209
- m[:3, 3] = (1 - t) * c2w[i, :3, 3] + t * c2w[i + 1, :3, 3]
210
- poses.append(m)
211
- intrs.append((1 - t) * K[i] + t * K[i + 1])
212
- last = np.eye(4)
213
- last[:3] = c2w[-1, :3]
214
- poses.append(last)
215
- intrs.append(K[-1])
216
- poses = poses + poses[::-1][1:] # ping-pong loop
217
- intrs = intrs + intrs[::-1][1:]
218
- return (
219
- torch.tensor(np.stack(poses), dtype=torch.float32),
220
- torch.stack([torch.as_tensor(k, dtype=torch.float32) for k in intrs]),
221
- )
222
-
223
-
224
- # ---- core inference ------------------------------------------------------------------
225
- @torch.no_grad()
226
- def encode_decode(encoder, batch, lmb: float):
227
- """Full learned-codec round trip: context -> bitstream -> Gaussians."""
228
- encoder.feature_codec.cfg.lmb = float(lmb)
229
- with torch.autocast("cuda", dtype=torch.bfloat16, enabled=batch["context"]["image"].is_cuda):
230
- out = encoder(batch["context"], 0, visualization_dump={}, return_bitstream=True)
231
- return out
232
-
233
-
234
- @torch.no_grad()
235
- def render(decoder, gaussians, extrinsics, intrinsics, hw, chunk: int = 16):
236
- """Render [V] views. `extrinsics`/`intrinsics` are [V,4,4]/[V,3,3] on device."""
237
- v = extrinsics.shape[0]
238
- near = torch.full((1, v), NEAR, device=extrinsics.device)
239
- far = torch.full((1, v), FAR, device=extrinsics.device)
240
- colors = []
241
- with torch.autocast("cuda", dtype=torch.bfloat16, enabled=extrinsics.is_cuda):
242
- for s in range(0, v, chunk):
243
- o = decoder.forward(
244
- gaussians,
245
- extrinsics[None, s : s + chunk],
246
- intrinsics[None, s : s + chunk],
247
- near[:, s : s + chunk],
248
- far[:, s : s + chunk],
249
- hw,
250
- depth_mode=None,
251
- )
252
- colors.append(o.color[0].float().clamp(0, 1))
253
- return torch.cat(colors, dim=0)
254
-
255
-
256
- def psnr(gt: torch.Tensor, pred: torch.Tensor) -> float:
257
- mse = ((gt - pred) ** 2).mean(dim=(1, 2, 3)).clamp_min(1e-10)
258
- return float((-10 * torch.log10(mse)).mean())
259
-
260
-
261
- # ---- Gaussian export -----------------------------------------------------------------
262
- def world_rotations(rotations: torch.Tensor, context_c2w: torch.Tensor) -> torch.Tensor:
263
- """Per-pixel Gaussian rotations are predicted in each context camera's frame."""
264
- v = context_c2w.shape[0]
265
- n = rotations.shape[0]
266
- c2w_rot = repeat(context_c2w[:, :3, :3], "v i j -> (v r) i j", r=n // v)
267
- local = R.from_quat(rotations.cpu().double().numpy()).as_matrix()
268
- world = c2w_rot.cpu().double().numpy() @ local
269
- return torch.from_numpy(R.from_matrix(world).as_quat()).float()
270
-
271
-
272
- def write_ply(
273
- path,
274
- pivot_c2w: torch.Tensor,
275
- means: torch.Tensor,
276
- scales: torch.Tensor,
277
- rotations_xyzw_world: torch.Tensor,
278
- harmonics: torch.Tensor,
279
- opacities: torch.Tensor,
280
- ):
281
- """Same output as the authors' `export_ply(..., align_to_view=True)`, vectorised."""
282
- means = means.float().cpu()
283
- scales = scales.float().cpu()
284
- harmonics = harmonics.float().cpu()
285
- opacities = opacities.float().cpu()
286
- pivot = pivot_c2w.float().cpu()
287
-
288
- view_rot = pivot[:3, :3].T
289
- means = (means - pivot[:3, 3]) @ view_rot.T
290
- harmonics = rotate_sh(harmonics, view_rot)
291
- rot = R.from_quat(rotations_xyzw_world.double().numpy()).as_matrix()
292
- rot = R.from_matrix(view_rot.double().numpy() @ rot).as_quat() # xyzw
293
- rot = np.concatenate([rot[:, 3:4], rot[:, :3]], axis=1) # wxyz
294
-
295
- dc = harmonics[..., 0].numpy()
296
- rest = harmonics[..., 1:].flatten(start_dim=1).numpy()
297
- logit_op = torch.logit(opacities.clamp(1e-6, 1 - 1e-6))[:, None].numpy()
298
- log_scales = scales.clamp_min(1e-12).log().numpy()
299
- m = means.numpy()
300
-
301
- cols = [m, np.zeros_like(m), dc, rest, logit_op, log_scales, rot]
302
- data = np.concatenate(cols, axis=1).astype(np.float32)
303
- names = ["x", "y", "z", "nx", "ny", "nz", "f_dc_0", "f_dc_1", "f_dc_2"]
304
- names += [f"f_rest_{i}" for i in range(rest.shape[1])]
305
- names += ["opacity", "scale_0", "scale_1", "scale_2", "rot_0", "rot_1", "rot_2", "rot_3"]
306
- arr = np.empty(data.shape[0], dtype=[(n, "f4") for n in names])
307
- for i, n in enumerate(names):
308
- arr[n] = data[:, i]
309
- Path(path).parent.mkdir(parents=True, exist_ok=True)
310
- PlyData([PlyElement.describe(arr, "vertex")]).write(str(path))
311
- return str(path)
312
-
313
-
314
- def export_gaussians(g: dict, context_c2w: torch.Tensor, outdir: Path,
315
- viewer_max: int = 600_000, viewer_min_opacity: float = 0.02):
316
- """Write a compact (DC colour, pruned), re-framed .ply of the Gaussians for the web viewer."""
317
- pivot = context_c2w[len(context_c2w) // 2]
318
- rot_world = world_rotations(g["rotations"], context_c2w)
319
-
320
- keep = g["opacities"] > viewer_min_opacity
321
- if int(keep.sum()) < 1000:
322
- keep = torch.ones_like(keep)
323
- idx = torch.nonzero(keep).squeeze(1)
324
- if idx.numel() > viewer_max:
325
- idx = idx[torch.randperm(idx.numel())[:viewer_max]]
326
- means, scales = g["means"][idx], g["scales"][idx]
327
-
328
- # Re-centre / re-scale (in the pivot camera frame) so the viewer's orbit camera frames it.
329
- view_rot = pivot[:3, :3].T
330
- local = (means - pivot[:3, 3]) @ view_rot.T
331
- center = local.median(dim=0).values
332
- radius = torch.quantile((local - center).norm(dim=-1).float(), 0.7)
333
- s = float(3.0 / max(float(radius), 1e-6))
334
- # shift in world coordinates so that the pivot-frame centre maps to the origin
335
- means = (means - pivot[:3, 3] - center @ view_rot) * s + pivot[:3, 3]
336
- viewer_path = write_ply(outdir / "codecsplat_viewer.ply", pivot, means, scales * s,
337
- rot_world[idx], g["harmonics"][idx][..., :1], g["opacities"][idx])
338
- return viewer_path