training loop: run ledger, studio export importer with an image pool, train_run with gold gate and auto-promotion, SLURM template, CI
CI / ledger-and-tools (push) Successful in 17s

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
GotthardGandClaude Fable 5.1 committed 2026-09-28 16:06:06 +02:00
1 parent 098bc8f0d6
commit 4698dbeaf9
18 files changed
+1313 -12

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
"""Eval harness: compare predicted YOLO-seg labels against a gold set.
Reports detection precision/recall/F1 per class (bbox IoU >= threshold to match)
and mean mask IoU over matched instances. Operates on label files only (no source
images needed), so it can score any pre-labeler / model against the frozen gold set.
python -m src.pipeline.evaluate --pred OUT/labels --gold GOLD/labels [--names dataset.yaml]
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Dict, List
from src.eval.metrics import aggregate, evaluate_image, full_report
from src.eval.yolo_labels import load_class_names, read_label_file
def _stems(label_dir: Path) -> Dict[str, Path]:
return {p.stem: p for p in label_dir.glob("*.txt")}
def evaluate_dirs(pred_dir, gold_dir, iou_thr: float = 0.5,
names: List[str] | None = None) -> Dict:
pred_dir, gold_dir = Path(pred_dir), Path(gold_dir)
gold_files = _stems(gold_dir)
pred_files = _stems(pred_dir)
# Gold defines the evaluation set; a missing prediction counts as all-FN.
per_image = []
missing_pred = 0
for stem, gpath in sorted(gold_files.items()):
gold = read_label_file(gpath)
ppath = pred_files.get(stem)
pred = read_label_file(ppath) if ppath else []
if ppath is None:
missing_pred += 1
per_image.append(evaluate_image(gold, pred, iou_thr))
report = full_report(aggregate(per_image), names)
report["dataset"] = {
"gold_images": len(gold_files),
"pred_images": len(pred_files),
"missing_predictions": missing_pred,
"iou_threshold": iou_thr,
}
return report
def _print_report(report: Dict) -> None:
ds = report["dataset"]
print(f"\nGold images: {ds['gold_images']} | predictions: {ds['pred_images']} "
f"| missing: {ds['missing_predictions']} | match IoU >= {ds['iou_threshold']}")
header = f"{'class':<14}{'P':>7}{'R':>7}{'F1':>7}{'maskIoU':>9}{'TP':>6}{'FP':>6}{'FN':>6}"
print("\n" + header)
print("-" * len(header))
def row(name, r):
print(f"{name:<14}{r['precision']:>7.3f}{r['recall']:>7.3f}{r['f1']:>7.3f}"
f"{r['mean_mask_iou']:>9.3f}{r['tp']:>6}{r['fp']:>6}{r['fn']:>6}")
for name, r in report["per_class"].items():
row(name, r)
print("-" * len(header))
row("OVERALL", report["overall"])
def main() -> int:
ap = argparse.ArgumentParser(description="Evaluate predicted YOLO-seg labels vs. a gold set")
ap.add_argument("--pred", required=True, help="Directory of predicted .txt labels")
ap.add_argument("--gold", required=True, help="Directory of gold .txt labels")
ap.add_argument("--iou", type=float, default=0.5, help="bbox IoU threshold to match instances")
ap.add_argument("--names", default=None, help="Optional dataset.yaml for class names")
ap.add_argument("--report", default=None, help="Optional path to write the JSON report")
args = ap.parse_args()
names = load_class_names(args.names) if args.names else []
report = evaluate_dirs(args.pred, args.gold, iou_thr=args.iou, names=names)
_print_report(report)
if args.report:
Path(args.report).write_text(json.dumps(report, indent=2), encoding="utf-8")
print(f"\nWrote {args.report}")
return 0
if __name__ == "__main__":
raise SystemExit(main())