Download eval/evaluate_identity.py from Azamaka/carprint: direct link, hf CLI and curl.
- Browser
- Download file 6.82 kB
-
https://huggingface.co/spaces/Azamaka/carprint/resolve/main/eval/evaluate_identity.py
- Command line
-
hf download hf://spaces/Azamaka/carprint/eval/evaluate_identity.py
-
curl -L -o evaluate_identity.py https://huggingface.co/spaces/Azamaka/carprint/resolve/main/eval/evaluate_identity.py
6.82 kB
| """Identity verification evaluation (ISO/IEC 19795-style counting). | |
| Manifest (JSON list): {"path", "car_id", "session", "model", "slot"}. | |
| Genuine comparisons: same car_id, different session. Impostor comparisons: different car_id (pairs by the same | |
| photographer of the same model are excluded as unknown ground truth unless they were visually verified as distinct | |
| cars of one "distinct_group"), split into | |
| "hard" (same model) and "other". Error rates are reported per comparison AND bounded per vehicle, with | |
| exact Clopper-Pearson 95% upper bounds; when there are 0 errors the bound is ~3/N (rule of three). | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import itertools | |
| import json | |
| import sys | |
| import time | |
| from collections import defaultdict | |
| from pathlib import Path | |
| import numpy as np | |
| from scipy.stats import beta | |
| ROOT = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(ROOT)) | |
| from carprint.core.image_io import load_image # noqa: E402 | |
| from carprint.identity.cohort import bank_paths # noqa: E402 | |
| from carprint.identity.pipeline import SUBJECT_STATS, USE_SUBJECT, make_frame # noqa: E402 | |
| from carprint.identity.verify import verify # noqa: E402 | |
| def zone_record(e) -> dict: | |
| """Everything needed to re-decide a pair offline under other thresholds (eval/calibrate.py).""" | |
| return {"ref": e.ref_frame, "nfa": round(e.zone.log10_nfa, 2), "nfa_d": round(e.log10_nfa_distinct, 2), | |
| "inl": e.zone.n_inliers, "dinl": round(e.distinct_inliers, 2), "marks": e.ref_marks_in_zone, | |
| "votes": [[int(cx), int(cy), round(v, 3)] for (cx, cy), v in e.votes.items()]} | |
| def upper95(errors: int, n: int) -> float | None: | |
| return None if n == 0 else float(beta.ppf(0.95, errors + 1, n - errors)) if errors < n else 1.0 | |
| def rate(errors: int, n: int) -> dict: | |
| return {"errors": errors, "n": n, "rate": (errors / n) if n else None, "upper95": upper95(errors, n)} | |
| def main(manifest_path: str, cohort_dir: str, out_path: str, max_impostor_per_stratum: int = 200): | |
| manifest_bytes = Path(manifest_path).read_bytes() | |
| items = json.loads(manifest_bytes) | |
| base = Path(manifest_path).parent | |
| sessions: dict[tuple, list] = defaultdict(list) | |
| for it in items: | |
| sessions[(it["car_id"], it["session"])].append(it) | |
| frames = {} | |
| t = time.perf_counter() | |
| for key, its in sessions.items(): | |
| frames[key] = [make_frame(f"{key[0]}|{key[1]}|{i}", it.get("slot", "extra"), | |
| load_image((base / it["path"]).read_bytes()).rgb) for i, it in enumerate(its)] | |
| print(f"features for {len(items)} images: {time.perf_counter() - t:.1f}s", flush=True) | |
| cohort_cache = {} | |
| def cohort(model: str): | |
| if model not in cohort_cache: | |
| paths = bank_paths(Path(cohort_dir), model) | |
| cohort_cache[model] = [make_frame(f"c{model}{i}", "extra", load_image(p.read_bytes()).rgb) for i, p in enumerate(paths)] | |
| return cohort_cache[model] | |
| model_of = {k: v[0]["model"] for k, v in sessions.items()} | |
| author_of = {k: {w.lower() for w in v[0].get("author", "").replace(",", " ").split() if len(w) >= 4} | |
| for k, v in sessions.items()} | |
| group_of = {k: v[0].get("distinct_group") for k, v in sessions.items()} | |
| keys = sorted(sessions) | |
| genuine = [(a, b) for a, b in itertools.combinations(keys, 2) if a[0] == b[0]] | |
| # Same photographer + same model is often the same car shot twice: unknown ground truth, excluded. | |
| impostor = [(a, b) for a, b in itertools.combinations(keys, 2) if a[0] != b[0] | |
| and (not (author_of[a] & author_of[b] and model_of[a] == model_of[b]) | |
| or (group_of[a] is not None and group_of[a] == group_of[b]))] | |
| rng = np.random.default_rng(0) | |
| strata = {"hard": [p for p in impostor if model_of[p[0]] == model_of[p[1]]], | |
| "other": [p for p in impostor if model_of[p[0]] != model_of[p[1]]]} | |
| for s in strata: | |
| if len(strata[s]) > max_impostor_per_stratum: | |
| idx = rng.choice(len(strata[s]), max_impostor_per_stratum, replace=False) | |
| strata[s] = [strata[s][i] for i in idx] | |
| records, lat = [], [] | |
| for label, pairs in [("genuine", genuine), ("hard", strata["hard"]), ("other", strata["other"])]: | |
| for a, b in pairs: | |
| t = time.perf_counter() | |
| coh = cohort(model_of[a]) | |
| v = verify(frames[a], frames[b], {f.frame_id: coh for f in frames[a]}) | |
| lat.append((time.perf_counter() - t) * 1000) | |
| records.append({"label": label, "ref": list(a), "cur": list(b), "decision": v.decision, "score": v.score_raw, | |
| "cohort": v.cohort_size, "zones": [zone_record(e) for e in v.zones]}) | |
| def summarize(label): | |
| rs = [r for r in records if r["label"] == label] | |
| dec = defaultdict(int) | |
| for r in rs: | |
| dec[r["decision"]] += 1 | |
| return rs, dict(dec) | |
| out = {"test_manifest_sha256": hashlib.sha256(manifest_bytes).hexdigest(), "generated_at": time.strftime("%Y-%m-%dT%H:%M:%S"), | |
| "n_images": len(items), "n_cars": len({k[0] for k in keys}), "n_sessions": len(keys), | |
| "vehicle_crop": USE_SUBJECT, "subject_stats": dict(SUBJECT_STATS), "identity": {}} | |
| if USE_SUBJECT: | |
| from carprint.identity.subject import detector_name | |
| out["detector"] = detector_name() | |
| g, gd = summarize("genuine") | |
| out["identity"]["genuine"] = {"decisions": gd, "fnmr": rate(gd.get("DIFFERENT", 0), len(g)), | |
| "review_rate": rate(gd.get("REVIEW", 0) + gd.get("RECAPTURE_REQUIRED", 0), len(g)), | |
| "tar": rate(gd.get("SAME", 0), len(g))} | |
| for s in ("hard", "other"): | |
| rs, d = summarize(s) | |
| cars = {c for r in rs for c in (r["ref"][0], r["cur"][0])} | |
| fm_cars = {c for r in rs if r["decision"] == "SAME" for c in (r["ref"][0], r["cur"][0])} | |
| out["identity"][f"impostor_{s}"] = {"decisions": d, "fmr": rate(d.get("SAME", 0), len(rs)), | |
| "fmr_vehicle_bound": rate(len(fm_cars), len(cars)), | |
| "review_rate": rate(d.get("REVIEW", 0) + d.get("RECAPTURE_REQUIRED", 0), len(rs))} | |
| out["latency_ms"] = {"p50": float(np.percentile(lat, 50)) if lat else None, | |
| "p95": float(np.percentile(lat, 95)) if lat else None, "n": len(lat)} | |
| out["records"] = records | |
| Path(out_path).write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8") | |
| print(json.dumps({k: v for k, v in out.items() if k != "records"}, ensure_ascii=False, indent=1)) | |
| if __name__ == "__main__": | |
| main(*sys.argv[1:4]) | |