"""Create a deterministic, held-out synthetic restoration benchmark. Only real-portrait folders are selected. The generated manifest and holdout list are ignored by Git; pass the holdout list to training to avoid leakage. """ from __future__ import annotations import argparse import csv import hashlib import shutil from pathlib import Path import cv2 import numpy as np ROOT = Path(__file__).resolve().parents[1] DEFAULT_SOURCE = ROOT / "models" / "CodeFormer" / "datasets" / "ffhq" / "ffhq_512" REAL_FOLDERS = {"faces", "pinterest"} EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"} def candidates(source: Path) -> list[Path]: files = [] for folder in REAL_FOLDERS: directory = source / folder if directory.is_dir(): files.extend(p for p in directory.rglob("*") if p.suffix.lower() in EXTENSIONS) return sorted(files) def degrade(image: np.ndarray, seed: int) -> np.ndarray: rng = np.random.default_rng(seed) blur_size = int(rng.choice([3, 5, 7])) output = cv2.GaussianBlur(image, (blur_size, blur_size), float(rng.uniform(0.4, 2.5))) h, w = output.shape[:2] scale = float(rng.uniform(1.5, 6.0)) small = cv2.resize(output, (max(32, int(w / scale)), max(32, int(h / scale))), interpolation=cv2.INTER_AREA) output = cv2.resize(small, (w, h), interpolation=cv2.INTER_CUBIC) output = np.clip(output.astype(np.float32) + rng.normal(0, rng.uniform(2, 25), output.shape), 0, 255).astype(np.uint8) quality = int(rng.integers(10, 71)) ok, encoded = cv2.imencode(".jpg", output, [cv2.IMWRITE_JPEG_QUALITY, quality]) if not ok: raise RuntimeError("JPEG degradation failed") return cv2.imdecode(encoded, cv2.IMREAD_COLOR) def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--source", type=Path, default=DEFAULT_SOURCE) parser.add_argument("--count", type=int, default=500) parser.add_argument("--seed", type=int, default=20260723) parser.add_argument("--dry-run", action="store_true") args = parser.parse_args() source = args.source.resolve() ranked = sorted(candidates(source), key=lambda p: hashlib.sha256(f"{args.seed}:{p.relative_to(source)}".encode()).hexdigest()) selected = ranked[:args.count] if len(selected) < args.count: raise SystemExit(f"Need {args.count} real portraits but found {len(selected)} in {source}") print(f"Selected {len(selected)} held-out real portraits from {len(ranked)} candidates.") if args.dry_run: return benchmark = ROOT / "benchmarks" inputs, references = benchmark / "inputs", benchmark / "references" inputs.mkdir(parents=True, exist_ok=True); references.mkdir(parents=True, exist_ok=True) manifest = benchmark / "manifest.csv" holdout = benchmark / "holdout_paths.txt" with manifest.open("w", newline="", encoding="utf-8") as csv_file, holdout.open("w", encoding="utf-8") as holdout_file: writer = csv.DictWriter(csv_file, fieldnames=["id", "input_path", "reference_path", "category", "notes"]); writer.writeheader() for index, path in enumerate(selected, 1): image = cv2.imread(str(path), cv2.IMREAD_COLOR) if image is None: raise RuntimeError(f"Unreadable image: {path}") sample_id = f"portrait_{index:04d}" cv2.imwrite(str(inputs / f"{sample_id}.jpg"), degrade(image, args.seed + index), [cv2.IMWRITE_JPEG_QUALITY, 95]) shutil.copy2(path, references / f"{sample_id}{path.suffix.lower()}") writer.writerow({"id": sample_id, "input_path": f"inputs/{sample_id}.jpg", "reference_path": f"references/{sample_id}{path.suffix.lower()}", "category": "synthetic_real_portrait", "notes": "deterministic holdout"}) holdout_file.write(path.relative_to(source).as_posix() + "\n") print(f"Created {manifest} and {holdout}. Do not train on the listed paths.") if __name__ == "__main__": main()