Record retained validation coverage
GeoIntel CI / docs-smoke (push) Has been cancelled
GeoIntel CI / contract-smoke (push) Has been cancelled

This commit is contained in:
Codex
2026-07-13 00:00:42 +02:00
parent 5b008668d7
commit d02b4e5219
5 changed files with 48 additions and 0 deletions
+5
View File
@@ -13,6 +13,11 @@
- Pinned the opt-in CPU runtime to PyTorch 2.13.0 and torchvision 0.28.0 from the official CPU wheel index, avoiding unused CUDA runtime packages while preserving the currently validated framework versions. - Pinned the opt-in CPU runtime to PyTorch 2.13.0 and torchvision 0.28.0 from the official CPU wheel index, avoiding unused CUDA runtime packages while preserving the currently validated framework versions.
- Kept AI dependencies opt-in and model weights local-only; no API, migration, model activation or inference contract changed. - Kept AI dependencies opt-in and model weights local-only; no API, migration, model activation or inference contract changed.
## Sprint 171.1 Validation coverage provenance (2026-07-12)
- Added explicit retained and empty validation sample lists to generated YOLO tile summaries.
- Quality-filtered holdouts such as Arendonk-heide remain visible in provenance without being counted as actual retained validation coverage.
## Sprint 171 Positive AOI expansion and split safety (2026-07-12) ## Sprint 171 Positive AOI expansion and split safety (2026-07-12)
- Added four explicit, real-reference Kempen training AOIs: Olen, Lille, Oud-Turnhout and Kasterlee center. - Added four explicit, real-reference Kempen training AOIs: Olen, Lille, Oud-Turnhout and Kasterlee center.
@@ -100,6 +100,23 @@ def test_default_validation_split_is_explicit_and_rejects_holdout_leakage() -> N
module.validate_validation_split(samples, {"turnhout", "missing"}) module.validate_validation_split(samples, {"turnhout", "missing"})
def test_validation_coverage_reports_holdouts_without_retained_tiles() -> None:
module = load_tile_exporter()
coverage = module.validation_sample_coverage(
[
{"sample_slug": "turnhout", "split": "val", "kept": True},
{"sample_slug": "retie", "split": "val", "kept": True},
{"sample_slug": "geel", "split": "train", "kept": True},
],
{"turnhout", "retie", "arendonk_heide"},
)
assert coverage == {
"retained_validation_sample_slugs": ["retie", "turnhout"],
"empty_validation_sample_slugs": ["arendonk_heide"],
}
def test_iter_tile_windows_covers_edges_without_duplicates() -> None: def test_iter_tile_windows_covers_edges_without_duplicates() -> None:
module = load_tile_exporter() module = load_tile_exporter()
+6
View File
@@ -7033,3 +7033,9 @@ Open:
## Remaining validation ## Remaining validation
- Build the AI-enabled all-in-one image on Tower after the current inactive model training run finishes, verify Torch reports a CPU build and rerun live migration/browser smokes before replacing the runtime. - Build the AI-enabled all-in-one image on Tower after the current inactive model training run finishes, verify Torch reports a CPU build and rerun live migration/browser smokes before replacing the runtime.
# Sprint 171.1 - Validation coverage provenance
- The expanded live export exposed that Arendonk-heide remained configured as a holdout while all of its low-variance tiles were correctly filtered out.
- Added `retained_validation_sample_slugs` and `empty_validation_sample_slugs` to tile dataset summaries so configured and actual validation coverage cannot be confused.
- Added a focused regression test and kept filtering behavior unchanged; no blank tile was reintroduced.
+3
View File
@@ -366,6 +366,9 @@ The tile exporter clips GRB building bounding boxes into each tile, writes
YOLO labels beside each tile image, keeps a deterministic ratio of empty YOLO labels beside each tile image, keeps a deterministic ratio of empty
negative tiles, and records `yolo_tile_dataset_summary.json` with negative tiles, and records `yolo_tile_dataset_summary.json` with
`positive_tile_count`, `negative_tile_count` and skipped negative tile counts. `positive_tile_count`, `negative_tile_count` and skipped negative tile counts.
It separately records configured, retained and empty validation sample slugs;
this keeps a holdout that lost every tile to quality filtering visible without
pretending it contributed evaluation data.
`--min-label-visible-ratio` drops labels where only a small clipped fragment of `--min-label-visible-ratio` drops labels where only a small clipped fragment of
the original building bbox is visible inside the tile; this reduces noisy the original building bbox is visible inside the tile; this reduces noisy
tile-edge labels in overlapping-tile datasets. Use `0` for legacy behavior. tile-edge labels in overlapping-tile datasets. Use `0` for legacy behavior.
@@ -181,6 +181,21 @@ def validate_validation_split(samples: list[dict[str, Any]], val_slugs: set[str]
return val_slugs return val_slugs
def validation_sample_coverage(
tiles: list[dict[str, Any]],
val_slugs: set[str],
) -> dict[str, list[str]]:
retained = {
str(tile.get("sample_slug") or "").strip().lower()
for tile in tiles
if tile.get("kept", True) and str(tile.get("split") or "") == "val"
}
return {
"retained_validation_sample_slugs": sorted(retained),
"empty_validation_sample_slugs": sorted(val_slugs - retained),
}
def edge_starts(length: int, tile_size: int, stride: int) -> list[int]: def edge_starts(length: int, tile_size: int, stride: int) -> list[int]:
if tile_size <= 0: if tile_size <= 0:
raise ValueError("tile_size must be positive") raise ValueError("tile_size must be positive")
@@ -555,6 +570,7 @@ def main() -> int:
for tile in skipped_negative_tiles for tile in skipped_negative_tiles
if tile.get("skip_reason") == LOW_VARIANCE_NEGATIVE_SKIP_REASON if tile.get("skip_reason") == LOW_VARIANCE_NEGATIVE_SKIP_REASON
] ]
validation_coverage = validation_sample_coverage(kept_tiles, val_slugs)
summary = { summary = {
"status": "ok", "status": "ok",
"dataset_yaml": str(dataset_yaml), "dataset_yaml": str(dataset_yaml),
@@ -570,6 +586,7 @@ def main() -> int:
"blank_range_threshold": args.blank_range_threshold, "blank_range_threshold": args.blank_range_threshold,
"source_sample_count": len(samples), "source_sample_count": len(samples),
"validation_sample_slugs": sorted(val_slugs), "validation_sample_slugs": sorted(val_slugs),
**validation_coverage,
"tile_count": len(kept_tiles), "tile_count": len(kept_tiles),
"positive_tile_count": len(positive_tiles), "positive_tile_count": len(positive_tiles),
"negative_tile_count": len(negative_tiles), "negative_tile_count": len(negative_tiles),