| """
|
| fetch_coco_subset.py - build train and eval sets from sayakpaul/coco-30-val-2014.
|
|
|
| The stream of 30K (image, caption) rows is split by row index:
|
| rows 0..EVAL_N-1 -> eval set (real images for FID + captions for generation)
|
| rows TRAIN_START..end -> training candidates
|
|
|
| Training images whose content hash also appears in the eval set are dropped,
|
| so the model is never trained on an image it is evaluated against (the same
|
| image can appear in multiple rows with different captions). Training rows are
|
| also deduped against each other by image hash.
|
|
|
| Outputs (in --out dir):
|
| coco_train.npz images uint8 (N, 64, 64, 3), center-cropped
|
| coco_train.captions.json N caption strings
|
| eval_real/real_00000.jpg... EVAL_N real images, 256x256 center-crop, q95
|
| eval_captions.json EVAL_N caption strings (for generation + CLIP)
|
|
|
| Usage:
|
| python fetch_coco_subset.py --out ../pm-work
|
| # much faster: pre-download the 10 parquet shards, then
|
| python fetch_coco_subset.py --out ../pm-work --parquet-dir ../pm-work/shards
|
| """
|
|
|
| import argparse
|
| import glob
|
| import hashlib
|
| import json
|
| import os
|
|
|
| import numpy as np
|
| from datasets import load_dataset
|
| from PIL import Image
|
|
|
| EVAL_N = 5000
|
| TRAIN_START = 10000
|
| TRAIN_RES = 64
|
| EVAL_RES = 256
|
|
|
|
|
| def center_crop_resize(img: Image.Image, size: int) -> Image.Image:
|
| w, h = img.size
|
| side = min(w, h)
|
| left, top = (w - side) // 2, (h - side) // 2
|
| return img.crop((left, top, left + side, top + side)).resize(
|
| (size, size), Image.BICUBIC
|
| )
|
|
|
|
|
| def img_hash(img: Image.Image) -> str:
|
| """Content hash of the image, robust to caption-duplicated rows."""
|
| return hashlib.md5(
|
| img.convert("RGB").resize((64, 64), Image.BILINEAR).tobytes()
|
| ).hexdigest()
|
|
|
|
|
| def main():
|
| p = argparse.ArgumentParser()
|
| p.add_argument("--out", required=True)
|
| p.add_argument("--parquet-dir", default=None,
|
| help="dir with the dataset's parquet shards already downloaded "
|
| "(sorted filename order == hub streaming row order)")
|
| args = p.parse_args()
|
|
|
| real_dir = os.path.join(args.out, "eval_real")
|
| os.makedirs(real_dir, exist_ok=True)
|
|
|
| if args.parquet_dir:
|
| files = sorted(glob.glob(os.path.join(args.parquet_dir, "*.parquet")))
|
| assert files, f"no parquet files in {args.parquet_dir}"
|
| ds = load_dataset("parquet", data_files=files, split="train", streaming=True)
|
| else:
|
| ds = load_dataset("sayakpaul/coco-30-val-2014", split="train", streaming=True)
|
|
|
| eval_hashes = set()
|
| eval_captions = []
|
| train_images, train_captions = [], []
|
| train_hashes = set()
|
| skipped_eval_overlap = skipped_dupe = 0
|
|
|
| for i, row in enumerate(ds):
|
| img, caption = row["image"], row["caption"]
|
|
|
| if i < EVAL_N:
|
| eval_hashes.add(img_hash(img))
|
| eval_captions.append(caption)
|
| center_crop_resize(img.convert("RGB"), EVAL_RES).save(
|
| os.path.join(real_dir, f"real_{i:05d}.jpg"), quality=95
|
| )
|
| if (i + 1) % 1000 == 0:
|
| print(f" eval: {i + 1}/{EVAL_N}", flush=True)
|
| continue
|
|
|
| if i < TRAIN_START:
|
| continue
|
|
|
| h = img_hash(img)
|
| if h in eval_hashes:
|
| skipped_eval_overlap += 1
|
| continue
|
| if h in train_hashes:
|
| skipped_dupe += 1
|
| continue
|
| train_hashes.add(h)
|
| thumb = center_crop_resize(img.convert("RGB"), TRAIN_RES)
|
| train_images.append(np.array(thumb, dtype=np.uint8))
|
| train_captions.append(caption)
|
| if len(train_images) % 2000 == 0:
|
| print(f" train: {len(train_images)} (row {i})", flush=True)
|
|
|
| images = np.stack(train_images)
|
| np.savez_compressed(os.path.join(args.out, "coco_train.npz"), images=images)
|
| with open(os.path.join(args.out, "coco_train.captions.json"), "w", encoding="utf-8") as f:
|
| json.dump(train_captions, f)
|
| with open(os.path.join(args.out, "eval_captions.json"), "w", encoding="utf-8") as f:
|
| json.dump(eval_captions, f)
|
|
|
| print(f"\neval: {len(eval_captions)} pairs @ {EVAL_RES}x{EVAL_RES} -> {real_dir}")
|
| print(f"train: {len(train_captions)} pairs @ {TRAIN_RES}x{TRAIN_RES} "
|
| f"(skipped: {skipped_eval_overlap} eval-overlap, {skipped_dupe} dupes)")
|
|
|
|
|
| if __name__ == "__main__":
|
| main()
|
|
|