| """Gaussian Splatting utilities.""" |
|
|
| import os |
| from pathlib import Path |
| from typing import Dict, List, Optional, Tuple, Union |
|
|
| import numpy as np |
| import torch |
|
|
|
|
| def export_gaussian_splatting( |
| gaussian_cloud: Union[np.ndarray, torch.Tensor], |
| output_path: Union[str, Path], |
| ) -> str: |
| """ |
| Export Gaussian Splatting representation to PLY format. |
| |
| Args: |
| gaussian_cloud: [N, 14] array/tensor with columns: |
| [x, y, z, scale_x, scale_y, scale_z, |
| rot_qx, rot_qy, rot_qz, rot_qw, |
| r, g, b, opacity] |
| """ |
| if isinstance(gaussian_cloud, torch.Tensor): |
| gaussian_cloud = gaussian_cloud.cpu().numpy() |
| |
| output_path = Path(output_path) |
| output_path.parent.mkdir(parents=True, exist_ok=True) |
| |
| try: |
| from plyfile import PlyData, PlyElement |
| |
| num_points = len(gaussian_cloud) |
| dtype = [ |
| ('x', 'f4'), ('y', 'f4'), ('z', 'f4'), |
| ('scale_0', 'f4'), ('scale_1', 'f4'), ('scale_2', 'f4'), |
| ('rot_0', 'f4'), ('rot_1', 'f4'), ('rot_2', 'f4'), ('rot_3', 'f4'), |
| ('f_dc_0', 'f4'), ('f_dc_1', 'f4'), ('f_dc_2', 'f4'), |
| ('opacity', 'f4'), |
| ] |
| |
| vertices = np.zeros(num_points, dtype=dtype) |
| vertices['x'] = gaussian_cloud[:, 0] |
| vertices['y'] = gaussian_cloud[:, 1] |
| vertices['z'] = gaussian_cloud[:, 2] |
| vertices['scale_0'] = gaussian_cloud[:, 3] |
| vertices['scale_1'] = gaussian_cloud[:, 4] |
| vertices['scale_2'] = gaussian_cloud[:, 5] |
| vertices['rot_0'] = gaussian_cloud[:, 6] |
| vertices['rot_1'] = gaussian_cloud[:, 7] |
| vertices['rot_2'] = gaussian_cloud[:, 8] |
| vertices['rot_3'] = gaussian_cloud[:, 9] |
| vertices['f_dc_0'] = gaussian_cloud[:, 10] |
| vertices['f_dc_1'] = gaussian_cloud[:, 11] |
| vertices['f_dc_2'] = gaussian_cloud[:, 12] |
| vertices['opacity'] = gaussian_cloud[:, 13] |
| |
| el = PlyElement.describe(vertices, 'vertex') |
| PlyData([el], text=True).write(str(output_path)) |
| |
| except ImportError: |
| |
| _write_ascii_ply(gaussian_cloud, output_path) |
| |
| return str(output_path) |
|
|
|
|
| def _write_ascii_ply(gaussian_cloud: np.ndarray, output_path: Path): |
| """Write simple ASCII PLY fallback.""" |
| num_points = len(gaussian_cloud) |
| |
| with open(output_path, 'w') as f: |
| f.write("ply\n") |
| f.write("format ascii 1.0\n") |
| f.write(f"element vertex {num_points}\n") |
| f.write("property float x\n") |
| f.write("property float y\n") |
| f.write("property float z\n") |
| f.write("property float scale_0\n") |
| f.write("property float scale_1\n") |
| f.write("property float scale_2\n") |
| f.write("property float rot_0\n") |
| f.write("property float rot_1\n") |
| f.write("property float rot_2\n") |
| f.write("property float rot_3\n") |
| f.write("property float f_dc_0\n") |
| f.write("property float f_dc_1\n") |
| f.write("property float f_dc_2\n") |
| f.write("property float opacity\n") |
| f.write("end_header\n") |
| |
| for point in gaussian_cloud: |
| f.write(" ".join(f"{v:.6f}" for v in point) + "\n") |
|
|
|
|
| def render_gaussian_splatting( |
| gaussian_cloud: torch.Tensor, |
| camera_pose: torch.Tensor, |
| image_size: Tuple[int, int] = (512, 512), |
| fov: float = 50.0, |
| ) -> torch.Tensor: |
| """ |
| Render Gaussian Splatting from given camera pose. |
| |
| Args: |
| gaussian_cloud: [N, 14] tensor |
| camera_pose: [4, 4] camera-to-world transform |
| image_size: (H, W) |
| fov: Field of view in degrees |
| |
| Returns: |
| Rendered image [3, H, W] |
| """ |
| |
| |
| |
| H, W = image_size |
| return torch.zeros(3, H, W, device=gaussian_cloud.device) |
|
|
|
|
| def merge_gaussian_clouds( |
| clouds: List[torch.Tensor], |
| ) -> torch.Tensor: |
| """Merge multiple Gaussian clouds into one.""" |
| if not clouds: |
| return torch.zeros(0, 14) |
| |
| valid_clouds = [c for c in clouds if len(c) > 0] |
| if not valid_clouds: |
| return torch.zeros(0, 14) |
| |
| return torch.cat(valid_clouds, dim=0) |
|
|