Reject blank positive imagery in training loop
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s

This commit is contained in:
Jens
2026-07-27 02:57:36 +02:00
parent c717201cb2
commit 70897a3265
9 changed files with 287 additions and 3 deletions
+17 -1
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
import argparse
import json
import statistics
from collections import Counter, defaultdict
from collections import Counter
from pathlib import Path
from typing import Any
@@ -246,6 +246,7 @@ def build_audit(summary: dict[str, Any], summary_path: Path, args: argparse.Name
]
positive_tiles = [tile for tile in tiles if tile not in negative_tiles]
train_negative_tiles = [tile for tile in negative_tiles if tile.get("split") == "train"]
low_variance_positive_tiles = [tile for tile in positive_tiles if tile.get("low_visual_variance")]
val_positive_samples = {
str(tile.get("sample_slug"))
for tile in positive_tiles
@@ -288,6 +289,15 @@ def build_audit(summary: dict[str, Any], summary_path: Path, args: argparse.Name
f"gate is {args.max_repeated_negative_share:.3f}."
),
)
if low_variance_positive_tiles:
add_warning(
warnings,
"positive_tiles_have_low_visual_variance",
(
f"{len(low_variance_positive_tiles)} positive tiles are visually blank/low-variance; "
"the imagery source does not support their labels."
),
)
if label_stats["missing_label_file_count"]:
add_warning(
warnings,
@@ -337,6 +347,7 @@ def build_audit(summary: dict[str, Any], summary_path: Path, args: argparse.Name
"train_tile_count": split_counts.get("train", 0),
"val_tile_count": split_counts.get("val", 0),
"train_negative_tile_count": len(train_negative_tiles),
"low_variance_positive_tile_count": len(low_variance_positive_tiles),
"repeated_background_negative_tile_count": repeated_negative_count,
"repeated_background_negative_share_of_negatives": repeated_negative_share,
"positive_tile_share": len(positive_tiles) / len(tiles) if tiles else 0.0,
@@ -371,6 +382,10 @@ def build_recommendations(warnings: list[dict[str, str]]) -> list[str]:
)
if "label_files_missing" in codes or "invalid_label_rows" in codes:
recommendations.append("Regenerate the YOLO tile dataset and review exporter path/label integrity.")
if "positive_tiles_have_low_visual_variance" in codes:
recommendations.append(
"Reject the raster product for affected AOIs or replace it with an officially complete imagery edition before training."
)
if not recommendations:
recommendations.append("Dataset audit passed the configured gates; continue with benchmarked training.")
return recommendations
@@ -389,6 +404,7 @@ def write_markdown(report: dict[str, Any], path: Path) -> None:
f"- Samples: {report['sample_count']} ({report['positive_sample_count']} positive, {report['background_sample_count']} background)",
f"- Repeated background negative share: {report['repeated_background_negative_share_of_negatives']:.3f}",
f"- Minimum visible label ratio: {format_optional_float(report.get('min_label_visible_ratio'))}",
f"- Blank/low-variance positive tiles: {report['low_variance_positive_tile_count']}",
"",
"## Label Quality",
"",