Decode in Python
Two read paths, both first-class: front to back, or index-first. See the feature matrix for what the conformance suite proves today.
pip install fourdgs
The whole scene, front to back
import fourdgs
scene = fourdgs.read("scene.4dgs") # a path, bytes, or a file-like object
state = scene.gaussians.state_at(1.5, scene.header.cutoff)
state["indices"] # which gaussians exist at t = 1.5
state["centers"] # their positions, moved by their own velocity
state["opacity"] # their alpha, faded by their own marginal
This is where decoding ends. state_at returns index arrays plus attributes so a caller can do what
it likes with them; what happens to those numbers afterwards is not the library's concern.
Pass the file's own cutoff rather than relying on the default whenever you have the Scene — the
threshold is the file's, and it is the same constant the encoder derived its velocity grid from.
A Scene carries more than its gaussians: header, quantization (the grids and the error bounds
the file declares), audio, camera, metadata, attachments, statistics, chunk_index,
summary_offsets, summary_crc_ok, skipped_opcodes and truncated. audio is None when the
scene has no soundtrack, which is the common case and not an error.
read(..., recover_truncated=True) is the default: a file cut short mid-write yields everything
complete before the cut, with scene.truncated set. Pass max_sh_band to stop short of the file's
full spherical-harmonic degree.
An instant, without reading the file
The indexed path opens a file the way a seeking client does — the Footer, then the index, then only the byte ranges the instant needs.
from fourdgs.indexed_reader import open_indexed, read_chunk
from fourdgs.readable import FileReadable
scene = open_indexed(FileReadable("scene.4dgs"))
scene.header.gaussian_count, scene.header.duration_sec, len(scene.index)
scene.chunks_for_time(2.5) # the entries covering an instant
scene.chunks_for_range(2.0, 4.0)
scene.bytes_for_time(2.5) # what that seek transfers, before asking for it
for entry in scene.chunks_for_time(2.5):
chunk = read_chunk(FileReadable("scene.4dgs"), scene, entry)
read_camera, read_metadata, read_attachments and read_audio fetch the front-matter records
the same way, each by its own byte range, so opening a scene with a large embedded audio track costs
nothing extra.
Any transport
Both readers depend on Readable — something that can report its size and read a byte range — and
on nothing else:
class Readable(Protocol):
def size(self) -> int: ...
def read(self, offset: int, length: int) -> bytes: ...
FileReadable and BytesReadable ship with the package. An HTTP range reader is a few lines and
belongs in the consumer.
A runnable version of the indexed path, which reports what each seek costs, is
python/examples/decode_summary.py,
which CI runs so it cannot rot.
Errors
Failures are typed, and the distinction matters: MalformedFile means the bytes are wrong,
UnsupportedVersion and UnsupportedCodec mean the file is legal and this build cannot read it,
TruncatedFile means it was cut short, and BoundViolation means a declared error bound was not
met. All derive from FourdgsError.
From the command line
4dgs info scene.4dgs # summarize the file and what seeking it costs
4dgs validate scene.4dgs # check it against the specification
4dgs decode scene.4dgs -t 2.5
The specification is the normative reference, concepts is the vocabulary, and the implementation notes collect the decode-side lessons that are easy to get wrong.