audit YOLO geometry and overlapping validation rows
This commit is contained in:
@@ -45,13 +45,50 @@ def validation_images(dataset_yaml: Path) -> list[Path]:
|
||||
images = sorted(
|
||||
path
|
||||
for path in directory.iterdir()
|
||||
if path.is_file() and path.suffix.casefold() in {".jpg", ".jpeg", ".png", ".tif", ".tiff"}
|
||||
if path.is_file()
|
||||
and path.suffix.casefold() in {".jpg", ".jpeg", ".png", ".tif", ".tiff"}
|
||||
)
|
||||
if not images:
|
||||
raise ValueError("validation image directory is empty")
|
||||
return images
|
||||
|
||||
|
||||
def dataset_overlap_evidence(dataset_yaml: Path) -> dict[str, Any]:
|
||||
summary_path = dataset_yaml.parent / "yolo_tile_dataset_summary.json"
|
||||
if not summary_path.is_file():
|
||||
return {
|
||||
"status": "unavailable",
|
||||
"summary_path": str(summary_path),
|
||||
"validation_rows_independent": None,
|
||||
}
|
||||
payload = json.loads(summary_path.read_text(encoding="utf-8"))
|
||||
tile_size = payload.get("tile_size")
|
||||
stride = payload.get("stride")
|
||||
if not isinstance(tile_size, int) or not isinstance(stride, int) or stride <= 0:
|
||||
return {
|
||||
"status": "invalid",
|
||||
"summary_path": str(summary_path),
|
||||
"summary_sha256": sha256_file(summary_path),
|
||||
"validation_rows_independent": None,
|
||||
}
|
||||
overlap_pixels = max(tile_size - stride, 0)
|
||||
return {
|
||||
"status": "overlapping" if overlap_pixels else "non_overlapping",
|
||||
"summary_path": str(summary_path),
|
||||
"summary_sha256": sha256_file(summary_path),
|
||||
"tile_size": tile_size,
|
||||
"stride": stride,
|
||||
"overlap_pixels": overlap_pixels,
|
||||
"validation_rows_independent": overlap_pixels == 0,
|
||||
"interpretation": (
|
||||
"Tile metrics can repeat the same source object and are valid for "
|
||||
"candidate ranking only, not independent object-level uncertainty."
|
||||
if overlap_pixels
|
||||
else "Tile rows do not overlap according to the dataset summary."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def metric_value(metrics: Any, attribute: str) -> float:
|
||||
value = getattr(metrics.box, attribute)
|
||||
return float(value)
|
||||
@@ -68,7 +105,9 @@ def background_detection_count(
|
||||
) -> tuple[int, int]:
|
||||
selected = [path for path in images if path.stem.casefold().startswith(prefixes)]
|
||||
if not selected:
|
||||
raise ValueError("no validation images match the declared pure-background prefixes")
|
||||
raise ValueError(
|
||||
"no validation images match the declared pure-background prefixes"
|
||||
)
|
||||
count = 0
|
||||
for start in range(0, len(selected), 16):
|
||||
results = model.predict(
|
||||
@@ -102,6 +141,7 @@ def main() -> int:
|
||||
parser.error("--background-confidence must be between zero and one")
|
||||
dataset_yaml = args.dataset_yaml.expanduser().resolve(strict=True)
|
||||
images = validation_images(dataset_yaml)
|
||||
overlap_evidence = dataset_overlap_evidence(dataset_yaml)
|
||||
prefixes = tuple(value.casefold() for value in args.background_prefix)
|
||||
|
||||
import torch
|
||||
@@ -161,7 +201,13 @@ def main() -> int:
|
||||
}
|
||||
)
|
||||
except Exception as exc: # preserve the complete attempted matrix
|
||||
row.update({"status": "error", "error_type": type(exc).__name__, "error": str(exc)[:1000]})
|
||||
row.update(
|
||||
{
|
||||
"status": "error",
|
||||
"error_type": type(exc).__name__,
|
||||
"error": str(exc)[:1000],
|
||||
}
|
||||
)
|
||||
finally:
|
||||
del model
|
||||
if torch.cuda.is_available():
|
||||
@@ -187,6 +233,7 @@ def main() -> int:
|
||||
"claim_boundary": "Non-protected validation ranking only; no test, challenge or promotion claim.",
|
||||
"dataset_yaml": str(dataset_yaml),
|
||||
"dataset_yaml_sha256": sha256_file(dataset_yaml),
|
||||
"dataset_overlap_evidence": overlap_evidence,
|
||||
"validation_image_count": len(images),
|
||||
"pure_background_prefixes": list(prefixes),
|
||||
"pure_background_confidence": args.background_confidence,
|
||||
@@ -198,7 +245,9 @@ def main() -> int:
|
||||
"attempts": rows,
|
||||
}
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
args.output.write_text(
|
||||
json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8"
|
||||
)
|
||||
return 0 if successful else 2
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user