headers/libraries in this environment, but does not require a system-wide CUDA installation.

September 14, 2026 · View on GitHub

ReconSplat
Generalizable 3D Scene Reconstruction Beyond Observed Views

Giuseppe Stracquadanio1    Kevin Raj1,2    Julia Grabinski1    Stefan Roth1,2,3

1TU Darmstadt    2Zuse School ELIZA    3hessian.AI

ECCV 2026

arXiv   Project Page   Pretrained Models   Video


ReconSplat teaser

💡 TL;DR: Sparse-view 3D reconstruction is ill-posed: regression-based methods recover reliable geometry but cannot complete unseen regions, while generative methods synthesize plausible novel content without corresponding geometry. ReconSplat learns a multi-view generative prior for both appearance and geometry. It refines observed scene evidence and jointly completes unseen RGB and depth, producing photorealistic novel views with sharp and consistent geometry.

⚙️ Installation

To get started, clone this repository and run setup.sh, which creates a conda env (Python 3.10), installs the required dependencies, and builds the custom CUDA rasterization kernel:

git clone https://github.com/visinf/reconsplat.git
cd reconsplat
./setup.sh              # optionally: ./setup.sh <env-name>  (defaults to "reconsplat")
conda activate reconsplat
What setup.sh does, if you prefer to run the steps manually
conda create -n reconsplat python=3.10
conda activate reconsplat
pip install "setuptools<81"
pip install torch==2.1.2 torchvision==0.16.2 torchaudio==2.1.2 --index-url https://download.pytorch.org/whl/cu118
pip install xformers==0.0.23.post1 --index-url https://download.pytorch.org/whl/cu118
pip install -r requirements.txt
# Build the custom CUDA 3DGS rasterizer. This requires nvcc and the matching CUDA 11.8
# headers/libraries in this environment, but does not require a system-wide CUDA installation.
conda config --set channel_priority flexible
conda install -c 'nvidia/label/cuda-11.8.0' cuda-toolkit=11.8.0
# Explicitly set target GPU architectures instead of relying on torch auto-detecting a GPU, 
# only needed if you are building from a cluster login/CPU node. 
export TORCH_CUDA_ARCH_LIST="7.0;7.5;8.0;8.6;8.9;9.0" 
pip install --no-build-isolation submodules/variational-gaussian-rasterization

To also run src/scripts/compute_met3r.py, install the extra dependencies it needs:

pip install --no-build-isolation-package featup --no-build-isolation-package pytorch3d \
    -r requirements-eval-extra.txt

🗂️ Datasets

Both datasets need pre-processing before training or evaluation: see DATASETS.md for how to obtain and prepare them.

🧠 Pretrained models

All checkpoints are available on the Hugging Face Hub at gpstracquadanio/reconsplat, which also documents the contents and training setup of each checkpoint. Running ReconSplat requires one checkpoint from each stage: a Stage 1 multi-view VAE and a Stage 2 multi-view denoiser.

Download them into checkpoints/:

pip install -U "huggingface_hub[cli]"
hf download gpstracquadanio/reconsplat --include "stage1-vae/*" "stage2-diffusion/*" \
    --local-dir checkpoints

Note

That pulls every checkpoint of both stages, (~52 GB). If you only want to run one model, look up its pair in the table below and fetch just those two files (~9 GB):

hf download gpstracquadanio/reconsplat --include "stage1-vae/re10k.ckpt" \
    "stage2-diffusion/re10k-upsample-ft.ckpt" --local-dir checkpoints

Each evaluation config loads the corresponding pairs:

+experiment=Stage 1 checkpointStage 2 checkpoint
re10k_diffusion_release_upsample_ft_evalsstage1-vae/re10k.ckptstage2-diffusion/re10k-upsample-ft.ckpt
re10k_diffusion_release_video_ft_evalsstage1-vae/re10k.ckptstage2-diffusion/re10k-video-ft.ckpt
dl3dv_diffusion_release_upsample_ft_evalsstage1-vae/dl3dv.ckptstage2-diffusion/dl3dv-upsample-ft.ckpt
dl3dv_diffusion_release_video_ft_evalsstage1-vae/dl3dv.ckptstage2-diffusion/dl3dv-video-ft.ckpt

We also release the base models for RE10K and DL3DV, i.e., the checkpoints the fine-tuned models above continue from. Use these if you want to continue training or fine-tune your own variant:

+experiment=Stage 1 checkpointStage 2 checkpoint
re10k_diffusion_release_evalsstage1-vae/re10k.ckptstage2-diffusion/re10k-base.ckpt
dl3dv_diffusion_release_ft_evalsstage1-vae/dl3dv.ckptstage2-diffusion/dl3dv-base.ckpt

📊 Evaluation

Qualitative comparisons

Reproduce our paper experiments by running the following command, substituting EXPERIMENT (i.e., which model to load) and INDEX (i.e, which evaluation protocol to run) from the table below:

EXPERIMENT=re10k_diffusion_release_evals
INDEX=assets/re10k_evaluation/evaluation_index_re10k_extra_ctx2_tgt4.json

python -m src.main +experiment=$EXPERIMENT \
    mode=test \
    dataset/view_sampler=evaluation \
    dataset.view_sampler.index_path=$INDEX \
    test.compute_scores=true \
    test.save_image=true

An evaluation index fixes which views are inputs / context views (NN) and which are held out / target views (MM), so it defines the protocol; the config selects the checkpoint pair. All indices live under assets/.

SettingEXPERIMENTINDEXNNMM
RE10K, Interpolationre10k_diffusion_release_upsample_ft_evalsre10k_evaluation/evaluation_index_re10k_inter_ctx2_tgt4.json24
RE10K, Extrapolationre10k_diffusion_release_upsample_ft_evalsre10k_evaluation/evaluation_index_re10k_extra_ctx2_tgt4.json24
DL3DV, Ntraj=150N_{traj}=150dl3dv_diffusion_release_ft_evalsdl3dv_evaluation/evaluation_index_ctx4_tgt4_n150.json44
DL3DV, Ntraj=300N_{traj}=300dl3dv_diffusion_release_ft_evalsdl3dv_evaluation/evaluation_index_ctx4_tgt4_n300.json44

Results are written per scene under test.output_path:

<output>/<scene>/
    color/<idx>.png          predicted novel views
    depth/<idx>.png|.pt      predicted novel view depth (colorized, and raw tensors)
    color_gt/ depth_gt/      ground truth (if available)
    context_image_<idx>.png  context views
    cam_dict/cameras.torch   intrinsics, extrinsics, view indices
    results.json             per-scene PSNR / SSIM / LPIPS / DISTS

Tip

test_step skips any scene that already has a results.json, so an interrupted evaluation resumes by re-running the same command.

📈 Other metrics

src/scripts/fid_score_predictions.py computes FID over exported prediction folders, and src/scripts/compute_met3r.py scores multi-view consistency (needs the extra dependencies from the Installation section).

🎥 Rendering trajectories

Use a video index together with a -video-ft checkpoint:

python -m src.main +experiment=re10k_diffusion_release_video_ft_evals \
    mode=test \
    dataset/view_sampler=evaluation \
    dataset.view_sampler.index_path=assets/re10k_evaluation/re10k_sub100_scenes_video_ctx_in_target.json \
    test.save_video=true test.save_image=true test.compute_scores=false
SettingEXPERIMENTINDEXNNMM
RE10K trajectories (limited to 100 scenes)re10k_diffusion_release_video_ft_evalsre10k_evaluation/re10k_sub100_scenes_video_ctx_in_target.json232–50
DL3DV trajectoriesdl3dv_diffusion_release_video_ft_evalsdl3dv_evaluation/dl3dv_start_0_distance_50_ctx_4v_video_0_50.json450

In all our qualitative video results, the full trajectory fits into a single denoising window, so every target view is denoised jointly and no chunking is involved.

If a trajectory does not fit in memory

For longer trajectories, or on GPUs where a whole trajectory does not fit at once, test.sample_in_chunks=true test.chunk_size=<CHUNK_SIZE> test.chunk_overlap=<OVERLAP_BETWEEN_CHUNKS> denoises the targets in windows instead. However, we have not verified that this strategy produces similar qualitative results.

🔬 Novel-view depth synthesis

ScanNet++ is used only for evaluation, to assess the accuracy of depth predictions against real ground-truth depth; it is never used for training. We provide an evaluation index with N=4N=4 context views and M=90M=90 target views for 12 scenes from the test split at assets/scannet.json.

To reproduce our evaluation, run:

# The output path will contain a summary with PSNR/SSIM/LPIPS/DISTS metrics. 
python -m src.main +experiment=dl3dv_diffusion_release_upsample_ft_scannet_evals \
    mode=test \
    dataset.roots=[path/to/your/scannet++] \
    dataset/view_sampler=evaluation \
    dataset.view_sampler.index_path=assets/scannet.json \
    test.output_path=outputs/<your scannet eval>

# To compute depth metrics, run:
python -m src.scripts.compute_metrics_reconsplat --root outputs/<your scannet eval>

☁️ Point clouds from predicted depth

The model predicts normalized depth in the fixed range [-1, 1]. During training, depth maps are normalized to this range before being encoded into latents, and the diffusion model operates entirely in this normalized space. See Section D.2 of our paper for details.

As a result, the decoded depth predictions cannot be directly unprojected into a geometrically correct point cloud. The camera poses are expressed in a fixed coordinate system, while the predicted depth is still normalized. Combining the two directly can distort the reconstruction, for example by collapsing points toward the camera centers.

Before unprojection, we therefore recover a scene-specific depth range by fitting a log-affine transformation

log(d)ad+b\log(d') \approx a \cdot d + b

where dd is the normalized depth prediction and dd' is the depth aligned to the reference scale. The fitted parameters aa and bb map the prediction to a depth range consistent with a reference depth for the same scene.

Note that this does not recover metric scale. The reference depth is itself non-metric, so the goal is to recover the correct scene geometry in the camera coordinate system rather than the true physical size of the scene.

For the reference, we use depth at the context views, i.e., the input views provided to ReconSplat. These depths can be obtained from either:

  • the dataset depth pseudo-labels for context views, when available; or
  • a depth prediction from VGGT, run only on the context views.

Therefore, this procedure does not require ground-truth depth and can be applied to new scenes entirely at inference time.

To fit the log-affine transformation, we need ReconSplat depth predictions at the context views themselves. The recommended way to obtain them is to include the context views in the target set during rendering: dataset.view_sampler.include_context_in_target=true. This keeps the usual target trajectory unchanged while additionally rendering the context views needed for scale fitting.

Scale fitting can be run automatically once rendering finishes by enabling test.fit_pcd_scale=true.

For example:

python -m src.main +experiment=re10k_diffusion_release_video_ft_evals \
    mode=test \
    dataset/view_sampler=evaluation \
    dataset.view_sampler.index_path=assets/re10k_evaluation/re10k_extrapolation_top_100_supplement_video.json \
    dataset.view_sampler.include_context_in_target=true \
    test.sample_in_chunks=true test.chunk_size=64 test.chunk_overlap=4 \
    test.save_image=true test.compute_scores=false \
    test.fit_pcd_scale=true

The scale fit uses a reference depth for the context views:

  • if the evaluation was run with dataset.load_depth_labels=true, the saved context-view depth pseudo-labels are used;
  • otherwise, a single VGGT forward pass is run on the context images.

The VGGT option also masks sky regions before fitting, since their arbitrary depth estimates can otherwise adversely affect the alignment.

Once rendering finishes, the fitted log-affine parameters are stored in scale_fit.json, and the reconstruction can be opened directly in our point-cloud viewer.

🌐 Browse the reconstructed point clouds

python -m src.scripts.pcd_web_viewer \
    --dataset re10k=outputs/<your re10k video eval> \
    --dataset dl3dv=outputs/<your dl3dv video eval> \
    --port <PORT>

The viewer is available at http://localhost:<PORT>. If the results are stored on a remote machine, tunnel the port first. The viewer lets you do some stuff, like adjusting the fitted scale, controlling point size, toggling camera frustums, and recording the progressive reconstruction as a GIF or MP4.

Alternative: fit the scale after rendering

If predictions were rendered without automatic scale fitting, you can fit the log-affine transformation afterward. The rendered target set must include the context views, either by setting

dataset.view_sampler.include_context_in_target=true

during rendering, or by using one of the provided *_ctx_in_target.json evaluation indices.

🔎 Using context-view depth

Run:

python -m src.scripts.fit_context_scale \
    --root outputs/<your video eval output>

The script uses saved context-view depth pseudo-labels when available, and otherwise obtains the reference depth from VGGT predictions on the context views.

Useful options include:

  • --no-context-vggt to disable the VGGT pass on the context views. Scenes without saved depth pseudo-labels are then skipped.
  • --no-sky-mask to disable sky masking before fitting.

🌀 Using Gaussian-rasterized depth

Gaussian-rasterized depth can also be used as a reference. However, it is generally weaker than VGGT, especially on DL3DV.

First export the rasterized depth:

python -m src.scripts.export_rasterized_depth \
    --experiment re10k_diffusion_release_video_ft \
    --index assets/re10k_evaluation/re10k_sub100_scenes_video_ctx_in_target.json \
    --out outputs/<your video eval> \
    --num-context-views 2

Then run the same fitting script to compute log-affine parameters:

python -m src.scripts.fit_context_scale \
    --root outputs/<your video eval>

fit_context_scale.py detects the exported rasterized depths automatically and adds the corresponding fit to the point-cloud viewer. --rasterized-depth-all fits the transformation against rasterized depth over all target views, rather than only the context views.

After fitting, open the results with pcd_web_viewer as described above.

🚀 Training

Note that training requires processing the datasets to generate depth pseudo-labels from a geometric foundation model first: see Processing the data.

We recommend setting precision: 32-true when training, since we have observed divergence with bf16-mixed when training our diffusion model. Our training scripts and dataloaders are compatible with multi-GPU training. data_loader.train.batch_size sets the per-GPU batch size.

1️⃣ Stage 1 — multi-view latent field

Run the following to reproduce our Stage 1 training.

+experiment=re10k_vae needs the original MVSplat checkpoint to initialize the multi-view encoder weights. Download it following MVSplat's evaluation instructions and save it as checkpoints/mvsplat/re10k.ckpt.

# RE10K
python -m src.main +experiment=re10k_vae \
    wandb.name=my_re10k_vae_run

# DL3DV: initialized from the RE10K VAE.
# Defaults to our released checkpoint (checkpoints/stage1-vae/re10k.ckpt); to use your own RE10K
# VAE run instead, override checkpointing.load_first_stage=path/to/your/checkpoint.
python -m src.main +experiment=dl3dv_vae \
    wandb.name=my_dl3dv_vae_run

2️⃣ Stage 2 — multi-view latent diffusion

The table below shows the Stage 2 checkpoint hierarchy: Init. from names the checkpoint each config starts from. N is the number of context views, M the number of target views. The -video-ft runs train only the temporal (3D convolution) blocks and freeze everything else; the -upsample-ft runs halve the effective downsampling factor (f = 8 → 4) by 2× bilinearly upsampling views.

ConfigInit. fromNMPer-GPU batchTraining steps
re10k_diffusion_release (RE10K base)SD 2.12410200K
re10k_diffusion_release_upsample_ftRE10K base24350K
re10k_diffusion_release_video_ftRE10K base2104100K
dl3dv_diffusion_release_ft (DL3DV base)RE10K base444140K
dl3dv_diffusion_release_upsample_ftDL3DV base44150K
dl3dv_diffusion_release_video_ftDL3DV base4103100K

Launch any config in the table above the same way, substituting its name. This example trains the RE10K base model:

# For the RE10K base model.
python -m src.main +experiment=re10k_diffusion_release \
    wandb.name=my_re10k_base_run

Important

Make sure checkpointing.load_first_stage points to the corresponding Stage 1 checkpoint (i.e., the re10k_vae output for all RE10K models, the dl3dv_vae output for all DL3DV models). To train a fine-tune, also point checkpointing.load_second_stage at the checkpoint of the model named in its Init. from column.

To resume an interrupted run, keep the same wandb.id (recommended) and pass the in-progress checkpoint, e.g.,:

python -m src.main +experiment=re10k_diffusion_release \
    checkpointing.resume=true \
    checkpointing.load_second_stage=checkpoints/ldm/<run>/epoch_0-step_4000.ckpt

Experiment logging is done via wandb; set wandb.entity/wandb.project in config/main.yaml, or pass wandb.mode=disabled to turn it off.

📁 Repository structure

The main entry points and directories are:

reconsplat/
    config/                    # Hydra configuration files
        experiment/            # Training and evaluation configs (+experiment=...)
        dataset/               # Dataset and view-sampler configs
        model/                 # Model architecture configs
        main.yaml              # Root configuration

    src/
        main.py                # Main training/evaluation entry point (python -m src.main)
        model/                 # Model architectures and components
        dataset/               # Dataset loaders and view samplers
        scripts/               # Standalone tools for point clouds, pseudo-labels, metrics, etc.
        evaluation/            # Evaluation metrics and index generation
        labels/                # Pseudo-label generation and third-party model wrappers
        misc/                  # Shared utilities for I/O, benchmarking, and depth/image processing
        visualization/         # Camera trajectories, color maps, and rendering utilities

    assets/                    # Evaluation indices and auxiliary assets
    submodules/                # Custom CUDA 3DGS rasterizer
    setup.sh                   # Sets up the environment and builds the CUDA rasterizer

📚 BibTeX

@article{stracquadanio2026reconsplat,
    title   = {{ReconSplat}: {G}eneralizable {3D} scene reconstruction beyond observed views},
    author  = {Stracquadanio, Giuseppe and Raj, Kevin and Grabinski, Julia and Roth, Stefan},
    journal = {{ECCV}},
    year    = {2026},
}

🙏 Acknowledgements

This codebase is based on latentSplat, MVSplat360, MV-LDM, VGGT and VDA. We deeply thank the authors of these projects for open-sourcing their codebase!