"""So sánh 3 cách MATCHING cho localization (class-agnostic) trên cùng dữ liệu: (0) HIỆN TẠI — containment + union, hai chiều tách rời (recall-side / precision-side). (A) COCO 1-1 — ghép 1-1 tham lam theo IoU giảm dần, mỗi GT/pred dùng 1 lần (chuẩn detection; không có confidence nên sort theo IoU). (B) COMPONENT — đồ thị chồng lấn (cạnh khi IoU>=.5 hoặc containment>=.5 hai chiều) → thành phần liên thông; cụm "khớp" nếu IoU(union_GT, union_pred)>=t → mọi GT/pred trong cụm tính TP (một phép ghép nhất quán, xử lý N-M). Báo P/R/F1@IoU cho tổng thể + vài lát cắt. Chỉ để đối chiếu, KHÔNG thay eval_layout. .venv/bin/python compare_matchers.py [--iou 0.5] [--granularity fine] """ from __future__ import annotations import argparse, json from collections import defaultdict from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).resolve().parent)) import eval_layout as E # benchmark/parser/ — chứa data/ (GT) và parser_results/ (batch_*.json + mapping.json) BASE = Path(__file__).resolve().parents[1] # ---- (A) COCO 1-1 greedy ---- def coco_tp(gts, preds, t): pairs = [] for i, g in enumerate(gts): for j, p in enumerate(preds): v = E.iou(g["box"], p["box"]) if v >= t: pairs.append((v, i, j)) pairs.sort(reverse=True) ug, up, tp = set(), set(), 0 for v, i, j in pairs: if i in ug or j in up: continue ug.add(i); up.add(j); tp += 1 return tp, tp # tp_gt == tp_pred (ghép 1-1) # ---- (B) connected components ---- def comp_tp(gts, preds, t): parent = {} def find(x): parent.setdefault(x, x) root = x while parent[root] != root: root = parent[root] while parent[x] != root: parent[x], x = root, parent[x] return root def union(a, b): ra, rb = find(a), find(b) if ra != rb: parent[ra] = rb for i in range(len(gts)): find(("g", i)) for j in range(len(preds)): find(("p", j)) for i, g in enumerate(gts): for j, p in enumerate(preds): gb, pb = g["box"], p["box"] if (E.iou(gb, pb) >= 0.5 or E.contain_ratio(gb, pb) >= 0.5 or E.contain_ratio(pb, gb) >= 0.5): union(("g", i), ("p", j)) comps = defaultdict(lambda: {"g": [], "p": []}) for i, g in enumerate(gts): comps[find(("g", i))]["g"].append(g["box"]) for j, p in enumerate(preds): comps[find(("p", j))]["p"].append(p["box"]) tp_g = tp_p = 0 for c in comps.values(): if not c["g"] or not c["p"]: continue if E.iou(E.union_box(c["g"]), E.union_box(c["p"])) >= t: tp_g += len(c["g"]); tp_p += len(c["p"]) return tp_g, tp_p # ---- (0) current two-sided ---- def cur_tp(gts, preds, t): rec = E.match_side(gts, preds, 0.5) prc = E.match_side(preds, gts, 0.5) tp_g = sum(1 for r in rec if r["union_iou"] >= t) tp_p = sum(1 for r in prc if r["union_iou"] >= t) return tp_g, tp_p def prf(tp_g, tp_p, n_gt, n_pred): r = tp_g / n_gt if n_gt else 0.0 p = tp_p / n_pred if n_pred else 0.0 f = 2 * p * r / (p + r) if (p + r) else 0.0 return p, r, f def main(): ap = argparse.ArgumentParser() ap.add_argument("--iou", type=float, default=0.5) ap.add_argument("--granularity", default="fine") ap.add_argument("--gt", type=Path, default=BASE / "data" / "OmniDocBench.json") ap.add_argument("--mapping", type=Path, default=BASE / "parser_results" / "mapping.json") ap.add_argument("--pred", type=Path, default=BASE / "parser_results", help="Thư mục chứa batch_*.json của parser.") args = ap.parse_args() t = args.iou gt = E.load_gt(args.gt, args.granularity) mp = json.load(open(args.mapping)) idx = E.build_pred_index(args.pred, mp) METHODS = {"current": cur_tp, "coco_1to1": coco_tp, "component": comp_tp} # acc[slice][method] = [tp_g, tp_p, n_gt, n_pred] acc = defaultdict(lambda: {m: [0, 0, 0, 0] for m in METHODS}) for name, pp in idx.items(): g = gt.get(name) if g is None: continue gts = E.prep_gt(g, True) preds = E.prep_pred(pp) keys = ["all"] lang = g["attr"].get("language"); lay = g["attr"].get("layout") if lang: keys.append(f"language={lang}") if lay: keys.append(f"layout={lay}") per = {} for m, fn in METHODS.items(): per[m] = fn(gts, preds, t) for k in keys: for m in METHODS: a = acc[k][m] a[0] += per[m][0]; a[1] += per[m][1] a[2] += len(gts); a[3] += len(preds) def show(k): print(f"\n### {k}") print(f" {'method':10s} P@{t} R@{t} F1@{t}") for m in METHODS: tp_g, tp_p, n_gt, n_pred = acc[k][m] p, r, f = prf(tp_g, tp_p, n_gt, n_pred) print(f" {m:10s} {p:.3f} {r:.3f} {f:.3f}") print(f"So sánh matching @IoU={t}, granularity={args.granularity}, pred={args.pred}") show("all") for k in ["layout=single_column", "layout=double_column", "layout=three_column", "language=english", "language=simplified_chinese"]: if k in acc: show(k) if __name__ == "__main__": main()