""" Archivo: make_sample_orthomosaic.py Fecha de modificación: 03/06/2026 Autor: Equipo AgroVisión Descripción: Genera un ortomosaico de arándano **simulado** (datos de prueba) para validar la demo del MVP: suelo marrón con textura, hileras de arbustos verdes con huecos de siembra y algunas malezas amarillas. Es determinista por semilla. El mock de inferencia detecta estos arbustos por color, de modo que las cajas caen sobre ellos en la UI. Acciones Principales: - Genera y guarda una imagen sintética de campo de arándano. Estructura Interna: - `generate_orthomosaic`: dibuja el campo y retorna imagen + conteos reales. - `main`: CLI que escribe la imagen y reporta los conteos. Entradas / Dependencias: - `cv2`, `numpy`. Salidas / Efectos: - Escribe un archivo PNG (por defecto `sample_data/blueberry_demo.png`). Ejecución: python scripts/make_sample_orthomosaic.py [--output sample_data/blueberry_demo.png] Ejemplo de Uso: python scripts/make_sample_orthomosaic.py --width 1024 --height 768 --rows 8 --per-row 16 """ from __future__ import annotations import argparse import random import cv2 import numpy as np _SOIL_BGR: tuple[int, int, int] = (50, 80, 120) # marrón _TILLED_LINE_BGR: tuple[int, int, int] = (35, 55, 85) # surco más oscuro _WEED_BGR: tuple[int, int, int] = (40, 200, 220) # amarillo _GAP_PROBABILITY: float = 0.12 # probabilidad de hueco de siembra _WEED_PROBABILITY: float = 0.06 # probabilidad de maleza junto a un arbusto def generate_orthomosaic( width: int = 1024, height: int = 768, rows: int = 8, per_row: int = 16, seed: int = 42 ) -> tuple[np.ndarray, int, int]: """ Genera un ortomosaico de arándano simulado y devuelve los conteos reales. Args: width (int): Ancho de la imagen en píxeles. height (int): Alto de la imagen en píxeles. rows (int): Número de hileras de cultivo. per_row (int): Posiciones de siembra por hilera. seed (int): Semilla para reproducibilidad. Returns: tuple[np.ndarray, int, int]: Imagen BGR, número de arbustos y número de malezas. """ rng = random.Random(seed) np_rng = np.random.default_rng(seed) image = np.empty((height, width, 3), dtype=np.uint8) image[:] = _SOIL_BGR noise = np_rng.integers(-15, 16, size=(height, width, 3), dtype=np.int16) image = np.clip(image.astype(np.int16) + noise, 0, 255).astype(np.uint8) row_step = height / (rows + 1) col_step = width / (per_row + 1) bush_count = 0 weed_count = 0 for row_index in range(1, rows + 1): row_y = int(row_index * row_step) cv2.line(image, (0, row_y), (width, row_y), _TILLED_LINE_BGR, 3) for col_index in range(1, per_row + 1): if rng.random() < _GAP_PROBABILITY: # hueco de siembra (falla) continue center_x = int(col_index * col_step) + rng.randint(-6, 6) center_y = row_y + rng.randint(-5, 5) radius = rng.randint(12, 18) green = rng.randint(120, 180) core_radius = max(3, radius // 3) core_color = (90, min(230, green + 60), 90) cv2.circle(image, (center_x, center_y), radius, (40, green, 40), -1) cv2.circle(image, (center_x, center_y), core_radius, core_color, -1) bush_count += 1 if rng.random() < _WEED_PROBABILITY: # maleza amarilla cercana weed_x = center_x + rng.randint(-int(col_step // 2), int(col_step // 2)) weed_y = center_y + int(row_step // 2) cv2.circle(image, (weed_x, weed_y), rng.randint(5, 9), _WEED_BGR, -1) weed_count += 1 return image, bush_count, weed_count def main() -> None: """Genera la imagen según los argumentos de la CLI y reporta los conteos reales.""" parser = argparse.ArgumentParser(description="Genera un ortomosaico de arándano simulado.") parser.add_argument( "--output", default="sample_data/blueberry_demo.png", help="Ruta de salida." ) parser.add_argument("--width", type=int, default=1024) parser.add_argument("--height", type=int, default=768) parser.add_argument("--rows", type=int, default=8) parser.add_argument("--per-row", type=int, default=16) parser.add_argument("--seed", type=int, default=42) args = parser.parse_args() image, bush_count, weed_count = generate_orthomosaic( width=args.width, height=args.height, rows=args.rows, per_row=args.per_row, seed=args.seed ) cv2.imwrite(args.output, image) print(f"Imagen escrita en {args.output} | arbustos={bush_count} | malezas={weed_count}") if __name__ == "__main__": main()