Airport Baggage Screening Scans
Download Recipe
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 """Airport Baggage Screening Image Generation Recipe 10 11 Generate synthetic airport baggage-screening style images with controlled 12 variation over scanner style, bag density, benign clutter, material mix, object 13 overlap, and high-level defensive threat-type labels. 14 15 Security note: 16 This recipe is intended for defensive model development, evaluation, 17 curriculum data, and human-review tooling. Do not use it to plan, optimize, 18 or describe ways to bypass real screening systems. The prompts avoid 19 operational bypass details and use high-level threat types rather than 20 concealment instructions. 21 22 Prerequisites: 23 - An image-generation provider key for the selected model. The defaults use 24 OpenRouter, so set OPENROUTER_API_KEY before running. 25 26 Run: 27 uv run airport_security_scans.py --num-records 10 28 """ 29 30 from __future__ import annotations 31 32 import argparse 33 from pathlib import Path 34 35 import data_designer.config as dd 36 from data_designer.interface import DataDesigner, DatasetCreationResults 37 38 DEFAULT_MODEL_PROVIDER = "openrouter" 39 DEFAULT_MODEL_ID = "google/gemini-3.1-flash-image-preview" 40 DEFAULT_MODEL_ALIAS = "baggage-screening-image-model" 41 42 43 def build_model_configs( 44 *, 45 model_provider: str, 46 model_id: str, 47 model_alias: str, 48 image_size: str, 49 aspect_ratio: str, 50 max_parallel_requests: int, 51 ) -> list[dd.ModelConfig]: 52 """Build a provider-agnostic image-generation model config.""" 53 return [ 54 dd.ModelConfig( 55 alias=model_alias, 56 model=model_id, 57 provider=model_provider, 58 inference_parameters=dd.ImageInferenceParams( 59 extra_body={ 60 "modalities": ["image", "text"], 61 "image_config": { 62 "aspect_ratio": aspect_ratio, 63 "image_size": image_size, 64 }, 65 }, 66 max_parallel_requests=max_parallel_requests, 67 ), 68 skip_health_check=True, 69 ) 70 ] 71 72 73 def add_category(config_builder: dd.DataDesignerConfigBuilder, name: str, values: list[str]) -> None: 74 """Add a categorical sampler column.""" 75 config_builder.add_column( 76 dd.SamplerColumnConfig( 77 name=name, 78 sampler_type=dd.SamplerType.CATEGORY, 79 params=dd.CategorySamplerParams(values=values), 80 ) 81 ) 82 83 84 def add_visual_variation_id(config_builder: dd.DataDesignerConfigBuilder) -> None: 85 """Add a unique row-level key that discourages duplicate image generations.""" 86 config_builder.add_column( 87 dd.SamplerColumnConfig( 88 name="visual_variation_id", 89 sampler_type=dd.SamplerType.UUID, 90 params=dd.UUIDSamplerParams(prefix="scan-", short_form=True), 91 ) 92 ) 93 94 95 def build_config( 96 *, 97 model_provider: str = DEFAULT_MODEL_PROVIDER, 98 model_id: str = DEFAULT_MODEL_ID, 99 model_alias: str = DEFAULT_MODEL_ALIAS, 100 image_size: str = "1K", 101 aspect_ratio: str = "4:3", 102 max_parallel_requests: int = 10, 103 ) -> dd.DataDesignerConfigBuilder: 104 """Build an airport baggage-screening image-generation pipeline.""" 105 model_configs = build_model_configs( 106 model_provider=model_provider, 107 model_id=model_id, 108 model_alias=model_alias, 109 image_size=image_size, 110 aspect_ratio=aspect_ratio, 111 max_parallel_requests=max_parallel_requests, 112 ) 113 config_builder = dd.DataDesignerConfigBuilder(model_configs=model_configs) 114 add_visual_variation_id(config_builder) 115 116 add_category( 117 config_builder, 118 "scanner_style", 119 [ 120 "dual-energy X-ray baggage scan with pseudo-color material mapping", 121 "computed tomography baggage scan slice rendered as pseudo-color X-ray", 122 "top-down carry-on baggage screening view", 123 "side-view checked-bag screening image", 124 ], 125 ) 126 127 add_category( 128 config_builder, 129 "bag_type", 130 [ 131 "small carry-on roller bag", 132 "soft backpack", 133 "messenger bag", 134 "hard-shell suitcase", 135 "duffel bag", 136 "camera equipment case", 137 ], 138 ) 139 140 add_category( 141 config_builder, 142 "bag_density", 143 [ 144 "sparse packing with many empty regions", 145 "moderate packing density", 146 "dense packing with overlapping objects", 147 "very dense packing with cluttered object boundaries", 148 ], 149 ) 150 151 add_category( 152 config_builder, 153 "benign_contents", 154 [ 155 "clothing, shoes, toiletries, and paperback books", 156 "laptop, chargers, headphones, notebooks, and snacks", 157 "camera body, lenses, batteries, cables, and clothing", 158 "children's toys, folded clothing, tablet, and water bottle", 159 "sports gear, towel, shoes, and plastic accessories", 160 "business travel items, documents, laptop, and power adapters", 161 ], 162 ) 163 164 add_category( 165 config_builder, 166 "material_mix", 167 [ 168 "mostly fabric and plastic with a few small metal objects", 169 "electronics-heavy bag with cables and batteries", 170 "mixed organic, plastic, and metal materials", 171 "mostly low-density organic material with scattered dense regions", 172 "many small overlapping metal and plastic objects", 173 ], 174 ) 175 176 add_category( 177 config_builder, 178 "threat_type", 179 [ 180 "none - clear benign bag with no threat-like visual pattern", 181 "dense electronics cluster requiring secondary review", 182 "oversized liquid-container-like region requiring secondary review", 183 "sharp-object-like silhouette requiring secondary review", 184 "unknown dense object requiring secondary review", 185 "clutter and overlapping objects preventing confident clearance", 186 "organic anomaly requiring secondary review", 187 "ambiguous tool-like silhouette requiring secondary review", 188 ], 189 ) 190 191 add_category( 192 config_builder, 193 "image_quality", 194 [ 195 "clean scanner output with crisp object boundaries", 196 "slightly noisy scanner output", 197 "low-contrast scan with compressed dynamic range", 198 "scan with mild motion blur", 199 "scan with color palette shifted toward orange and blue material classes", 200 ], 201 ) 202 203 config_builder.add_column( 204 dd.ImageColumnConfig( 205 name="baggage_scan", 206 prompt=AIRPORT_SECURITY_SCAN_PROMPT, 207 model_alias=model_alias, 208 ) 209 ) 210 211 return config_builder 212 213 214 def create_dataset( 215 config_builder: dd.DataDesignerConfigBuilder, 216 *, 217 num_records: int, 218 dataset_name: str, 219 artifact_path: Path | str | None = None, 220 ) -> DatasetCreationResults: 221 data_designer = DataDesigner(artifact_path=artifact_path) 222 data_designer.validate(config_builder) 223 return data_designer.create(config_builder, num_records=num_records, dataset_name=dataset_name) 224 225 226 AIRPORT_SECURITY_SCAN_PROMPT = """\ 227 Create a synthetic airport baggage-screening training image that shows only the scan content. 228 229 Image requirements: 230 - Visual variation ID, for internal diversity only: {{ visual_variation_id }} 231 - Scanner style: {{ scanner_style }} 232 - Bag type: {{ bag_type }} 233 - Bag density: {{ bag_density }} 234 - Benign contents: {{ benign_contents }} 235 - Material mix: {{ material_mix }} 236 - Threat type metadata target, not text to render: {{ threat_type }} 237 - Image quality: {{ image_quality }} 238 239 Render the image as a realistic pseudo-color baggage scan, not a normal photo. 240 Show overlapping objects, material-color variation, partial occlusion, and 241 scanner-like attenuation. The image should be useful for defensive model 242 development and human-review training. 243 244 Generate exactly one final scan image for this row. Do not return alternate 245 versions, a grid, a pair of examples, a before/after image, multiple scans, or 246 multiple panels. Use the visual variation ID only as an internal diversity key 247 for object placement, scanner angle, and material pattern; never render it as 248 text. 249 250 The output must be the scan image only. Do not add labels, legends, captions, 251 classification text, bounding boxes, arrows, callouts, segmentation overlays, 252 heatmaps, UI panels, scanner controls, watermarks, timestamps, filenames, row 253 IDs, colored outlines, or any additional layer of text. Do not include 254 operational airport details, real airport names, passenger names, barcodes, 255 boarding passes, bypass instructions, or anything that describes how to hide or 256 evade detection. Use the threat type only to shape the broad visual contents of 257 the bag scan. 258 """ 259 260 261 def parse_args() -> argparse.Namespace: 262 parser = argparse.ArgumentParser(description="Generate synthetic airport baggage-screening images.") 263 parser.add_argument("--num-records", type=int, default=10, help="Number of baggage scan images to generate.") 264 parser.add_argument("--dataset-name", default="synthetic-baggage-screening-scans", help="Output dataset name.") 265 parser.add_argument("--artifact-path", type=Path, default=None, help="Optional Data Designer artifact directory.") 266 parser.add_argument("--model-provider", default=DEFAULT_MODEL_PROVIDER, help="Image model provider name.") 267 parser.add_argument("--model-id", default=DEFAULT_MODEL_ID, help="Provider model ID.") 268 parser.add_argument("--model-alias", default=DEFAULT_MODEL_ALIAS, help="Alias used by image columns.") 269 parser.add_argument("--image-size", default="1K", help="OpenRouter image size tier, such as 1K, 2K, or 4K.") 270 parser.add_argument("--aspect-ratio", default="4:3", help="Provider-specific aspect ratio value.") 271 parser.add_argument("--max-parallel-requests", type=int, default=10, help="Maximum parallel image requests.") 272 return parser.parse_args() 273 274 275 def main() -> None: 276 args = parse_args() 277 config_builder = build_config( 278 model_provider=args.model_provider, 279 model_id=args.model_id, 280 model_alias=args.model_alias, 281 image_size=args.image_size, 282 aspect_ratio=args.aspect_ratio, 283 max_parallel_requests=args.max_parallel_requests, 284 ) 285 results = create_dataset( 286 config_builder, 287 num_records=args.num_records, 288 dataset_name=args.dataset_name, 289 artifact_path=args.artifact_path, 290 ) 291 dataset = results.load_dataset() 292 print(f"Generated {len(dataset)} synthetic baggage-screening rows.") 293 print(f"Dataset artifacts: {results.artifact_storage.base_dataset_path}") 294 295 296 if __name__ == "__main__": 297 main()