After the VIO program finishes, copy its output into the matching results folder:
results/<sequence_name>/estimated_trajectory.txt
The VIO program writes this file to its own working directory; move or copy it here so the post-processing scripts can find it via --sequence <sequence_name>.
The post-processing scripts in scripts/postprocessing/ operate on that file (and the GPS data already extracted by the preprocessing) to produce visualizations and GPS-aligned trajectories.
All scripts accept a --sequence <name> shorthand that sets sensible default
paths relative to results/<name>/. Explicit path flags always override the
defaults. Run any script with --help for the full flag list.
# 1. Align VIO trajectory to GPS (recommended flags for a flat-ground rover)
python scripts/postprocessing/align_vio_to_gps.py --sequence <sequence_name> \
--horizontal_only --z_mode flat --z_flat_value 0.0
# 2. Plot the trajectory overview (saved to results/<sequence_name>/visualizations/)
python scripts/postprocessing/plot_estimated_trajectory.py --sequence <sequence_name> --no-show
# 3. (Optional) NeRF export
python scripts/postprocessing/tum_timestamps_to_nerfstudio_transforms.py --sequence pipeline_from_uturn
# 4. (Optional) COLMAP GPS reference images
python scripts/postprocessing/create_colmap_ref_images_from_gps.py --sequence pipeline_from_uturn
Script: scripts/postprocessing/align_vio_to_gps.py
Purpose: Fit a global similarity transform (Sim(3) or Sim(2)) between VIO positions and GPS-derived ENU positions, then apply it to every pose.
Dependencies: numpy. matplotlib optional for residual plots.
Default paths from --sequence NAME:
| File | Path |
|---|---|
| Input trajectory | results/<NAME>/estimated_trajectory.txt |
| GPS log | results/<NAME>/gps.txt |
| Output trajectory | results/<NAME>/estimated_trajectory_gps.txt |
Recommended usage (flat-ground rover):
python scripts/postprocessing/align_vio_to_gps.py --sequence <sequence_name> \
--horizontal_only --z_mode flat --z_flat_value 0.0
--horizontal_only fits a 2D yaw + scale in the E/N plane only, avoiding GPS altitude noise.
--z_mode flat --z_flat_value 0.0 sets all output Z to 0.0 in the ENU frame (i.e. the
reference altitude of the first matched GPS fix). This is the most stable choice for ground
robots and NeRF training on flat terrain.
Key options:
--horizontal_only Fit only in E/N plane (recommended for ground robots)
--z_mode {offset,gps,flat}
offset Z_out = z_vio + median(gps_z - vio_z) VIO vertical shape, no scale applied
gps Z_out = interpolated GPS altitude only when GPS vertical is reliable
flat Z_out = constant (default 0.0) best for flat terrain / NeRF
--z_flat_value 0.0 Z constant when --z_mode flat; 0.0 = ENU reference altitude
--no_scale Rigid transform only (scale = 1)
--sweep_time_offset Search for best GPS time alignment over a range of offsets
With diagnostics output:
python scripts/postprocessing/align_vio_to_gps.py --sequence <sequence_name> \
--horizontal_only --z_mode flat --z_flat_value 0.0 \
--residuals_csv results/<sequence_name>/residuals.csv \
--plot_residuals_en_path results/<sequence_name>/visualizations/residuals_en.png
Z mode summary:
| Mode | Output Z | When to use |
|---|---|---|
offset |
z_vio + median(gps_z − vio_z) |
Preserve VIO vertical trend (no scale) |
gps |
Interpolated GPS altitude | When GPS vertical is reliable |
flat |
Constant --z_flat_value (= 0.0) |
Flat terrain, NeRF training (default rec) |
Purpose: Four-panel static overview + optional animated video.
Dependencies: numpy, matplotlib. Animation (--animate) also requires ffmpeg on PATH.
Default paths from --sequence NAME:
| File/Output | Path |
|---|---|
| Input trajectory | results/<NAME>/estimated_trajectory_gps.txt (falls back to estimated_trajectory.txt) |
| Static PNG | results/<NAME>/visualizations/trajectory_overview.png |
| Animation | results/<NAME>/visualizations/trajectory_animation.mp4 |
The four panels (saved in one PNG):
- XY path — coloured by elapsed time.
- Camera heading — yaw from the quaternion, unwrapped, in degrees.
- Movement direction — tangent angle from consecutive XY positions, unwrapped.
- Z position over time.
Plot with interactive window (can manually save in the window's menu):
python scripts/postprocessing/plot_estimated_trajectory.py --sequence pipeline_from_uturn
Save without opening a window:
python scripts/postprocessing/plot_estimated_trajectory.py --sequence pipeline_from_uturn --no-show
Override the input trajectory file:
python scripts/postprocessing/plot_estimated_trajectory.py \
results/pipeline_from_uturn/estimated_trajectory_gps.txt \
--sequence pipeline_from_uturn --no-show
Animated video:
python scripts/postprocessing/plot_estimated_trajectory.py \
--sequence pipeline_from_uturn \
--no-show \
--animate \
--animate-fps 15
Generates results/pipeline_from_uturn/visualizations/trajectory_animation.mp4.
Each video frame shows the XY path building up, with a green arrow for camera
heading and a blue arrow for movement direction at the current position.
ffmpeg must be on PATH (or installed via conda) for MP4 export.
Purpose: Convert a TUM trajectory + camera timestamps + image folder into
a NerfStudio transforms.json.
Dependencies: numpy. Pillow optional (needed only when --width/--height
are omitted and image dimensions must be read from disk).
Default paths from --sequence NAME:
| File | Path |
|---|---|
| Trajectory | results/<NAME>/estimated_trajectory_gps.txt (falls back to estimated_trajectory.txt) |
| Cam timestamps | results/<NAME>/<NAME>/cam_timestamps.txt |
| Images | results/<NAME>/<NAME>/images/ |
| Output | results/<NAME>/nerfstudio/transforms.json |
Usage:
python scripts/postprocessing/tum_timestamps_to_nerfstudio_transforms.py \
--sequence pipeline_from_uturn
Point ns-train at the directory containing transforms.json:
ns-train nerfacto --data results/pipeline_from_uturn/nerfstudio
Key options:
--stride N Keep every Nth frame (thin the sequence)
--allow_length_mismatch Use min(images, timestamps) if counts differ
--applied_transform Optional 3×4 JSON for NerfStudio coordinate rotation
NerfStudio expects camera-to-world matrices in the OpenGL convention (+Y up,
-Z forward). If the scene appears upside-down or mirrored, provide an
--applied_transform.
Purpose: Generate a COLMAP ref_images file so that a sparse reconstruction
can be geo-registered with model_aligner --ref_images_path.
Dependencies: standard library only.
Default paths from --sequence NAME:
| File | Path |
|---|---|
| Cam timestamps | results/<NAME>/<NAME>/cam_timestamps.txt |
| GPS log | results/<NAME>/gps.txt |
| Output | results/<NAME>/ref_images_gps.txt |
Usage:
python scripts/postprocessing/create_colmap_ref_images_from_gps.py \
--sequence pipeline_from_uturn
Output format (one line per frame, pass to COLMAP with --ref_is_gps 1):
000001.png 42.014204000 -93.788170000 333.708000
000002.png ...
GPS coordinates are linearly interpolated to each camera timestamp.
results/<name>/
gps.txt ← extracted by preprocessing
estimated_trajectory.txt ← VIO program output
estimated_trajectory_gps.txt ← GPS-aligned (step 1)
vio_to_enu_transform.txt ← alignment parameters (optional)
ref_images_gps.txt ← COLMAP reference (step 4)
visualizations/
trajectory_overview.png ← 4-panel static plot (step 2)
trajectory_animation.mp4 ← animated video (step 2, optional)
residuals_en.png ← alignment quality plot (step 1, optional)
nerfstudio/
transforms.json ← NerfStudio export (step 3)
<name>/
images/
cam_timestamps.txt
imu_data.csv
| Package | Used by |
|---|---|
numpy |
all scripts except create_colmap_ref_images_from_gps.py |
matplotlib |
plot_estimated_trajectory.py, align_vio_to_gps.py (residual plots) |
Pillow |
tum_timestamps_to_nerfstudio_transforms.py (read image dimensions when --width/--height are not given) |
ffmpeg (CLI) |
plot_estimated_trajectory.py (MP4 animation only) |
Install Python packages:
pip install -r requirements.txt
ffmpeg for animation export must be on PATH or installed via conda:
conda install -c conda-forge ffmpeg