From 8a4a995659ba650648d2230397784cf4cbe12dd3 Mon Sep 17 00:00:00 2001 From: Jens Date: Mon, 27 Jul 2026 05:53:14 +0200 Subject: [PATCH] Support dense full-AOI building evaluation --- backend/tests/test_belgium_training_loop.py | 2 ++ docs/BELGIUM_BUILDING_TRAINING_LOOP.md | 1 + scripts/evaluate_belgium_building_candidate.py | 8 ++++++++ scripts/run_belgium_building_training_loop.py | 13 ++++++++++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/backend/tests/test_belgium_training_loop.py b/backend/tests/test_belgium_training_loop.py index 2ff52d90..c9a1feb9 100644 --- a/backend/tests/test_belgium_training_loop.py +++ b/backend/tests/test_belgium_training_loop.py @@ -31,6 +31,8 @@ def test_training_command_is_cuda_deterministic_and_bound_to_frozen_inputs(tmp_p assert "deterministic=True" in command assert "seed=42" in command assert "epochs=160" in command + assert "max_det=1000" in command + assert "imgsz=640" in command assert f"data={tmp_path / 'dataset.yaml'}" in command diff --git a/docs/BELGIUM_BUILDING_TRAINING_LOOP.md b/docs/BELGIUM_BUILDING_TRAINING_LOOP.md index 92c10a53..ae8c9f10 100644 --- a/docs/BELGIUM_BUILDING_TRAINING_LOOP.md +++ b/docs/BELGIUM_BUILDING_TRAINING_LOOP.md @@ -35,6 +35,7 @@ data. A new iteration adds independent training AOIs instead. | Temporal truth | Unknown per-pixel dates remain unknown; acquisition dates may not masquerade as observation dates | | Threshold selection | Calibration set only; maximise the worst regional F1 before aggregate F1 | | Test aggregate | F1 at least 0.55 at the frozen footprint/detection match IoU 0.25 contract | +| Dense-tile capacity | Retain up to 1000 detections per tile; the library default of 300 is below observed Belgian urban label density | | Regional test | Every region: F1 at least 0.45, precision at least 0.50 and recall at least 0.40 | | Pure background | Zero detections on every pure-empty tile at the selected threshold | | Production | Exact candidate checksum and fail-closed promotion report required | diff --git a/scripts/evaluate_belgium_building_candidate.py b/scripts/evaluate_belgium_building_candidate.py index 33729fc5..c95e9605 100644 --- a/scripts/evaluate_belgium_building_candidate.py +++ b/scripts/evaluate_belgium_building_candidate.py @@ -82,6 +82,12 @@ def main() -> int: parser.add_argument("--split", default="val") parser.add_argument("--augment", action="store_true", help="Enable deterministic YOLO test-time augmentation.") parser.add_argument("--imgsz", type=int, default=640) + parser.add_argument( + "--max-det", + type=int, + default=1000, + help="Maximum detections retained per tile; dense Belgian urban tiles exceed YOLO's default 300.", + ) args = parser.parse_args() from ultralytics import YOLO @@ -106,6 +112,7 @@ def main() -> int: device=args.device, augment=args.augment, imgsz=args.imgsz, + max_det=args.max_det, verbose=False, ) observations: list[dict[str, Any]] = [] @@ -155,6 +162,7 @@ def main() -> int: "match_iou": args.match_iou, "test_time_augmentation": args.augment, "inference_imgsz": args.imgsz, + "max_detections_per_tile": args.max_det, "tile_count": len(tiles), "sweeps": sweeps, } diff --git a/scripts/run_belgium_building_training_loop.py b/scripts/run_belgium_building_training_loop.py index 6129a35d..f0e5b8b9 100644 --- a/scripts/run_belgium_building_training_loop.py +++ b/scripts/run_belgium_building_training_loop.py @@ -40,6 +40,8 @@ def training_command( seed: int, batch: int, workers: int, + max_det: int = 1000, + imgsz: int = 640, ) -> list[str]: return [ yolo, @@ -47,13 +49,14 @@ def training_command( f"model={model}", f"data={data}", f"epochs={epochs}", - "imgsz=640", + f"imgsz={imgsz}", f"batch={batch}", "device=0", f"workers={workers}", "patience=35", "cache=disk", "close_mosaic=20", + f"max_det={max_det}", f"seed={seed}", "deterministic=True", f"project={project}", @@ -88,6 +91,8 @@ def main() -> int: parser.add_argument("--epochs", type=int, default=160) parser.add_argument("--batch", type=int, default=2) parser.add_argument("--workers", type=int, default=4) + parser.add_argument("--max-det", type=int, default=1000) + parser.add_argument("--imgsz", type=int, default=640) parser.add_argument("--seed", type=int, default=20260731) parser.add_argument("--yolo", default="yolo") parser.add_argument("--dry-run", action="store_true") @@ -133,6 +138,8 @@ def main() -> int: seed=args.seed + index, batch=args.batch, workers=args.workers, + max_det=args.max_det, + imgsz=args.imgsz, ) if args.dry_run: print(json.dumps({"training_command": command}, indent=2)) @@ -166,6 +173,10 @@ def main() -> int: str(report), "--device", "cuda:0", + "--max-det", + str(args.max_det), + "--imgsz", + str(args.imgsz), ], iteration_dir / f"{role}.log", )