veil-pgd / scripts /make_figures.py
Klaus Clawd
Release v0.2.1: recover attack strength, cross-arch judges, uncapped frontier eval
255b4a8
Raw
History Blame Contribute Delete
4.6 kB
"""Rebuild the README figures from a completed run (clean vs protected).
python scripts/make_figures.py --run runs/v021_base
Writes:
assets/compare_clean_vs_adv.png a few clean|protected pairs, high-res
assets/grid_before_after_16.png 16 images, clean over protected, 4x4
"""
from __future__ import annotations
import argparse
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
IMAGES = Path("examples/testset60")
ASSETS = Path("assets")
CELL = 320
PAD = 14
LABEL_H = 26
def load_manifest() -> list[tuple[str, str]]:
rows = []
for line in Path("examples/testset60.csv").read_text().splitlines():
line = line.strip()
if line and not line.startswith("#"):
p, t = line.split(",", 1)
rows.append((Path(p).name, t.strip()))
return rows
def font(size: int) -> ImageFont.FreeTypeFont:
for p in ("/System/Library/Fonts/Supplemental/Arial.ttf",
"/System/Library/Fonts/Helvetica.ttc",
"/Library/Fonts/Arial.ttf"):
if Path(p).exists():
return ImageFont.truetype(p, size)
return ImageFont.load_default()
def sq(img: Image.Image, n: int = CELL) -> Image.Image:
return img.convert("RGB").resize((n, n), Image.LANCZOS)
def labeled(img: Image.Image, text: str, f) -> Image.Image:
canvas = Image.new("RGB", (img.width, img.height + LABEL_H), "white")
canvas.paste(img, (0, LABEL_H))
d = ImageDraw.Draw(canvas)
d.text((4, 4), text, fill="black", font=f)
return canvas
def compare(run: Path, rows: list[tuple[str, str]], picks: list[str]) -> None:
f = font(18)
fh = font(20)
items = [(fn, t) for fn, t in rows if fn in picks]
w = 2 * CELL + 3 * PAD
h = len(items) * (CELL + LABEL_H + PAD) + PAD + 30
canvas = Image.new("RGB", (w, h), "white")
d = ImageDraw.Draw(canvas)
d.text((PAD, 6), "Clean", fill="black", font=fh)
d.text((2 * PAD + CELL, 6), "Protected (VEIL-PGD)", fill="black", font=fh)
y = 34
for fn, truth in items:
stem = Path(fn).stem
clean = sq(Image.open(IMAGES / fn))
adv = sq(Image.open(run / "adv" / f"{stem}.png"))
canvas.paste(labeled(clean, truth, f), (PAD, y))
canvas.paste(labeled(adv, truth, f), (2 * PAD + CELL, y))
y += CELL + LABEL_H + PAD
ASSETS.mkdir(exist_ok=True)
canvas.save(ASSETS / "compare_clean_vs_adv.png")
print("wrote assets/compare_clean_vs_adv.png", canvas.size)
def grid(run: Path, rows: list[tuple[str, str]], picks: list[str]) -> None:
f = font(16)
items = [(fn, t) for fn, t in rows if fn in picks][:16]
cols = 4
cellw = CELL
cellh = 2 * CELL + LABEL_H
n = len(items)
grows = (n + cols - 1) // cols
w = cols * cellw + (cols + 1) * PAD
h = grows * (cellh + PAD) + PAD
canvas = Image.new("RGB", (w, h), "white")
for i, (fn, truth) in enumerate(items):
r, c = divmod(i, cols)
x = PAD + c * (cellw + PAD)
y = PAD + r * (cellh + PAD)
stem = Path(fn).stem
clean = sq(Image.open(IMAGES / fn))
adv = sq(Image.open(run / "adv" / f"{stem}.png"))
block = Image.new("RGB", (cellw, cellh), "white")
d = ImageDraw.Draw(block)
d.text((4, 4), truth, fill="black", font=f)
block.paste(clean, (0, LABEL_H))
block.paste(adv, (0, LABEL_H + CELL))
canvas.paste(block, (x, y))
ASSETS.mkdir(exist_ok=True)
canvas.save(ASSETS / "grid_before_after_16.png")
print("wrote assets/grid_before_after_16.png", canvas.size)
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--run", default="runs/v021_base")
args = ap.parse_args()
run = Path(args.run)
rows = load_manifest()
have = [fn for fn, _ in rows if (run / "adv" / f"{Path(fn).stem}.png").exists()]
# one image per class for the small comparison
seen, comp_picks = set(), []
for fn, t in rows:
if fn in have and t not in seen:
seen.add(t)
comp_picks.append(fn)
compare(run, rows, comp_picks[:4])
# 16 images spread across classes for the grid
grid_picks = []
by_class: dict[str, list[str]] = {}
for fn, t in rows:
if fn in have:
by_class.setdefault(t, []).append(fn)
rr = 0
while len(grid_picks) < 16 and any(len(v) > rr for v in by_class.values()):
for t in by_class:
if len(by_class[t]) > rr and len(grid_picks) < 16:
grid_picks.append(by_class[t][rr])
rr += 1
grid(run, rows, grid_picks)
if __name__ == "__main__":
main()