Help Needed: Converting PCD to Gaussian Splatting PLY #174278
Replies: 1 comment
-
A Gaussian Splatting PLY is not a regular point cloud or mesh PLY. It must contain a very specific set of vertex properties, stored as binary little endian, and several of those properties are encoded, not raw. If you export a “plain” PLY from PCD with only
That layout and naming is what the popular viewers expect. If your file lacks these fields, or if the types or order differ, you will see crashes or garbage. ([GitHub]1) On top of that, several fields are stored in “training space” and must be decoded by the viewer. In particular:
Finally, Babylon and PlayCanvas now support several splat formats. If you give them a PLY that matches the expected Gaussian schema, they load fine. If you give them a generic PLY from CloudCompare or MeshLab, they do not, because those tools export mesh or point attributes, not Gaussian attributes. ([Babylon.js Docs]3) A reliable pipeline from PCD to Gaussian PLYYou have two choices. The first is the quick pragmatic route that makes your PCD “look” like a Gaussian PLY with reasonable defaults. The second is the high quality route that actually fits anisotropic Gaussians. Option A: Minimal conversion that works in web viewersUse your PCD positions and colors, then synthesize the missing Gaussian fields:
If you just want a viewer friendly file and do not need SH color, you can also convert the PLY you produced into a compressed variant that the web engines like for performance.
Option B: Higher quality conversionIf you actually want anisotropic Gaussians that match the surface, you need per point covariance and orientation, not only a guessed isotropic size. That usually means:
This produces much better results than isotropic blobs but takes more preprocessing. A small end to end sketch in PythonThis is the skeleton you can adapt inside your FastAPI route. It reads a PCD, synthesizes missing Gaussian fields, and writes a valid PLY. It uses Open3D for I/O and KNN, and import numpy as np
import open3d as o3d
from plyfile import PlyData, PlyElement
SH_C0 = 0.28209479177387814
def to_logit(a):
a = np.clip(a, 1e-6, 1-1e-6)
return np.log(a/(1-a))
def estimate_sigma(points, k=8):
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
kdtree = o3d.geometry.KDTreeFlann(pcd)
sigmas = np.zeros(len(points), dtype=np.float32)
for i, p in enumerate(points):
_, idx, dists2 = kdtree.search_knn_vector_3d(p, k)
# skip distance to self at index 0
d = np.sqrt(np.maximum(dists2[1:], 1e-12))
sigmas[i] = np.median(d) * 0.75
return sigmas
def pcd_to_gauss_ply(pcd_path, ply_out, alpha=0.6):
pcd = o3d.io.read_point_cloud(pcd_path)
xyz = np.asarray(pcd.points, dtype=np.float32)
if pcd.has_colors():
rgb = np.asarray(pcd.colors, dtype=np.float32)
else:
rgb = np.ones_like(xyz) * 0.5 # gray
# DC SH from color
f_dc = (rgb - 0.5) / SH_C0
# No view dependent terms
f_rest = np.zeros((len(xyz), 45), dtype=np.float32) # degree 3 -> 45 terms
# opacities in logit space
opacity = np.full((len(xyz), 1), to_logit(alpha), dtype=np.float32)
# isotropic size from KNN, stored as log scales
sigma = estimate_sigma(xyz)
s = np.log(np.maximum(sigma, 1e-6)).astype(np.float32)
scales = np.stack([s, s, s], axis=1)
# identity quaternion WXYZ
rot = np.tile(np.array([1.0, 0.0, 0.0, 0.0], dtype=np.float32), (len(xyz), 1))
# normals optional -> zeros
nrm = np.zeros_like(xyz, dtype=np.float32)
# interleave into structured array matching the header order
dtype = [
('x', 'f4'), ('y', 'f4'), ('z', 'f4'),
('nx','f4'), ('ny','f4'), ('nz','f4'),
('f_dc_0','f4'), ('f_dc_1','f4'), ('f_dc_2','f4')
] + [(f'f_rest_{i}', 'f4') for i in range(45)] + [
('opacity','f4'),
('scale_0','f4'), ('scale_1','f4'), ('scale_2','f4'),
('rot_0','f4'), ('rot_1','f4'), ('rot_2','f4'), ('rot_3','f4')
]
arr = np.empty(len(xyz), dtype=dtype)
arr['x'], arr['y'], arr['z'] = xyz[:,0], xyz[:,1], xyz[:,2]
arr['nx'], arr['ny'], arr['nz'] = nrm[:,0], nrm[:,1], nrm[:,2]
arr['f_dc_0'], arr['f_dc_1'], arr['f_dc_2'] = f_dc[:,0], f_dc[:,1], f_dc[:,2]
for i in range(45):
arr[f'f_rest_{i}'] = f_rest[:, i]
arr['opacity'] = opacity[:,0]
arr['scale_0'], arr['scale_1'], arr['scale_2'] = scales[:,0], scales[:,1], scales[:,2]
arr['rot_0'], arr['rot_1'], arr['rot_2'], arr['rot_3'] = rot[:,0], rot[:,1], rot[:,2], rot[:,3]
el = PlyElement.describe(arr, 'vertex')
PlyData([el], text=False).write(ply_out) Drop this into your FastAPI handler after you receive the PCD bytes. Return the resulting Engine specific tips
Quick checklist for your FastAPI converter
If you follow the steps above you should be able to import your converted files into all three links you posted without the browser falling over. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Body
I’m using FastAPI to convert PCD files (sent from the frontend) into Gaussian Splatting PLY files.
However, the converted PLY files cannot be opened in platforms such as:
When I try to import them, the browser crashes.
Has anyone encountered this issue or could guide how to convert PCD to PLY for Gaussian Splatting properly?
Any help would be greatly appreciated.
Guidelines
Beta Was this translation helpful? Give feedback.
All reactions