| """Fetch specific figure images from ArxivCap on HuggingFace via DuckDB range requests.""" |
| import os |
| import re |
| from collections import defaultdict |
| from pathlib import Path |
|
|
| HF_SRC = "hf://datasets/MMInstruction/ArxivCap/data" |
|
|
|
|
| def yymm_of(arxiv_id): |
| s = str(arxiv_id) |
| if "/" in s: |
| return re.sub(r"\D", "", s.split("/", 1)[1])[:4] |
| return s[:4] |
|
|
|
|
| def safe_name(arxiv_id, fig_index): |
| return re.sub(r"[^0-9a-zA-Z.]+", "_", str(arxiv_id)) + f"_f{fig_index}.jpg" |
|
|
|
|
| def fetch_images(needed, image_dir, hf_src=HF_SRC): |
| """needed: set of (arxiv_id, fig_index 1-based). Skips files already on disk.""" |
| import duckdb |
| image_dir = Path(image_dir) |
| image_dir.mkdir(parents=True, exist_ok=True) |
| todo = {(a, f) for (a, f) in needed |
| if not (image_dir / safe_name(a, f)).exists()} |
| if not todo: |
| print(" all images already cached") |
| return |
| by_chunk = defaultdict(set) |
| for a, f in todo: |
| by_chunk[yymm_of(a)].add((a, f)) |
| print(f" fetching {len(todo)} figures from {len(by_chunk)} ArxivCap chunks") |
|
|
| con = duckdb.connect() |
| for setting in ["SET http_retries = 8", "SET http_retry_wait_ms = 2000", |
| "SET threads = 2"]: |
| try: |
| con.execute(setting) |
| except Exception: |
| pass |
| token = os.environ.get("HF_TOKEN", "") |
| if token: |
| con.execute(f"CREATE SECRET hf (TYPE HUGGINGFACE, TOKEN '{token}')") |
| else: |
| print(" WARNING: no HF_TOKEN; anonymous HF requests rate-limit fast") |
|
|
| for yymm, pairs in sorted(by_chunk.items()): |
| ids = sorted({a for a, _ in pairs}) |
| idlist = ",".join("'" + i.replace("'", "''") + "'" for i in ids) |
| src = f"{hf_src}/arXiv_src_{yymm}_*.parquet" |
| try: |
| tab = con.execute( |
| f"SELECT arxiv_id, caption_images FROM read_parquet('{src}') " |
| f"WHERE arxiv_id IN ({idlist})").fetch_arrow_table() |
| except Exception as e: |
| print(f" chunk {yymm}: fetch failed ({str(e)[:120]}), skipping") |
| continue |
| got = 0 |
| for row in tab.to_pylist(): |
| a = row["arxiv_id"] |
| caps = row["caption_images"] or [] |
| for (aa, f) in pairs: |
| if aa != a or f > len(caps): |
| continue |
| entry = caps[f - 1] or {} |
| cil = entry.get("cil_pairs") or [] |
| img = (cil[0] or {}).get("image") if cil else None |
| data = img.get("bytes") if isinstance(img, dict) else img |
| if not data: |
| continue |
| (image_dir / safe_name(a, f)).write_bytes(data) |
| got += 1 |
| print(f" chunk {yymm}: {got} figures saved") |
|
|
|
|
| if __name__ == "__main__": |
| import argparse |
| import csv |
| ap = argparse.ArgumentParser() |
| ap.add_argument("ids_csv", help="csv with columns arxiv_id,fig_index") |
| ap.add_argument("--image-dir", default="vision_images") |
| args = ap.parse_args() |
| needed = {(r["arxiv_id"], int(r["fig_index"])) |
| for r in csv.DictReader(open(args.ids_csv))} |
| fetch_images(needed, Path(args.image_dir)) |
|
|