Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Download interface/backend/datasets.py from SMSD75/SynthScope: direct link, hf CLI and curl.
- Browser
- Download file 16.7 kB
-
https://huggingface.co/spaces/SMSD75/SynthScope/resolve/main/interface/backend/datasets.py
- Command line
-
hf download hf://spaces/SMSD75/SynthScope/interface/backend/datasets.py
-
curl -L -o datasets.py https://huggingface.co/spaces/SMSD75/SynthScope/resolve/main/interface/backend/datasets.py
16.7 kB
| """Dataset explorer backend: discover on-disk datasets, sample random batches, serve | |
| thumbnails, and report which dataset each detector was trained on. | |
| The dataset *configs* (conf/dataset/*.yaml) point at paths that are partly stale on the GPU | |
| node (the /nvmestore roots are not mounted on cn023, and the video sets live in ad-hoc repro | |
| dirs). So this module keeps an explicit registry of candidate roots and auto-detects which | |
| ones actually exist, rather than trusting the configs. Labels are inferred from path | |
| components (``0_real``/``real``/``nature`` -> real, ``1_fake``/``fake``/``ai`` -> fake), which | |
| covers every on-disk layout (ForenSynths ``<gen>/{0_real,1_fake}``, GenImage ``{ai,nature}``, | |
| GenVideo ``Real`` + ``Fake/<gen>``, ReStraV ``genvideo/{0_real,1_fake}``). | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import _synthscope_env # noqa: F401 (maps legacy AGID_* variables to SYNTHSCOPE_*) | |
| import random | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| IMAGE_EXTS = {".jpg", ".jpeg", ".jfif", ".png", ".webp", ".bmp", ".tif", ".tiff"} | |
| VIDEO_EXTS = {".mp4", ".avi", ".mov", ".mkv", ".webm", ".m4v"} | |
| # Directory-name tokens that identify a sample's ground-truth label. | |
| REAL_TOKENS = {"0_real", "real", "reals", "nature", "natural", "coco", "imagenet"} | |
| FAKE_TOKENS = {"1_fake", "fake", "fakes", "ai", "generated", "gen", "synthesis"} | |
| # How many paths to keep in the in-memory pool per label (reservoir-sampled over the full tree) | |
| # and the hard cap on tree entries scanned, so sampling stays fast on 100k+ -image datasets. | |
| POOL_PER_LABEL = 4000 | |
| SCAN_CAP = 400_000 | |
| class DatasetDef: | |
| key: str | |
| name: str | |
| modality: str # "image" | "video" | |
| roots: list[str] # candidate roots; first existing one wins | |
| splits: list[str] = field(default_factory=lambda: ["train", "val", "test"]) | |
| note: str = "" | |
| # Explicit registry (auto-detected against disk at request time). | |
| DATASET_DEFS: list[DatasetDef] = [ | |
| DatasetDef("cnndetection", "CNNDetection / ForenSynths (ProGAN)", "image", | |
| ["/hddstore/ssalehi/SynthScope/CNNDetection"], ["train", "val", "test"], | |
| "ProGAN-family GAN images; the ForenSynths benchmark."), | |
| DatasetDef("genimage", "GenImage (SD-v1.4)", "image", | |
| ["/hddstore/ssalehi/SynthScope/GenImage"], ["train", "test"], | |
| "ImageNet-class diffusion (SD-v1.4) vs real ImageNet."), | |
| DatasetDef("cospy", "CoSpyBench", "image", | |
| ["/hddstore/ssalehi/SynthScope/CoSpyBench"], ["test"], | |
| "Co-Spy cross-generator evaluation benchmark."), | |
| DatasetDef("cospy_subset", "Co-Spy ProGAN subset", "image", | |
| ["/hddstore/ssalehi/SynthScope/spai_train/cospy_subset"], ["train", "val", "test"]), | |
| DatasetDef("spai_ldm", "SPAI LDM trainset (vs COCO)", "image", | |
| ["/hddstore/ssalehi/SynthScope/spai_train/staged"], ["train", "val", "test"], | |
| "Latent-diffusion fakes vs COCO reals (SPAI from-scratch)."), | |
| DatasetDef("genvideo_val", "GenVideo-Val", "video", | |
| ["/hddstore/ssalehi/SynthScope/d3_repro/genvideo_val/GenVideo-Val"], ["."], | |
| "GenVideo validation pool (msrvtt reals + 10 generators)."), | |
| DatasetDef("restrav_data", "ReStraV data (VidProM β GenVideo split)", "video", | |
| ["/hddstore/ssalehi/SynthScope/restrav_data"], ["train", "val", "test"], | |
| "Train = VidProM/DeMamba (VSC reals); test = GenVideo (msrvtt reals)."), | |
| DatasetDef("ditfake", "DiTFake (DiT/FLUX/SD3 images)", "image", | |
| ["/hddstore/ssalehi/SynthScope/DiTFake"], ["test"], | |
| "DiT/FLUX/SD3-generated images vs COCO reals."), | |
| DatasetDef("aigibench", "AIGIBench (multi-generator)", "image", | |
| ["/hddstore/ssalehi/SynthScope/AIGIBench"], ["test"], | |
| "25-generator AI-image benchmark (GAN/diffusion/face-swap) vs real."), | |
| DatasetDef("evalcrafter", "EvalCrafter (T2V, generated)", "video", | |
| ["/hddstore/ssalehi/SynthScope/EvalCrafter"], ["test"], | |
| "14 text-to-video generators β all AI-generated (no reals)."), | |
| DatasetDef("genvidbench", "GenVidBench", "video", | |
| ["/hddstore/ssalehi/SynthScope/GenVidBench"], ["test"], | |
| "Real (Vript, HD-VG-130M) vs 8 video generators."), | |
| DatasetDef("freqnetdetection", "FreqNet test set", "image", | |
| ["/hddstore/ssalehi/SynthScope/freqnet"], ["test"], | |
| "FreqNet evaluation set (ForenSynths-style real/fake)."), | |
| DatasetDef("universialfakedetection", "UniversalFakeDetect (Ojha)", "image", | |
| ["/hddstore/ssalehi/SynthScope/universialfakedetection"], ["test"], | |
| "Ojha et al. CVPR'23 cross-generator test set."), | |
| # Cross-generator benchmarks added for the full reproduction suite. | |
| DatasetDef("genimage_full", "GenImage (8 generators)", "image", | |
| ["/hddstore/ssalehi/SynthScope/GenImage_full"], ["test"], | |
| "Full 8-generator GenImage test (Midjourney, SD-v1.4/1.5, ADM, GLIDE, Wukong, VQDM, BigGAN)."), | |
| DatasetDef("synthbuster", "Synthbuster (9 diffusion generators)", "image", | |
| ["/hddstore/ssalehi/SynthScope/Synthbuster"], ["test"], | |
| "Synthbuster (Zenodo 10066460) β DALL-E 2/3, Firefly, GLIDE, Midjourney-v5, SD 1.3/1.4/2/XL; " | |
| "used by SPAI. Proxy reals (GenImage nature) since Synthbuster ships fakes only."), | |
| DatasetDef("diffusionforensics", "DiffusionForensics / DIRE", "image", | |
| ["/hddstore/ssalehi/SynthScope/DiffusionForensics"], ["test"], | |
| "8 diffusion subsets (ADM, DDPM, IDDPM, LDM, PNDM, SDv1, SDv2, VQ-Diffusion); NPR Table 3."), | |
| DatasetDef("diffusion1kstep", "Diffusion-1kStep", "image", | |
| ["/hddstore/ssalehi/SynthScope/Diffusion1kStep"], ["test"], | |
| "DALLE, DDPM, guided-diffusion, improved-diffusion, midjourney; NPR Table 5."), | |
| DatasetDef("aigcdetect", "AIGCDetectBenchmark (17 gen)", "image", | |
| ["/hddstore/ssalehi/SynthScope/AIGCDetectBenchmark"], ["test"], | |
| "17-generator GAN+diffusion benchmark; Co-Spy / AIDE / CuRe cross-generator table."), | |
| DatasetDef("commgen15", "CommGen15 (15 commercial gen)", "image", | |
| ["/hddstore/ssalehi/SynthScope/CommGen15"], ["test"], | |
| "15 commercial generators (Midjourney, Sora, Flux, Kling, ...); PGC Table 3."), | |
| DatasetDef("realchain", "RealChain (7 recent generators)", "image", | |
| ["/hddstore/ssalehi/SynthScope/RealChain"], ["test"], | |
| "Flux1, SDv3.5, QwenImage, Hunyuan3, NanoBanana, Seedream4, i2i vs 7000 reals, all JPEG q60; " | |
| "CuRe Table 1. CC BY-NC 4.0 (non-commercial)."), | |
| DatasetDef("evalgen", "EvalGEN (5 diffusion + AR gen)", "image", | |
| ["/hddstore/ssalehi/SynthScope/EvalGEN"], ["test"], | |
| "Flux, GoT, Infinity, OmniGen, NOVA on GenEval prompts (DDA); paired with MSCOCO val2017 reals."), | |
| DatasetDef("synthwildx", "SynthWildX (images from X)", "image", | |
| ["/hddstore/ssalehi/SynthScope/SynthWildX"], ["test"], | |
| "DALL-E 3, Firefly, Midjourney v5 vs real, all posted on X; fetched from X, so deleted posts are missing."), | |
| DatasetDef("wildrf", "WildRF (Facebook, Reddit, Twitter)", "image", | |
| ["/hddstore/ssalehi/SynthScope/WildRF"], ["test"], | |
| "In-the-wild real and fake images from three social platforms (LaDeDa paper); one folder per platform."), | |
| DatasetDef("bfree_online", "BFree-Online (viral images)", "image", | |
| ["/hddstore/ssalehi/SynthScope/BFreeOnline"], ["test"], | |
| "Real and fake images that went viral, several web copies each (B-Free); fetched from the URL list, " | |
| "dead links skipped. Nonprofit use only."), | |
| DatasetDef("rrdataset", "RRDataset (original / transfer / redigital)", "image", | |
| ["/hddstore/ssalehi/SynthScope/RRDataset"], ["test"], | |
| "Real vs AI images as created, after sharing online, and after re-digitization (ICCV 2025)."), | |
| DatasetDef("videofeedback", "VideoFeedback (TIGER-Lab)", "video", | |
| ["/hddstore/ssalehi/SynthScope/VideoFeedback"], ["test"], | |
| "11 video generators vs DiDeMo+Panda70M real; STALL benchmark."), | |
| DatasetDef("comgenvid", "ComGenVid (Sora/VEO3)", "video", | |
| ["/hddstore/ssalehi/SynthScope/ComGenVid"], ["test"], | |
| "Sora + VEO3 fakes vs MSVD real; STALL's new benchmark."), | |
| DatasetDef("genvideo", "GenVideo-Val (10 generators)", "video", | |
| ["/hddstore/ssalehi/SynthScope/GenVideo"], ["test"], | |
| "DeMamba's GenVideo-Val per generator vs 10,000 MSR-VTT reals; D3 Table 1, STALL."), | |
| DatasetDef("videophy", "VideoPhy (text-to-video)", "video", | |
| ["/hddstore/ssalehi/SynthScope/VideoPhy"], ["test"], | |
| "VideoPhy test clips from up to 10 generators vs MSR-VTT reals; D3 Table 3."), | |
| DatasetDef("vidprom", "VidProM (4 generators, subsample)", "video", | |
| ["/hddstore/ssalehi/SynthScope/VidProM"], ["test"], | |
| "First 1,000 clips per generator (ModelScope, Pika, Text2Video-Zero, VideoCrafter2) vs MSR-VTT reals; " | |
| "CC BY-NC 4.0."), | |
| ] | |
| # Which dataset each detector was trained on (best-effort, from interface/scripts/train_model.sh | |
| # and experiments/*). `official` flags checkpoints whose deployed weights are the authors' (the | |
| # SynthScope from-scratch dataset is noted, but the served weights are the official ProGAN-trained ones). | |
| MODEL_TRAIN_DATASET: dict[str, dict] = { | |
| "clip": {"dataset": "cnndetection"}, | |
| "cnn": {"dataset": "cnndetection"}, | |
| "dinov2": {"dataset": "cnndetection"}, | |
| "dinov3": {"dataset": "cnndetection"}, | |
| "adapted_dino": {"dataset": "cnndetection"}, | |
| "safe": {"dataset": "cnndetection"}, | |
| "pgc": {"dataset": "cospy_subset"}, | |
| "co_spy": {"dataset": "cospy_subset"}, | |
| "vib_net": {"dataset": "genimage"}, | |
| "aide": {"dataset": "genimage", "official": True}, | |
| "npr": {"dataset": "genimage", "official": True}, | |
| "spai": {"dataset": "spai_ldm"}, | |
| "fat_former": {"dataset": "cnndetection", "official": True}, | |
| "restrav": {"dataset": "restrav_data"}, | |
| "d3": {"dataset": None, "training_free": True}, | |
| "stall": {"dataset": None, "training_free": True}, | |
| } | |
| # Node-local scratch differs per node (cn023 -> /hddstore, cees -> /ssdstore, cn030/31 -> /nvmestore). | |
| # Try each configured root, then the same tail remapped onto the other scratch mounts, so the explorer | |
| # works wherever the data was staged. Override the prefix set with SYNTHSCOPE_DATA_PREFIXES (colon-separated). | |
| _SCRATCH_PREFIXES = tuple(p for p in ( | |
| os.environ.get("SYNTHSCOPE_DATA_PREFIXES") or "/hddstore:/ssdstore:/nvmestore").split(":") if p) | |
| def _root_candidates(r: str) -> list[str]: | |
| cands = [r] | |
| # SYNTHSCOPE_DATA_DIR: one folder holding datasets by their usual folder name (e.g. <dir>/CommGen15), as on a Space | |
| if os.environ.get("SYNTHSCOPE_DATA_DIR"): | |
| cands.insert(0, str(Path(os.environ["SYNTHSCOPE_DATA_DIR"]).expanduser() / Path(r).name)) | |
| for src in _SCRATCH_PREFIXES: | |
| if r.startswith(src + "/"): | |
| tail = r[len(src):] | |
| cands += [pre + tail for pre in _SCRATCH_PREFIXES if pre != src] | |
| break | |
| return cands | |
| def _resolve_root(d: DatasetDef) -> Path | None: | |
| for r in d.roots: | |
| for c in _root_candidates(r): | |
| p = Path(c) | |
| if p.is_dir(): | |
| return p | |
| return None | |
| def _exts_for(modality: str) -> set[str]: | |
| return VIDEO_EXTS if modality == "video" else IMAGE_EXTS | |
| def _split_dir(root: Path, split: str) -> Path: | |
| return root if split in (".", "", None) else root / split | |
| def label_of(rel: Path) -> str | None: | |
| """Infer 'real'/'fake' from a relative path's directory tokens (None if unknown).""" | |
| parts = [p.lower() for p in rel.parts] | |
| for p in parts: | |
| if p in FAKE_TOKENS: | |
| return "fake" | |
| for p in parts: | |
| if p in REAL_TOKENS: | |
| return "real" | |
| return None | |
| def available_splits(d: DatasetDef, root: Path) -> list[str]: | |
| out = [] | |
| for s in d.splits: | |
| if _split_dir(root, s).is_dir(): | |
| out.append(s) | |
| return out | |
| def list_datasets() -> list[dict]: | |
| """All registry datasets whose data is present on disk, with resolved path + splits.""" | |
| out = [] | |
| for d in DATASET_DEFS: | |
| root = _resolve_root(d) | |
| if root is None: | |
| continue | |
| out.append({ | |
| "key": d.key, | |
| "name": d.name, | |
| "modality": d.modality, | |
| "note": d.note, | |
| "path": str(root), | |
| "splits": available_splits(d, root), | |
| }) | |
| return out | |
| def get_def(key: str) -> DatasetDef: | |
| for d in DATASET_DEFS: | |
| if d.key == key: | |
| return d | |
| raise KeyError(f"Unknown dataset '{key}'") | |
| def _iter_files(base: Path, exts: set[str]): | |
| scanned = 0 | |
| for dirpath, _dirs, files in os.walk(base): | |
| for fn in files: | |
| if Path(fn).suffix.lower() in exts: | |
| yield Path(dirpath) / fn | |
| scanned += 1 | |
| if scanned >= SCAN_CAP: | |
| return | |
| def _pools(base: Path, exts: set[str]) -> dict[str, list[Path]]: | |
| """Reservoir-sample up to POOL_PER_LABEL paths per label across the whole tree.""" | |
| pools: dict[str, list[Path]] = {"real": [], "fake": []} | |
| seen: dict[str, int] = {"real": 0, "fake": 0} | |
| rng = random.Random(1234) | |
| for fp in _iter_files(base, exts): | |
| lab = label_of(fp.relative_to(base)) | |
| if lab is None: | |
| continue | |
| seen[lab] += 1 | |
| pool = pools[lab] | |
| if len(pool) < POOL_PER_LABEL: | |
| pool.append(fp) | |
| else: # reservoir replacement | |
| j = rng.randint(0, seen[lab] - 1) | |
| if j < POOL_PER_LABEL: | |
| pool[j] = fp | |
| return pools | |
| def sample_batch(key: str, split: str, n: int, seed: int) -> list[dict]: | |
| """Return ``n`` random items (balanced real/fake when possible) from ``key``/``split``.""" | |
| d = get_def(key) | |
| root = _resolve_root(d) | |
| if root is None: | |
| raise FileNotFoundError(f"Dataset '{key}' not present on disk") | |
| base = _split_dir(root, split) | |
| if not base.is_dir(): | |
| raise FileNotFoundError(f"Split '{split}' not found for '{key}'") | |
| pools = _pools(base, _exts_for(d.modality)) | |
| rng = random.Random(seed) | |
| half = max(1, n // 2) | |
| items: list[Path] = [] | |
| for lab in ("real", "fake"): | |
| p = pools[lab] | |
| rng.shuffle(p) | |
| items += [(fp, lab) for fp in p[:half]] | |
| # top up if one class is short | |
| if len(items) < n: | |
| extra = [(fp, lab) for lab in ("real", "fake") for fp in pools[lab][half:]] | |
| rng.shuffle(extra) | |
| items += extra[: n - len(items)] | |
| rng.shuffle(items) | |
| items = items[:n] | |
| return [ | |
| {"rel": str(fp.relative_to(root)), "label": lab, | |
| "name": fp.name, "modality": d.modality} | |
| for fp, lab in items | |
| ] | |
| def resolve_media(key: str, rel: str) -> Path: | |
| """Resolve ``rel`` under the dataset root, guarding against ``..`` traversal. | |
| Some datasets (e.g. ``restrav_data``) populate their split dirs with *symlinks* into other | |
| SynthScope data folders, so we must NOT ``.resolve()`` and then reject by the symlink's target | |
| (that would 403 every restrav_data thumbnail). Instead we forbid absolute paths and ``..`` | |
| components in the user-supplied ``rel`` β which keeps the addressed path lexically under the | |
| dataset root β then follow the symlink (``is_file``) to serve the actual file. | |
| """ | |
| d = get_def(key) | |
| root = _resolve_root(d) | |
| if root is None: | |
| raise FileNotFoundError(f"Dataset '{key}' not present on disk") | |
| rel_path = Path(rel) | |
| if rel_path.is_absolute() or ".." in rel_path.parts: | |
| raise PermissionError("Path escapes dataset root") | |
| target = root / rel_path | |
| if not target.is_file(): # follows the symlink to the real video/image | |
| raise FileNotFoundError(str(target)) | |
| return target | |
| # Hydra dataset configs that are training sets, not benchmarks (excluded from the benchmark count shown on the page). | |
| _TRAINING_ONLY = {"cospy_subset", "spai_ldm"} | |
| def supported_counts() -> dict: | |
| """How many benchmark datasets SynthScope can download and lay out (one Hydra config each).""" | |
| conf = Path(__file__).resolve().parents[2] / "conf" / "dataset" | |
| return {m: sum(1 for p in (conf / m).glob("*.yaml") if p.stem not in _TRAINING_ONLY) for m in ("image", "video")} | |