Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 16,699 Bytes
cd24b4a 463a482 cd24b4a 463a482 cd24b4a 463a482 cd24b4a 463a482 cd24b4a b91ce50 cd24b4a dca285c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 | """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
@dataclass
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")}
|