Crop Disease Detection Images
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 """Agriculture Crop Disease Detection Image Recipe 10 11 Generate synthetic crop disease detection images with controlled variation over 12 crop type, growth stage, viewpoint, disease or confounding condition, severity, 13 weather, irrigation, and field condition. The objective is to create examples 14 where the expected crop-health label is known, including healthy negatives and 15 hard confounders, so teams can evaluate detection prompts, build labeling 16 rubrics, calibrate reviewers, and prototype crop-disease workflows before using 17 governed field imagery. 18 19 Prerequisites: 20 - An image-generation provider key for the selected model. The defaults use 21 OpenRouter, so set OPENROUTER_API_KEY before running. 22 23 Run: 24 uv run agriculture_crop_imagery.py --num-records 10 25 """ 26 27 from __future__ import annotations 28 29 import argparse 30 from pathlib import Path 31 32 import data_designer.config as dd 33 from data_designer.interface import DataDesigner, DatasetCreationResults 34 35 DEFAULT_MODEL_PROVIDER = "openrouter" 36 DEFAULT_MODEL_ID = "google/gemini-3.1-flash-image-preview" 37 DEFAULT_MODEL_ALIAS = "agriculture-image-model" 38 39 40 def 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 69 def 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 79 def 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 90 def 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 231 def 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 243 AGRICULTURE_IMAGE_PROMPT = """\ 244 Create a realistic crop disease detection image. 245 246 Scene 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 256 Make the image useful for crop disease detection, visual QA, reviewer 257 calibration, and data-labeling experiments. The requested crop, condition, 258 severity, and field context should be visually inspectable. Show realistic 259 plant structure, leaves, rows, soil, and disease symptoms when requested. For 260 healthy examples, show clear healthy leaves or canopy with no visible disease. 261 For confounders, make the non-disease condition plausible enough to test a 262 classifier or VLM prompt. Do not include real farm names, readable license 263 plates, watermarks, or people as the primary subject. Generate exactly one 264 final crop image for this row. Do not return alternate versions, a grid, a pair 265 of examples, before/after panels, or multiple frames. Use the visual variation 266 ID only as an internal diversity key; never render it as text. 267 """ 268 269 270 def 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 284 def 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 305 if __name__ == "__main__": 306 main()