From 3e66c7869424563b2373498381124a5b65772f96 Mon Sep 17 00:00:00 2001 From: Jens Date: Thu, 30 Jul 2026 01:27:05 +0200 Subject: [PATCH] Harden regional dataset split validation --- scripts/build_regional_yolo_dataset.py | 43 +++++++++++++++++++---- tests/test_build_regional_yolo_dataset.py | 12 +++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/scripts/build_regional_yolo_dataset.py b/scripts/build_regional_yolo_dataset.py index cd1a5fb6..c1bd5298 100644 --- a/scripts/build_regional_yolo_dataset.py +++ b/scripts/build_regional_yolo_dataset.py @@ -46,11 +46,17 @@ def build( if sample is None: unknown.append(tile["sample_slug"]) continue - split = str(tile.get("split") or sample.get("split") or "") - if split in PROTECTED_SPLITS: + tile_split = str(tile.get("split") or "") + manifest_split = str(sample.get("split") or "") + if tile_split in PROTECTED_SPLITS or manifest_split in PROTECTED_SPLITS: protected.append(tile["sample_slug"]) continue - if sample.get("region") == region and split in {"train", "val"}: + if tile_split != manifest_split: + raise ValueError( + f"tile/manifest split mismatch for {tile['sample_slug']}: " + f"{tile_split!r} != {manifest_split!r}" + ) + if sample.get("region") == region and tile_split in {"train", "val"}: selected.append((tile, sample)) if unknown: raise ValueError(f"summary references unknown samples: {sorted(set(unknown))}") @@ -98,6 +104,31 @@ def build( return train, val, evidence +def select_paths( + summary: dict[str, Any], manifest: dict[str, Any], region: str +) -> tuple[list[str], list[str]]: + """Backward-compatible unweighted regional selection API.""" + samples = {item["sample_slug"]: item for item in manifest["samples"]} + compatible_summary = { + **summary, + "tiles": [ + tile + for tile in summary["tiles"] + if tile["sample_slug"] in samples + and samples[tile["sample_slug"]].get("split") in {"train", "val"} + ], + } + train, val, _evidence = build( + summary=compatible_summary, + manifest=manifest, + region=region, + priority_contexts=set(), + priority_repeat=1, + negative_repeat=1, + ) + return sorted(train), sorted(val) + + def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--summary", type=Path, required=True) @@ -139,9 +170,9 @@ def main() -> int: "validation_sha256": sha256(val_path), "dataset_yaml": str(yaml_path), }) - (args.output_dir / "regional-dataset-evidence.json").write_text( - json.dumps(evidence, indent=2), encoding="utf-8" - ) + encoded_evidence = json.dumps(evidence, indent=2) + (args.output_dir / "regional-dataset-evidence.json").write_text(encoded_evidence, encoding="utf-8") + (args.output_dir / "regional-dataset.json").write_text(encoded_evidence, encoding="utf-8") print(json.dumps(evidence, indent=2)) return 0 diff --git a/tests/test_build_regional_yolo_dataset.py b/tests/test_build_regional_yolo_dataset.py index 9443e3a3..82d03ca5 100644 --- a/tests/test_build_regional_yolo_dataset.py +++ b/tests/test_build_regional_yolo_dataset.py @@ -45,3 +45,15 @@ def test_regional_dataset_rejects_protected_tiles() -> None: summary=summary, manifest=manifest, region="flanders", priority_contexts=set(), priority_repeat=1, negative_repeat=1, ) + + +def test_regional_dataset_rejects_manifest_protection_hidden_by_tile_split() -> None: + manifest = {"samples": [ + {"sample_slug": "f-cal", "region": "flanders", "split": "calibration", "context": "ribbon"}, + ]} + summary = {"tiles": [{"sample_slug": "f-cal", "split": "train", "image_path": "/cal.png"}]} + with pytest.raises(ValueError, match="protected"): + module.build( + summary=summary, manifest=manifest, region="flanders", + priority_contexts=set(), priority_repeat=1, negative_repeat=1, + )