| 1 | # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | # SPDX-License-Identifier: Apache-2.0 |
| 3 | # /// script |
| 4 | # requires-python = ">=3.10" |
| 5 | # dependencies = [ |
| 6 | # "data-designer", |
| 7 | # ] |
| 8 | # /// |
| 9 | """Autonomous Vehicle Traffic Scenario Image Generation Recipe |
| 10 | |
| 11 | Generate synthetic autonomous-vehicle ego-camera images with controlled |
| 12 | variation over region, road type, weather, time of day, traffic density, |
| 13 | surface condition, traffic controls, and long-tail scenario elements. Use the |
| 14 | generated images for perception review sets, visual QA, or |
| 15 | simulator-validation prompts. |
| 16 | |
| 17 | Synthetic images are not a replacement for real sensor logs, simulator runs, or |
| 18 | safety validation. They are useful for rapidly creating controlled visual |
| 19 | examples around rare or hazardous conditions. |
| 20 | |
| 21 | Prerequisites: |
| 22 | - An image-generation provider key for the selected model. The defaults use |
| 23 | OpenRouter, so set OPENROUTER_API_KEY before running. |
| 24 | |
| 25 | Run: |
| 26 | uv run traffic_scenarios.py --num-records 10 |
| 27 | """ |
| 28 | |
| 29 | from __future__ import annotations |
| 30 | |
| 31 | import argparse |
| 32 | from pathlib import Path |
| 33 | |
| 34 | import data_designer.config as dd |
| 35 | from data_designer.interface import DataDesigner, DatasetCreationResults |
| 36 | |
| 37 | DEFAULT_MODEL_PROVIDER = "openrouter" |
| 38 | DEFAULT_MODEL_ID = "google/gemini-3.1-flash-image-preview" |
| 39 | DEFAULT_MODEL_ALIAS = "traffic-scene-model" |
| 40 | |
| 41 | |
| 42 | def build_model_configs( |
| 43 | *, |
| 44 | model_provider: str, |
| 45 | model_id: str, |
| 46 | model_alias: str, |
| 47 | image_size: str, |
| 48 | aspect_ratio: str, |
| 49 | max_parallel_requests: int, |
| 50 | ) -> list[dd.ModelConfig]: |
| 51 | """Build a provider-agnostic image-generation model config.""" |
| 52 | return [ |
| 53 | dd.ModelConfig( |
| 54 | alias=model_alias, |
| 55 | model=model_id, |
| 56 | provider=model_provider, |
| 57 | inference_parameters=dd.ImageInferenceParams( |
| 58 | extra_body={ |
| 59 | "modalities": ["image", "text"], |
| 60 | "image_config": { |
| 61 | "aspect_ratio": aspect_ratio, |
| 62 | "image_size": image_size, |
| 63 | }, |
| 64 | }, |
| 65 | max_parallel_requests=max_parallel_requests, |
| 66 | ), |
| 67 | skip_health_check=True, |
| 68 | ) |
| 69 | ] |
| 70 | |
| 71 | |
| 72 | def add_category(config_builder: dd.DataDesignerConfigBuilder, name: str, values: list[str]) -> None: |
| 73 | """Add a categorical sampler column.""" |
| 74 | config_builder.add_column( |
| 75 | dd.SamplerColumnConfig( |
| 76 | name=name, |
| 77 | sampler_type=dd.SamplerType.CATEGORY, |
| 78 | params=dd.CategorySamplerParams(values=values), |
| 79 | ) |
| 80 | ) |
| 81 | |
| 82 | |
| 83 | def build_config( |
| 84 | *, |
| 85 | model_provider: str = DEFAULT_MODEL_PROVIDER, |
| 86 | model_id: str = DEFAULT_MODEL_ID, |
| 87 | model_alias: str = DEFAULT_MODEL_ALIAS, |
| 88 | image_size: str = "1K", |
| 89 | aspect_ratio: str = "16:9", |
| 90 | max_parallel_requests: int = 10, |
| 91 | ) -> dd.DataDesignerConfigBuilder: |
| 92 | """Build an autonomous-vehicle ego-camera image-generation pipeline.""" |
| 93 | model_configs = build_model_configs( |
| 94 | model_provider=model_provider, |
| 95 | model_id=model_id, |
| 96 | model_alias=model_alias, |
| 97 | image_size=image_size, |
| 98 | aspect_ratio=aspect_ratio, |
| 99 | max_parallel_requests=max_parallel_requests, |
| 100 | ) |
| 101 | config_builder = dd.DataDesignerConfigBuilder(model_configs=model_configs) |
| 102 | |
| 103 | add_category( |
| 104 | config_builder, |
| 105 | "geographic_region", |
| 106 | [ |
| 107 | "US - dense urban (NYC-style)", |
| 108 | "US - sprawling suburban (Los Angeles-style)", |
| 109 | "US - rural Midwest", |
| 110 | "Europe - narrow streets (Italian/French town)", |
| 111 | "Europe - orderly infrastructure (German autobahn)", |
| 112 | "Asia - mixed traffic (India/Thailand)", |
| 113 | "Asia - modern cityscape (Singapore/Tokyo)", |
| 114 | ], |
| 115 | ) |
| 116 | |
| 117 | add_category( |
| 118 | config_builder, |
| 119 | "road_type", |
| 120 | [ |
| 121 | "urban city street with tall buildings", |
| 122 | "urban street with mixed retail/residential", |
| 123 | "suburban residential street with trees", |
| 124 | "suburban commercial strip with parking lots", |
| 125 | "highway - 3 lanes each direction", |
| 126 | "highway - 5 lanes each direction with HOV", |
| 127 | "rural two-lane country road", |
| 128 | "rural highway with sparse markings", |
| 129 | "mountain road with curves and guardrails", |
| 130 | "coastal road with scenic views", |
| 131 | "bridge - suspension or arch style", |
| 132 | "bridge - concrete overpass", |
| 133 | "tunnel - well-lit with ceiling lights", |
| 134 | "tunnel - dim lighting", |
| 135 | "parking lot - shopping center", |
| 136 | "parking garage - multi-level", |
| 137 | "intersection - 4-way with traffic lights", |
| 138 | "intersection - complex 6-way", |
| 139 | "roundabout - single lane", |
| 140 | "roundabout - multi-lane", |
| 141 | "construction zone with detour", |
| 142 | "school zone with crossing", |
| 143 | ], |
| 144 | ) |
| 145 | |
| 146 | add_category( |
| 147 | config_builder, |
| 148 | "weather", |
| 149 | [ |
| 150 | "clear sunny day", |
| 151 | "partly cloudy", |
| 152 | "overcast with gray skies", |
| 153 | "light rain - misty windshield", |
| 154 | "moderate rain - active wipers", |
| 155 | "heavy rain - reduced visibility under 100ft", |
| 156 | "light fog - moderate visibility", |
| 157 | "dense fog - visibility under 50ft", |
| 158 | "light snow - flurries", |
| 159 | "moderate snow - accumulating on road", |
| 160 | "heavy snow - whiteout conditions", |
| 161 | "sleet/freezing rain", |
| 162 | "dust storm - desert conditions", |
| 163 | "high winds - debris visible", |
| 164 | ], |
| 165 | ) |
| 166 | |
| 167 | add_category( |
| 168 | config_builder, |
| 169 | "time_of_day", |
| 170 | [ |
| 171 | "dawn - pre-sunrise twilight", |
| 172 | "early morning - golden hour light", |
| 173 | "mid-morning - bright sun, long shadows", |
| 174 | "midday - overhead sun, minimal shadows", |
| 175 | "afternoon - sun starting to lower", |
| 176 | "late afternoon - golden hour", |
| 177 | "dusk - post-sunset twilight", |
| 178 | "night - well-lit with streetlights", |
| 179 | "night - moderately lit urban", |
| 180 | "night - poorly lit rural", |
| 181 | "night - headlights only, no street lighting", |
| 182 | ], |
| 183 | ) |
| 184 | |
| 185 | add_category( |
| 186 | config_builder, |
| 187 | "traffic_density", |
| 188 | [ |
| 189 | "empty - no other vehicles visible", |
| 190 | "sparse - 1-2 vehicles in distance", |
| 191 | "light - 3-5 vehicles visible", |
| 192 | "moderate - steady flow of traffic", |
| 193 | "heavy - congested, slow-moving", |
| 194 | "stop-and-go - bumper-to-bumper", |
| 195 | ], |
| 196 | ) |
| 197 | |
| 198 | sparse_vehicle_mix = [ |
| 199 | "sedans and compact cars", |
| 200 | "mix of cars and SUVs", |
| 201 | "includes large trucks/semi-trailers", |
| 202 | "includes buses", |
| 203 | "includes motorcycles and scooters", |
| 204 | "includes bicycles and e-bikes", |
| 205 | "includes delivery vans/box trucks", |
| 206 | ] |
| 207 | flowing_vehicle_mix = [*sparse_vehicle_mix, "mixed vehicle types - diverse traffic"] |
| 208 | config_builder.add_column( |
| 209 | dd.SamplerColumnConfig( |
| 210 | name="vehicle_mix", |
| 211 | sampler_type=dd.SamplerType.SUBCATEGORY, |
| 212 | params=dd.SubcategorySamplerParams( |
| 213 | category="traffic_density", |
| 214 | values={ |
| 215 | "empty - no other vehicles visible": ["no other vehicles visible"], |
| 216 | "sparse - 1-2 vehicles in distance": sparse_vehicle_mix, |
| 217 | "light - 3-5 vehicles visible": flowing_vehicle_mix, |
| 218 | "moderate - steady flow of traffic": flowing_vehicle_mix, |
| 219 | "heavy - congested, slow-moving": flowing_vehicle_mix, |
| 220 | "stop-and-go - bumper-to-bumper": flowing_vehicle_mix, |
| 221 | }, |
| 222 | ), |
| 223 | ) |
| 224 | ) |
| 225 | |
| 226 | add_category( |
| 227 | config_builder, |
| 228 | "scenario_element", |
| 229 | [ |
| 230 | "pedestrian crossing at marked crosswalk", |
| 231 | "pedestrian jaywalking mid-block", |
| 232 | "pedestrian with stroller/wheelchair", |
| 233 | "group of pedestrians crossing", |
| 234 | "child chasing ball toward street", |
| 235 | "jogger/runner on shoulder", |
| 236 | "pedestrian wearing dark clothing at night", |
| 237 | "pedestrian with umbrella obscuring face in rain", |
| 238 | "elderly pedestrian crossing slowly with walker", |
| 239 | "pedestrian distracted by phone while crossing", |
| 240 | "pedestrians exiting parked bus on roadside", |
| 241 | "crowd spilling onto road from sidewalk event", |
| 242 | "cyclist in dedicated bike lane", |
| 243 | "cyclist merging into traffic lane", |
| 244 | "cyclist making left turn", |
| 245 | "cyclist riding against traffic on wrong side", |
| 246 | "e-scooter rider weaving between cars", |
| 247 | "e-scooter rider on sidewalk entering crosswalk", |
| 248 | "group of cyclists in paceline on shoulder", |
| 249 | "cyclist with cargo trailer taking full lane", |
| 250 | "motorcycle lane splitting", |
| 251 | "motorcycle filtering through stopped traffic", |
| 252 | "motorcycle approaching from blind spot", |
| 253 | "school bus with stop sign extended and flashing", |
| 254 | "ambulance approaching with lights and sirens", |
| 255 | "police vehicle with lights activated", |
| 256 | "fire truck in oncoming lane", |
| 257 | "emergency vehicle approaching from behind in mirror", |
| 258 | "tow truck loading vehicle on roadside", |
| 259 | "construction zone - workers present with cones", |
| 260 | "construction zone - lane closure with signs", |
| 261 | "road crew filling potholes with equipment in lane", |
| 262 | "utility workers with cherry picker blocking lane", |
| 263 | "temporary steel plates covering road excavation", |
| 264 | "stopped vehicle - hazard lights on shoulder", |
| 265 | "vehicle broken down in lane", |
| 266 | "disabled vehicle in lane with warning triangle", |
| 267 | "vehicle stalled in intersection", |
| 268 | "vehicle with open hood - person inspecting engine", |
| 269 | "vehicle suddenly braking ahead", |
| 270 | "vehicle making unexpected lane change without signal", |
| 271 | "vehicle backing out of parking spot", |
| 272 | "vehicle running red light from cross street", |
| 273 | "vehicle driving wrong way on one-way street", |
| 274 | "vehicle making illegal U-turn", |
| 275 | "vehicle swerving to avoid pothole", |
| 276 | "vehicle drifting out of lane (distracted driver)", |
| 277 | "vehicle cutting in from merging lane aggressively", |
| 278 | "slow-moving vehicle (farm equipment/golf cart) on road", |
| 279 | "delivery truck double-parked with flashers", |
| 280 | "garbage truck with crew working", |
| 281 | "parked car door opening into traffic", |
| 282 | "semi-truck jackknifed across lanes", |
| 283 | "wide-load vehicle with escort car", |
| 284 | "ice cream truck stopped with children nearby", |
| 285 | "ride-share vehicle stopped abruptly for pickup", |
| 286 | "moving truck partially blocking lane while loading", |
| 287 | "food truck parked on street with customer queue", |
| 288 | "animal (deer) on roadside", |
| 289 | "animal (dog) loose on road", |
| 290 | "flock of birds on road surface", |
| 291 | "animal (coyote/fox) darting across road", |
| 292 | "fallen tree branch partially blocking lane", |
| 293 | "debris/cargo on road surface", |
| 294 | "large pothole in driving lane", |
| 295 | "standing water/flooded section of road", |
| 296 | "oil spill or fluid on road surface", |
| 297 | "tire tread/retread debris on highway", |
| 298 | "mattress or furniture fallen from truck on road", |
| 299 | "manhole cover missing or displaced", |
| 300 | "traffic cone or barrel knocked into lane", |
| 301 | "sun glare directly ahead through windshield", |
| 302 | "headlight glare from oncoming vehicle at night", |
| 303 | "spray/mist from vehicle ahead on wet road", |
| 304 | "shadow from overpass creating sudden darkness", |
| 305 | "smoke from nearby fire drifting across road", |
| 306 | "reflection of wet road creating mirror effect", |
| 307 | "traffic light malfunctioning - flashing red", |
| 308 | "obscured traffic sign by overgrown vegetation", |
| 309 | "contradictory road signs at intersection", |
| 310 | "pedestrian signal countdown with people still crossing", |
| 311 | "railroad crossing with gates descending and lights flashing", |
| 312 | "toll booth approach with lanes merging", |
| 313 | ], |
| 314 | ) |
| 315 | |
| 316 | add_category( |
| 317 | config_builder, |
| 318 | "road_surface", |
| 319 | [ |
| 320 | "dry asphalt - good condition", |
| 321 | "dry asphalt - faded lane markings", |
| 322 | "wet reflective surface", |
| 323 | "wet with puddles", |
| 324 | "icy patches visible", |
| 325 | "black ice conditions", |
| 326 | "snow-covered - lane markings obscured", |
| 327 | "gravel surface", |
| 328 | "unpaved dirt road", |
| 329 | "potholes and road damage visible", |
| 330 | "recent patching - uneven surface", |
| 331 | "construction - temporary markings", |
| 332 | ], |
| 333 | ) |
| 334 | |
| 335 | add_category( |
| 336 | config_builder, |
| 337 | "traffic_control", |
| 338 | [ |
| 339 | "traffic light - green", |
| 340 | "traffic light - yellow/amber", |
| 341 | "traffic light - red", |
| 342 | "stop sign clearly visible", |
| 343 | "yield sign", |
| 344 | "speed limit sign - 25 mph", |
| 345 | "speed limit sign - 55 mph", |
| 346 | "no traffic control - uncontrolled intersection", |
| 347 | "construction signage and cones", |
| 348 | "temporary traffic lights", |
| 349 | ], |
| 350 | ) |
| 351 | |
| 352 | config_builder.add_column( |
| 353 | dd.ImageColumnConfig( |
| 354 | name="traffic_scene", |
| 355 | prompt=TRAFFIC_SCENE_PROMPT, |
| 356 | model_alias=model_alias, |
| 357 | ) |
| 358 | ) |
| 359 | |
| 360 | return config_builder |
| 361 | |
| 362 | |
| 363 | def create_dataset( |
| 364 | config_builder: dd.DataDesignerConfigBuilder, |
| 365 | *, |
| 366 | num_records: int, |
| 367 | dataset_name: str, |
| 368 | artifact_path: Path | str | None = None, |
| 369 | ) -> DatasetCreationResults: |
| 370 | data_designer = DataDesigner(artifact_path=artifact_path) |
| 371 | data_designer.validate(config_builder) |
| 372 | return data_designer.create(config_builder, num_records=num_records, dataset_name=dataset_name) |
| 373 | |
| 374 | |
| 375 | TRAFFIC_SCENE_PROMPT = """\ |
| 376 | Create a photorealistic autonomous-vehicle ego-camera perception scene. |
| 377 | |
| 378 | The image must look like it was captured by a camera mounted on the self-driving |
| 379 | ego vehicle, not by a roadside camera, drone, or cinematic photographer. Keep |
| 380 | the viewpoint physically plausible for an AV sensor. When appropriate, show a |
| 381 | subtle hood edge, windshield edge, or bumper edge, but do not show a full car |
| 382 | interior or dashboard UI. |
| 383 | |
| 384 | Scene requirements: |
| 385 | - Geographic region: {{ geographic_region }} |
| 386 | - Road type: {{ road_type }} |
| 387 | - Weather: {{ weather }} |
| 388 | - Time of day: {{ time_of_day }} |
| 389 | - Traffic density: {{ traffic_density }} |
| 390 | - Vehicle mix: {{ vehicle_mix }} |
| 391 | - Key scenario element: {{ scenario_element }} |
| 392 | - Road surface: {{ road_surface }} |
| 393 | - Traffic control: {{ traffic_control }} |
| 394 | |
| 395 | The scene should clearly show road geometry, lane markings, traffic signs, |
| 396 | traffic control devices, surrounding vehicles, vulnerable road users when |
| 397 | requested, and the key scenario element from the ego vehicle's camera. Preserve |
| 398 | regional driving characteristics such as road width, side of road, sign style, |
| 399 | and lane markings. Use realistic lighting, lens geometry, motion perspective, |
| 400 | weather effects, and visibility. Generate exactly one final ego-camera image |
| 401 | for this row. Do not return alternate versions, a grid, a pair of examples, |
| 402 | before/after panels, or multiple camera frames. Do not include text overlays, |
| 403 | labels, watermarks, dashcam timestamps, bounding boxes, sensor UI, or navigation |
| 404 | UI. |
| 405 | """ |
| 406 | |
| 407 | |
| 408 | def parse_args() -> argparse.Namespace: |
| 409 | parser = argparse.ArgumentParser(description="Generate synthetic autonomous-vehicle traffic scenes.") |
| 410 | parser.add_argument("--num-records", type=int, default=10, help="Number of traffic scenes to generate.") |
| 411 | parser.add_argument("--dataset-name", default="synthetic-traffic-scenarios", help="Output dataset name.") |
| 412 | parser.add_argument("--artifact-path", type=Path, default=None, help="Optional Data Designer artifact directory.") |
| 413 | parser.add_argument("--model-provider", default=DEFAULT_MODEL_PROVIDER, help="Image model provider name.") |
| 414 | parser.add_argument("--model-id", default=DEFAULT_MODEL_ID, help="Provider model ID.") |
| 415 | parser.add_argument("--model-alias", default=DEFAULT_MODEL_ALIAS, help="Alias used by image columns.") |
| 416 | parser.add_argument("--image-size", default="1K", help="OpenRouter image size tier, such as 1K, 2K, or 4K.") |
| 417 | parser.add_argument("--aspect-ratio", default="16:9", help="Provider-specific aspect ratio value.") |
| 418 | parser.add_argument("--max-parallel-requests", type=int, default=10, help="Maximum parallel image requests.") |
| 419 | return parser.parse_args() |
| 420 | |
| 421 | |
| 422 | def main() -> None: |
| 423 | args = parse_args() |
| 424 | config_builder = build_config( |
| 425 | model_provider=args.model_provider, |
| 426 | model_id=args.model_id, |
| 427 | model_alias=args.model_alias, |
| 428 | image_size=args.image_size, |
| 429 | aspect_ratio=args.aspect_ratio, |
| 430 | max_parallel_requests=args.max_parallel_requests, |
| 431 | ) |
| 432 | results = create_dataset( |
| 433 | config_builder, |
| 434 | num_records=args.num_records, |
| 435 | dataset_name=args.dataset_name, |
| 436 | artifact_path=args.artifact_path, |
| 437 | ) |
| 438 | dataset = results.load_dataset() |
| 439 | print(f"Generated {len(dataset)} synthetic traffic-scene rows.") |
| 440 | print(f"Dataset artifacts: {results.artifact_storage.base_dataset_path}") |
| 441 | |
| 442 | |
| 443 | if __name__ == "__main__": |
| 444 | main() |