Promote focused small-building detector
GeoIntel CI / docs-smoke (push) Canceled after 0s
GeoIntel CI / contract-smoke (push) Canceled after 0s

This commit is contained in:
Codex
2026-07-13 12:15:27 +02:00
parent b3f7c3ca63
commit 1689dce928
20 changed files with 546 additions and 61 deletions
+53 -4
View File
@@ -26,7 +26,14 @@ PURE_EMPTY_BACKGROUND_CATEGORY = "pure_empty_negative"
SPARSE_BACKGROUND_CATEGORY = "sparse_building_context"
LOW_VARIANCE_NEGATIVE_SKIP_REASON = "low_visual_variance_negative"
DEFAULT_VALIDATION_SAMPLE_SLUGS = frozenset(
{"turnhout", "retie", "westerlo", "arendonk_heide"}
{
"turnhout",
"retie",
"westerlo",
"arendonk_heide",
"vosselaar_center",
"grobbendonk_center",
}
)
DEFAULT_VALIDATION_SAMPLES = ",".join(sorted(DEFAULT_VALIDATION_SAMPLE_SLUGS))
rasterio: Any = None
@@ -69,12 +76,20 @@ def parse_args() -> argparse.Namespace:
)
parser.add_argument("--tile-size", type=int, default=int(os.environ.get("OPERATOR_YOLO_TILE_SIZE", "256")))
parser.add_argument("--stride", type=int, default=int(os.environ.get("OPERATOR_YOLO_TILE_STRIDE", "128")))
parser.add_argument(
"--samples",
default=os.environ.get("OPERATOR_YOLO_SAMPLES", ""),
help=(
"Optional comma/space separated manifest sample slugs to export. "
"An empty value keeps every manifest sample."
),
)
parser.add_argument(
"--val-samples",
default=os.environ.get("OPERATOR_YOLO_VAL_SAMPLES", DEFAULT_VALIDATION_SAMPLES),
help=(
"Comma/space separated sample slugs assigned to validation. "
"Defaults to the documented Turnhout, Retie, Westerlo and Arendonk-heide holdouts."
"Defaults to all documented validation samples."
),
)
parser.add_argument(
@@ -152,6 +167,31 @@ def split_slugs(raw: str) -> set[str]:
return {value.strip().lower() for value in raw.replace(",", " ").split() if value.strip()}
def select_manifest_samples(
samples: list[dict[str, Any]],
requested_slugs: set[str],
) -> tuple[list[dict[str, Any]], list[str]]:
manifest_slugs = {
str(sample.get("sample_slug") or "").strip().lower()
for sample in samples
if str(sample.get("sample_slug") or "").strip()
}
if not requested_slugs:
return samples, []
unknown = requested_slugs - manifest_slugs
if unknown:
raise SystemExit(
"YOLO sample selection references unknown samples: " + ", ".join(sorted(unknown))
)
selected = [
sample
for sample in samples
if str(sample.get("sample_slug") or "").strip().lower() in requested_slugs
]
excluded = sorted(manifest_slugs - requested_slugs)
return selected, excluded
def validate_validation_split(samples: list[dict[str, Any]], val_slugs: set[str]) -> set[str]:
sample_slugs = {
str(sample.get("sample_slug") or "").strip().lower()
@@ -533,9 +573,13 @@ def main() -> int:
ensure_yolo_directories(args.output_dir)
manifest = json.loads(args.manifest_path.read_text(encoding="utf-8-sig"))
samples = manifest.get("samples") or []
if not samples:
manifest_samples = manifest.get("samples") or []
if not manifest_samples:
raise SystemExit("Operator sample manifest contains no samples")
samples, excluded_sample_slugs = select_manifest_samples(
manifest_samples,
split_slugs(args.samples),
)
val_slugs = validate_validation_split(samples, split_slugs(args.val_samples))
exported_tiles: list[dict[str, Any]] = []
for sample in samples:
@@ -584,7 +628,12 @@ def main() -> int:
"min_label_visible_ratio": args.min_label_visible_ratio,
"drop_low_variance_negatives": args.drop_low_variance_negatives,
"blank_range_threshold": args.blank_range_threshold,
"source_manifest_sample_count": len(manifest_samples),
"source_sample_count": len(samples),
"selected_sample_slugs": sorted(
str(sample.get("sample_slug") or "").strip().lower() for sample in samples
),
"excluded_sample_slugs": excluded_sample_slugs,
"validation_sample_slugs": sorted(val_slugs),
**validation_coverage,
"tile_count": len(kept_tiles),