acron.sog

Gaussian-splat bundles: SOG v2 — “Spatially Ordered Gaussians”, the compressed format written by PlayCanvas’s splat-transform tool and served by the splat hosts built on it — read as they are, and the 3DGS .ply a trainer writes, packed into the same shape on load. SogBundle is the package’s splat representation whatever the source; read_splats() picks the reader by extension.

A .sog is a stored ZIP of a meta.json and webp images (“planes”), or the same files in a directory. Every plane is one W×H image with splat i at texel (i % W, i // W), rows counted from the TOP of the image; the splats are Morton-ordered, so the first k of them are a spatial chunk, not a sample (SogBundle.decimate() gives a sample).

The planes, all RGBA8 (v2):

  • means_l / means_u — low and high bytes of a 16-bit position per axis, linear in a log-transformed space sign(x)·log(1+|x|) between meta.means.mins / maxs.

  • quats — “smallest three”: the largest-magnitude component of the unit quaternion is dropped, a carries 252 + its index in wxyz order and rgb the other three as (v/√2 + 0.5)·255.

  • scales — three indices into meta.scales.codebook (log scales).

  • sh0 — three indices into meta.sh0.codebook (the DC spherical-harmonic term; colour is 0.5 + c·SH_C0) and the opacity in a.

  • shN_labels / shN_centroids — a 64K-entry k-means palette of the higher SH bands (view-dependent colour): a 16-bit label per splat into a centroid image of 64 entries per row, each entry coeffs texels of codebook indices (one per colour channel). Read with with_sh=True; acron.splats evaluates them per update. (A bundle built from a PLY has no k-means palette: every splat is its own entry, the label is 24-bit and the palette row holds as many entries as a texture row fits — SogBundle.sh_entries_per_row; the shader reads both layouts.)

No numpy. The array work is native: acron.gsplat.GSplatBundle (C++, acron/src/gsplat) reads the .sog (Acron’s ZipArchive + a built-in lossless-webp codec — Acron and the engine’s thirdparty set have no webp, so the module carries a dependency-free VP8L decoder/encoder) and the .ply, packs arrays into planes, decodes them on the CPU and writes both formats. A C++ game uses that class directly; this module is its Python face. The planes stay in Acron arrays and go to the GPU as they are (acron.splats decodes them in GLSL); Python sees them through read-only memoryviews over the native storage (CPTA_uchar: no copy, and no way to resize or overwrite what the native decode reads). SogBundle.positions(), quaternions(), scales(), colors() and sh_coefficients() are the CPU decode — for sorting, tests and tools — and return a SplatArray: a float32 buffer with a shape (a memoryview over the native array; with numpy, np.asarray(a) / np.frombuffer(a.data, np.float32) wraps it without a copy). The array arguments of the encoders take a SplatArray, any buffer (array('f'), a numpy array) or nested sequences.

SogBundle(native)

A splat bundle: the native GSplatBundle (native) and its planes as memoryviews.

SplatArray(buffer, shape)

A float32 array with a shape, over a native buffer — what the CPU decode returns.

Global Functions

bundle_from_arrays(positions, quaternions, scales, colors, sh=None, *, name: str = 'arrays', palette: str = 'auto') → SogBundle

A bundle in memory from plain arrays — the encoder’s first half, the inverse of the decode. quaternions are (x, y, z, w); scales are linear; colors linear rgb + opacity; sh, if given, is (n, 3 | 8 | 15, 3) higher-order coefficients. Each argument is a SplatArray, a buffer or nested sequences of rows.

palette — how the SH palette is laid out: "sog" (64 entries per row, 16-bit labels: what a .sog file holds; needs n ≤ 65536), "direct" (one entry per splat, as many per row as a texture row fits, 24-bit labels — any n a texture can hold), or "auto" ("sog" when it fits, else "direct").

decode_webp(data: bytes) → PNMImage

A lossless webp as a 4-channel PNMImage (the native codec; lossy webp, a malformed file or one over webp_max_pixels() pixels is refused with a ValueError).

encode_webp(image, width: int | None = None, height: int | None = None) → bytes

A lossless webp of a PNMImage, or of RGBA8 texels (row 0 = top): an (H, W, 4) buffer such as a SogBundle.planes entry, or a flat one with width / height.

floats(values) → PointerToArray_float

values as a flat PTA_float (a SplatArray / PTA_float is passed through; any float32 buffer is copied once; other buffers and nested sequences are converted).

inverse_log_transform(y: float) → float
log_transform(x: float) → float
read_ply(path, *, with_sh: bool = True, name: str | None = None) → SogBundle

A 3DGS .ply (the trainer’s output: per-vertex x y z, f_dc_0..2, opacity as a logit, scale_0..2 as logs, rot_0..3 as (w, x, y, z), and f_rest_0..44 channel-major) as a bundle. Binary (either endianness) and ASCII files are read; with_sh keeps the higher bands (a direct palette: 15 texels per splat, so 400 MB of texture for a 6.8 M scan).

read_sog(path, *, with_sh: bool = False) → SogBundle

Read a .sog bundle (a stored ZIP) or a directory holding its files.

read_splats(path, *, with_sh: bool = True, **kw) → SogBundle

A bundle from a .sog (file or directory) or a 3DGS .ply, by extension.

set_webp_max_pixels(pixels: int) → None

Raise or lower the webp decoder’s pixel cap (process-wide; 0 restores the default). A plane over the cap is refused before anything is allocated.

webp_max_pixels() → int

The largest width × height the webp decoder accepts (default 64 Mpixel).

write_bundle(path, b: SogBundle) → None

Serialise an in-memory bundle as a .sog (stored ZIP of webp planes). A direct (wide) SH palette is not a .sog layout and is refused.

write_ply(path, positions, quaternions, scales, colors, sh=None) → None

A 3DGS .ply from plain arrays (binary little-endian) — the inverse of read_ply(), for tests and tools. Arguments as bundle_from_arrays().

write_sog(path, positions, quaternions, scales, colors, sh=None) → None

Write a v2 bundle from plain arrays — the inverse of the decode, for tests and tools (lossless webp, linear 256-entry codebooks). Arguments as bundle_from_arrays(); with sh the file’s palette holds one entry per splat, so n ≤ 65536.