Initial public release
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
"""Detections that straddle a tile seam must not become two half buildings.
|
||||
|
||||
Tiling uses a fixed overlap. An object wider than that overlap is truncated by
|
||||
both tiles, so the two boxes barely intersect and plain IoU suppression keeps
|
||||
them both: two false positives plus one missed footprint for every seam
|
||||
building. The suppressor therefore also compares overlap against the smaller
|
||||
box, and truncated boxes that sit against an interior tile edge are dropped in
|
||||
favour of the neighbouring tile's complete view.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from shapely.geometry import box
|
||||
|
||||
from app.services.detection_service import DetectionService
|
||||
|
||||
|
||||
def _candidate(name: str, geometry, confidence: float, *, tile_index: int = 0, tile_bounds=None):
|
||||
return {
|
||||
"class_name": "building",
|
||||
"confidence": confidence,
|
||||
"geometry": geometry,
|
||||
"bbox": [0.0, 0.0, 1.0, 1.0],
|
||||
"source_tile_path": f"/tiles/tile_{tile_index:04d}.tif",
|
||||
"properties": {"tile_index": tile_index, "name": name},
|
||||
"tile_bounds": tile_bounds,
|
||||
}
|
||||
|
||||
|
||||
def test_identical_overlapping_predictions_are_still_suppressed() -> None:
|
||||
kept = DetectionService._suppress_duplicate_candidates(
|
||||
[
|
||||
_candidate("a", box(0.0, 0.0, 1.0, 1.0), 0.7),
|
||||
_candidate("b", box(0.02, 0.02, 1.02, 1.02), 0.9),
|
||||
],
|
||||
iou_threshold=0.5,
|
||||
)
|
||||
|
||||
assert [item["properties"]["name"] for item in kept] == ["b"]
|
||||
|
||||
|
||||
def test_a_box_contained_in_a_larger_one_is_suppressed() -> None:
|
||||
"""A truncated seam half sits inside the complete box from the next tile."""
|
||||
|
||||
complete = box(0.0, 0.0, 10.0, 10.0)
|
||||
truncated_half = box(0.0, 0.0, 4.0, 10.0) # IoU with ``complete`` is 0.4
|
||||
|
||||
kept = DetectionService._suppress_duplicate_candidates(
|
||||
[
|
||||
_candidate("complete", complete, 0.88),
|
||||
_candidate("truncated", truncated_half, 0.61),
|
||||
],
|
||||
iou_threshold=0.5,
|
||||
)
|
||||
|
||||
assert [item["properties"]["name"] for item in kept] == ["complete"]
|
||||
|
||||
|
||||
def test_genuinely_adjacent_buildings_are_both_kept() -> None:
|
||||
"""Terraced houses touch but do not contain one another."""
|
||||
|
||||
kept = DetectionService._suppress_duplicate_candidates(
|
||||
[
|
||||
_candidate("left", box(0.0, 0.0, 10.0, 10.0), 0.9),
|
||||
_candidate("right", box(10.0, 0.0, 20.0, 10.0), 0.85),
|
||||
],
|
||||
iou_threshold=0.5,
|
||||
)
|
||||
|
||||
assert sorted(item["properties"]["name"] for item in kept) == ["left", "right"]
|
||||
|
||||
|
||||
def test_different_classes_are_never_merged() -> None:
|
||||
first = _candidate("a", box(0.0, 0.0, 10.0, 10.0), 0.9)
|
||||
second = _candidate("b", box(0.0, 0.0, 10.0, 10.0), 0.8)
|
||||
second["class_name"] = "solar_panel"
|
||||
|
||||
kept = DetectionService._suppress_duplicate_candidates([first, second], iou_threshold=0.5)
|
||||
|
||||
assert len(kept) == 2
|
||||
|
||||
|
||||
def test_boxes_clipped_by_an_interior_tile_edge_are_dropped() -> None:
|
||||
"""The overlapping neighbour tile still sees the whole object."""
|
||||
|
||||
tile = box(0.0, 0.0, 10.0, 10.0)
|
||||
raster = box(0.0, 0.0, 30.0, 10.0)
|
||||
|
||||
candidates = [
|
||||
# Sits against the tile's right edge: truncated by the tile, not real.
|
||||
_candidate("edge", box(9.0, 2.0, 10.0, 4.0), 0.8, tile_bounds=tile.bounds),
|
||||
# Comfortably inside the tile.
|
||||
_candidate("interior", box(2.0, 2.0, 4.0, 4.0), 0.8, tile_bounds=tile.bounds),
|
||||
]
|
||||
|
||||
kept = DetectionService._drop_tile_edge_truncations(
|
||||
candidates, raster_bounds=raster.bounds, tolerance=0.001
|
||||
)
|
||||
|
||||
assert [item["properties"]["name"] for item in kept] == ["interior"]
|
||||
|
||||
|
||||
def test_boxes_against_the_raster_edge_are_kept() -> None:
|
||||
"""No neighbouring tile exists there, so the box is all the evidence there is."""
|
||||
|
||||
tile = box(0.0, 0.0, 10.0, 10.0)
|
||||
raster = box(0.0, 0.0, 10.0, 10.0)
|
||||
|
||||
candidates = [_candidate("edge", box(9.0, 2.0, 10.0, 4.0), 0.8, tile_bounds=tile.bounds)]
|
||||
|
||||
kept = DetectionService._drop_tile_edge_truncations(
|
||||
candidates, raster_bounds=raster.bounds, tolerance=0.001
|
||||
)
|
||||
|
||||
assert [item["properties"]["name"] for item in kept] == ["edge"]
|
||||
|
||||
|
||||
def test_edge_filter_keeps_candidates_without_tile_bounds() -> None:
|
||||
candidates = [_candidate("unknown", box(2.0, 2.0, 4.0, 4.0), 0.8, tile_bounds=None)]
|
||||
|
||||
kept = DetectionService._drop_tile_edge_truncations(
|
||||
candidates, raster_bounds=(0.0, 0.0, 30.0, 10.0), tolerance=0.001
|
||||
)
|
||||
|
||||
assert len(kept) == 1
|
||||
Reference in New Issue
Block a user