Skip to content

[feat] Expose fractionalBits in PackOptions#80

Open
RiyaanB wants to merge 1 commit intonianticlabs:mainfrom
RiyaanB:feat/pack-options-fractional-bits-zstd-level
Open

[feat] Expose fractionalBits in PackOptions#80
RiyaanB wants to merge 1 commit intonianticlabs:mainfrom
RiyaanB:feat/pack-options-fractional-bits-zstd-level

Conversation

@RiyaanB
Copy link
Copy Markdown
Contributor

@RiyaanB RiyaanB commented May 4, 2026

Context

Exposes the position fixed-point precision (fractionalBits) as a configurable PackOption. The format already encodes fractionalBits in NgspFileHeader byte 13, and the decoder reads it on every load — so this change is purely encoder-side. Any value the encoder accepts round-trips through every existing decoder; no version bump or format change.

Per-axis representable range is ±2^(23 − fractionalBits); resolution is 2^−fractionalBits:

N range resolution
4 ±524 288 m ~6 cm MIN_FRACTIONAL_BITS
8 ±32 768 m ~4 mm
12 (default) ±2 048 m ~0.24 mm
16 ±128 m ~15 µm
20 ±8 m ~0.95 µm
23 ±1 m ~0.12 µm MAX_FRACTIONAL_BITS

Default is unchanged, so all existing callers behave identically.

Changes

  • Add PackOptions.fractionalBits (default 12, range [MIN_FRACTIONAL_BITS = 4, MAX_FRACTIONAL_BITS = 23]).
  • Out-of-range values are rejected at save time with a logged error, matching the existing sh1Bits validation style. The lower bound rules out N ∈ {0, 1, 2, 3} — values that give decimeter-or-coarser resolution over thousand-kilometre ranges and have no plausible Gaussian-splat use case.
  • Replace the hardcoded packed.fractionalBits = 12 in packGaussians with o.fractionalBits.
  • Python: PackOptions.fractional_bits plus module constants spz.MIN_FRACTIONAL_BITS, spz.MAX_FRACTIONAL_BITS, spz.DEFAULT_FRACTIONAL_BITS.
  • README PackOptions section updated.

Test plan

tests/python/test_pack_options_v4.py (10 cases, all passing locally; 58 / 58 in the full Python suite):

  • Defaults preserved. All 48 pre-existing tests pass unchanged. SHA256 of save_spz output with default PackOptions() is byte-identical to upstream/main on both samples (hornedlizard.spzc5dcc897…2d286, racoonfamily.spz1a5d4676…767df).
  • fractional_bits round-trip. Parametrized over n ∈ {4, 8, 12, 16, 20, 23}. Each case asserts:
    • the fractionalBits field of NgspFileHeader (byte 13: magic[0..3], version[4..7], numPoints[8..11], shDegree[12], fractionalBits[13]) echoes the set value;
    • position round-trip max error is ≤ 2^−n, with input extent = min(8, 0.5 × 2^(23−n)) so values fit strictly within the representable range — including n = 23, where extent collapses to 0.5 and range to ±1.
  • fractional_bits rejection. Parametrized over {MIN_FRACTIONAL_BITS − 1, 0, MAX_FRACTIONAL_BITS + 1} — every out-of-range value causes save_spz to return false.

Compatibility & relationship to PR #81

This PR does not protect against picking a fractionalBits value that's too high for the cloud's extent — positions outside ±2^(23−N) silently wrap with the existing encoder. That bug predates this PR (any cloud with extent > 2 km already wraps today at the hardcoded N = 12) and is fixed in a separate PR: #81 — Reject positions that overflow the int24 fixed-point range.

The two compose cleanly: with both landed, the encoder rejects out-of-range fractionalBits and out-of-range positions for whatever fractionalBits is in effect. This PR doesn't strictly depend on #81 — it's safe to merge in either order, since #81 also protects existing N=12 callers regardless of whether fractionalBits is configurable.

The original draft of this PR also exposed compressionLevel. That part has been dropped — without an official format spec the conservative default is to keep encoder output variance small. fractionalBits is the part the format already pins via the header field.

Comment thread src/cc/load-spz.h
// Higher values would either silently truncate the encoded fixed-point value
// (it would no longer fit in int24) or invoke undefined behavior in the
// (1 << fractionalBits) computation used by both encode and decode.
constexpr int MAX_FRACTIONAL_BITS = 23;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should probably be a reasonable MIN_FRACTIONAL_BITS to enforce avoiding unrealistically small cases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! How about MIN_FRACTIONAL_BITS = 4? gives ±524km range / 6cm resolution as the floor — covers any aerial / city-scale use case, rules out N=0...3 which seem nonsensical (sub-half-meter precision over hundreds of km)

@RiyaanB RiyaanB force-pushed the feat/pack-options-fractional-bits-zstd-level branch from e849096 to 792c46e Compare May 5, 2026 00:15
@RiyaanB RiyaanB changed the title [feat] Expose fractionalBits and ZSTD compression level in PackOptions [feat] Expose fractionalBits in PackOptions May 5, 2026
Exposes the position fixed-point precision as a configurable PackOption.
The format already encodes fractionalBits in NgspFileHeader byte 13 and
the decoder reads it; this change is purely encoder-side. Any value the
encoder accepts round-trips through every existing decoder.

Per-axis representable range is +/- 2^(23 - fractionalBits) and
resolution is 2^-fractionalBits:
  N=4:  +/- 524288 m, ~6 cm     (MIN_FRACTIONAL_BITS)
  N=8:  +/- 32768 m,  ~4 mm
  N=12: +/- 2048 m,   0.24 mm   (default, unchanged)
  N=16: +/- 128 m,    15 um
  N=20: +/- 8 m,      0.95 um
  N=23: +/- 1 m,      0.12 um   (MAX_FRACTIONAL_BITS)

Default is unchanged (12), so existing callers behave identically.
Verified by SHA256: save_spz with default PackOptions on both sample
files produces output byte-identical to upstream/main.

Out-of-range fractionalBits is rejected at save time with a logged
error, matching the existing sh1Bits validation style. The lower bound
MIN_FRACTIONAL_BITS=4 was added per review feedback to rule out values
that have no plausible Gaussian-splat use case (N=3 has 12.5 cm
resolution over a 1048 km range; lower N gets progressively worse).

Note: this PR does not protect against picking a fractionalBits value
that's too high for the cloud's extent -- positions outside the
representable range silently wrap with the existing encoder. That bug
predates this PR (any cloud with extent > 2 km already wraps at the
hardcoded N=12) and is fixed in a separate PR (nianticlabs#81), which composes
cleanly: when both land, encoder rejects out-of-range fractionalBits
*and* out-of-range positions for the chosen fractionalBits.

Tests (tests/python/test_pack_options_v4.py, 10 cases):
- Defaults match historical values; all 48 pre-existing tests pass
  unchanged with byte-identical output.
- For fractional_bits in {4, 8, 12, 16, 20, 23}: byte 13 of the file
  (the fractionalBits field of NgspFileHeader) echoes the set value,
  and position round-trip max error is bounded by the quantization
  step 2^-N. Per-N input extent is sized to fit strictly within the
  representable range.
- fractional_bits below MIN_FRACTIONAL_BITS (3, 0) and above
  MAX_FRACTIONAL_BITS (24) are all rejected.
@RiyaanB RiyaanB force-pushed the feat/pack-options-fractional-bits-zstd-level branch from 792c46e to a3cea38 Compare May 5, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants