"""Diagnose sparse-page errors: crop both MISSED (FN) and FALSE-POSITIVE (FP) boxes. Usage: python _analyze_sparse.py [355|dean]""" import csv, glob, re, sys from pathlib import Path import fitz from PIL import Image Image.MAX_IMAGE_PIXELS = None HERE = Path(__file__).resolve().parent ROOT = HERE.parent ver = sys.argv[1] if len(sys.argv) > 1 else "v12_fastocr" grp = sys.argv[2] if len(sys.argv) > 2 else "355" PDF = (ROOT / "raw_pdfs" / ("355 E 86th St Mechanical 100% CD Set_flattened.pdf" if grp == "355" else "2023.10.12_953 Dean Street_M-marked.pdf")) GT = {2: HERE/"ground_truth"/"355_p02_cellar.csv", 13: HERE/"ground_truth"/"355_p13_15th.csv", 4: HERE/"ground_truth"/"dean_p04.csv", 6: HERE/"ground_truth"/"dean_p06.csv"} RUN = HERE / "runs" / ver / grp OUT = HERE / "sparse_crops"; OUT.mkdir(exist_ok=True) DPI = 300 def load(p, is_gt): out = [] for r in csv.DictReader(open(p, encoding="utf-8")): if not r.get("x"): continue act = (r.get("action") or "").strip() if is_gt and act.lower() == "delete": continue tag = r.get("tag", "") if is_gt and re.match(r"(?i)^ac[-\s]", act): tag = act try: x,y,w,h = int(float(r["x"])),int(float(r["y"])),int(float(r["w"])),int(float(r["h"])) except (KeyError, ValueError): continue out.append({"tag": tag.upper().replace(" ",""), "x":x,"y":y,"w":w,"h":h, "status": (r.get("status") or "")}) return out def iou(a,b): ix0,iy0=max(a["x"],b["x"]),max(a["y"],b["y"]) ix1=min(a["x"]+a["w"],b["x"]+b["w"]); iy1=min(a["y"]+a["h"],b["y"]+b["h"]) iw,ih=max(0,ix1-ix0),max(0,iy1-iy0); inter=iw*ih return 0.0 if inter<=0 else inter/(a["w"]*a["h"]+b["w"]*b["h"]-inter) doc = fitz.open(PDF) pages = [2,13] if grp=="355" else [4,6] for page in pages: gts = load(GT[page], True) pf = [f for f in glob.glob(str(RUN/"floor_*_review.csv")) if any(str(r.get("page_num"))==str(page) for r in csv.DictReader(open(f,encoding="utf-8")))] preds = load(pf[0], False) if pf else [] fn = [g for g in gts if max((iou(g,p) for p in preds), default=0) < 0.3] fp = [p for p in preds if max((iou(p,g) for g in gts), default=0) < 0.3] print(f"\n=== {ver} {grp} p{page}: GT {len(gts)}, pred {len(preds)}, " f"MISSED {len(fn)}, FALSE-POS {len(fp)} ===") for m in fn: print(f" MISS {m['tag']:8s} at ({m['x']},{m['y']}) {m['w']}x{m['h']}") for m in fp: print(f" FP {m['tag']:8s} at ({m['x']},{m['y']}) {m['w']}x{m['h']} status={m['status']}") pg = doc.load_page(page-1); mat = fitz.Matrix(DPI/72,DPI/72) pix = pg.get_pixmap(matrix=mat, alpha=False) img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) for kind, items in (("miss", fn), ("fp", fp)): for i,m in enumerate(items): cx,cy=m["x"]+m["w"]//2,m["y"]+m["h"]//2; pad=420 crop=img.crop((max(0,cx-pad),max(0,cy-pad),min(img.width,cx+pad),min(img.height,cy+pad))) crop.save(OUT/f"{grp}_p{page}_{kind}{i+1}_{m['tag']}.png") print(f"\nCrops -> {OUT}")