Skip to content

SOG meta.json: optional camera block (rest pose + pixel intrinsics) so a .sog is self-describing #319

Description

@dfattal

A .sog handed to a third-party viewer carries no viewpoint. Today the camera lives only in the viewer layer — settings.json / the sse-bootstrap inline block (supersplat-viewer/src/schemas/v2.ts, cameras[0].initial: {position, target, fov}) — which write-html.ts writes as a sibling of the .sog, never inside it. splat-transform in.ply out.sog (no --viewer-settings, which is HTML-only per the README) produces a file with no viewpoint at all, and so does SuperSplat's own SOG export (an editor-exported .sog has keys version,count,means,scales,quats,sh0,shN and no sibling settings.json).

Two different things are being conflated. settings.json is presentation — an authored framing, correctly kept out of the data. But a capture camera is intrinsic to the data: the gaussians were unprojected through it. Rendering a splat built at focal f_s through a frustum of f_v scales the image by f_v/f_s about the centre, so a viewer that guesses gets a silent zoom error. fov alone can't express it either — it has no principal point, so a deconverged or cropped capture is unrepresentable.

We generate SOGs from calibrated stereo pairs (single-photo 3DGS prediction), where the splat origin is the left capture camera and the correct rest view needs an off-axis frustum. We currently carry those numbers in our own database and re-attach them client-side, which means the .sog alone renders as intended for nobody else. The shape we need is the one INRIA's sidecar cameras.json already uses (utils/camera_utils.py::camera_to_JSON: position, rotation, pixel fx/fy, width/height) — established practice, just never inside a container.

Proposal: one optional top-level key in meta.json, sibling of count, version unchanged:

"camera": {
  "convention": "opencv",
  "rest": { "position": [0, 0, 0], "rotation": [0, 0, 0, 1] },
  "intrinsics": { "fx": 1194.666, "fy": 1194.666, "cx": 1024.0, "cy": 576.0, "width": 2048, "height": 1152 },
  "stereo": { "baseline_m": 0.063 }
}
  • rest: position in splat units, rotation xyzw camera→world; convention names the camera axes (opencv = +x right, +y down, +z forward) since a viewer must know which half-turn to apply.
  • intrinsics in pixels of the source image with explicit width/height, so the principal point survives.
  • stereo optional (second eye at +x·baseline_m).
  • ~250 bytes. Purely advisory; a viewer that doesn't want it ignores it.

This is already spec-legal — §5 says "readers should ignore unrecognized fields", read-sog.ts only checks version !== 2, the engine's sog.js cherry-picks fields — and we have shipped SOGs with an extra key for months without a reader complaint. The one thing that makes it need upstreaming rather than a private key: write-sog.ts rebuilds metaObj from scratch, so any re-encode silently drops it.

Happy to send the PR (writer preserves/accepts the block, MetaV2 type, spec page §2/§5) if the shape is acceptable. Related: #38 (SOG v2 format proposal); orthogonal to supersplat#924, which is the presentation layer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions