Spaces:
Running
Running
github-actions[bot]
Deploy from GitHub 1af737237f5d10dfa9cf12849d98f47c3b30ce37 (filtered: code + Dockerfile + README + NOTICES only)
a2f55b2 Download scripts/dataset/simulate_shared_copy.py from Veridicate/scanner: direct link, hf CLI and curl.
- Browser
- Download file 4.42 kB
-
https://huggingface.co/spaces/Veridicate/scanner/resolve/main/scripts/dataset/simulate_shared_copy.py
- Command line
-
hf download hf://spaces/Veridicate/scanner/scripts/dataset/simulate_shared_copy.py
-
curl -L -o simulate_shared_copy.py https://huggingface.co/spaces/Veridicate/scanner/resolve/main/scripts/dataset/simulate_shared_copy.py
4.42 kB
| """ | |
| Produce "shared copy" variants of eval images: what survives being passed around. | |
| Why | |
| --- | |
| Every P0 measurement so far is on *untouched downloads*, where a C2PA manifest | |
| decides the verdict and detection is 100%. That is not how images reach a | |
| visitor. A messaging app, a social upload or a re-save downscales the image, | |
| recompresses it as JPEG, and discards the metadata — including the signature. | |
| At that point the model is alone. | |
| This script isolates exactly that: same pixels, minus the manifest, plus the | |
| recompression an ordinary sharing path applies. Comparing the two conditions | |
| gives the cost of losing provenance, measured rather than assumed. | |
| What this is NOT | |
| ---------------- | |
| **Not the screenshot arm.** A screenshot adds display rescaling, a second | |
| round of resampling, possible UI chrome and colour-profile conversion. This is | |
| the milder, cleaner case: metadata stripped and one recompression. Treat the | |
| numbers as an OPTIMISTIC bound on the sharing path — real screenshots should be | |
| no better and are likely worse. Owner-collected screenshots remain the thing | |
| that settles it; see the P0 spec. | |
| Output is written to `ai/<tool>/reencoded_sim/`, deliberately suffixed `_sim` | |
| so no reader mistakes a generated condition for collected evidence. | |
| Usage | |
| ----- | |
| python scripts/dataset/simulate_shared_copy.py --root data/closed_eval | |
| python scripts/dataset/simulate_shared_copy.py --root data/closed_eval --force | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| from pathlib import Path | |
| from PIL import Image | |
| # Chosen to mimic a mainstream messaging/social pipeline rather than a worst | |
| # case: most resize the long edge to roughly this and recompress at high-ish | |
| # quality. A harsher setting would flatter the argument for P1 by making the | |
| # baseline look worse than reality. | |
| LONG_EDGE = 1080 | |
| JPEG_QUALITY = 85 | |
| def shared_copy(src: Path, dst: Path) -> None: | |
| """Downscale, recompress as JPEG, and drop every metadata block.""" | |
| with Image.open(src) as im: | |
| im = im.convert("RGB") | |
| w, h = im.size | |
| scale = LONG_EDGE / max(w, h) | |
| if scale < 1.0: | |
| im = im.resize((round(w * scale), round(h * scale)), Image.LANCZOS) | |
| # A fresh image object carries no info dict, so the C2PA chunk, EXIF and | |
| # ICC profile are all left behind — the same way a share pipeline does it. | |
| out = Image.new("RGB", im.size) | |
| out.putdata(list(im.getdata())) | |
| out.save(dst, "JPEG", quality=JPEG_QUALITY, optimize=True) | |
| def main() -> None: | |
| ap = argparse.ArgumentParser(description=__doc__, | |
| formatter_class=argparse.RawDescriptionHelpFormatter) | |
| ap.add_argument("--root", type=Path, default=Path("data/closed_eval")) | |
| ap.add_argument("--force", action="store_true", | |
| help="Regenerate even if the output folder already has files.") | |
| args = ap.parse_args() | |
| ai_root = args.root / "ai" | |
| if not ai_root.is_dir(): | |
| raise SystemExit(f"{ai_root} does not exist.") | |
| markers = (b"caBX", b"jumbf", b"c2pa") | |
| total = 0 | |
| for tool_dir in sorted(p for p in ai_root.iterdir() if p.is_dir()): | |
| src_dir = tool_dir / "original" | |
| if not src_dir.is_dir(): | |
| continue | |
| dst_dir = tool_dir / "reencoded_sim" | |
| dst_dir.mkdir(parents=True, exist_ok=True) | |
| existing = list(dst_dir.glob("*.jpg")) | |
| if existing and not args.force: | |
| print(f"{tool_dir.name}: {len(existing)} already present, skipping " | |
| f"(use --force to regenerate)") | |
| continue | |
| made = leaked = 0 | |
| for src in sorted(src_dir.glob("*.png"), key=lambda p: p.stem): | |
| dst = dst_dir / f"{src.stem}.jpg" | |
| shared_copy(src, dst) | |
| # Verify the point of the exercise actually happened. If a manifest | |
| # survived, the condition is not what it claims to be. | |
| if any(m in dst.read_bytes() for m in markers): | |
| leaked += 1 | |
| made += 1 | |
| total += made | |
| status = "all manifests stripped" if not leaked else f"{leaked} STILL CARRY C2PA" | |
| print(f"{tool_dir.name}: wrote {made} shared copies -> {dst_dir} ({status})") | |
| print(f"\n{total} images written. Re-run the eval to compare conditions:") | |
| print(" python scripts/eval_closed_generators.py --dir data/closed_eval") | |
| if __name__ == "__main__": | |
| main() | |