A Rust implementation of LEPCC (Limited Error Point Cloud Compression), a high-performance compression algorithm for 3D point cloud data. This is a port of the original C++ implementation by Esri, providing full compatibility with the I3S point cloud format.
- 🚀 High Performance: Bit-level compression achieving 10x-300x compression ratios
- 🎯 Lossy XYZ Compression: Configurable error tolerance for coordinate compression
- 🎨 RGB Color Compression: Up to 3x compression with minimal visual quality loss
- 📊 Intensity & Attributes: Lossless compression for classification and intensity data
- 🌐 I3S Format Compatible: Reads and writes
.pccxyzand.pccrgbfiles - ✅ C++ Interoperable: Compatible files with the reference C++ implementation
- 🔧 Zero Dependencies: Pure Rust with minimal dependencies for easy integration
Add lepcc to your Cargo.toml:
[dependencies]
lepcc = "0.1"Or add via command line:
cargo add lepccuse lepcc::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create sample points
let points = vec![
Point3D::new(0.0, 0.0, 0.0),
Point3D::new(1.0, 1.0, 1.0),
Point3D::new(2.0, 0.0, 1.0),
Point3D::new(3.0, 2.0, 2.0),
];
// Compress with 1cm accuracy
let max_err = 0.01; // 1 centimeter
let compressed = compress_xyz(&points, max_err, max_err, max_err)?;
println!("Compressed {} points to {} bytes", points.len(), compressed.len());
// Decompress
let decompressed = decompress_xyz(&compressed)?;
println!("Decompressed {} points", decompressed.len());
Ok(())
}use lepcc::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let colors = vec![
RGB::new(255, 0, 0), // Red
RGB::new(0, 255, 0), // Green
RGB::new(0, 0, 255), // Blue
];
let compressed = compress_rgb(&colors)?;
let decompressed = decompress_rgb(&compressed)?;
assert_eq!(colors, decompressed);
Ok(())
}use lepcc::prelude::*;
use std::fs;
fn compress_to_file() -> Result<(), Box<dyn std::error::Error>> {
// Read points from OBJ file
let points = read_obj_points("input.obj")?;
// Compress
let compressed = compress_xyz(&points, 0.01, 0.01, 0.01)?;
// Save to I3S format
fs::write("output.pccxyz", compressed)?;
Ok(())
}
fn read_obj_points(path: &str) -> Result<Vec<Point3D>, Box<dyn std::error::Error>> {
let mut points = Vec::new();
let content = fs::read_to_string(path)?;
for line in content.lines() {
if line.starts_with("v ") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 4 {
let x: f64 = parts[1].parse()?;
let y: f64 = parts[2].parse()?;
let z: f64 = parts[3].parse()?;
points.push(Point3D::new(x, y, z));
}
}
}
Ok(points)
}| Function | Input | Output | Description |
|---|---|---|---|
compress_xyz |
&[Point3D], errors |
Vec<u8> |
Compress 3D coordinates |
compress_xyz_array |
&[f64], errors |
Vec<u8> |
Compress from flat array |
decompress_xyz |
&[u8] |
Vec<Point3D> |
Decompress 3D coordinates |
decompress_xyz_array |
&[u8] |
Vec<f64> |
Decompress to flat array |
compress_rgb |
&[RGB] |
Vec<u8> |
Compress RGB colors |
compress_rgb_array |
&[u8] |
Vec<u8> |
Compress from flat RGB array |
decompress_rgb |
&[u8] |
Vec<RGB> |
Decompress RGB colors |
decompress_rgb_array |
&[u8] |
Vec<u8> |
Decompress to flat RGB array |
compress_intensity |
&[u16] |
Vec<u8> |
Compress intensity values |
decompress_intensity |
&[u8] |
Vec<u16> |
Decompress intensity values |
compress_flag_bytes |
&[u8] |
Vec<u8> |
Compress flag/classification |
decompress_flag_bytes |
&[u8] |
Vec<u8> |
Decompress flag bytes |
get_blob_type |
&[u8] |
BlobType |
Detect blob type |
get_blob_size |
&[u8] |
u32 |
Get blob size |
get_num_points |
&[u8] |
u32 |
Get point count |
For compatibility with existing data formats, convenience functions are provided:
// Compress from flat [x0, y0, z0, x1, y1, z1, ...] array
let xyz_array = vec![0.0, 0.0, 0.0, 1.0, 1.0, 1.0];
let compressed = compress_xyz_array(&xyz_array, 0.01, 0.01, 0.01)?;
// Decompress to flat array
let decompressed = decompress_xyz_array(&compressed)?;
// Same for RGB:[r0, g0, b0, r1, g1, b1, ...]
let rgb_array = vec![255, 0, 0, 0, 255, 0];
let compressed_rgb = compress_rgb_array(&rgb_array)?;
let decompressed_rgb = decompress_rgb_array(&compressed_rgb)?;The max_err parameters control the compression quality:
let max_err = 0.01; // 1 centimeter tolerance
let max_err = 0.001; // 1 millimeter tolerance (lower compression)
let max_err = 0.1; // 10 centimeter tolerance (higher compression)Guidelines:
- Small errors (0.001 - 0.01): High quality, lower compression (~10-30x)
- Medium errors (0.01 - 0.1): Good balance (~30-80x)
- Large errors (0.1 - 1.0): High compression, visible artifacts (~80-300x)
use lepcc::prelude::*;
use std::fs;
fn modify_slpk_geometry(
slpk_path: &str,
node_index: usize,
geometry_index: usize
) -> Result<(), Box<dyn std::error::Error>> {
// I3S SLPK structure: nodes/{node}/geometries/{index}.bin.pccxyz
let geometry_path = format!(
"{}/nodes/{}/geometries/{}.bin.pccxyz",
slpk_path, node_index, geometry_index
);
// Read existing compressed data
let compressed = fs::read(&geometry_path)?;
// Get metadata
let num_points = get_num_points(&compressed)?;
let blob_size = get_blob_size(&compressed)?;
println!("File: {} bytes, {} points", blob_size, num_points);
// Modify or process data
let decompressed = decompress_xyz(&compressed)?;
// ... modify points ...
let new_compressed = compress_xyz(&decompressed, 0.01, 0.01, 0.01)?;
// Write back
fs::write(geometry_path, new_compressed)?;
Ok(())
}use lepcc::prelude::*;
use std::path::Path;
fn process_directory(input_dir: &str, output_dir: &str) -> Result<(), Box<dyn std::error::Error>> {
for entry in Path::new(input_dir).read_dir()? {
let path = entry?.path();
if path.extension().map_or(false, |e| e == "obj") {
// Read OBJ
let points = read_obj_points(path.to_str().unwrap())?;
// Compress
let compressed = compress_xyz(&points, 0.01, 0.01, 0.01)?;
// Write output
let output_name = path.file_stem().unwrap();
let output_path = Path::new(output_dir)
.join(format!("{}.pccxyz", output_name.to_str().unwrap()));
fs::write(output_path, compressed)?;
println!("Processed: {:?}", path);
}
}
Ok(())
}All functions return Result<T, LepccError>:
use lepcc::prelude::*;
match compress_xyz(&points, 0.01, 0.01, 0.01) {
Ok(compressed) => {
println!("Compressed {} bytes", compressed.len());
}
Err(e) => {
eprintln!("Compression failed: {}", e);
// Handle specific error types
match e {
LepccError::WrongParam(msg) => println!("Invalid parameter: {}", msg),
LepccError::BufferTooSmall { needed, provided } => {
println!("Need {} bytes but only have {}", needed, provided);
}
_ => println!("Other error: {:?}", e),
}
}
}Typical compression ratios for point clouds (100K - 10M points):
| Data Type | Original | Compressed | Ratio | Decode Speed* |
|---|---|---|---|---|
| XYZ (1mm tol) | 2.4 MB | 120 KB | 20:1 | ~100 MB/s |
| XYZ (1cm tol) | 2.4 MB | 30 KB | 80:1 | ~120 MB/s |
| XYZ (10cm tol) | 2.4 MB | 10 KB | 240:1 | ~150 MB/s |
| RGB | 300 KB | 100 KB | 3:1 | ~200 MB/s |
| Intensity | 200 KB | 50 KB | 4:1 | ~250 MB/s |
*Decompression speed on Intel i7. Compression speed is ~2-3x slower but still fast for typical datasets.
| Format | Read | Write | Notes |
|---|---|---|---|
| I3S .pccxyz | ✓ | ✓ | Full compatibility |
| I3S .pccrgb | ✓ | ✓ | Full compatibility |
| I3S .pccint | ✓ | ✓ | Intensity attribute |
| I3S .pccflags | ✓ | ✓ | Classification/flags |
| Platform | Tested | Status |
|---|---|---|
| Windows | ✓ | MSVC stable/nightly |
| Linux | ✓ | GCC 7+, Clang 10+ |
| macOS | ✓ | Clang 10+ |
The Rust encoder produces bit-for-bit identical compressed data to the C++ reference implementation. Files encoded with Rust can be decoded by:
- ✓ C++ reference decoder
- ✓ Rust decoder
- ✓ ArcGIS Pro and other I3S-compatible software
Note: There is a known minor difference in the checksum field (algorithm is correct but uses slightly different representation). The actual compressed data is identical and files are functionally equivalent.
LEPCC is designed for I3S (Indexed 3D Scene Layer) point clouds:
use lepcc::prelude::*;
struct I3SPointCloud {
// Geometry (compressed XYZ)
geometry: Vec<u8>, // .pccxyz format
// Optional attributes
rgb: Option<Vec<u8>>, // .pccrgb format
intensity: Option<Vec<u8>>, // intensity
classification: Option<Vec<u8>>, // flags
}
fn create_i3s_point_cloud(points: &[Point3D]) -> Result<I3SPointCloud, LepccError> {
let geometry = compress_xyz(points, 0.01, 0.01, 0.01)?;
Ok(I3SPointCloud {
geometry,
rgb: None,
intensity: None,
classification: None,
})
}See the examples/ directory for more complete examples:
encode_bunny.rs- Encode Stanford Bunny modeldecode_only.rs- Decode compressed files
Run examples:
# Build and run an example
cargo run --release --example encode_bunny
# Decode a file
cargo run --release --example decode_only -- input.pccxyz
# Run all examples
for ex in examples/*.rs; do
cargo run --release --example $(basename $ex .rs)
doneRun the test suite:
cargo testRun with release optimizations:
cargo test --releaseRun specific tests:
cargo test test_compression_roundtrip
cargo test test_rgb_compression# Debug build
cargo build
# Release build (recommended for production)
cargo build --release# Run Clippy
cargo clippy -- -D warnings
# Format code
cargo fmt# Build documentation
cargo doc --open
# Build with private items
cargo doc --open --document-private-itemslepcc-rust/
├── src/
│ ├── lib.rs # Library entry point
│ ├── api.rs # High-level public API
│ ├── types.rs # Public types (Point3D, RGB, etc.)
│ ├── error.rs # Error types
│ ├── lepcc_xyz.rs # XYZ compression/decompression
│ ├── cluster_rgb.rs # RGB color compression
│ ├── intensity.rs # Intensity compression
│ ├── flag_bytes.rs # Flag bytes compression
│ ├── bit_stuffer2.rs # Bit-level compression
│ ├── huffman.rs # Huffman coding
│ ├── bit_mask.rs # Bit masking utilities
│ └── common.rs # Common utilities (checksum, etc.)
├── examples/ # Example programs
├── tests/ # Integration tests
└── Cargo.toml # Project configuration
To run benchmarks (criterion crate):
# Install criterion if not already installed
cargo install cargo-criterion
# Run benchmarks
cargo criterionLicensed under the MIT License. See LICENSE for details.
Original LEPCC algorithm and C++ implementation by Esri.
- Esri LEPCC GitHub - Original C++ implementation
- I3S Specification - Indexed 3D Scene Layer format -LERC Paper - Related compression algorithm
- Initial release
- Full XYZ, RGB, Intensity, and FlagBytes compression/decompression
- I3S format compatibility
- C++ decoder compatibility
For issues, questions, or contributions:
- Open an issue on GitHub
- Check the examples directory
- Review the API documentation
- Read the lib.rs documentation for module organization
Note: This library faithfully implements the original C++ algorithm. The compressed data is bit-identical to the reference implementation, with only a known minor difference in the checksum field that does not affect functionality or compatibility.