Comfy-Org/ComfyUI · error · ValueError
SplatToFile3D: gaussian is empty
Error message
SplatToFile3D: gaussian is empty
What it means
Raised by _gaussian_ply_bytes when serializing a gaussian splat to the 3DGS .ply format and the positions tensor of the selected batch item has zero rows (n == 0). SplatToFile3D.execute slices item 0 up to _real_len(splat, 0) before calling this writer, so the error means the first (and only supported) batch item contains no gaussians. The check exists because an empty vertex list would produce a structurally invalid .ply with zero vertices that downstream viewers cannot open.
Source
Thrown at comfy_extras/nodes_gaussian_splat.py:64
def _quantile(x, q):
# torch.quantile errors above 2**24 elements; stride-subsample large inputs for the estimate.
lim = 1 << 24
if x.numel() > lim:
x = x[:: x.numel() // lim + 1]
return torch.quantile(x, q)
def _gaussian_ply_bytes(positions, scales, rotations, opacities, sh) -> bytes:
"""Serialize render-ready gaussian tensors as a binary 3DGS .ply.
positions (N,3) world; scales (N,3) linear; rotations (N,4) quat wxyz; opacities (N,1) in [0,1];
sh (N,K,3) SH coefficients. Activated values are inverted to the standard 3D gaussian splat storage convention
(log scale, logit opacity).
"""
xyz = positions.cpu().numpy().astype(np.float32)
n = xyz.shape[0]
if n == 0:
raise ValueError("SplatToFile3D: gaussian is empty")
normals = np.zeros_like(xyz)
f = sh.cpu().numpy().astype(np.float32) # (N, K, 3)
f_dc = f[:, 0, :] # (N, 3)
f_rest = f[:, 1:, :].transpose(0, 2, 1).reshape(n, -1) # (N, 3*(K-1)) channel-major
op = opacities.cpu().numpy().astype(np.float32).reshape(n, 1).clip(1e-6, 1 - 1e-6)
op = np.log(op / (1.0 - op)) # inverse sigmoid (logit)
scale = np.log(scales.cpu().numpy().astype(np.float32).clip(min=1e-8))
rot = rotations.cpu().numpy().astype(np.float32) # (N, 4)
attrs = (['x', 'y', 'z', 'nx', 'ny', 'nz']
+ [f'f_dc_{i}' for i in range(3)]
+ [f'f_rest_{i}' for i in range(f_rest.shape[1])]
+ ['opacity'] + [f'scale_{i}' for i in range(3)] + [f'rot_{i}' for i in range(4)])
elements = np.empty(n, dtype=[(a, 'f4') for a in attrs])
elements[:] = list(map(tuple, np.concatenate([xyz, normals, f_dc, f_rest, op, scale, rot], axis=1)))
header = "ply\nformat binary_little_endian 1.0\n" + f"element vertex {n}\n"
header += "".join(f"property float {a}\n" for a in attrs) + "end_header\n"View on GitHub (pinned to 1c6d8d45b3)
Solutions
- Inspect the upstream node that produced the SPLAT and confirm it actually generated gaussians (check its length/real-length output or log).
- If the splat is batched and only a non-first item is populated, re-batch so the non-empty item is item 0 (SplatToFile3D only writes item 0).
- Relax any culling/densification threshold upstream that can drop all gaussians for an item.
- Guard in the workflow: skip the save node when the item length is 0 instead of letting it raise.
Example fix
// before
splat = some_empty_splat
file = SplatToFile3D().execute(splat, format="ply")
// after
end = _real_len(splat, 0)
if end == 0:
raise ValueError("upstream produced 0 gaussians for item 0")
file = SplatToFile3D().execute(splat, format="ply") Defensive patterns
Strategy: validation
Validate before calling
from comfy_extras.nodes_gaussian_splat import _real_len
end = _real_len(splat, 0)
if end == 0:
raise ValueError("upstream splat item 0 is empty; fix generation before export") Type guard
def has_splat_item(splat, i: int) -> bool:
return splat is not None and splat.positions.shape[0] > i and _real_len(splat, i) > 0 Try / catch
try:
out = SplatToFile3D().execute(splat, format="ply")
except ValueError as e:
if "gaussian is empty" in str(e):
logging.warning("skipping empty splat export")
else:
raise Prevention
- Check the real length of batch item 0 before wiring SplatToFile3D.
- Ensure upstream splat generation cannot silently return zero gaussians (assert on counts).
- When batching, put the populated item at index 0 since the node only exports item 0.
When it happens
Trigger: Calling SplatToFile3D with a SPLAT whose item 0 has length 0: an upstream decode/splat-produce step yielded zero gaussians, or the lengths/real-length bookkeeping says 0 even though the padded tensors are non-empty. Also happens when a batch has >1 items and only a later item is non-empty (the node warns it uses the first item, then fails because item 0 is empty).
Common situations: An upstream gaussian-splat generation node returned an empty result (e.g., a threshold/culling step removed every splat, or a decoder produced nothing for that latent); connecting a MergeSplat output where item 0 was empty; testing the node with placeholder/zero-length data.
Related errors
- No images provided to create_image_parts; at least one image
- Cannot create grid from empty image list
- File3DToSplat: .splat size is not a multiple of 32 bytes
- File3DToSplat: .ksplat has no splats
- MergeSplat: no gaussians to merge
AI-assisted analysis of Comfy-Org/ComfyUI@1c6d8d45b3 (2026-08-14).
Data as JSON: /api/errors/382deb23373cc3ce.
Report an issue: GitHub.