#!/usr/bin/env python """Recompute the cross-source matrix on the commodity-matched subset. The overlap set pairs each class across the two sources by name, but one pairing is not a commodity pairing: the wild source's `coriander` is fresh coriander leaf (cilantro) while the studio source's is dried coriander seed. Those are different plant organs, so a model trained on one and tested on the other is not being tested on an acquisition shift. This script re-derives the headline numbers with that class excluded, from the per-class counts already stored in the evidence artifacts, and reports both figures side by side. Arithmetic only, no model is run. Ginger is deliberately KEPT: fresh and dried ginger are the same organ (rhizome) in two preparations, and the transfer accuracy (95 percent) confirms the pairing holds. """ import json from pathlib import Path from statistics import mean, stdev BASE = Path("/mnt/d/SpiceNet") if Path("/mnt/d/SpiceNet").exists() else Path("D:/SpiceNet") SEEDS = [42, 1337, 2024] EXCLUDE = {"coriander"} def subset_acc(per_class, drop): """Weighted accuracy over the classes we keep.""" n = sum(c["n"] for k, c in per_class.items() if k not in drop) hit = sum(c["n"] * c["acc"] for k, c in per_class.items() if k not in drop) return 100.0 * hit / n, n def msd(v): return mean(v), (stdev(v) if len(v) > 1 else 0.0) def main(): rows = {k: {"all": [], "matched": []} for k in ("in_ss", "in_in", "ss_in", "ss_ss")} per_seed = [] for s in SEEDS: j = json.loads((BASE / "outputs" / f"shortcut_evidence_s{s}.json").read_text()) rec = {"seed": s} for tag, block in (("in", j["in"]), ("ss", j["ss"])): for split in ("ss", "in"): pc = block[split]["per_class"] a_all, n_all = subset_acc(pc, set()) a_mat, n_mat = subset_acc(pc, EXCLUDE) rows[f"{tag}_{split}"]["all"].append(a_all) rows[f"{tag}_{split}"]["matched"].append(a_mat) rec[f"{tag}->{split}"] = (a_all, a_mat, n_all, n_mat) per_seed.append(rec) def line(key): a = msd(rows[key]["all"]) m = msd(rows[key]["matched"]) return a, m (in_ss_a, in_ss_m) = line("in_ss") (in_in_a, in_in_m) = line("in_in") (ss_in_a, ss_in_m) = line("ss_in") (ss_ss_a, ss_ss_m) = line("ss_ss") coll_a = in_in_a[0] - in_ss_a[0] coll_m = in_in_m[0] - in_ss_m[0] free_a = ss_ss_a[0] - ss_in_a[0] free_m = ss_ss_m[0] - ss_in_m[0] print(f"excluded classes: {sorted(EXCLUDE)}") print(f"test images per seed: all={per_seed[0]['in->ss'][2]} matched={per_seed[0]['in->ss'][3]}") print() print("STUDIO-TRAINED (in)") print(f" within studio : all {in_in_a[0]:.2f}+-{in_in_a[1]:.2f} matched {in_in_m[0]:.2f}+-{in_in_m[1]:.2f}") print(f" -> wild : all {in_ss_a[0]:.2f}+-{in_ss_a[1]:.2f} matched {in_ss_m[0]:.2f}+-{in_ss_m[1]:.2f}") print(f" COLLAPSE : all {coll_a:.2f} matched {coll_m:.2f}") print() print("WILD-TRAINED (ss)") print(f" within wild : all {ss_ss_a[0]:.2f}+-{ss_ss_a[1]:.2f} matched {ss_ss_m[0]:.2f}+-{ss_ss_m[1]:.2f}") print(f" -> studio : all {ss_in_a[0]:.2f}+-{ss_in_a[1]:.2f} matched {ss_in_m[0]:.2f}+-{ss_in_m[1]:.2f}") print(f" COST : all {free_a:.2f} matched {free_m:.2f}") print() print("per seed, studio->wild:") for r in per_seed: a, m, _, _ = r["in->ss"] print(f" seed {r['seed']:>4}: all {a:.2f} matched {m:.2f}") print() print(f"asymmetry ratio: all {coll_a/max(free_a,1e-9):.1f}x matched {coll_m/max(free_m,1e-9):.1f}x") if __name__ == "__main__": main()