A minimal PyTorch project for optimizing a 2D Gaussian scene against a procedurally generated ground truth. Built as a learning tool to understand differentiable rendering and gradient-based scene optimization before stepping into full 3D Gaussian Splatting.
- 2D Gaussian scene model — learnable parameters per Gaussian: position, scale, rotation, opacity, and color.
- Differentiable renderer — rasterizes Gaussians onto a pixel grid using weighted-average alpha blending; fully vectorised, no sequential loop.
- Ground truth generator — renders a fixed random scene and exposes it as both a training tensor and a saveable PIL image.
- Initializer with difficulty levels — initialize the scene at four levels
relative to the ground truth, from trivial (
EXACT) to hard (RANDOM), to stress-test optimizer convergence from different starting points. - L1 + SSIM loss — combined perceptual loss following the 3DGS paper convention (α = 0.15 L1, 0.85 SSIM).
- Learning rate scheduler — cosine annealing, exponential decay, or
constant; configurable via
LRScheduleenum. - Adaptive densification — clones under-sampled Gaussians, splits over-sized ones, and prunes near-transparent ones; Adam momentum is preserved for surviving Gaussians across densification steps.
- Training timing — per-step wall times logged at every interval, plus a
full summary (
total,mean,min,maxms/step, throughput) attached to everyTrainResultfor easy performance comparison. - Device auto-detection — selects CUDA → MPS → CPU automatically;
torch.compileis applied on CUDA for additional throughput. - Notebooks — interactive demos for ground truth generation and training visualization across init levels.
gaussian_optimizer_2d/
├── src/
│ └── gaussian_optimizer_2d/
│ ├── gaussian2d.py # Learnable Gaussian2DScene (nn.Module)
│ ├── ground_truth.py # GroundTruth dataclass + generator
│ ├── initializer.py # InitLevel enum + initialize_scene()
│ ├── renderer.py # render() — weighted-average rasterizer
│ ├── loss.py # L1SSIMLoss
│ ├── densification.py # DensifyConfig, DensificationState, densify()
│ └── train.py # TrainConfig, TrainResult, TimingStats, train()
├── notebooks/
│ ├── ground_truth_demo.ipynb # Generate and display a ground truth scene
│ └── training_demo.ipynb # Train across init levels, compare results
├── tests/
├── docs/
│ └── rendering_blending_modes.md # Analysis of blending approaches
├── pyproject.toml
└── Makefile
make installFor notebooks, install dev dependencies:
uv sync --extra devRun training from the command line:
make runUse the API:
from src.gaussian_optimizer_2d.train import train, TrainConfig, LRSchedule
from src.gaussian_optimizer_2d.initializer import InitLevel
from src.gaussian_optimizer_2d.densification import DensifyConfig
result = train(TrainConfig(
n_gaussians=20,
height=256,
width=256,
n_iterations=1000,
lr=1e-2,
lr_schedule=LRSchedule.COSINE,
init_level=InitLevel.RANDOM,
densify=DensifyConfig(every_n_steps=100, max_gaussians=500),
))
print(result.timing.summary())
result.ground_truth.save("ground_truth.png")Open a notebook:
uv run jupyter notebook notebooks/training_demo.ipynb| Level | Description |
|---|---|
EXACT |
Copy ground truth params — loss ≈ 0 from step 0 |
NOISY |
Perturb true params with small noise |
POSITIONS_ONLY |
True Gaussian centers, random appearance |
RANDOM |
Fully random, independent of ground truth |
Adaptive density control runs every every_n_steps steps:
| Condition | Action |
|---|---|
| High gradient + small scale | Clone — add a nearby copy to increase coverage |
| High gradient + large scale | Split — replace with two smaller children along the principal axis |
| Opacity below threshold | Prune — remove the Gaussian unconditionally |
Adam momentum is preserved for surviving and cloned Gaussians; split children start fresh.
Every TrainResult includes a TimingStats object:
result.timing.total_s # total wall time in seconds
result.timing.mean_ms # average ms per step
result.timing.steps_per_sec # throughput
result.timing.summary() # one-line string for quick comparisonPer-interval timing is also printed during training alongside loss and learning rate.