mapvggt / mapgs /render /rasterizer.py
ChenmingWu's picture
Upload folder using huggingface_hub
b2efbe4 verified
Raw
History Blame Contribute Delete
4.37 kB
"""Differentiable Gaussian rasterizer wrapper around gsplat.
We render, in a *single* pass (design §3.3), per view:
* ``rgb`` or ``feature`` channels (alpha-composited colors),
* ``depth`` = alpha-composited expected depth D_hat = sum_i T_i a_i z_i / sum_i T_i a_i
(gsplat ``RGB+ED`` mode), which is the GS-native depth used by L_mapdepth,
* ``alpha`` (accumulated opacity), and
* optional ``aux`` channels (e.g. a soft lane-membership map for L_lane).
gsplat consumes ``world2cam`` view matrices and scalar-first ``wxyz`` quats, so
the conventions in :mod:`mapgs.geometry.transforms` carry through unchanged.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
import torch
from mapgs.geometry.transforms import se3_inverse
from mapgs.render.gaussians import Gaussians
try:
from gsplat import rasterization as _gsplat_rasterization
_HAS_GSPLAT = True
except Exception: # pragma: no cover
_HAS_GSPLAT = False
@dataclass
class RenderOutput:
color: torch.Tensor # [V, C, H, W] (C=3 rgb or feature_dim)
depth: torch.Tensor # [V, H, W] expected (alpha-normalized) depth
alpha: torch.Tensor # [V, H, W] accumulated opacity
aux: Optional[torch.Tensor] = None # [V, A, H, W] auxiliary composited channels (e.g. lane)
def rgb(self) -> torch.Tensor:
"""First 3 channels of ``color`` (valid when not in feature mode)."""
return self.color[:, :3]
class GaussianRasterizer:
"""Thin, differentiable wrapper. One scene (one Gaussian set) -> many views."""
def __init__(self, near: float = 0.01, far: float = 500.0, background: float = 0.0,
eps2d: float = 0.3):
if not _HAS_GSPLAT:
raise ImportError(
"gsplat is required for rendering. Install with `pip install gsplat`."
)
self.near = near
self.far = far
self.background = background
self.eps2d = eps2d
def render(
self,
gaussians: Gaussians,
K: torch.Tensor, # [V, 3, 3]
cam2world: torch.Tensor, # [V, 4, 4]
height: int,
width: int,
aux_colors: Optional[torch.Tensor] = None, # [N, A] per-gaussian aux channels
) -> RenderOutput:
V = K.shape[0]
device = gaussians.means.device
K = K.to(device)
cam2world = cam2world.to(device)
viewmats = se3_inverse(cam2world) # world2cam
if aux_colors is None:
aux_colors = gaussians.aux # ride-along aux channels (e.g. lane indicator)
C = gaussians.colors.shape[-1]
colors = gaussians.colors
A = 0
if aux_colors is not None:
A = aux_colors.shape[-1]
colors = torch.cat([colors, aux_colors], dim=-1) # [N, C+A]
bg = None
if self.background != 0.0:
bg = torch.full((V, colors.shape[-1]), float(self.background), device=device)
# gsplat: render all composited channels + 1 expected-depth channel.
out, alpha, _info = _gsplat_rasterization(
means=gaussians.means,
quats=gaussians.quats,
scales=gaussians.scales,
opacities=gaussians.opacities,
colors=colors,
viewmats=viewmats,
Ks=K,
width=width,
height=height,
near_plane=self.near,
far_plane=self.far,
eps2d=self.eps2d,
render_mode="RGB+ED",
backgrounds=bg,
packed=False,
)
# out: [V, H, W, C+A+1]; alpha: [V, H, W, 1]
depth = out[..., -1] # [V, H, W]
composited = out[..., : C + A] # [V, H, W, C+A]
color = composited[..., :C].permute(0, 3, 1, 2).contiguous()
aux = None
if A > 0:
aux = composited[..., C:].permute(0, 3, 1, 2).contiguous()
return RenderOutput(
color=color,
depth=depth,
alpha=alpha[..., 0],
aux=aux,
)
def render_depth(self, gaussians: Gaussians, K, cam2world, height, width) -> torch.Tensor:
"""Depth-only convenience (still does a full pass; used by L_extrap z-buffer)."""
return self.render(gaussians, K, cam2world, height, width).depth