GeoIntel release gates / Compile, test, contracts and builds (push) Successful in 1m49s
GeoIntel release gates / Python and npm vulnerability policy (push) Successful in 21s
GeoIntel release gates / Production AI image, SBOM and container scan (push) Successful in 5m39s
GeoIntel release gates / Deploy exact gated revision to Unraid (push) Failing after 58m43s
229 lines
7.8 KiB
Python
229 lines
7.8 KiB
Python
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from scripts.evaluate_yolo_checkpoint_matrix import (
|
|
dataset_overlap_evidence,
|
|
main,
|
|
model_lineage_independence_evidence,
|
|
validate_pure_background_prefixes,
|
|
write_blocked_manifest,
|
|
)
|
|
|
|
|
|
def test_dataset_overlap_evidence_marks_repeated_validation_rows(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
dataset_yaml = tmp_path / "dataset.yaml"
|
|
dataset_yaml.write_text("path: .\nval: images/val\n", encoding="utf-8")
|
|
(tmp_path / "yolo_tile_dataset_summary.json").write_text(
|
|
json.dumps({"tile_size": 512, "stride": 256}), encoding="utf-8"
|
|
)
|
|
|
|
evidence = dataset_overlap_evidence(dataset_yaml)
|
|
|
|
assert evidence["status"] == "overlapping"
|
|
assert evidence["overlap_pixels"] == 256
|
|
assert evidence["validation_tiles_non_overlapping"] is False
|
|
assert evidence["statistical_independence_established"] is False
|
|
|
|
|
|
def test_dataset_overlap_evidence_is_explicit_when_summary_missing(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
evidence = dataset_overlap_evidence(tmp_path / "dataset.yaml")
|
|
|
|
assert evidence["status"] == "unavailable"
|
|
assert evidence["validation_tiles_non_overlapping"] is None
|
|
assert evidence["statistical_independence_established"] is False
|
|
|
|
|
|
def test_nonoverlap_does_not_overclaim_statistical_independence(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
dataset_yaml = tmp_path / "dataset.yaml"
|
|
dataset_yaml.write_text("path: .\nval: images/val\n", encoding="utf-8")
|
|
(tmp_path / "yolo_tile_dataset_summary.json").write_text(
|
|
json.dumps({"tile_size": 512, "stride": 512}), encoding="utf-8"
|
|
)
|
|
|
|
evidence = dataset_overlap_evidence(dataset_yaml)
|
|
|
|
assert evidence["validation_tiles_non_overlapping"] is True
|
|
assert evidence["statistical_independence_established"] is False
|
|
assert "not established" in evidence["interpretation"]
|
|
|
|
|
|
def _write_summary(path: Path, rows: list[tuple[str, str]]) -> None:
|
|
path.write_text(
|
|
json.dumps(
|
|
{
|
|
"tile_size": 512,
|
|
"stride": 512,
|
|
"tiles": [
|
|
{"sample_slug": sample_slug, "split": split}
|
|
for sample_slug, split in rows
|
|
],
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def test_model_lineage_independence_detects_exposure_in_every_split(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
evaluation = tmp_path / "evaluation"
|
|
evaluation.mkdir()
|
|
dataset_yaml = evaluation / "dataset.yaml"
|
|
dataset_yaml.write_text("path: .\nval: images/val\n", encoding="utf-8")
|
|
_write_summary(
|
|
evaluation / "yolo_tile_dataset_summary.json",
|
|
[("turnhout", "val"), ("postel_bos", "val")],
|
|
)
|
|
active = tmp_path / "active.json"
|
|
challenger = tmp_path / "challenger.json"
|
|
_write_summary(active, [("postel_bos", "train"), ("turnhout", "val")])
|
|
_write_summary(
|
|
challenger, [("postel_bos", "train"), ("dessel", "train")]
|
|
)
|
|
|
|
evidence = model_lineage_independence_evidence(
|
|
dataset_yaml, [active, challenger]
|
|
)
|
|
|
|
assert evidence["status"] == "overlap"
|
|
assert evidence["overlapping_evaluation_samples"] == ["postel_bos", "turnhout"]
|
|
assert evidence["independent_for_all_supplied_lineage_corpora"] is False
|
|
assert [
|
|
row["overlapping_evaluation_samples"] for row in evidence["lineage_corpora"]
|
|
] == [["postel_bos", "turnhout"], ["postel_bos"]]
|
|
assert evidence["lineage_corpora"][0]["exposure_roles"]["turnhout"] == ["val"]
|
|
|
|
|
|
def test_model_lineage_independence_accepts_disjoint_samples(tmp_path: Path) -> None:
|
|
evaluation = tmp_path / "evaluation"
|
|
evaluation.mkdir()
|
|
dataset_yaml = evaluation / "dataset.yaml"
|
|
dataset_yaml.write_text("path: .\nval: images/val\n", encoding="utf-8")
|
|
_write_summary(
|
|
evaluation / "yolo_tile_dataset_summary.json", [("turnhout", "val")]
|
|
)
|
|
training = tmp_path / "training.json"
|
|
_write_summary(training, [("mol", "train")])
|
|
|
|
evidence = model_lineage_independence_evidence(dataset_yaml, [training])
|
|
|
|
assert evidence["status"] == "independent"
|
|
assert evidence["overlapping_evaluation_samples"] == []
|
|
assert evidence["independent_for_all_supplied_lineage_corpora"] is True
|
|
|
|
|
|
def test_model_lineage_independence_is_unavailable_without_ancestral_corpus(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
evaluation = tmp_path / "evaluation"
|
|
evaluation.mkdir()
|
|
dataset_yaml = evaluation / "dataset.yaml"
|
|
dataset_yaml.write_text("path: .\nval: images/val\n", encoding="utf-8")
|
|
_write_summary(
|
|
evaluation / "yolo_tile_dataset_summary.json", [("turnhout", "val")]
|
|
)
|
|
|
|
evidence = model_lineage_independence_evidence(dataset_yaml, [])
|
|
|
|
assert evidence["status"] == "unavailable"
|
|
assert evidence["overlapping_evaluation_samples"] == []
|
|
assert evidence["independent_for_all_supplied_lineage_corpora"] is False
|
|
|
|
|
|
def test_blocked_manifest_records_that_models_and_gpu_were_not_used(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
dataset_yaml = tmp_path / "dataset.yaml"
|
|
dataset_yaml.write_text("path: .\nval: images/val\n", encoding="utf-8")
|
|
output = tmp_path / "evidence" / "matrix.json"
|
|
|
|
write_blocked_manifest(
|
|
output,
|
|
dataset_yaml,
|
|
{
|
|
"status": "overlap",
|
|
"independent_for_all_supplied_lineage_corpora": False,
|
|
},
|
|
)
|
|
|
|
payload = json.loads(output.read_text(encoding="utf-8"))
|
|
assert payload["status"] == "blocked_model_lineage_sample_exposure"
|
|
assert payload["model_loading_attempted"] is False
|
|
assert payload["gpu_inference_attempted"] is False
|
|
|
|
|
|
def test_cli_blocks_lineage_validation_exposure_before_model_resolution(
|
|
tmp_path: Path, monkeypatch
|
|
) -> None:
|
|
evaluation = tmp_path / "evaluation"
|
|
images = evaluation / "images" / "val"
|
|
images.mkdir(parents=True)
|
|
(images / "turnhout_r0_c0.png").write_bytes(b"not opened before gate")
|
|
dataset_yaml = evaluation / "dataset.yaml"
|
|
dataset_yaml.write_text("path: .\nval: images/val\n", encoding="utf-8")
|
|
_write_summary(
|
|
evaluation / "yolo_tile_dataset_summary.json", [("turnhout", "val")]
|
|
)
|
|
ancestor = tmp_path / "ancestor.json"
|
|
_write_summary(ancestor, [("turnhout", "val"), ("mol", "train")])
|
|
output = tmp_path / "blocked.json"
|
|
monkeypatch.setattr(
|
|
sys,
|
|
"argv",
|
|
[
|
|
"evaluate_yolo_checkpoint_matrix.py",
|
|
"--dataset-yaml",
|
|
str(dataset_yaml),
|
|
"--model",
|
|
str(tmp_path / "model-is-never-resolved.pt"),
|
|
"--output",
|
|
str(output),
|
|
"--background-prefix",
|
|
"background",
|
|
"--lineage-summary",
|
|
str(ancestor),
|
|
"--require-lineage-sample-independence",
|
|
],
|
|
)
|
|
|
|
assert main() == 3
|
|
payload = json.loads(output.read_text(encoding="utf-8"))
|
|
assert payload["status"] == "blocked_model_lineage_sample_exposure"
|
|
assert payload["model_loading_attempted"] is False
|
|
|
|
|
|
def test_pure_background_prefix_rejects_nonempty_labels(tmp_path: Path) -> None:
|
|
image_dir = tmp_path / "images" / "val"
|
|
label_dir = tmp_path / "labels" / "val"
|
|
image_dir.mkdir(parents=True)
|
|
label_dir.mkdir(parents=True)
|
|
image = image_dir / "sparse_bg_0001.png"
|
|
image.write_bytes(b"image")
|
|
(label_dir / "sparse_bg_0001.txt").write_text(
|
|
"0 0.5 0.5 0.1 0.1\n", encoding="utf-8"
|
|
)
|
|
|
|
import pytest
|
|
|
|
with pytest.raises(ValueError, match="non-empty labels"):
|
|
validate_pure_background_prefixes([image], ("sparse_bg",))
|
|
|
|
|
|
def test_pure_background_prefix_accepts_empty_labels(tmp_path: Path) -> None:
|
|
image_dir = tmp_path / "images" / "val"
|
|
label_dir = tmp_path / "labels" / "val"
|
|
image_dir.mkdir(parents=True)
|
|
label_dir.mkdir(parents=True)
|
|
image = image_dir / "pure_bg_0001.png"
|
|
image.write_bytes(b"image")
|
|
(label_dir / "pure_bg_0001.txt").write_text("", encoding="utf-8")
|
|
|
|
assert validate_pure_background_prefixes([image], ("pure_bg",)) == [image]
|