ScenarioControl: Vision-Language Controllable Vectorized Latent Scenario Generation
Lili Gao1*, Yanbo Xu2*, William Koch2*, Samuele Ruffino1, Luke Rowe3, Behdad Chalaki1, Dmitriy Rivkin1, Julian Ost1,2, Roger Girgis1,3, Mario Bijelic1,2, Felix Heide1,2
1 Torc Robotics, 2 Princeton University, 3 Mila
European Conference on Computer Vision (ECCV), 2026
* Equal contribution
We propose ScenarioControl, a vision-language controllable framework for learned driving scenario generation.
scenariocontrol.mp4
- Setup
- Dataset Preparation
- Pre-Trained Checkpoints
- Training
- Inference
- Evaluation
- Simulation
- Citation
- Acknowledgements
Start by cloning the repository
git clone https://github.com/princeton-computational-imaging/ScenarioControl.git
cd ScenarioControl
This repository assumes you have a "scratch" directory for larger files (datasets, checkpoints, etc.). If disk space is not an issue, you can keep everything in the repository directory:
export SCRATCH_ROOT=$(pwd) # prefer a separate drive? Point SCRATCH_ROOT there instead.
Define environment variables to let the code know where things live:
source $(pwd)/scripts/define_env_variables.sh
# create conda environment
conda env create -f environment.yml
conda activate ScenarioControl
# login to wandb for experiment logging
export WANDB_API_KEY=<your_api_key>
wandb login
Dataset for downloading is coming soon! You could also follow the steps below to extract and preprocess the data yourself.
We use the same extracted NuPlan data as SLEDGE, with minor modifications tailored for ScenarioControl. Our modified fork for extracting the Nuplan data is available here.
-
Install dependencies & download raw NuPlan data
Follow the guide in theinstallation.mdfile of our forked repo.
This will walk you through:- Downloading the NuPlan dataset
- Setting up the correct environment variables
- Installing the
sledge-devkit
-
Extract NuPlan data
Run the following in the forked Repo to preprocess the NuPlan data:cd $SLEDGE_DEVKIT_ROOT/scripts/autoencoder/rvae/ bash feature_caching_rvae_temporal.sh bash feature_caching_rvae_temporal_test.sh python merge_meta.py -
Extract train/val/test splits and preprocess data for training
Run the following to extract train/val/test splits and create the preprocessed data for training.bash scripts/extract_nuplan_data_3dtemp_wimages.sh # create train/val/test splits and create eval set for computing metrics bash scripts/preprocess_nuplan_dataset_3dtemp_wimages.sh # preprocess data to facilitate efficient model training -
Extract DINO patch features and depth maps for image conditioning
Run the following to extract DINOv3 patch features and a monocular depth map for each camera frame.export NUPLAN_DATA_FOLDER=/path/to/nuplan-v1.1 # root of the raw nuPlan dataset (contains sensor_blobs/) bash scripts/extract_dino_depth_features.sh -
Prepare captions for prompt conditioning
Download our captions extracted with GPT-4.1-mini and place them under$SCRATCH_ROOT/captions_bev/gpt_bev
Coming soon
Pre-trained checkpoints can be downloaded from Google Drive. Place the checkpoints directory into your scratch ($SCRATCH_ROOT) directory.
- Autoencoder: put under
$SCRATCH_ROOT/checkpoints/scenario_control_autoencoder3d_nuplan - Unconditional Pretrained LDM: put under
$SCRATCH_ROOT/checkpoints/scenario_control_ldm_base_nuplan - LDM for image-conditioning: put under
$SCRATCH_ROOT/checkpoints/scenario_control_ldm_img_cond_nuplan - LDM for prompt-conditioning: put under
$SCRATCH_ROOT/checkpoints/scenario_control_ldm_prompt_cond_nuplan
# Cached latents are saved to: ae.eval.cache_latents.latent_dir
python eval.py \
dataset_name=nuplan \
model_name=autoencoder3dtemp \
ae.eval.run_name=scenario_control_autoencoder3d_nuplan \
ae.dataset.load_images=True \
ae.eval.cache_latents.enable_caching=True \
ae.eval.cache_latents.split_name=[train|val|test] \
--config-name=config3dtempTrains the base unconditional LDM from scratch on all scene types (load_scene_type='012'). Image/prompt conditioning are later added on top of this checkpoint via ldm.train.finetune=True + ldm.train.pretrained_dir (see below).
CUDA_VISIBLE_DEVICES=0,1,2,3 python train.py \
dataset_name=nuplan \
model_name=ldm_cond \
ldm.model.autoencoder_run_name=scenario_control_autoencoder3d_nuplan \
ldm.dataset.load_scene_type='012' \
ldm.model.img_conditioning=False \
ldm.train.run_name=scenario_control_ldm_base_nuplan \
ldm.train.devices=4 \
ldm.train.lr=5e-5 \
ldm.train.track=True \
ldm.train.save_top_k=-1 \
ldm.train.check_val_every_n_epoch=5 \
ldm.train.max_steps=500000 \
ldm.train.num_samples_to_visualize=3 \
ldm.datamodule.train_batch_size=64 \
ldm.datamodule.val_batch_size=64 \
--config-name=config3dtempFinetunes a pretrained unconditional LDM checkpoint to add single-image conditioning (DINO patch features + depth map, see Dataset Preparation). ldm.train.freeze_pretrained=True freezes every weight that loaded from ldm.train.pretrained_dir, training only the newly-added image-conditioning layers.
CUDA_VISIBLE_DEVICES=0,1,2,3 python train.py \
dataset_name=nuplan \
model_name=ldm_cond \
ldm.model.autoencoder_run_name=scenario_control_autoencoder3d_nuplan \
ldm.dataset.load_single_img_cond=True \
ldm.dataset.load_scene_type='12' \
ldm.model.img_conditioning=True \
ldm.model.decode_in_training=True \
ldm.train.run_name=scenario_control_ldm_img_cond_nuplan \
ldm.train.devices=4 \
ldm.train.lr=5e-5 \
ldm.train.track=True \
ldm.train.finetune=True \
ldm.train.pretrained_dir=$SCRATCH_ROOT/checkpoints/scenario_control_ldm_base_nuplan \
ldm.train.freeze_pretrained=True \
ldm.train.collision_weight=0.001 \
ldm.datamodule.train_batch_size=64 \
ldm.datamodule.val_batch_size=64 \
--config-name=config3dtempFinetunes a pretrained unconditional LDM checkpoint to add text-prompt conditioning via a frozen UMT5 text encoder. Captions can be cached (coming soon!) and are encoded on the fly since ldm.dataset.use_cached_text_embeds=False; set it to True instead if you've precomputed a .pt text-embedding cache.
CUDA_VISIBLE_DEVICES=0,1,2,3 python train.py \
dataset_name=nuplan \
model_name=ldm_cond \
ldm.model.autoencoder_run_name=scenario_control_autoencoder3d_nuplan \
ldm.dataset.load_scene_type='01' \
ldm.dataset.load_captions=True \
ldm.model.text_conditioning=True \
ldm.dataset.use_cached_text_embeds=False \
ldm.model.decode_in_training=True \
ldm.train.run_name=scenario_control_ldm_prompt_cond_nuplan \
ldm.train.devices=4 \
ldm.train.lr=5e-5 \
ldm.train.track=True \
ldm.train.finetune=True \
ldm.train.pretrained_dir=$SCRATCH_ROOT/checkpoints/scenario_control_ldm_base_nuplan \
ldm.train.freeze_pretrained=True \
ldm.train.collision_weight=0.001 \
ldm.datamodule.train_batch_size=64 \
ldm.datamodule.val_batch_size=64 \
--config-name=config3dtempInitial Scene Generation (Image Conditioning):
python test.py \
dataset_name=nuplan \
model_name=ldm_cond \
ckpt_path=$SCRATCH_ROOT/checkpoints/scenario_control_ldm_img_cond_nuplan/last.ckpt \
ldm.model.autoencoder_run_name=scenario_control_autoencoder3d_nuplan \
ldm.model.img_conditioning=True \
ldm.model.decode_in_training=True \
ldm.dataset.load_single_img_cond=True \
ldm.dataset.load_scene_type='2' \
ldm.eval.mode=initial_scene \
ldm.eval.run_name=scenario_control_ldm_img_cond_nuplan_test \
ldm.eval.num_samples=100 \
ldm.eval.visualize=True \
ldm.eval.visualize_gt=True \
ldm.eval.cache_samples=True \
ldm.datamodule.test_batch_size=64 \
--config-name=config3dtempInitial Scene Generation (Prompt Conditioning):
python test.py \
dataset_name=nuplan \
model_name=ldm_cond \
ckpt_path=$SCRATCH_ROOT/checkpoints/scenario_control_ldm_prompt_cond_nuplan/last.ckpt \
ldm.model.autoencoder_run_name=scenario_control_autoencoder3d_nuplan \
ldm.model.text_conditioning=True \
ldm.model.decode_in_training=True \
ldm.dataset.load_captions=True \
ldm.dataset.use_cached_text_embeds=False \
ldm.dataset.load_scene_type='0' \
ldm.eval.mode=initial_scene \
ldm.eval.run_name=scenario_control_ldm_prompt_cond_nuplan_test \
ldm.eval.num_samples=100 \
ldm.eval.visualize=True \
ldm.eval.cache_samples=True \
ldm.datamodule.test_batch_size=64 \
--config-name=config3dtempComing soon!
Coming soon!
If you find this work useful, please cite ScenarioControl:
@inproceedings{gao2026scenariocontrol,
title = {ScenarioControl: Vision-Language Controllable Vectorized Latent Scenario Generation},
author = {Gao, Lili and Xu, Yanbo and Koch, William and Ruffino, Samuele and Rowe, Luke and Chalaki, Behdad and Rivkin, Dmitriy and Ost, Julian and Girgis, Roger and Bijelic, Mario and Heide, Felix},
booktitle = {Proceedings of the European Conference on Computer Vision (ECCV)},
year = {2026}
}Special thanks to the authors of the following open-source repositories: