Current section
Files
Jump to
Current section
Files
docs/spatial_formats.md
# Spatial wire formats (v0.2.0 freeze)
This document freezes the on-wire layouts used by `ExCodecs.Spatial` in **v0.2.0**.
A future Rust backend (v0.2.1) must produce **byte-compatible** output for these
formats. There is **no CRC / checksum** in v1; integrity is the caller's responsibility.
Attribute keys on points are **strings** after construction / decode. Prefer string
keys when building clouds for cross-backend compatibility.
## One framework, specialized category APIs
| Category | Entry point | Discovery |
|----------|-------------|-----------|
| Registry binary codecs (`:zstd`, …) | `ExCodecs.encode/3`, `decode/3` | `available_codecs(:compression)` |
| Spatial domain codecs | `ExCodecs.Spatial.encode/2`, `decode/2` | `available_codecs(:spatial)` or `ExCodecs.Spatial.available_formats/0` |
Both categories belong to the ExCodecs framework and share its tagged-result
and error conventions. Their entry points differ because registry codecs map
binary↔binary, while spatial codecs map domain structs↔formats.
Spatial formats (`:ply`, `:spatial_binary`, `:gsplat`) are entries in the same
ETS-backed catalog as compression codecs. Their metadata has
`category: :spatial` and `interface: :spatial`; that interface marker keeps
operation dispatch on `ExCodecs.Spatial` rather than the binary API.
## PLY (`format: :ply`)
Standard PLY interchange (ASCII or binary little/big endian). Gaussian PLY uses
common property names (`f_dc_*`, `opacity`, `scale_*`, `rot_*`, `f_rest_*`).
Encode options (after Spatial selects `:ply`):
- `:ply_format` or `:format` — `:ascii` (default), `:binary` / `:binary_le`, `:binary_be`
- `:comments` — header comments
- `:as` — decode as `:auto`, `:point_cloud`, or `:gaussian_cloud`
### Schema promotion
If **any** point in a cloud has color / alpha / normals, the schema includes those
properties for **all** points. Missing values are defaulted (RGB `0`, alpha `255`,
normal `{0,0,0}`). Mixed optional fields therefore round-trip with defaults filled in.
### Streaming
`stream_decode` / `stream_encode` currently **materialize** the full payload (or
enumerable) then enumerate. Prefer explicit `source: :file` or `source: :binary`
when the argument is ambiguous.
`:auto` treats a binary as a path only when it looks path-like (under 4 KiB, no
`ply`/`EXCP`/`GSPL` magic, and contains `/` or `\` or ends with `.ply`/`.excp`/
`.gspl`/`.bin`) **and** `File.regular?/1` is true. Edge cases:
- A real file with no separator/extension is **not** auto-opened.
- A short binary containing `/` that happens to be a regular file path **is** opened.
## EXCP — `:spatial_binary` (version 1)
Little-endian compact point clouds.
```
Offset Size Field
0 4 magic "EXCP"
4 2 version u16 LE (= 1)
6 2 flags u16 LE
8 8 count u64 LE
16 … records
```
**Flags**
| Bit | Meaning |
|-----|---------|
| 0 | color (RGB u8 × 3) |
| 1 | alpha (RGBA; implies color bytes include alpha) |
| 2 | normals (f32 × 3) |
**Record order:** `x,y,z` as `f32` LE, then optional color bytes, then optional
normals. Flags are global for the file; points missing a promoted field are
zero-filled (alpha default 255).
**Not stored:** generic attributes, bounds, transform, metadata. Trailing bytes
after `count` records are ignored. Truncation yields `:invalid_data` /
`:truncated_input` as implemented.
## GSPL — `:gsplat` (version 1)
Little-endian compact Gaussian clouds.
```
Offset Size Field
0 4 magic "GSPL"
4 2 version u16 LE (= 1)
6 2 flags u16 LE (bit0 set when SH rest present)
8 8 count u64 LE
16 2 sh_rest u16 LE (floats of SH rest per Gaussian; 0 if none)
18 … records
```
**Record (always):** 14 × `f32` LE —
1. position XYZ (3)
2. DC color RGB (3) — maps to `Gaussian.color`
3. opacity (1)
4. scale XYZ (3)
5. rotation quaternion WXYZ (4)
Then `sh_rest` × `f32` LE (shared length for all Gaussians; shorter lists padded
with zeros on encode).
**Not stored:** per-Gaussian metadata maps, cloud metadata. Flags on decode are
currently informational; `sh_rest` count in the header is authoritative.
## Integrity
v1 formats have **no checksum**. For integrity, wrap payloads with a registry
codec (e.g. compress after encode) or store an external hash.