Getting Started¶
Quickstart¶
With a good set of photographs, use SfM Tool to create and view a sparse reconstruction in a few steps. This uses COLMAP and GLOMAP under the hood.
We'll use the dino dog toy dataset — 85 photographs of a dog toy taken from different angles. The table and the toy have texture detail that gives a rich set of SIFT features for the solve.
1. Install and set up¶
Create a workspace directory and copy your images into it.
2. Initialize the workspace¶
$ sfm ws init --max-features 4000
Initialized workspace: .../dino
Configuration file: .../dino/.sfm-workspace.json
feature_tool: sfmtool
contrast_threshold: 0.0067
octave_layers: 3
max_num_features: 4000
3. Solve¶
Run the global SfM solver (GLOMAP). This automatically extracts SIFT features, matches them across images, and solves for camera poses and 3D structure.
$ sfm solve -g images/ --max-features 2000
Running global SfM with GLOMAP...
Image files:
.../dino/images/dino_dog_toy_%02d.jpg (85 files, sequence 1-85)
...
Found reconstruction 0:
Cameras: 1, Images: 85, Points: 10580
Saved reconstruction to: .../dino/sfmr/20260413-00-solve-dino_dog_toy_1-85.sfmr
Completed in 1m 4.75s
4. Explore in 3D¶
Launch the SfM Explorer GUI and open the .sfmr file from the sfmr/
directory.
Note
SfM Explorer is part of the PyPI package from sfmtool 0.3, so sfm
explorer works after pip install sfmtool. The 0.2 package does not
include it; with 0.2, run the viewer from a source checkout with
pixi run gui. Its automated window tests run on Windows, macOS and
Linux, but most hands-on use has been on Windows. On Linux it needs a
Vulkan driver.

Converting Video to Image Sequences¶
To try many different things with a particular 3D reconstruction, having the
data as a video complicates individual frame access. Therefore the sfm command expects you
to always convert to images first.
Here's an example of using ffmpeg to convert a
single video in the current directory into a sequence of images in a frames
subdirectory:
# Assumes there's one video in the current directory
VIDEO_FILE=$(echo *.mp4)
IMAGE_BASE=$(basename "$VIDEO_FILE")
IMAGE_BASE=${IMAGE_BASE%.*}
mkdir -p frames
ffmpeg -i "$VIDEO_FILE" \
-qscale:v 2 \
frames/"${IMAGE_BASE}_%04d.jpg"
Viewing 3D Point Tracks¶
A point track connects a 3D point to its 2D observations across multiple images. Exploring tracks is a great way to understand how SfM links features across photographs.
1. Set up the layout¶
Arrange SfM Explorer so that the Image Detail and Track View panels are visible side by side.

In this screenshot, I've also double-clicked on the third image to set the 3D viewport, selected a feature in the Image Detail panel, and changed the Image Detail panel to view the reprojection error as a heat map. The feature is part of a track with 18 observations, and the table shows the SIFT feature sizes and reprojection error in both pixels and degrees.
2. Navigate the 3D viewport¶
The 3D viewport is in camera view mode. Use the mouse or trackpad to look around freely, and hold Control to zoom the field of view in and out. You can click on image frustums to select them, which will show them in the Image Detail panel, and you can click on 3D points to select them and show their tracks. Here, I've looked over to the right, selected a 3D point, then selected one of the cameras in its track.

3. Free navigation¶
Hold Shift and drag the mouse (or use the trackpad gesture) to exit the camera view and pan freely in 3D. Now the mouse or trackpad will orbit around a target. You can also use keyboard controls to fly through the scene:
- A / D — fly left / right
- W / S — fly forward / back
- R / F — fly up / down
- Q / E — tilt left / right

In this screenshot, the Image Detail now shows the track length heat map, and I picked out a feature that's part of a long track with 58 observations. If you're comfortable in 3D video games, it should be easy to move the viewport to get a good view of all the track projection rays as I have done.
4. Manipulate the target¶
Hold Alt and drag the mouse (or use the trackpad gesture) to make the target visible. Instead of orbiting, the drag will now look around freely from a stationary point. To keep the target visible and see its position and orientation while you navigate the scene, tick Target indicator in the Layers section of the display controls at the top right of the 3D view.

The orbiting behavior is affected by the target, which shows the ground plane as a "compass" and a vertical line showing the up/down direction. If you look at the compass edge-on, you can use the Q and E keys to rotate the target, and get it aligned with any ground plane in your SfM solution. This will make orbiting around it more comfortable.
Inspecting a Reconstruction¶
1. Reconstruction summary¶
Use sfm inspect -v to get a detailed summary of a reconstruction from the
command line. Even from the command line, you can get an idea about the 3D
points from this output. The bounding box extents are accompanied by histograms
that show the point density projected to each axis. Similarly, you can see a
histogram of the reprojection error, track lengths, and 3D distance to the
nearest other point. (Without -v, sfm inspect prints a one-screen summary.)
$ sfm inspect -v sfmr/20260413-00-solve-dino_dog_toy_1-85.sfmr
Reconstruction file: 20260413-00-solve-dino_dog_toy_1-85.sfmr
======================================================================
Metadata:
Operation: sfm_solve
Tool: glomap
Tool version: unknown
Timestamp: 2026-04-13T20:35:01.368085-07:00
Workspace:
Absolute path: C:\Dev\dino-test
Relative path: ..
Resolved workspace: C:\Dev\dino-test
Feature tool: sfmtool
Reconstruction summary:
Images: 85
Thumbnails: yes
Patch bitmaps: no
Image paths:
images\dino_dog_toy_%02d.jpg (85 files, sequence 1-85)
Cameras: 1
3D points: 10580
Observations: 80387
Avg observations per point: 7.60
Cameras:
Camera 0: SIMPLE_RADIAL 2040x1536
Parameter Value
-------------------- --------------
focal_length 1472.560231
principal_point_x 1020.000000
principal_point_y 768.000000
radial_distortion_k1 0.021340
Rig configuration: none
3D Point statistics:
Position range:
X: [-14.750, 31.935]
X distribution:
▁ ▁ ▁ ▁▁▁▁▁▁▂▅█▆▄▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁ ▁ ▁
-14.750 31.935
Y: [-6.598, 15.843]
Y distribution:
▁ ▁▁▁ ▁▁▁▁▁▁▁▁▁▂▃▆█▆▄▅▇▇▅▁▁▁▁▁▁ ▁ ▁▁▁ ▁ ▁▁▁▁
-6.598 15.843
Z: [-7.850, 14.627]
Z distribution:
▁ ▁▁ ▁▁▁▁▁▁ ▁▁▁▁▂▆█▇▅▃▂▃▃▂▁▁▁▁▁ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁
-7.850 14.627
Reprojection error:
Mean: 1.165 pixels
Median: 1.152 pixels
Min: 0.045 pixels
Max: 3.912 pixels
Error distribution:
▁▁▁▁▁▂▂▃▃▄▅▄▆▆▆▇▇▇▇█▆▆▆▆▅▄▄▃▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁ ▁ ▁▁
0.045 3.912
Observation statistics:
Min observations per point: 2
Max observations per point: 58
Median observations per point: 5
Track length distribution:
▁█▄▂▁▁▁ ▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁ ▁▁▁ ▁▁ ▁
2.000 58.000
Nearest neighbor distances:
Min: 0.000000
Max: 5.487719
Mean: 0.037203
Median: 0.024898
NN distance distribution:
█▁▁▁▁▁▁▁▁▁▁ ▁▁ ▁ ▁ ▁ ▁ ▁
0.000 5.488
Completed in 0.04s
2. Image reprojection statistics¶
The sfm analyze --metrics report shows per-image reprojection error
statistics, sorted by mean error descending. Each row includes a histogram of
that image's error distribution, plus the observation count, mean track length,
and a flag for images with elevated error.
$ sfm analyze --metrics sfmr/20260413-00-solve-dino_dog_toy_1-85.sfmr
Per-image metrics analysis for: 20260413-00-solve-dino_dog_toy_1-85.sfmr
====================================================================================================
Reconstruction: 85 images, 10,580 points, 80,387 observations
Mean reprojection error: 1.165 px (median: 1.152 px)
Flagged images: 1 elevated-error
Image (by mean error desc) Obs MeanErr MedErr MaxErr MeanTL Flag
----------------------------------------------------------------------------------------------------
images/dino_dog_toy_15.jpg 1176 1.764 1.593 4.312 17.8 !
▁▂▃▃▅▅▅▆▇▆▆▆▆▇▇█▇▅▆▅▂▅▄▃▃▃▄▂▄▂▂▁▁▃▂▂▂▁▁▁▁ ▁
images/dino_dog_toy_16.jpg 1036 1.710 1.539 4.005 15.7
▁▁▂▄▄▅▅█▅▅▅▄▄▄▄▅▃▄▃▄▃▂▃▂▄▃▂▂▃▁▁▂▂▂▂▁▁▂▁▁
images/dino_dog_toy_43.jpg 1076 1.692 1.572 4.161 17.0
▁▁▁▄▃▅▄█▅▄▄▄▃▄▂▃▃▃▄▄▃▃▃▃▂▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁
images/dino_dog_toy_34.jpg 1071 1.638 1.491 4.293 16.8
▁▂▄▆▆▆█▆▅▇▅▅▆▅▅▆▅▆▅▄▃▃▄▃▃▂▃▄▂▂▂▂▂▂▂▂▁▁▁ ▁
images/dino_dog_toy_44.jpg 846 1.616 1.434 4.128 16.6
▁▁▂▃▄▅▅█▆▅▃▃▄▃▃▃▄▃▃▃▃▄▃▃▂▂▃▁▂▁▁▁▁▁▁▁▁▁▁▁▁
...
images/dino_dog_toy_26.jpg 865 0.882 0.659 3.936 14.4
▁▄▆▇▇█▇▅▃▄▃▂▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁▁
images/dino_dog_toy_25.jpg 1056 0.878 0.691 3.879 14.3
▁▅▆▆█▇▆▅▆▄▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
----------------------------------------------------------------------------------------------------
0 5.1 px
!! mean error > 2.305 px (2x reconstruction median of 1.152 px)
! mean error > 1.729 px (1.5x reconstruction median of 1.152 px)
-- no observations (image registered but no points)
MeanTL = mean track length (avg number of images observing each point)
Completed in 0.10s
3. Depth range per image¶
The sfm analyze --z-range report shows the range of 3D point depths as seen
from each camera, with a histogram of the depth distribution.
$ sfm analyze --z-range sfmr/20260413-00-solve-dino_dog_toy_1-85.sfmr
Z range statistics for: 20260413-00-solve-dino_dog_toy_1-85.sfmr
======================================================================
Images: 85
3D points: 10580
Histogram buckets: 128
Summary statistics:
Images with data: 85/85
Min Z range: [0.738, 9.019]
Max Z range: [1.873, 36.001]
Observed points per image: [103, 1672]
Per-image Z ranges:
images/dino_dog_toy_01.jpg:
Z range: [4.250, 15.881]
Observed: n= 851, med=5.486, mean=5.878
[▁▄▅▆▅█▇▄▃▃▂▂▂▂▁▂▂▁▁▁▁▁▁▁▁ ▁ ▁ ▁▁▁ ▁▁▁ ▁ ▁]
images/dino_dog_toy_02.jpg:
Z range: [3.864, 14.936]
Observed: n=1264, med=4.838, mean=5.103
[▃▁▂▆█▆▄▄▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁ ▁ ▁ ▁]
images/dino_dog_toy_03.jpg:
Z range: [3.846, 14.450]
Observed: n=1540, med=4.771, mean=4.992
[▂▁▂▆█▆▄▄▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁ ▁▁▁▁▁ ▁▁]
images/dino_dog_toy_04.jpg:
Z range: [3.822, 14.340]
Observed: n=1672, med=4.767, mean=5.019
[▂▁▂▇▆█▄▃▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁▁▁ ▁ ▁]
images/dino_dog_toy_05.jpg:
Z range: [3.850, 13.771]
Observed: n=1439, med=4.763, mean=5.010
[▃▁▂▆▇█▅▄▄▂▂▂▁▁▁▁▁▁▁▁ ▁▁▁▁ ▁▁ ▁▁ ▁▁▁▁ ▁▁▁▁▁▁▁]
...
images/dino_dog_toy_84.jpg:
Z range: [0.974, 2.033]
Observed: n= 182, med=1.275, mean=1.363
[▁▁▁▁▂▁▁▂▁▁▂▂▄▃▄▅▄█▃▂▁▂▂▁▃▁ ▄▁▁▁▁▁ ▁▁ ▁▂ ▂ ▁ ▁▁▁▁ ▁▁▁▁ ▁ ▁▂▁▁ ▁]
images/dino_dog_toy_85.jpg:
Z range: [1.091, 1.873]
Observed: n= 138, med=1.328, mean=1.388
[▂▁ ▂▅ ▂▁▃▅▃▃▄▄▄██▇▆▅▁▄▃▂▁▁▃▂▅▂▁ ▄▁ ▂▂▂▁▂▂▄ ▂▄▄▁ ▁▂▃ ▁ ▁ ▁]
Completed in 0.04s
4. Detect sequence discontinuities¶
The sfm motion command analyzes successive camera motions in image
sequences and flags jumps in translation or rotation. This is especially useful
for reconstructions made from video footage, where a discontinuity can indicate
a problem with the solve.
$ sfm motion sfmr/20260413-00-solve-dino_dog_toy_1-85.sfmr
Reconstruction: 85 images, 10,580 points, 80,387 observations
Found 1 sequence(s)
Analyzing sequence: images\dino_dog_toy_%02d.jpg (85 frames)
Successive motion -- median translation: 0.8931, median rotation: 10.06°
Extrapolation thresholds: trans>2.6794 (3x median step) rot>15.0° (fixed)
Frame Image L.trans L.rot R.trans R.rot Flag
-----------------------------------------------------------------------------------------------
1 images/dino_dog_toy_01.jpg - - 0.3631 2.15°
2 images/dino_dog_toy_02.jpg - - 0.2910 3.86°
3 images/dino_dog_toy_03.jpg 0.3631 2.15° 0.1158 2.31°
4 images/dino_dog_toy_04.jpg 0.2910 3.86° 0.1039 1.34°
5 images/dino_dog_toy_05.jpg 0.1158 2.31° 0.0841 1.66°
...
83 images/dino_dog_toy_83.jpg 0.1783 3.92° 0.2595 5.16°
84 images/dino_dog_toy_84.jpg 0.3085 5.90° - -
85 images/dino_dog_toy_85.jpg 0.2595 4.88° - -
================================================================================
Summary
================================================================================
images\dino_dog_toy: 85 frames, 10 discontinuity(s)
Edge Dist(prev) Dist Dist(next) Rot SharedPts Err(A) Err(B)
-------------------------------------------------------------------------------------------------
8->9 0.7198 0.4371 0.9174 <23.60°> 637 1.259px 1.600px
11->12 1.2370 0.8553 0.6491 <11.66°> 871 1.202px 0.986px
16->17 0.8221 0.1831 1.8127 <30.90°> 623 1.710px 1.230px
27->28 0.4765 <9.1148> 0.6144 6.60° 121 1.388px 1.389px
37->38 0.6539 <4.5843> 0.9920 <63.77°> 44 1.429px 1.190px
44->45 0.9375 <9.5281> 2.3214 <54.10°> 83 1.616px 1.188px
53->54 0.7773 1.1221 1.1162 <24.71°> 74 1.030px 1.312px
56->57 0.7577 0.7312 0.6561 <11.42°> 414 1.306px 1.455px
64->65 0.6032 0.7401 1.0490 <21.51°> 354 1.146px 1.228px
70->71 1.3146 1.0790 1.2156 <10.70°> 382 1.349px 1.366px
Total: 10 discontinuity(s) across 1 sequence(s).
Completed in 0.10s
This dataset isn't from video — the images were taken from different angles around the toy — so the detected discontinuities here are expected large changes in viewpoint rather than actual problems.