Crop Disease Detection Images

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"""Agriculture Crop Disease Detection Image Recipe
10
11Generate synthetic crop disease detection images with controlled variation over
12crop type, growth stage, viewpoint, disease or confounding condition, severity,
13weather, irrigation, and field condition. The objective is to create examples
14where the expected crop-health label is known, including healthy negatives and
15hard confounders, so teams can evaluate detection prompts, build labeling
16rubrics, calibrate reviewers, and prototype crop-disease workflows before using
17governed field imagery.
18
19Prerequisites:
20 - An image-generation provider key for the selected model. The defaults use
21 OpenRouter, so set OPENROUTER_API_KEY before running.
22
23Run:
24 uv run agriculture_crop_imagery.py --num-records 10
25"""
26
27from __future__ import annotations
28
29import argparse
30from pathlib import Path
31
32import data_designer.config as dd
33from data_designer.interface import DataDesigner, DatasetCreationResults
34
35DEFAULT_MODEL_PROVIDER = "openrouter"
36DEFAULT_MODEL_ID = "google/gemini-3.1-flash-image-preview"
37DEFAULT_MODEL_ALIAS = "agriculture-image-model"
38
39
40def build_model_configs(
41 *,
42 model_provider: str,
43 model_id: str,
44 model_alias: str,
45 image_size: str,
46 aspect_ratio: str,
47 max_parallel_requests: int,
48) -> list[dd.ModelConfig]:
49 return [
50 dd.ModelConfig(
51 alias=model_alias,
52 model=model_id,
53 provider=model_provider,
54 inference_parameters=dd.ImageInferenceParams(
55 extra_body={
56 "modalities": ["image", "text"],
57 "image_config": {
58 "aspect_ratio": aspect_ratio,
59 "image_size": image_size,
60 },
61 },
62 max_parallel_requests=max_parallel_requests,
63 ),
64 skip_health_check=True,
65 )
66 ]
67
68
69def add_category(config_builder: dd.DataDesignerConfigBuilder, name: str, values: list[str]) -> None:
70 config_builder.add_column(
71 dd.SamplerColumnConfig(
72 name=name,
73 sampler_type=dd.SamplerType.CATEGORY,
74 params=dd.CategorySamplerParams(values=values),
75 )
76 )
77
78
79def add_visual_variation_id(config_builder: dd.DataDesignerConfigBuilder) -> None:
80 """Add a unique row-level key that discourages duplicate image generations."""
81 config_builder.add_column(
82 dd.SamplerColumnConfig(
83 name="visual_variation_id",
84 sampler_type=dd.SamplerType.UUID,
85 params=dd.UUIDSamplerParams(prefix="crop-", short_form=True),
86 )
87 )
88
89
90def build_config(
91 *,
92 model_provider: str = DEFAULT_MODEL_PROVIDER,
93 model_id: str = DEFAULT_MODEL_ID,
94 model_alias: str = DEFAULT_MODEL_ALIAS,
95 image_size: str = "1K",
96 aspect_ratio: str = "4:3",
97 max_parallel_requests: int = 10,
98) -> dd.DataDesignerConfigBuilder:
99 model_configs = build_model_configs(
100 model_provider=model_provider,
101 model_id=model_id,
102 model_alias=model_alias,
103 image_size=image_size,
104 aspect_ratio=aspect_ratio,
105 max_parallel_requests=max_parallel_requests,
106 )
107 config_builder = dd.DataDesignerConfigBuilder(model_configs=model_configs)
108 add_visual_variation_id(config_builder)
109
110 add_category(
111 config_builder,
112 "crop_type",
113 [
114 "corn",
115 "soybean",
116 "wheat",
117 "rice",
118 "tomato",
119 "grape vineyard",
120 "apple orchard",
121 "lettuce",
122 "potato",
123 "strawberry",
124 ],
125 )
126 add_category(
127 config_builder,
128 "growth_stage",
129 [
130 "seedling",
131 "vegetative growth",
132 "flowering",
133 "fruiting",
134 "grain fill",
135 "near harvest",
136 ],
137 )
138 add_category(
139 config_builder,
140 "viewpoint",
141 [
142 "close-up leaf-level scouting photo",
143 "row-level field photo",
144 "drone oblique field view",
145 "top-down drone crop-row view",
146 "greenhouse bench view",
147 "orchard row view",
148 ],
149 )
150 add_category(
151 config_builder,
152 "disease_or_condition",
153 [
154 "healthy crop with no visible disease",
155 "powdery mildew on leaves",
156 "rust-colored fungal pustules on leaf surfaces",
157 "early blight with concentric brown leaf spots",
158 "late blight with irregular dark lesions",
159 "bacterial leaf spot with small dark speckles",
160 "downy mildew patches on leaf undersides",
161 "leaf curl with mosaic discoloration",
162 "insect feeding damage as a disease confounder",
163 "nutrient deficiency yellowing as a disease confounder",
164 ],
165 )
166 disease_severity_values = [
167 "low severity affecting isolated plants",
168 "moderate severity affecting patches",
169 "high severity affecting large field sections",
170 ]
171 config_builder.add_column(
172 dd.SamplerColumnConfig(
173 name="severity",
174 sampler_type=dd.SamplerType.SUBCATEGORY,
175 params=dd.SubcategorySamplerParams(
176 category="disease_or_condition",
177 values={
178 "healthy crop with no visible disease": ["none - healthy negative"],
179 "powdery mildew on leaves": disease_severity_values,
180 "rust-colored fungal pustules on leaf surfaces": disease_severity_values,
181 "early blight with concentric brown leaf spots": disease_severity_values,
182 "late blight with irregular dark lesions": disease_severity_values,
183 "bacterial leaf spot with small dark speckles": disease_severity_values,
184 "downy mildew patches on leaf undersides": disease_severity_values,
185 "leaf curl with mosaic discoloration": disease_severity_values,
186 "insect feeding damage as a disease confounder": ["confounder - not a disease severity label"],
187 "nutrient deficiency yellowing as a disease confounder": [
188 "confounder - not a disease severity label"
189 ],
190 },
191 ),
192 )
193 )
194 add_category(
195 config_builder,
196 "field_condition",
197 [
198 "uniform crop stand",
199 "patchy emergence",
200 "uneven row spacing",
201 "visible irrigation lines",
202 "muddy soil after rain",
203 "dry cracked soil",
204 "mulched bed system",
205 ],
206 )
207 add_category(
208 config_builder,
209 "weather_lighting",
210 [
211 "bright midday sun",
212 "soft overcast light",
213 "golden hour light",
214 "after-rain humid conditions",
215 "hazy smoky sky",
216 "greenhouse diffuse lighting",
217 ],
218 )
219
220 config_builder.add_column(
221 dd.ImageColumnConfig(
222 name="crop_image",
223 prompt=AGRICULTURE_IMAGE_PROMPT,
224 model_alias=model_alias,
225 )
226 )
227
228 return config_builder
229
230
231def create_dataset(
232 config_builder: dd.DataDesignerConfigBuilder,
233 *,
234 num_records: int,
235 dataset_name: str,
236 artifact_path: Path | str | None = None,
237) -> DatasetCreationResults:
238 data_designer = DataDesigner(artifact_path=artifact_path)
239 data_designer.validate(config_builder)
240 return data_designer.create(config_builder, num_records=num_records, dataset_name=dataset_name)
241
242
243AGRICULTURE_IMAGE_PROMPT = """\
244Create a realistic crop disease detection image.
245
246Scene requirements:
247- Visual variation ID, for internal diversity only: {{ visual_variation_id }}
248- Crop type: {{ crop_type }}
249- Growth stage: {{ growth_stage }}
250- Viewpoint: {{ viewpoint }}
251- Disease or condition: {{ disease_or_condition }}
252- Severity: {{ severity }}
253- Field condition: {{ field_condition }}
254- Weather and lighting: {{ weather_lighting }}
255
256Make the image useful for crop disease detection, visual QA, reviewer
257calibration, and data-labeling experiments. The requested crop, condition,
258severity, and field context should be visually inspectable. Show realistic
259plant structure, leaves, rows, soil, and disease symptoms when requested. For
260healthy examples, show clear healthy leaves or canopy with no visible disease.
261For confounders, make the non-disease condition plausible enough to test a
262classifier or VLM prompt. Do not include real farm names, readable license
263plates, watermarks, or people as the primary subject. Generate exactly one
264final crop image for this row. Do not return alternate versions, a grid, a pair
265of examples, before/after panels, or multiple frames. Use the visual variation
266ID only as an internal diversity key; never render it as text.
267"""
268
269
270def parse_args() -> argparse.Namespace:
271 parser = argparse.ArgumentParser(description="Generate synthetic crop disease detection imagery.")
272 parser.add_argument("--num-records", type=int, default=10, help="Number of crop images to generate.")
273 parser.add_argument("--dataset-name", default="crop-disease-detection-images", help="Output dataset name.")
274 parser.add_argument("--artifact-path", type=Path, default=None, help="Optional Data Designer artifact directory.")
275 parser.add_argument("--model-provider", default=DEFAULT_MODEL_PROVIDER, help="Image model provider name.")
276 parser.add_argument("--model-id", default=DEFAULT_MODEL_ID, help="Provider model ID.")
277 parser.add_argument("--model-alias", default=DEFAULT_MODEL_ALIAS, help="Alias used by image columns.")
278 parser.add_argument("--image-size", default="1K", help="OpenRouter image size tier, such as 1K, 2K, or 4K.")
279 parser.add_argument("--aspect-ratio", default="4:3", help="Provider-specific aspect ratio value.")
280 parser.add_argument("--max-parallel-requests", type=int, default=10, help="Maximum parallel image requests.")
281 return parser.parse_args()
282
283
284def main() -> None:
285 args = parse_args()
286 config_builder = build_config(
287 model_provider=args.model_provider,
288 model_id=args.model_id,
289 model_alias=args.model_alias,
290 image_size=args.image_size,
291 aspect_ratio=args.aspect_ratio,
292 max_parallel_requests=args.max_parallel_requests,
293 )
294 results = create_dataset(
295 config_builder,
296 num_records=args.num_records,
297 dataset_name=args.dataset_name,
298 artifact_path=args.artifact_path,
299 )
300 dataset = results.load_dataset()
301 print(f"Generated {len(dataset)} crop disease detection image rows.")
302 print(f"Dataset artifacts: {results.artifact_storage.base_dataset_path}")
303
304
305if __name__ == "__main__":
306 main()