# -*- coding: utf-8 -*- """judge_icons.py —— 图标点选题的**目视判读图**生成器。 把 `send_campaign.py` 的一次实验目录变成一张对照图:每行一次跑,列出 [提示图第 j 个目标] vs [第 j 个点击点 ±32px 的背景 patch] (j = 1,2,3) 这样"点得对不对 / 顺序对不对"一眼可判 —— 顺序即红框上的 #1/#2/#3。 用法: .venv/bin/python scripts/verify/judge_icons.py /tmp/icon_campaign/ [out.png] """ from __future__ import annotations import json import os import re import sys from pathlib import Path from PIL import Image, ImageDraw ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from tcaptcha.ocr.slicer import slice_prompt_image # noqa: E402 CELL = 120 PAD = 8 ROWH = CELL + 26 def build(campaign_dir: str, out_path: str) -> str: d = Path(campaign_dir) runs = sorted([p.name for p in d.iterdir() if re.fullmatch(r"run\d+", p.name)], key=lambda s: int(s[3:])) if not runs: raise SystemExit("目录下没有 run* 子目录: %s" % d) width = PAD + 60 + 3 * (CELL + PAD + CELL) + PAD + 150 height = PAD + len(runs) * (ROWH + PAD) canvas = Image.new("RGB", (width, height), (245, 245, 245)) dr = ImageDraw.Draw(canvas) y = PAD for rn in runs: rd = d / rn info = json.loads((rd / "points.json").read_text(encoding="utf-8")) ec = info.get("errorCode") pts = info.get("points") or [] offs = info.get("centroid_offsets_px") or [] dr.text((PAD, y + 4), "%s\nec=%s" % (rn, ec), fill=(20, 20, 20)) try: prompt = Image.open(rd / "prompt.jpg").convert("RGB") crops = slice_prompt_image(prompt) except Exception: # noqa: BLE001 crops = [] try: bg = Image.open(rd / "bg.jpg").convert("RGB") except Exception: # noqa: BLE001 bg = None x = PAD + 60 for j in range(3): if j < len(crops): pc = crops[j][0].convert("RGB").resize((CELL, CELL), Image.LANCZOS) else: pc = Image.new("RGB", (CELL, CELL), (255, 210, 210)) canvas.paste(pc, (x, y + 20)) dr.rectangle([x, y + 20, x + CELL, y + 20 + CELL], outline=(0, 0, 0)) x2 = x + CELL + PAD if bg is not None and j < len(pts): px, py = pts[j] box = (max(0, px - 32), max(0, py - 32), min(bg.width, px + 32) + 1, min(bg.height, py + 32) + 1) patch = bg.crop(box).resize((CELL, CELL), Image.LANCZOS) tag = "#%d(%d,%d)" % (j + 1, px, py) if j < len(offs) and offs[j] is not None: tag += " 偏%.0fpx" % offs[j] dr.text((x2 + 2, y + 4), tag, fill=(0, 0, 0)) else: patch = Image.new("RGB", (CELL, CELL), (232, 232, 232)) canvas.paste(patch, (x2, y + 20)) dr.rectangle([x2, y + 20, x2 + CELL, y + 20 + CELL], outline=(200, 0, 0), width=3) x = x2 + CELL + PAD y += ROWH + PAD canvas.save(out_path) return out_path if __name__ == "__main__": if len(sys.argv) < 2: raise SystemExit(__doc__) src = sys.argv[1] dst = sys.argv[2] if len(sys.argv) > 2 else os.path.join(src, "judge.png") print("saved", build(src, dst))