Cloudini: lossy pointcloud compression that quantises before it defers to ZSTD
Pointcloud compression library
At a glance
- What is it?
- Cloudini is a C++ pointcloud compression library that encodes each channel separately, quantises the floating point channels and then hands the result to LZ4 or ZSTD, so it trades a configurable millimetre-level quantisation error for a much smaller payload. It works with PCL and ROS but compiles standalone.
- Who is it for?
- Adopt Cloudini if you are recording or streaming LiDAR or depth data and the storage or bandwidth cost of raw pointclouds is the problem, since the library removes a large share of the bytes before the general purpose compressor sees them. Do not adopt it if you need lossless pointcloud data for downstream processing, because the floating point channels are quantised.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Quantise first, compress second
Cloudini is a pointcloud compression library, and the two-step mechanism is stated plainly in the documentation.
The algorithm contains two steps: encoding the pointcloud, channel by channel, and then compression using either LZ4 or ZSTD.
That ordering is the whole idea. A general purpose compressor is good at finding redundancy in bytes, but a pointcloud's floating point coordinates are mostly noise to it at the bit level: the X, Y and Z values of neighbouring points differ in their last few bits, and ZSTD cannot know that those differences do not matter. Cloudini applies its own quantisation first, using a resolution the user provides, so the coordinate data becomes a low-precision integer grid that a general purpose compressor can actually squeeze.
The encoding is lossy for floating point channels, typically the X, Y and Z channels, and lossless for RGBA and integer channels. Packed colours stored in a FLOAT32 field named rgb or rgba are detected by name and never quantized, which is the detail that keeps an image from degrading when the geometry is allowed to.
On the question of what lossy means here, the documentation is unusually reassuring and unusually specific. Typical LiDARs have an accuracy and noise in the order of plus or minus 1 cm, so using a resolution of 1 mm, with a maximum quantisation error of plus or minus 0.5 mm, is usually a very conservative option. The README also notes that a voxel-based downsampling step in your own pointcloud processing algorithm is likely to be applying a coarser transformation than the quantisation this library applies.
The primary language is C++, the licence is Apache-2.0, and the recent releases are 1.4.1 fixing Windows compilation on 2026-09-28, 1.4.0 introducing a new encoder on the same day, and 1.3.1 on 2026-09-20.
Where the speed advantage comes from
The stated performance claim is specific enough to be checkable, which is unusual and welcome.
The project expects considerably better compression than ZSTD or LZ4 alone, at a similar or higher speed. The measurements are on real-world clouds from 13 sensors, using public datasets and the samples in the repository, with the 1.4.0 defaults: V6 at 1 mm resolution, refined to the data, followed by ZSTD, where ZSTD alone means ZSTD level 1, the level Cloudini uses, on the same raw cloud.
The reasoning for why Cloudini is not slower is that ZSTD has much less data left to compress. Encoding is 1.4 to 2 times faster than ZSTD alone on the Velodyne clouds, the PCD sample and the stereo cloud, and 0.87 to 1.05 times its speed on Ouster and Hesai. Decoding runs at 0.67 times the speed of ZSTD alone on Hesai up to 1.42 times on KITTI.
The shape of that is worth reading carefully. The encoding is a win because you are doing less work overall. The decoding is not symmetric, and on Hesai it is a third slower than plain ZSTD, because the decoder has to reconstruct the quantised coordinates rather than just inflate bytes. If you compress once and decompress many times, that asymmetry favours the other option.
The measurements were taken on one pinned core of an i7-13700H laptop, best of 5 runs, which is the kind of detail that makes a number arguable. One pinned core rather than a whole machine, a laptop rather than a desktop, and a best-of-five rather than a distribution.
The library's own focus is speed, but it still achieves very good compression ratios, and the compression ratio is hard to predict because it depends on the way the original data is encoded. That last point is the one to internalise before you set a resolution: the ratio you get on your data is a property of your data.
The padding problem in ROS pointcloud messages
The README makes an argument about ROS message encoding that is worth reading, because it explains why a dedicated library is worth having at all.
ROS pointcloud messages are extremely inefficient, because they include some padding in the message that, in extreme cases, may reach up to 50 percent. The parenthetical that follows is blunt about the scale: almost 50 percent of that 10 Gb rosbag is useless padding.
This is the structural reason a pointcloud-specific compressor pays off. The padding is not compressible noise in the general sense, it is fixed-size fields in the serialized message, and the reason it exists is that the ROS message format is fixed-width and type-stable so that subscribers can allocate arrays of the right size ahead of time. A general purpose compressor can find some of that redundancy, but it cannot know that a field is padding rather than data, and it cannot drop the field entirely.
Encoding channel by channel is what lets Cloudini exploit this. If you work per channel, you know which fields are geometry, which are colour and which are reserved, and you can handle each on its own terms instead of compressing a byte soup in which the useful coordinates and the padding are interleaved.
The two main use cases follow from the same place. The first is to improve the storage of datasets containing pointcloud data, with rosbags named as the notable example. The second is to decrease the bandwidth used when streaming pointclouds over a network. Both are cases where the payload is large, repetitive in structure, and dominated by a format that was designed for something other than compression.
Building it, and measuring it on your own data
The compile instructions are short. Some dependencies are downloaded automatically using CPM, and to avoid downloading them again when you rebuild your project, the documentation suggests setting CPM_SOURCE_CACHE.
To build the main library, cloudini_lib, there are two commands.
cmake -B build/release -S cloudini_lib -DCMAKE_BUILD_TYPE=Release
cmake --build build/release --parallelFor ROS, the instruction is that you pull this repo into your ws/src folder and execute colcon build as usual.
The more useful command for an adopter is the benchmark tool, because it lets you check the numbers on your own data rather than trusting the charts. You can measure the compression ratio and speed on your own data with mcap_codec_benchmark, built with cloudini_lib and with no ROS needed, on any MCAP file containing sensor_msgs/msg/PointCloud2 topics.
./build/release/tools/mcap_codec_benchmark my_bag.mcap --mode V6 --zstdRunning that before you commit to a resolution is the single most useful thing in the documentation. The compression ratio depends on how the original data is encoded, so a ratio measured on a public dataset tells you very little about what your sensor will produce.
The distribution story is also worth noting. The package is on GitHub Releases and on Conan Center, and the build badges cover Ubuntu, Windows and macOS, plus ROS 2 distributions Humble, Jazzy, Lyrical and Rolling, and a Pixi package. The tree also holds conda/ and cloudini_ros/, cloudini_rosbag_converter, cloudini_web/ and cloudini_foxglove/ directories.
The ROS side, and the parts that need no ROS at all
The ROS specific utilities are described separately, and one of them is deliberately usable without a ROS installation.
The point_cloud_transport plugins integrate with the standard transport mechanism for pointcloud topics. The cloudini_topic_converter is a node that converts a sensor_msgs/PointCloud2 topic into a compressed point_cloud_interfaces/CompressedPointCloud2 with compressing:=true, or vice versa with compressing:=false.
The cloudini_rosbag_converter is a command line tool that, given a rosbag limited to MCAP format, converts all sensor_msgs/PointCloud2 topics into compressed point_cloud_interfaces/CompressedPointCloud2 or vice versa. It does not need a ROS installation, and a pre-compiled Linux AppImage can be downloaded from the release page.
That last point is the practical detail. Converting a bag offline, without bringing up a ROS graph, is the workflow most people need when they are reprocessing archived data or checking how much a bag would shrink, and an AppImage for it removes the dependency problem entirely.
There is also a WebAssembly module, used by the Foxglove extension and by the Python decoder, and its build assumes Emscripten is installed.
emcmake cmake -B build/wasm -S ./cloudini_lib -DCLOUDINI_BUILD_TOOLS=OFF
cd build/wasm
emmake makeA WASM build is a reasonable idea for a browser-side decoder, since it lets a tool like Foxglove decode compressed pointclouds without a native plugin.
And there is a Python side too, cloudini_py, which shares that decoder role.
Where the lossy trade is the wrong trade
There are three limits, and the first one is a decision rather than a defect.
The first is that the quantisation is irreversible. The README raises the reader's worry that if you want to record raw data you should not use this, and the author disagrees with that reasoning on the grounds that you are working with noisy data in the first place, and that a pointcloud processing algorithm is almost certainly applying voxel-based downsampling larger than the quantisation applied by this library. That argument holds for a sensor whose noise floor is centimetres. It does not hold for a metrology system or a simulation pipeline where the coordinate precision is the measurement.
The second is the asymmetry in decoding speed. On the Hesai cloud the decode runs at 0.67 times the speed of ZSTD alone. If your workflow is compress once and decompress repeatedly, that is the number to look at, and the answer may be plain ZSTD or LZ4 on the raw data.
The third is the dependency shape. The library works seamlessly with PCL and ROS, and the main library can be compiled and used independently if needed, but the CMake build pulls dependencies through CPM, which means a network fetch during configuration unless you set CPM_SOURCE_CACHE. For a hermetic or air-gapped build, that cache variable is not optional, it is the thing that makes the build reproducible.
On the alternative, the honest comparison is with two other approaches. Using ZSTD or LZ4 directly on the raw pointcloud is simpler, has no quantisation step, and decodes faster, and on a cloud that is already well compressed it may be the better answer. The other alternative is the pointcloud-specific codec families that treat the geometry as a structured grid rather than a byte stream, which is the same idea as Cloudini arrived at from a different direction. Cloudini's argument against the general purpose path is the ROS padding: a compressor that does not know which fields are padding will keep paying for them, and the speed advantage comes from the bytes Cloudini removes before the compressor ever sees them.
Editorial conclusion
Adopt Cloudini if you are recording or streaming LiDAR or depth data and the storage or bandwidth cost of raw pointclouds is the problem, since the library removes a large share of the bytes before the general purpose compressor sees them. Do not adopt it if you need lossless pointcloud data for downstream processing, because the floating point channels are quantised. Before you commit, run mcap_codec_benchmark on a bag from your own sensor, and note the last push was on 2026-10-17 with release 1.4.1 on 2026-09-28.
Frequently asked questions
What does Cloudini do to a pointcloud before compressing it?
It encodes the pointcloud channel by channel, applying a user-provided quantisation resolution to the floating point channels such as X, Y and Z, and it leaves RGBA and integer channels lossless. The quantised result is then compressed with LZ4 or ZSTD.
Is Cloudini lossy and how much error does it introduce?
The encoding is lossy for floating point channels and lossless for RGBA and integer channels, with packed colours in a FLOAT32 field named rgb or rgba detected by name and never quantized. At a 1 mm resolution the maximum quantisation error is plus or minus 0.5 mm, which the documentation calls very conservative given that typical LiDARs have accuracy and noise in the order of plus or minus 1 cm.
How do I build the Cloudini library?
Run cmake -B build/release -S cloudini_lib -DCMAKE_BUILD_TYPE=Release and then cmake --build build/release --parallel. Dependencies are fetched through CPM, so setting CPM_SOURCE_CACHE avoids downloading them again on rebuilds.
Can I measure the Cloudini compression ratio on my own data?
Yes, with mcap_codec_benchmark built with cloudini_lib and no ROS required, on any MCAP file containing sensor_msgs/msg/PointCloud2 topics. Run ./build/release/tools/mcap_codec_benchmark my_bag.mcap --mode V6 --zstd.
What ROS tools does Cloudini ship?
Point cloud transport plugins, a cloudini_topic_converter node that converts between sensor_msgs/PointCloud2 and compressed point_cloud_interfaces/CompressedPointCloud2, and a cloudini_rosbag_converter command line tool for MCAP bags that needs no ROS installation and is available as a pre-compiled Linux AppImage.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/facontidavide-cloudini)Was this useful?