Expand description
§depthpack
Compact codec for 16-bit raster fields with nodata — built for depth maps produced by projecting LiDAR into panoramic (equirectangular) camera frames.
A depthpack blob stores an integer lattice of u16 counts
(0 = nodata) plus a physical mapping value = count · scale + offset and an opaque unit label. The encoder predicts each valid
count from its causal neighbours with the LOCO-I / JPEG-LS median
edge detector (MED) and compresses the zigzag residuals plus a
1-bpp validity mask with zstd. Smooth surfaces — the dominant content
of a depth map built from a triangulated point cloud — leave
near-zero residuals, which is why this beats generic image codecs on
both size and speed for this data.
The lattice roundtrips bit-exact; the codec never quantizes. Pick
your precision by choosing scale and quantizing before you encode
(e.g. millimetres → scale = 0.001, unit = "m"; a 5 mm lattice →
scale = 0.005). depthpack applies scale/offset on
decode_scaled but never interprets unit — it carries the
label verbatim, so a survey-foot producer stores foot counts with
unit = "ftUS" and no unit conversion happens anywhere in this crate.
§Feature flags
The crate is pure Rust by default — both encode and decode
compile for wasm32-unknown-unknown unchanged (zstd via ruzstd).
zstd-c(optional) — swap the encoder’s entropy stage to the Czstdbindings. Measured on a real 7200×3600 depth map: ~2.6× faster encode and ~27% smaller output than the pure-Rust encoder (ruzstd’sFastestcompresses less densely than C zstd level 1), and it honoursEncodeOptions::zstd_level. Recommended for native bulk pipelines; decoding always stays pure Rust either way.
§Example
let (w, h) = (64u32, 32u32);
// A smooth surface (millimetre counts) with a nodata hole.
let counts: Vec<u16> = (0..w * h)
.map(|i| if i % 97 == 0 { 0 } else { 2500 + (i % w) as u16 })
.collect();
// Millimetre lattice interpreted as metres.
let opts = depthpack::EncodeOptions { scale: 0.001, unit: "m".into(), ..Default::default() };
let blob = depthpack::encode(&counts, w, h, &opts).unwrap();
// Raw lattice roundtrips bit-exact…
let img = depthpack::decode(&blob).unwrap();
assert_eq!(img.values, counts);
// …and decode_scaled applies scale, NaN for nodata.
let scaled = depthpack::decode_scaled(&blob).unwrap();
assert_eq!(scaled.unit, "m");
assert!((scaled.values[1] - 2.501).abs() < 1e-6); // count 2501 · 0.001 m
assert!(scaled.values[0].is_nan()); // nodataStructs§
- Depth
Image - A decoded raster: the raw integer lattice plus its physical mapping.
valuesis row-major,0= nodata. - Encode
Options - Options for
encode. - Header
- Parsed container header. Obtain with
decode_header. - Scaled
Image - A decoded raster in physical units:
count · scale + offset, withNaNat nodata pixels. Returned bydecode_scaled.
Enums§
Constants§
- HEADER_
LEN - Fixed header length in bytes.
- MAGIC
- Magic bytes at the start of every blob.
- MAX_
PIXELS - Upper bound on
width × heightaccepted by the decoder, so a malformed header cannot request an arbitrarily large allocation. 2²⁷ px comfortably covers 7200×3600 panoramas with a wide margin. - UNIT_
LEN - Maximum length of the
unitlabel in bytes. - VERSION
- Container version this library reads and writes.
Functions§
- decode
- Decode a depthpack blob into a fresh
DepthImage(raw lattice + physical mapping). - decode_
header - Parse and validate the fixed header without decoding pixel data.
- decode_
into - Decode into a caller-provided lattice buffer of exactly
width × heightcounts, avoiding the output allocation. Nodata pixels are written as0. - decode_
scaled - Decode a blob directly to physical values (
count · scale + offset), withNaNat nodata pixels. depthpack applies the scale but never the unit — the returnedScaledImage::unitis the stored label. - decode_
scaled_ into - Decode physical values into a caller-provided
f32buffer of exactlywidth × height(NaN= nodata). Uses an internalu16scratch for the lattice reconstruction, then appliesscale/offset. - encode
- Encode a row-major lattice of
u16counts (0= nodata).