Skip to main content

Compressed Point Clouds in Foxglove

Publish Draco-encoded point clouds and visualize them in the 3D panel — with all the same features as PointCloud.


Hero image for compressed point clouds
Analese Steverson Pugh

Author: Analese Steverson Pugh

May 4, 2026

Share this post

Dense LiDAR point clouds dominate bandwidth and storage in most robotics stacks. Foxglove now supports a CompressedPointCloud schema — publish Draco-encoded point clouds and visualize them directly in the 3D panel, with all the same features you already use for PointCloud.

Getting started

The CompressedPointCloud schema follows the same pattern as CompressedImage and CompressedVideo:

fieldtypedescription
timestampTimestampTimestamp of the point cloud
frame_idstringFrame of reference
posePoseOrigin of the point cloud relative to the frame of reference
databytesComplete Draco-encoded point cloud payload
formatstringCompression format. Supported: "draco"

Set format to "draco", put the encoded bytes in data, and fill in timestamp, frame_id, and pose. Open the topic in a 3D panel and Foxglove handles decoding and rendering automatically.

Foxglove runs the Draco WASM decoder in a background Web Worker, so decompression doesn’t block the UI. The decoder reconstructs an interleaved point buffer equivalent to PointCloud and hands it to the same rendering path — color modes (Flat, Color map, Gradient, RGBA separate fields), decay, point size, and stixels all work with compressed topics.

Example

Here’s the core of a Python encoder using DracoPy — encode positions plus any named attributes, and the output goes straight into CompressedPointCloud.data:

import DracoPy
import numpy as np

def encode_draco(points: np.ndarray) -> bytes:
    positions = points[:, :3].astype(np.float32)
    return DracoPy.encode(
        positions,
        quantization_bits=14,
        compression_level=7,
        create_metadata=False,
        preserve_order=True,
        generic_attributes={
            "intensity": points[:, 3].astype(np.float32).reshape(-1, 1),
            "ring": np.rint(points[:, 4]).astype(np.uint16).reshape(-1, 1),
        },
    )

For a complete working example that generates a Draco-compressed MCAP from nuScenes LiDAR data, see foxglove/compressed-point-cloud-example. nuScenes data is copyright © Motional and available under a CC BY-NC-SA 4.0 license.

ROS integration

CompressedPointCloud is available in foxglove_msgs >= 3.2.6. Upgrade the package and publish foxglove_msgs/msg/CompressedPointCloud topics.

Switching from PointCloud to CompressedPointCloud

Compared to PointCloud, the schema drops fields and point_stride — Draco carries that metadata inside the encoded payload, so there’s no need to duplicate it on the outer message.

Foxglove maps Draco attributes to PointCloud-equivalent fields:

  • POSITIONx, y, z (must be 3-component float32)
  • Draco COLOR-typed attributesred, green, blue (+ alpha if 4-component)
  • Named attributes (via Draco metadata key name or attribute_name) → preserved by name (for example, intensity, ring)
  • Multi-component attributes → expanded with _x, _y, _z, _w suffixes

So the data requirements are the same as PointCloud: your Draco payload needs at minimum a POSITION attribute. Color and other per-point attributes are optional and will show up in the 3D panel settings for color mapping.

Use string keys for generic attributes when encoding — DracoPy automatically embeds the key as a name metadata entry on each attribute, which Foxglove reads to label fields in the panel. If you use integer keys instead, attributes get generic names like attribute_0.

When to stay on PointCloud: If you need lossless data for downstream algorithms, want zero decode overhead, or have custom fields that don’t round-trip through Draco cleanly.

Tips

  • Quantization. Draco quantizes by default. Most LiDAR visualization tolerates this well, but tune quantization bits if you need tighter fidelity.
  • Bandwidth vs. CPU. On constrained links (robot → cloud, remote debug), the tradeoff is almost always worth it. Typical compression is 3–6x depending on data.
  • One cloud per message. Each CompressedPointCloud must contain exactly one complete Draco point cloud.
  • Decay. Works with compressed topics. With decay enabled, Foxglove caps queued decodes per topic and drops frames once the queue is full. Without decay, only the latest completed decode is rendered.

FAQ

What format does data need to be in?

A complete Draco-encoded point cloud (not a mesh) with format set to "draco". The payload must contain the same point attributes you’d use in a PointCloud message: at minimum a 3-component float32 POSITION attribute (Foxglove maps this to x, y, z), and optionally a COLOR attribute (red, green, blue, alpha) and any other named attributes like intensity or ring. Use string keys in generic_attributes (for example, "intensity", "ring") so Foxglove can recover the field names — DracoPy embeds the key as attribute metadata automatically. The format field is a string, so additional codecs can be supported in the future without a schema change.

Does Foxglove handle Draco’s structure-of-arrays output?

Yes. The decoder converts Draco’s per-attribute arrays into the interleaved byte layout the renderer expects. You don’t need to do anything on your end.

Is it lossless?

Draco quantizes by default, making it lossy. Increase quantization bit depth to reduce loss at the cost of compression ratio.

Get started

See the CompressedPointCloud schema docs for reference implementations in Protobuf, FlatBuffers, ROS 1, ROS 2, and more.

Join our Discord community or follow us on Twitter and LinkedIn for the latest.

Start building with Foxglove.

Get started for free