Skip to main content

SfM Backends

Structure-from-Motion (SfM) is the first stage of the pipeline. It takes your images and estimates where each camera was positioned in 3D space. These camera poses are then used to train the Gaussian Splat model. Two SfM backends run today:

Backend comparison

prebuilt_colmap is selected automatically when your zip contains a sparse/ model — it is not something you request via sfm.backend. The pipeline also registers none_arkit, a pose passthrough, but no API input gives it the model it copies, so selecting it fails the scene (refunded; NoneArkitBackend.run in sfm_backends/none_arkit.py).
The API also accepts legacy short-names for backward compatibility: glomap and colmap both resolve to colmap_sift_glomap, and none resolves to none_arkit. Prefer the canonical names in new code; the colmap alias emits a deprecation warning and may be removed.

colmap_sift_glomap (default)

GPU SIFT feature extraction (via COLMAP) combined with GLOMAP’s modern global mapper, which solves all camera poses simultaneously rather than incrementally.
Advantages:
  • Significantly faster than incremental COLMAP
  • GPU SIFT for feature extraction
  • Global optimization is more robust for video walkthroughs
  • Default for all presets
When to use: Almost always. This is the recommended backend for video input and most photo sets. If you don’t override sfm.backend, this is what runs.

Options

Set these in sfm.options. SfM reads only this object: the top-level sfm.matcher is ignored, and sfm.max_image_size sets only the training resolution (SfmRequest.from_api_payload in sfm_backends/configs.py). The backend also accepts tuning keys for COLMAP and GLOMAP (camera_model, max_num_features, triangulation thresholds and others, in ColmapSiftGlomapOptions); they are unsupported and may change. An unknown key or bad value is refused when /process dispatches the job: 400 invalid_input (Invalid sfm options: ...), and the charge is refunded (translatePydantic422 in api/src/lib/scenes.ts).

Camera poses you already have

There are two ways to supply poses. Only a COLMAP model skips SfM. Pass camera poses directly in the POST /scenes/{id}/process request body. The API stores them server-side. No zip required — upload your video as normal. GLOMAP still solves the camera poses from the frames (_stage_sfm in pipeline/training/impl.py); the ARKit poses set which way is up and, for a video, which frames are cut: up to 150, at the pose timestamps (_extract_arkit_timestamp_frames).
One pose:
Pose format: Minimum 5 poses, maximum 1000. You don’t set sfm.backend: the API sends the poses with the job, and the pipeline runs GLOMAP as for any upload. The scene’s orientation comes from these poses. The pipeline reads gravity off the world’s +Y axis, so record them in an ARKit session with worldAlignment set to .gravity (the default) or .gravityAndHeading, not .camera. It needs at least 30 frames or photos paired with a pose and a median tilt residual (each pair’s up against the fitted up) of 3° or less; otherwise it estimates up from the cameras, which lays portrait captures on their side (MIN_PAIRS, MAX_MEDIAN_TILT_DEG in pipeline/training/arkit_gravity.py). Photos are used as stored: EXIF orientation is ignored when arkit_poses is set, so each image’s pixels must be in the camera axes its transform describes, as ARKit’s capturedImage is. If you’re building an iOS app, the Splat3D Swift SDK handles all of this for you — ARKit capture, pose encoding, upload, and polling in one call.

Path B: COLMAP zip upload

If you’ve already run COLMAP (or Metashape, RealityCapture, or your own SfM code that exports COLMAP binaries), bundle your images and sparse model into a zip. The pipeline detects the sparse/ model, skips SfM entirely, and starts training immediately. This is the fastest path to a splat and gives you full control over the reconstruction. Zip structure:
A single wrapper folder is fine (my-scene/images/ + my-scene/sparse/0/) — zip the folder or its contents, whichever your tool produces. If several models are present, sparse/0 is used; among several sparse/0 folders, the one with images/ beside its sparse/. Two tool outputs also work zipped as they are:
  • Inria 3DGS convert.py — the undistorted sparse/0 and images/ are used; input/ and distorted/ are ignored.
  • nerfstudio ns-process-data — the model in colmap/sparse/0 is paired with the photos in the top-level images/; images_2/ and the other downscaled copies are ignored.
You do not need to set sfm.backend — detection is automatic. Uploading a zip with no sparse/ directory runs the normal GLOMAP path.

Requirements

Your model is validated before training starts, so a mismatch fails fast with a message instead of burning GPU minutes: Photos in the zip that the model never registered are ignored. Getting COLMAP binary output:

When to use pre-supplied poses

Matching strategies

Feature matching only happens when SfM actually runs, so a COLMAP model zip (prebuilt_colmap) ignores these settings. Otherwise you can control the matching strategy in sfm.options:
For video input, sequential matching is usually both faster and more reliable since frames are naturally ordered, and it is what video gets when you set none. Photo sets get exhaustive.