From f4a139dd7651ef9dea238a9ffbc394580fcf6911 Mon Sep 17 00:00:00 2001 From: Jens Date: Mon, 27 Jul 2026 09:03:05 +0200 Subject: [PATCH] Calibrate systematic roof box offsets --- .../evaluate_belgium_building_candidate.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/evaluate_belgium_building_candidate.py b/scripts/evaluate_belgium_building_candidate.py index e5515711..31a4dc57 100644 --- a/scripts/evaluate_belgium_building_candidate.py +++ b/scripts/evaluate_belgium_building_candidate.py @@ -52,12 +52,19 @@ def metrics(tp: int, fp: int, fn: int) -> dict[str, float | int]: } -def scale_box(box: tuple[float, float, float, float], factor: float) -> tuple[float, float, float, float]: +def scale_box( + box: tuple[float, float, float, float], factor: float, offset_x: float = 0.0, offset_y: float = 0.0 +) -> tuple[float, float, float, float]: """Scale a detector box around its center for calibration-only geometry correction.""" x1, y1, x2, y2 = box cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 half_width, half_height = (x2 - x1) * factor / 2, (y2 - y1) * factor / 2 - return cx - half_width, cy - half_height, cx + half_width, cy + half_height + return ( + cx - half_width + offset_x, + cy - half_height + offset_y, + cx + half_width + offset_x, + cy + half_height + offset_y, + ) def read_references(path: Path, width: int, height: int) -> list[tuple[float, float, float, float]]: @@ -97,6 +104,8 @@ def main() -> int: help="Maximum detections retained per tile; dense Belgian urban tiles exceed YOLO's default 300.", ) parser.add_argument("--box-scale", type=float, default=1.0) + parser.add_argument("--box-offset-x", type=float, default=0.0) + parser.add_argument("--box-offset-y", type=float, default=0.0) parser.add_argument("--proposal-classifier", type=Path) parser.add_argument("--proposal-classifier-threshold", type=float, default=0.5) parser.add_argument("--proposal-crop-scale", type=float, default=1.4) @@ -142,7 +151,12 @@ def main() -> int: for tile, result in zip(tiles, results, strict=True): height, width = result.orig_shape predictions = [ - (scale_box(tuple(map(float, box)), args.box_scale), float(score)) + ( + scale_box( + tuple(map(float, box)), args.box_scale, args.box_offset_x, args.box_offset_y + ), + float(score), + ) for box, score in zip(result.boxes.xyxy.cpu().tolist(), result.boxes.conf.cpu().tolist(), strict=True) ] if proposal_classifier is not None: @@ -213,6 +227,8 @@ def main() -> int: "inference_imgsz": args.imgsz, "max_detections_per_tile": args.max_det, "box_scale": args.box_scale, + "box_offset_x": args.box_offset_x, + "box_offset_y": args.box_offset_y, "proposal_classifier": str(args.proposal_classifier) if args.proposal_classifier else None, "proposal_classifier_threshold": args.proposal_classifier_threshold if args.proposal_classifier else None, "proposal_crop_scale": args.proposal_crop_scale if args.proposal_classifier else None,