Gate YOLO tile labels by visible ratio
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-09 13:12:33 +02:00
parent c4a2e13d43
commit a20d9b70c7
8 changed files with 93 additions and 4 deletions
+24 -2
View File
@@ -78,6 +78,15 @@ def parse_args() -> argparse.Namespace:
default=float(os.environ.get("OPERATOR_YOLO_MIN_LABEL_PX", "4")),
help="Minimum clipped box width/height in pixels before a tile label is kept.",
)
parser.add_argument(
"--min-label-visible-ratio",
type=float,
default=float(os.environ.get("OPERATOR_YOLO_MIN_LABEL_VISIBLE_RATIO", "0")),
help=(
"Minimum visible share of the original object bbox required before a clipped tile label is kept. "
"Use 0 to keep legacy edge-fragment labels."
),
)
parser.add_argument(
"--background-negative-repeat",
type=int,
@@ -217,7 +226,7 @@ def load_reference_pixel_boxes(reference_path: Path, dataset: Any, min_label_px:
return boxes
def labels_for_tile(tile_window: TileWindow, boxes: list[PixelBox], min_label_px: float) -> list[str]:
def labels_for_tile(tile_window: TileWindow, boxes: list[PixelBox], min_label_px: float, min_visible_ratio: float = 0.0) -> list[str]:
labels: list[str] = []
tile_min_col = tile_window.col_off
tile_min_row = tile_window.row_off
@@ -233,6 +242,11 @@ def labels_for_tile(tile_window: TileWindow, boxes: list[PixelBox], min_label_px
box_height = max_row - min_row
if box_width < min_label_px or box_height < min_label_px:
continue
original_area = max((box.max_col - box.min_col) * (box.max_row - box.min_row), 0.0)
visible_area = box_width * box_height
visible_ratio = visible_area / original_area if original_area > 0 else 0.0
if min_visible_ratio > 0 and visible_ratio < min_visible_ratio:
continue
local_min_col = min_col - tile_min_col
local_max_col = max_col - tile_min_col
local_min_row = min_row - tile_min_row
@@ -296,6 +310,7 @@ def export_sample_tiles(
stride: int,
negative_keep_ratio: float,
min_label_px: float,
min_label_visible_ratio: float,
background_negative_repeat: int,
) -> list[dict[str, Any]]:
sample_slug = str(sample["sample_slug"])
@@ -312,7 +327,12 @@ def export_sample_tiles(
with rasterio.open(raster_path) as dataset:
boxes = load_reference_pixel_boxes(reference_path, dataset, min_label_px=min_label_px)
for tile_index, tile_window in enumerate(iter_tile_windows(dataset.width, dataset.height, tile_size, stride)):
labels = labels_for_tile(tile_window, boxes, min_label_px=min_label_px)
labels = labels_for_tile(
tile_window,
boxes,
min_label_px=min_label_px,
min_visible_ratio=min_label_visible_ratio,
)
is_negative = not labels
if is_negative and not keep_negative_tile(sample_slug, tile_index, negative_keep_ratio):
exported.append(
@@ -391,6 +411,7 @@ def main() -> int:
stride=args.stride,
negative_keep_ratio=args.negative_keep_ratio,
min_label_px=args.min_label_px,
min_label_visible_ratio=args.min_label_visible_ratio,
background_negative_repeat=args.background_negative_repeat,
)
)
@@ -414,6 +435,7 @@ def main() -> int:
"negative_keep_ratio": args.negative_keep_ratio,
"background_negative_repeat": args.background_negative_repeat,
"min_label_px": args.min_label_px,
"min_label_visible_ratio": args.min_label_visible_ratio,
"source_sample_count": len(samples),
"tile_count": len(kept_tiles),
"positive_tile_count": len(positive_tiles),