| import os |
| import sys |
| import io |
| import pandas as pd |
| from PIL import Image |
| import requests |
| import time |
| from bs4 import BeautifulSoup |
| |
| from selenium import webdriver |
| from selenium.webdriver.chrome.options import Options |
| from webdriver_manager.chrome import ChromeDriverManager |
|
|
| |
| |
| |
| SOURCES = { |
| "face": { |
| "parquet_url": "https://huggingface.co/datasets/Ryan-sjtu/ffhq512-caption/resolve/main/data/train-00000-of-00054-9b5f7c3e6bc03b3b.parquet", |
| "num_images": 300, |
| "subdir": "faces", |
| "prefix": "face", |
| "description": "FFHQ 512x512 real face photos", |
| }, |
| "landscape": { |
| "parquet_url": "https://huggingface.co/datasets/mertcobanov/nature-dataset/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet", |
| "num_images": 300, |
| "subdir": "landscapes", |
| "prefix": "landscape", |
| "description": "Nature & landscape scenery (50k images)", |
| }, |
| "anime": { |
| "parquet_url": "https://huggingface.co/datasets/amirali900/Anime-Face-Dataset-10k/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet", |
| "num_images": 300, |
| "subdir": "anime", |
| "prefix": "anime", |
| "description": "Anime Face Dataset 10k illustrations", |
| }, |
| "blue_archive": { |
| |
| "type": "web", |
| "search_url": "https://www.pinterest.com/search/pins/?q=blue%20archive", |
| "num_images": 300, |
| "subdir": "blue_archive", |
| "prefix": "blue", |
| "description": "Blue Archive images scraped from Pinterest", |
| }, |
| } |
|
|
| |
| ANIME_FALLBACK_URLS = [ |
| "https://huggingface.co/datasets/huggan/anime-faces/resolve/main/data/train-00000-of-00001.parquet", |
| ] |
|
|
| LANDSCAPE_FALLBACK_URLS = [ |
| "https://huggingface.co/datasets/jlbaker361/flickr_humans/resolve/main/data/train-00000-of-00001.parquet", |
| ] |
|
|
|
|
| def load_parquet_safe(url: str) -> pd.DataFrame | None: |
| """Try to load a parquet file from URL, return None on failure.""" |
| print(f" Loading: {url}") |
| try: |
| df = pd.read_parquet(url) |
| print(f" [OK] Loaded {len(df)} rows.") |
| return df |
| except Exception as e: |
| print(f" [FAIL] Failed: {e}") |
| return None |
|
|
|
|
| def find_image_column(df: pd.DataFrame) -> str | None: |
| """Detect which column holds image data (bytes dict or PIL-compatible).""" |
| for col in df.columns: |
| sample = df[col].iloc[0] |
| if isinstance(sample, dict) and "bytes" in sample: |
| return col |
| if isinstance(sample, bytes): |
| return col |
| return None |
|
|
|
|
| def extract_image_bytes(cell) -> bytes | None: |
| """Extract raw image bytes from a dataframe cell regardless of format.""" |
| if isinstance(cell, dict): |
| return cell.get("bytes") |
| if isinstance(cell, bytes): |
| return cell |
| return None |
|
|
|
|
| |
| |
| |
| def scrape_image_urls(search_url: str, max_images: int) -> list: |
| """Scrape image URLs from a Pinterest search page. |
| |
| The function fetches the HTML of the search results, extracts ``<img>`` |
| tags and returns up to ``max_images`` URLs that end with common image file |
| extensions. Pagination is handled by appending ``&page=N`` to the query URL |
| when needed. |
| """ |
| print(f" Scraping Pinterest for up to {max_images} images …") |
| headers = { |
| "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " |
| "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36" |
| } |
| collected: list[str] = [] |
| page = 1 |
| |
| chrome_options = Options() |
| chrome_options.add_argument("--headless") |
| chrome_options.add_argument("--disable-gpu") |
| chrome_options.add_argument("--no-sandbox") |
| driver = webdriver.Chrome(ChromeDriverManager().install(), options=chrome_options) |
|
|
| while len(collected) < max_images: |
| |
| url = ( |
| f"{search_url}&page={page}" if "page=" not in search_url else f"{search_url.split('page=')[0]}page={page}" |
| ) |
| try: |
| driver.get(url) |
| |
| driver.implicitly_wait(3) |
| page_html = driver.page_source |
| except Exception as e: |
| print(f" [WARN] Selenium failed to load page {page}: {e}") |
| break |
|
|
| |
| soup = BeautifulSoup(page_html, "html.parser") |
| imgs = soup.find_all("img") |
| new_urls = [img.get("src") for img in imgs if img.get("src")] |
| |
| new_urls = [u for u in new_urls if u not in collected and u.lower().endswith((".png", ".jpg", ".jpeg"))] |
| if not new_urls: |
| |
| break |
| collected.extend(new_urls) |
| print(f" Found {len(collected)} image URLs so far …") |
| page += 1 |
|
|
| |
| driver.quit() |
| return collected[:max_images] |
|
|
|
|
| def download_images_from_urls(urls: list, save_dir: str, prefix: str, start_idx: int, target: int) -> int: |
| """Download, resize and save images from a list of URLs. |
| |
| Mirrors the parquet pipeline: images are resized to 512×512 and saved as PNG |
| files named ``{prefix}_{index:05d}.png``. |
| """ |
| saved = 0 |
| for url in urls: |
| if saved >= target: |
| break |
| try: |
| resp = requests.get(url, stream=True, timeout=10) |
| resp.raise_for_status() |
| img = Image.open(resp.raw).convert("RGB") |
| img = img.resize((512, 512), Image.Resampling.LANCZOS) |
| filename = f"{prefix}_{start_idx + saved:05d}.png" |
| filepath = os.path.join(save_dir, filename) |
| img.save(filepath, "PNG") |
| saved += 1 |
| if saved % 20 == 0 or saved == target: |
| print(f" [{prefix}] {saved}/{target} images saved …") |
| except Exception as e: |
| print(f" [warn] Failed to download {url}: {e}") |
| continue |
| return saved |
|
|
| def crawl_source(name: str, cfg: dict, base_dataset_dir: str): |
| """Download images for a single source category.""" |
| save_dir = os.path.join(base_dataset_dir, cfg["subdir"]) |
| os.makedirs(save_dir, exist_ok=True) |
|
|
| print(f"\n{'='*55}") |
| print(f" [{name.upper()}] {cfg['description']}") |
| print(f" Save dir : {save_dir}") |
| print(f" Target : {cfg['num_images']} images") |
| print(f"{'='*55}") |
|
|
| |
| |
| |
| |
| |
| existing = [f for f in os.listdir(save_dir) if f.endswith('.png') and f.startswith(cfg['prefix'])] |
| highest_idx = -1 |
| for f in existing: |
| try: |
| idx = int(f.replace(cfg['prefix'] + '_', '').replace('.png', '')) |
| highest_idx = max(highest_idx, idx) |
| except ValueError: |
| continue |
| start_idx = highest_idx + 1 |
|
|
| if cfg.get('type') == 'web': |
| urls = scrape_image_urls(cfg['search_url'], cfg['num_images']) |
| saved = download_images_from_urls(urls, save_dir, cfg['prefix'], start_idx, cfg['num_images']) |
| print(f"\n [OK] [{name.upper()}] Done: {saved} images saved to {save_dir}") |
| return saved |
|
|
| |
| df = load_parquet_safe(cfg["parquet_url"]) |
|
|
| |
| if df is None: |
| fallbacks = [] |
| if name == "anime": |
| fallbacks = ANIME_FALLBACK_URLS |
| elif name == "landscape": |
| fallbacks = LANDSCAPE_FALLBACK_URLS |
| for fb_url in fallbacks: |
| print(f" Trying fallback: {fb_url}") |
| df = load_parquet_safe(fb_url) |
| if df is not None: |
| break |
|
|
| if df is None: |
| print(f" [FAIL] All sources failed for '{name}'. Skipping.") |
| return 0 |
|
|
| img_col = find_image_column(df) |
| if img_col is None: |
| print(f" [FAIL] No image column detected in dataset. Columns: {list(df.columns)}") |
| return 0 |
|
|
| print(f" Image column: '{img_col}'") |
|
|
| |
| existing = [ |
| f for f in os.listdir(save_dir) |
| if f.endswith(".png") and f.startswith(cfg["prefix"]) |
| ] |
| highest_idx = -1 |
| for f in existing: |
| try: |
| idx = int(f.replace(cfg["prefix"] + "_", "").replace(".png", "")) |
| highest_idx = max(highest_idx, idx) |
| except ValueError: |
| continue |
|
|
| start_idx = highest_idx + 1 |
| saved = 0 |
| target = cfg["num_images"] |
|
|
| for i in range(len(df)): |
| if saved >= target: |
| break |
| try: |
| cell = df[img_col].iloc[i] |
| img_bytes = extract_image_bytes(cell) |
| if not img_bytes: |
| continue |
|
|
| img = Image.open(io.BytesIO(img_bytes)).convert("RGB") |
| img = img.resize((512, 512), Image.Resampling.LANCZOS) |
|
|
| filename = f"{cfg['prefix']}_{start_idx + saved:05d}.png" |
| filepath = os.path.join(save_dir, filename) |
| img.save(filepath, "PNG") |
| saved += 1 |
|
|
| if saved % 20 == 0 or saved == target: |
| print(f" [{name}] {saved}/{target} images saved...") |
|
|
| except Exception as e: |
| print(f" [warn] Row {i} error: {e}") |
| continue |
|
|
| print(f"\n [OK] [{name.upper()}] Done: {saved} images saved to {save_dir}") |
| return saved |
|
|
|
|
| def main(): |
| project_dir = os.path.dirname(os.path.abspath(__file__)) |
| codeformer_dir = os.path.join(project_dir, "models", "CodeFormer") |
| base_dataset_dir = os.path.join(codeformer_dir, "datasets", "ffhq", "ffhq_512") |
| os.makedirs(base_dataset_dir, exist_ok=True) |
|
|
| print("=" * 55) |
| print(" MULTI-CATEGORY DATASET CRAWLER") |
| print(" Categories: Face | Landscape | Anime Girl") |
| print("=" * 55) |
|
|
| total_saved = 0 |
| for name, cfg in SOURCES.items(): |
| saved = crawl_source(name, cfg, base_dataset_dir) |
| total_saved += saved |
| time.sleep(0.5) |
|
|
| |
| all_images = [] |
| for root, _, files in os.walk(base_dataset_dir): |
| for f in files: |
| if f.lower().endswith(".png"): |
| all_images.append(f) |
|
|
| print("\n" + "=" * 55) |
| print(f" CRAWL COMPLETE") |
| print(f" New images this run : {total_saved}") |
| print(f" Total dataset size : {len(all_images)} images") |
| print(f" Dataset location : {base_dataset_dir}") |
| print("=" * 55) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|