Airport Baggage Screening Scans

View as Markdown
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
11Generate synthetic airport baggage-screening style images with controlled
12variation over scanner style, bag density, benign clutter, material mix, object
13overlap, and high-level defensive threat-type labels.
14
15Security 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
22Prerequisites:
23 - An image-generation provider key for the selected model. The defaults use
24 OpenRouter, so set OPENROUTER_API_KEY before running.
25
26Run:
27 uv run airport_security_scans.py --num-records 10
28"""
29
30from __future__ import annotations
31
32import argparse
33from pathlib import Path
34
35import data_designer.config as dd
36from data_designer.interface import DataDesigner, DatasetCreationResults
37
38DEFAULT_MODEL_PROVIDER = "openrouter"
39DEFAULT_MODEL_ID = "google/gemini-3.1-flash-image-preview"
40DEFAULT_MODEL_ALIAS = "baggage-screening-image-model"
41
42
43def 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
73def 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
84def 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
95def 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
214def 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
226AIRPORT_SECURITY_SCAN_PROMPT = """\
227Create a synthetic airport baggage-screening training image that shows only the scan content.
228
229Image 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
239Render the image as a realistic pseudo-color baggage scan, not a normal photo.
240Show overlapping objects, material-color variation, partial occlusion, and
241scanner-like attenuation. The image should be useful for defensive model
242development and human-review training.
243
244Generate exactly one final scan image for this row. Do not return alternate
245versions, a grid, a pair of examples, a before/after image, multiple scans, or
246multiple panels. Use the visual variation ID only as an internal diversity key
247for object placement, scanner angle, and material pattern; never render it as
248text.
249
250The output must be the scan image only. Do not add labels, legends, captions,
251classification text, bounding boxes, arrows, callouts, segmentation overlays,
252heatmaps, UI panels, scanner controls, watermarks, timestamps, filenames, row
253IDs, colored outlines, or any additional layer of text. Do not include
254operational airport details, real airport names, passenger names, barcodes,
255boarding passes, bypass instructions, or anything that describes how to hide or
256evade detection. Use the threat type only to shape the broad visual contents of
257the bag scan.
258"""
259
260
261def 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
275def 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
296if __name__ == "__main__":
297 main()