Open-source implementation of the TRiGS paper — coupled SE(3) motion, hierarchical Bézier residuals, and local anchor-centered deformation for dynamic 3D Gaussian Splatting.
TRiGS: Temporal Rigid-Body Motion for Scalable 4D Gaussian Splatting. Suwoong Yeom*, Joonsik Nam*, Seunggyu Choi*, Lucas Yunkyu Lee, Sangmin Kim, Jaesik Park, Joonsoo Kim, Kugjin Yun, Kyeongbo Kong†, Sukju Kang†. (Sogang University · Pusan National University · POSTECH · Seoul National University · ETRI) * equal contribution † corresponding authors
This repository is an independent, open-source re-implementation. All algorithmic credit belongs to the original authors — please cite their paper (see Citation) if you use this code or the ideas it realizes. We thank the authors for making their work public and for the clear supplementary-material derivations that made this implementation tractable.
TRiGS reconstructs dynamic 3D Gaussian scenes by assigning each primitive a continuous, geometrically-consistent rigid-body trajectory. Unlike prior 4DGS methods that rely on piecewise-linear velocity and short temporal windows (and therefore fragment motion into many short-lived primitives), TRiGS keeps a fixed canonical set whose identity persists across the whole sequence.
Key ideas (from the paper):
-
Coupled SE(3) motion. Per-primitive translation and rotation are optimized jointly as a single Lie-algebra coefficient
ζᵢ(t) = (ωᵢ, νᵢ) ∈ 𝔰𝔢(3)and mapped to SE(3) via the closed-form exponential — removing the rotation/translation ambiguity that plagues independent parameterizations. -
Hierarchical Bézier residuals.
ζᵢ(t) = ζᵢᵇᵃˢᵉ + ζᵢʳᵉˢ(τ)whereζᵢʳᵉˢis a quadratic Bézier curve in a normalized local timeτ ∈ [0, 1]defined over the effective visibility windowWᵢ = [μₜ,ᵢ − 2sₜ,ᵢ, μₜ,ᵢ + 2sₜ,ᵢ]. This captures smooth nonlinear motion without over-parameterizing weakly-supervised time regions. -
Local anchor-centered deformation with gauge fixing. Each primitive carries a learnable anchor
aᵢthat acts as its pivot; we canonicalize it asaᵢ,⊥(orthogonal to the rotation axis) and keep only the axis-aligned translationνᵢ,∥, eliminating the optimization ambiguity between anchor and translation. -
Motion-guided relocation. Instead of densifying, the paper recycles low-opacity primitives by cloning active ones weighted by a four-term score (opacity · motion-magnitude × visibility · accumulated ∇₂D · accumulated ∂L/∂μₜ). The canonical count stays fixed.
For a section-by-section mapping between the paper and this codebase see docs/PAPER_MAPPING.md.
TriGS/
├── src/trigs/
│ ├── math/ # SE(3) exp/log, quaternion utilities
│ │ ├── se3.py
│ │ └── quaternion.py
│ ├── core/ # Trainer, losses, camera, renderer, transforms
│ │ ├── trainer.py # TrigsTrainer + TrigsTrainingConfig
│ │ ├── losses.py # reconstruction / bezier_motion / rigid_coherence
│ │ ├── camera.py
│ │ ├── renderer.py
│ │ └── transforms.py
│ ├── strategy/ # Motion-guided relocation strategy (gsplat Strategy subclass)
│ │ └── motion_guided_relocation.py
│ ├── io/ # COLMAP/SelfCap IO, normalization, trajectories, dataset, PLY
│ ├── init/ # Canonical-set builders for all input modes
│ │ ├── from_plys.py # PLY-sequence initialization helper
│ │ ├── from_colmap.py # ROMA/COLMAP NPZ → canonical params
│ │ ├── from_selfcap.py # SelfCap pcd sequence → canonical params
│ │ └── roma_combine.py # Per-frame NPYs → combined NPZ
│ └── cli/
│ ├── train_from_plys.py # trigs-train-plys
│ ├── train_from_colmap.py # trigs-train-colmap
│ └── train_from_selfcap.py # trigs-train-selfcap
├── scripts/
│ ├── preprocess_selfcap.py
│ ├── run_plys.sh
│ └── run_colmap.sh
├── tests/ # SE(3) math + trainer smoke tests
├── docs/
│ └── PAPER_MAPPING.md # Paper ↔ code cross-reference
├── LICENSE # AGPL-3.0
├── README.md
└── pyproject.toml
This project uses uv for dependency management.
git clone https://github.com/OpsiClear-4DGS/TriGSVanilla.git
cd TriGS
uv sync --preview-features extra-build-dependenciesThe --preview-features extra-build-dependencies flag is required: gsplat
and pytorch3d are built from source and don't declare their build-time deps
(torch, setuptools, numpy) in their own pyproject.toml. TriGS injects
them via [tool.uv.extra-build-dependencies], which is a uv preview feature
on current releases. Once uv stabilizes it, plain uv sync will work.
To avoid typing the flag every time:
# bash / zsh
export UV_PREVIEW_FEATURES=extra-build-dependencies
# PowerShell (persistent)
setx UV_PREVIEW_FEATURES extra-build-dependenciesOn Windows, building pytorch3d may fail with:
error: could not create '<...very long path...>': The filename or extension is too long
Cause. pytorch3d's setup.py uses absolute source paths, and setuptools
stacks them onto the build-output path. Combined with Windows' 260-char
MAX_PATH limit and uv's default cache under %LOCALAPPDATA%\uv\cache (~80
chars), the output path exceeds the limit.
Fix — relocate uv's cache to a short path:
setx UV_CACHE_DIR "C:\uc"Open a new PowerShell (so the env var takes effect), delete any stale
.venv, and re-run:
Remove-Item -Recurse -Force .venv
uv sync --preview-features extra-build-dependenciesAlternative (requires admin + reboot): enable Windows long-path support system-wide:
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" `
-Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -ForceTriGS supports three input modes through a single trainer. All produce the
same canonical ParameterDict (means, quats, scales, opacities, SH, temporal
window params, anchors, ζ-base + Bézier controls) that drives training.
Input: a directory of dense per-frame Gaussian PLYs.
Every keyframe_stride-th PLY becomes part of the canonical set; the trainer
renders GT from the PLYs themselves.
uv run trigs-train-plys \
--input-dir /path/to/dense_frames \
--output-dir /path/to/output \
--config.keyframe-stride 3 \
--config.fps 30 \
--config.n-steps 30000Or via the script wrapper:
bash scripts/run_plys.sh /path/to/dense_frames /path/to/output 3 30000 0Outputs: trigs_ckpt.pt, frames_ply/frame_000000.ply, …, and optionally
renders/cam_XXXX/render_YYYYYY.png.
Input: COLMAP sparse reconstruction (<data_dir>/sparse/0/ +
<data_dir>/images/cam##/) plus a directory of per-frame ROMA-triangulated
point NPY files (points3d_frameXXXXXX.npy, colors_frameXXXXXX.npy).
Under the hood the pipeline (1) runs
trigs.init.roma_combine to build an NPZ
of keyframe points with kNN-estimated velocities, (2) converts it to a TriGS
canonical set via
trigs.init.from_colmap.build_canonical_from_npz
(initializing scales from a per-keyframe kNN distance, and running the same
radius-ball anchor + (r × v) ω initialization as the PLY path), and (3)
calls TrigsTrainer.train_from_params with GT images loaded via
FreeTimeParser/FreeTimeDataset.
uv run trigs-train-colmap \
--data-dir /path/to/colmap_data \
--roma-input-dir /path/to/per_frame_npys \
--output-dir /path/to/output \
--start-frame 0 --end-frame 61 --keyframe-step 5Or:
bash scripts/run_colmap.sh \
/path/to/per_frame_npys /path/to/colmap_data /path/to/output \
0 61 5 0Input: a SelfCap <scene>-release directory with videos/, optimized/,
and per-frame pcds/.
Dataset/project page: Long Volumetric Video (SelfCap Dataset).
First preprocess a frame window (extract jpgs + copy matching point clouds +
write preprocess_meta.json):
uv run python scripts/preprocess_selfcap.py \
--scene-dir /path/to/corgi-release \
--output-dir /path/to/corgi_prepped \
--start-frame 0 --end-frame 1000 \
--factor 1 --fps 60 --workers 24Then train TriGS on the preprocessed directory:
uv run trigs-train-selfcap \
--data-dir /path/to/corgi_prepped \
--output-dir /path/to/output \
--dense-subdir dense_pcds \
--sparse-subdir pcds \
--config.keyframe-stride 3 \
--config.n-steps 30000Notes:
preprocess_selfcap.pyauto-detectspcdabsolute-frame origin from the minimum filename inpcds/; override with--pcd-originwhen needed.- By default,
preprocess_selfcap.pyrectifies images and rewritesoptimized/intri.ymlto a pinhole model baked to cropped image space. Pass--no-rectify-pinholeto keep original distorted images/calibration. - Pass
--no-synctotrigs-train-selfcapto disableoptimized/sync.jsonper-camera timing correction.
Unlike methods that densify/prune during training, TriGS keeps the canonical
primitive count constant. Capacity that ends up unused (σ(αᵢ) < τ_α) is
recycled by motion-guided relocation — cloning active primitives into
inactive slots, weighted by four normalized cues:
score_i = p^(α) + p^(dν) + p^(2D) + p^(t)
where the four terms are opacity, motion × visibility, accumulated 2D
projected-means gradient, and accumulated ∂L/∂μₜ gradient. Implemented as a
gsplat Strategy subclass
so it plugs into any gsplat training loop.
All temporal quantities (μₜ, σₜ, ν, ω, Bézier controls, loss weights)
are stored in seconds — not normalized [0, 1]. This makes paper
hyperparameters (λ_motion = 1e-4, λ_rigid = 1, λ_c = 50, …) transfer
directly across sequences of different length and FPS.
- Motion-only phase (first
unfreeze_temporal_stepiters): opacities,μₜ,log σₜare frozen. Forces the model to find a motion decomposition that fits all frames before it can cheat by shrinking temporal windows. - Full training phase: temporal params unfreeze; Adam moments are reset for a clean restart.
Both the PLY and COLMAP paths end up running the same three-step init on their per-keyframe Gaussians:
- Centered finite-difference velocity from ±1 frame neighbors.
- Radius-ball anchor centroids (anchor ≠ mean, so rotation actually displaces the primitive).
- ω = (r × v) / ‖r‖² with residual ν = v − ω × r — so the translation component only carries what rotation can't explain.
# Install with dev dependencies
uv sync --preview-features extra-build-dependencies --group dev
# Run tests (SE(3) math + trainer smoke tests)
uv run pytest
# Type check / lint
uv run mypy src/trigs/ --ignore-missing-imports
uv run ruff check src/trigs/If you use this code or build on the TRiGS method, please cite the original paper:
@article{yeom2026trigs,
title = {TRiGS: Temporal Rigid-Body Motion for Scalable 4D Gaussian Splatting},
author = {Yeom, Suwoong and Nam, Joonsik and Choi, Seunggyu and
Lee, Lucas Yunkyu and Kim, Sangmin and Park, Jaesik and
Kim, Joonsoo and Yun, Kugjin and Kong, Kyeongbo and Kang, Sukju},
journal = {arXiv preprint},
year = {2026},
url = {https://wwwjjn.github.io/TRiGS-project_page/}
}Please also cite the projects this implementation builds on:
- 3D Gaussian Splatting (Kerbl et al., SIGGRAPH 2023)
- FreeTimeGS (Wang et al., CVPR 2025) — the translation-only baseline TriGS generalizes, and the dataset-loading lineage of this code.
- LongVolcap / SelfCap Dataset (Xu et al., SIGGRAPH Asia 2024): https://zju3dv.github.io/longvolcap/
- gsplat (nerfstudio-project) — CUDA rasterization backend.
AGPL-3.0. See LICENSE.
This project is an independent open-source re-implementation of the TRiGS paper. It is not affiliated with, nor endorsed by, the paper's authors or institutions.