#!/usr/bin/env python3 """ Download ALL training data with REAL images for ArcisVLM. This is the SINGLE entry point for data preparation. Run this before ANY training. Every dataset downloads actual photographs — no dummies, no noise, no fallbacks. Usage: python3 scripts/download_all_data.py # Download all stages python3 scripts/download_all_data.py --stage 1 # Stage 1 only python3 scripts/download_all_data.py --stage 2 # Stage 2 only python3 scripts/download_all_data.py --stage 3 # Stage 3 only python3 scripts/download_all_data.py --max-per-dataset 10000 # Cap per dataset (for testing) Disk requirements: Stage 1: ~50GB (500K image-caption pairs) Stage 2: ~150GB (2M VQA samples with images) Stage 3: ~20GB (200K detection/surveillance) Total: ~220GB """ import argparse import json import os import sys import time from pathlib import Path def _save_image(image, path): """Save a PIL image to disk. Returns True on success.""" try: os.makedirs(os.path.dirname(path), exist_ok=True) if hasattr(image, 'save'): image.convert("RGB").save(path, quality=85) return True except Exception: return False return False def _download_hf_dataset(name, hf_repo, split, output_dir, max_samples, q_key=None, a_key=None, img_key="image", config_name=None, caption_mode=False): """Download a HuggingFace dataset with real images to JSONL.""" jsonl_path = os.path.join(output_dir, f"{name}.jsonl") img_dir = os.path.join(output_dir, "images", name) # Resume support: check existing if os.path.exists(jsonl_path): with open(jsonl_path) as f: existing = sum(1 for _ in f) if existing >= max_samples * 0.9: print(f" [{name}] Already have {existing} samples, skipping") return existing print(f" [{name}] Downloading from {hf_repo} ({split}, max {max_samples})...") os.makedirs(img_dir, exist_ok=True) from datasets import load_dataset kwargs = {"streaming": True} if config_name: ds = load_dataset(hf_repo, config_name, split=split, **kwargs) else: ds = load_dataset(hf_repo, split=split, **kwargs) count = 0 skipped_no_image = 0 with open(jsonl_path, "w") as f: for item in ds: if count >= max_samples: break # Extract text if caption_mode: # Image captioning format question = "Describe this image." answer = item.get("caption", item.get("text", item.get("sentence", ""))) if isinstance(answer, list): answer = answer[0] if answer else "" elif q_key and a_key: question = item.get(q_key, "") answer = item.get(a_key, "") if isinstance(answer, list): if answer and isinstance(answer[0], dict): answer = answer[0].get("answer", str(answer[0])) elif answer: answer = str(answer[0]) else: answer = "" else: continue if not answer: continue # Get and save image — REQUIRED, skip if no image image = item.get(img_key) if image is None: skipped_no_image += 1 continue img_filename = f"{name}_{count:07d}.jpg" img_path = os.path.join(img_dir, img_filename) if not _save_image(image, img_path): skipped_no_image += 1 continue sample = { "question": str(question).strip(), "answer": str(answer).strip(), "image_path": img_path, "dataset": name, } f.write(json.dumps(sample) + "\n") count += 1 if count % 5000 == 0: print(f" [{name}] {count:,} samples (skipped {skipped_no_image} without images)...") print(f" [{name}] Done: {count:,} samples saved (skipped {skipped_no_image})") return count def _download_coco_detection(output_dir, max_samples): """Download COCO detection dataset with real images and human-readable category names.""" # COCO category ID → human-readable name mapping COCO_CATEGORIES = { 0: "person", 1: "bicycle", 2: "car", 3: "motorcycle", 4: "airplane", 5: "bus", 6: "train", 7: "truck", 8: "boat", 9: "traffic light", 10: "fire hydrant", 11: "stop sign", 12: "parking meter", 13: "bench", 14: "bird", 15: "cat", 16: "dog", 17: "horse", 18: "sheep", 19: "cow", 20: "elephant", 21: "bear", 22: "zebra", 23: "giraffe", 24: "backpack", 25: "umbrella", 26: "handbag", 27: "tie", 28: "suitcase", 29: "frisbee", 30: "skis", 31: "snowboard", 32: "sports ball", 33: "kite", 34: "baseball bat", 35: "baseball glove", 36: "skateboard", 37: "surfboard", 38: "tennis racket", 39: "bottle", 40: "wine glass", 41: "cup", 42: "fork", 43: "knife", 44: "spoon", 45: "bowl", 46: "banana", 47: "apple", 48: "sandwich", 49: "orange", 50: "broccoli", 51: "carrot", 52: "hot dog", 53: "pizza", 54: "donut", 55: "cake", 56: "chair", 57: "couch", 58: "potted plant", 59: "bed", 60: "dining table", 61: "toilet", 62: "tv", 63: "laptop", 64: "mouse", 65: "remote", 66: "keyboard", 67: "cell phone", 68: "microwave", 69: "oven", 70: "toaster", 71: "sink", 72: "refrigerator", 73: "book", 74: "clock", 75: "vase", 76: "scissors", 77: "teddy bear", 78: "hair drier", 79: "toothbrush", } name = "coco_detect" jsonl_path = os.path.join(output_dir, f"{name}.jsonl") img_dir = os.path.join(output_dir, "images", name) os.makedirs(img_dir, exist_ok=True) if os.path.exists(jsonl_path): with open(jsonl_path) as f: existing = sum(1 for _ in f) if existing >= max_samples * 0.9: print(f" [{name}] Already have {existing} samples, skipping") return existing print(f" [{name}] Downloading from detection-datasets/coco (max {max_samples})...") from datasets import load_dataset ds = load_dataset("detection-datasets/coco", split="train", streaming=True) count = 0 with open(jsonl_path, "w") as f: for item in ds: if count >= max_samples: break image = item.get("image") if image is None: continue # Extract object categories as HUMAN-READABLE NAMES (not integer IDs!) objects = item.get("objects", {}) categories = objects.get("category", []) if isinstance(objects, dict) else [] if isinstance(categories, list) and categories: # Map IDs to names, deduplicate, preserve order names = [] seen = set() for c in categories[:15]: name_str = COCO_CATEGORIES.get(int(c), f"object") if name_str not in seen: names.append(name_str) seen.add(name_str) caption = ", ".join(names) else: caption = "a photograph" img_filename = f"coco_detect_{count:07d}.jpg" img_path = os.path.join(img_dir, img_filename) if not _save_image(image, img_path): continue # Diverse questions for richer training import random q_templates = [ f"What objects are in this image?", f"Describe what you see.", f"List the objects visible in this scene.", f"What is in this photograph?", f"How many objects can you identify?", ] question = q_templates[count % len(q_templates)] # Rich answer format if len(names) == 1: answer = f"I can see a {names[0]}." elif len(names) == 2: answer = f"I can see a {names[0]} and a {names[1]}." else: answer = f"I can see {', '.join(names[:-1])}, and {names[-1]}." sample = { "question": question, "answer": answer, "image_path": img_path, "dataset": "coco_detect", } f.write(json.dumps(sample) + "\n") count += 1 if count % 5000 == 0: print(f" [coco_detect] {count:,} samples...") print(f" [{name}] Done: {count:,} samples") return count def _download_scienceqa(output_dir, max_samples): """Download ScienceQA with real images.""" name = "scienceqa" jsonl_path = os.path.join(output_dir, f"{name}.jsonl") img_dir = os.path.join(output_dir, "images", name) os.makedirs(img_dir, exist_ok=True) if os.path.exists(jsonl_path): with open(jsonl_path) as f: existing = sum(1 for _ in f) if existing >= max_samples * 0.9: print(f" [{name}] Already have {existing} samples, skipping") return existing print(f" [{name}] Downloading from derek-thomas/ScienceQA (max {max_samples})...") from datasets import load_dataset ds = load_dataset("derek-thomas/ScienceQA", split="train", streaming=True) count = 0 skipped = 0 with open(jsonl_path, "w") as f: for item in ds: if count >= max_samples: break image = item.get("image") if image is None: skipped += 1 continue question = item.get("question", "") answer_idx = item.get("answer", 0) choices = item.get("choices", []) # Map answer index to text if isinstance(answer_idx, int) and choices and answer_idx < len(choices): answer = str(choices[answer_idx]) else: answer = str(answer_idx) if not question or not answer: continue img_filename = f"{name}_{count:07d}.jpg" img_path = os.path.join(img_dir, img_filename) if not _save_image(image, img_path): continue sample = { "question": question, "answer": answer, "image_path": img_path, "dataset": name, } f.write(json.dumps(sample) + "\n") count += 1 if count % 2000 == 0: print(f" [{name}] {count:,} samples (skipped {skipped} without images)...") print(f" [{name}] Done: {count:,} samples (skipped {skipped})") return count def download_stage1(output_dir, max_per_dataset): """Download Stage 1 JEPA pretraining data (image-caption pairs with real photos).""" print("\n" + "=" * 60) print("STAGE 1: JEPA Pretraining Data (image-caption pairs)") print("=" * 60) os.makedirs(output_dir, exist_ok=True) total = 0 datasets = [ # COCO detection — 120K real photos (extract object list as caption) {"name": "coco_detect", "hf_repo": "detection-datasets/coco", "split": "train", "target": 120000, "custom_loader": "coco"}, # ScienceQA — 12K real images with questions {"name": "scienceqa", "hf_repo": "derek-thomas/ScienceQA", "split": "train", "target": 12000, "custom_loader": "scienceqa"}, ] for ds in datasets: target = min(ds["target"], max_per_dataset) if max_per_dataset else ds["target"] custom = ds.get("custom_loader") try: if custom == "coco": count = _download_coco_detection(output_dir, target) elif custom == "scienceqa": count = _download_scienceqa(output_dir, target) else: count = _download_hf_dataset( name=ds["name"], hf_repo=ds["hf_repo"], split=ds["split"], output_dir=output_dir, max_samples=target, img_key=ds.get("img_key", "image"), config_name=ds.get("config_name"), caption_mode=ds.get("caption_mode", False), ) total += count except Exception as e: print(f" [{ds['name']}] FAILED: {e}") print(f"\nStage 1 total: {total:,} samples") return total def _download_coco_captions(output_dir, max_samples): """Download COCO images with FULL SENTENCE captions (for Caption agent).""" name = "coco_captions" jsonl_path = os.path.join(output_dir, f"{name}.jsonl") img_dir = os.path.join(output_dir, "images", name) os.makedirs(img_dir, exist_ok=True) if os.path.exists(jsonl_path): with open(jsonl_path) as f: existing = sum(1 for _ in f) if existing >= max_samples * 0.9: print(f" [{name}] Already have {existing} samples, skipping") return existing # Same COCO dataset but generate descriptive captions instead of object lists COCO_CATS = { 0: "person", 1: "bicycle", 2: "car", 3: "motorcycle", 4: "airplane", 5: "bus", 6: "train", 7: "truck", 8: "boat", 9: "traffic light", 10: "fire hydrant", 11: "stop sign", 12: "parking meter", 13: "bench", 14: "bird", 15: "cat", 16: "dog", 17: "horse", 18: "sheep", 19: "cow", 20: "elephant", 21: "bear", 22: "zebra", 23: "giraffe", 24: "backpack", 25: "umbrella", 26: "handbag", 27: "tie", 28: "suitcase", 29: "frisbee", 30: "skis", 31: "snowboard", 32: "sports ball", 33: "kite", 34: "baseball bat", 35: "baseball glove", 36: "skateboard", 37: "surfboard", 38: "tennis racket", 39: "bottle", 40: "wine glass", 41: "cup", 42: "fork", 43: "knife", 44: "spoon", 45: "bowl", 46: "banana", 47: "apple", 48: "sandwich", 49: "orange", 50: "broccoli", 51: "carrot", 52: "hot dog", 53: "pizza", 54: "donut", 55: "cake", 56: "chair", 57: "couch", 58: "potted plant", 59: "bed", 60: "dining table", 61: "toilet", 62: "tv", 63: "laptop", 64: "mouse", 65: "remote", 66: "keyboard", 67: "cell phone", 68: "microwave", 69: "oven", 70: "toaster", 71: "sink", 72: "refrigerator", 73: "book", 74: "clock", 75: "vase", 76: "scissors", 77: "teddy bear", 78: "hair drier", 79: "toothbrush", } import random print(f" [{name}] Generating descriptive captions from COCO (max {max_samples})...") from datasets import load_dataset ds = load_dataset("detection-datasets/coco", split="train", streaming=True) count = 0 templates = [ "This image shows {scene}.", "A scene containing {scene}.", "In this photograph, there are {scene}.", "The image depicts {scene}.", "Visible in this scene: {scene}.", ] with open(jsonl_path, "w") as f: for item in ds: if count >= max_samples: break image = item.get("image") if image is None: continue objects = item.get("objects", {}) categories = objects.get("category", []) if isinstance(objects, dict) else [] if not categories: continue names = list(dict.fromkeys(COCO_CATS.get(int(c), "object") for c in categories[:10])) # Count objects from collections import Counter cat_counts = Counter(COCO_CATS.get(int(c), "object") for c in categories) parts = [] for obj, cnt in cat_counts.most_common(5): if cnt == 1: parts.append(f"a {obj}") else: parts.append(f"{cnt} {obj}s") if len(parts) > 1: scene = ", ".join(parts[:-1]) + f", and {parts[-1]}" else: scene = parts[0] caption = random.choice(templates).format(scene=scene) img_filename = f"{name}_{count:07d}.jpg" img_path = os.path.join(img_dir, img_filename) if not _save_image(image, img_path): continue f.write(json.dumps({ "question": "Describe this image in detail.", "answer": caption, "image_path": img_path, "dataset": name, }) + "\n") count += 1 if count % 5000 == 0: print(f" [{name}] {count:,}...") print(f" [{name}] Done: {count:,}") return count def _download_coco_count(output_dir, max_samples): """Derive counting data from COCO detection (for Count agent).""" name = "coco_count" jsonl_path = os.path.join(output_dir, f"{name}.jsonl") img_dir = os.path.join(output_dir, "images", name) os.makedirs(img_dir, exist_ok=True) if os.path.exists(jsonl_path): with open(jsonl_path) as f: existing = sum(1 for _ in f) if existing >= max_samples * 0.9: print(f" [{name}] Already have {existing} samples, skipping") return existing COCO_CATS = { 0: "person", 1: "bicycle", 2: "car", 3: "motorcycle", 4: "airplane", 5: "bus", 6: "train", 7: "truck", 8: "boat", 9: "traffic light", 14: "bird", 15: "cat", 16: "dog", 17: "horse", 18: "sheep", 19: "cow", 20: "elephant", 22: "zebra", 23: "giraffe", 56: "chair", 57: "couch", 59: "bed", 60: "dining table", } import random print(f" [{name}] Generating counting data from COCO (max {max_samples})...") from datasets import load_dataset ds = load_dataset("detection-datasets/coco", split="train", streaming=True) count = 0 q_templates = [ "How many {obj} are in this image?", "Count the {obj}.", "How many {obj} can you see?", ] with open(jsonl_path, "w") as f: for item in ds: if count >= max_samples: break image = item.get("image") if image is None: continue objects = item.get("objects", {}) categories = objects.get("category", []) if isinstance(objects, dict) else [] if not categories: continue from collections import Counter cat_counts = Counter(COCO_CATS.get(int(c)) for c in categories if int(c) in COCO_CATS) if not cat_counts: continue img_filename = f"{name}_{count:07d}.jpg" img_path = os.path.join(img_dir, img_filename) if not _save_image(image, img_path): continue # Generate counting Q&A for the most common object obj, cnt = cat_counts.most_common(1)[0] question = random.choice(q_templates).format(obj=obj) f.write(json.dumps({ "question": question, "answer": str(cnt), "image_path": img_path, "dataset": name, }) + "\n") count += 1 if count % 5000 == 0: print(f" [{name}] {count:,}...") print(f" [{name}] Done: {count:,}") return count def _generate_surveillance_qa(output_dir, max_samples): """Generate surveillance-domain Q&A for Alert + Reason agents.""" name = "surveillance_qa" jsonl_path = os.path.join(output_dir, f"{name}.jsonl") if os.path.exists(jsonl_path): with open(jsonl_path) as f: existing = sum(1 for _ in f) if existing >= max_samples * 0.9: print(f" [{name}] Already have {existing} samples, skipping") return existing import random print(f" [{name}] Generating surveillance Q&A (max {max_samples})...") locations = ["parking lot", "lobby", "hallway", "entrance", "warehouse", "street", "intersection", "loading dock", "stairwell", "elevator area", "gate", "rooftop"] objects = ["person", "car", "truck", "bicycle", "motorcycle", "bus", "dog", "backpack", "suitcase", "umbrella", "skateboard"] actions = ["walking", "running", "standing", "sitting", "carrying a bag", "talking on phone", "entering", "exiting", "loitering", "crossing the road"] times = ["daytime", "nighttime", "dawn", "dusk", "overcast conditions"] severities = ["LOW", "MEDIUM", "HIGH", "CRITICAL"] alert_scenarios = [ ("Person detected in restricted area near {loc}.", "HIGH", "Unauthorized access"), ("Unattended bag found near {loc}.", "HIGH", "Suspicious object"), ("Person loitering at {loc} for extended period.", "MEDIUM", "Loitering"), ("Vehicle parked in no-parking zone at {loc}.", "LOW", "Parking violation"), ("Person running in {loc}.", "MEDIUM", "Unusual behavior"), ("Group gathering detected at {loc}.", "LOW", "Crowd formation"), ("Person climbing fence near {loc}.", "HIGH", "Trespassing attempt"), ("Fight detected between two people at {loc}.", "CRITICAL", "Violence"), ("Person fallen on ground at {loc}.", "CRITICAL", "Fall detection"), ("Smoke detected near {loc}.", "CRITICAL", "Fire hazard"), ("No anomalies detected. Normal activity at {loc}.", "LOW", "All clear"), ("Tailgating detected at {loc} entrance.", "MEDIUM", "Access control violation"), ] count = 0 with open(jsonl_path, "w") as f: for _ in range(max_samples): loc = random.choice(locations) scenario = random.choice(alert_scenarios) description = scenario[0].format(loc=loc) severity = scenario[1] category = scenario[2] # Alert format if random.random() < 0.4: question = "Is there any security concern in this scene?" answer = f"ALERT: {description} Severity: {severity}. Category: {category}." # Reason format elif random.random() < 0.7: n_people = random.randint(0, 8) n_vehicles = random.randint(0, 5) time = random.choice(times) question = "Analyze this scene and describe what you observe." answer = (f"Scene analysis: The {loc} during {time}. " f"I observe {n_people} people and {n_vehicles} vehicles. " f"{description}") # Count format else: n = random.randint(0, 15) obj = random.choice(objects) question = f"How many {obj}s are in this scene?" answer = str(n) f.write(json.dumps({ "question": question, "answer": answer, "dataset": name, }) + "\n") count += 1 print(f" [{name}] Done: {count:,} (text-only, no images)") return count def _download_ocrvqa(output_dir, max_samples): """Download OCR-VQA with proper question-answer extraction. OCR-VQA has 'questions' (list) and 'answers' (list), not singular fields. Each image has ~5 Q&A pairs about book covers. """ name = "ocrvqa" jsonl_path = os.path.join(output_dir, f"{name}.jsonl") img_dir = os.path.join(output_dir, "images", name) os.makedirs(img_dir, exist_ok=True) if os.path.exists(jsonl_path): with open(jsonl_path) as f: existing = sum(1 for _ in f) if existing >= max_samples * 0.9: print(f" [{name}] Already have {existing} samples, skipping") return existing print(f" [{name}] Downloading from howard-hou/OCR-VQA (max {max_samples})...") from datasets import load_dataset ds = load_dataset("howard-hou/OCR-VQA", split="train", streaming=True) count = 0 with open(jsonl_path, "w") as f: for item in ds: if count >= max_samples: break image = item.get("image") if image is None: continue # Extract Q&A pairs from LISTS questions = item.get("questions", []) answers = item.get("answers", []) if not questions or not answers: continue img_filename = f"{name}_{count:07d}.jpg" img_path = os.path.join(img_dir, img_filename) if not _save_image(image, img_path): continue # Write each Q&A pair as a separate sample for q, a in zip(questions, answers): if q and a and len(q) > 3 and len(a) > 0: f.write(json.dumps({ "question": str(q), "answer": str(a), "image_path": img_path, "dataset": name, }) + "\n") count += 1 if count >= max_samples: break if count % 5000 == 0: print(f" [{name}] {count:,}...") print(f" [{name}] Done: {count:,}") return count def _download_aokvqa(output_dir, max_samples): """Download A-OKVQA with proper answer extraction. A-OKVQA 'direct_answers' is a LIST of 10 annotator answers. We take the most common answer (majority vote). """ name = "aokvqa" jsonl_path = os.path.join(output_dir, f"{name}.jsonl") img_dir = os.path.join(output_dir, "images", name) os.makedirs(img_dir, exist_ok=True) if os.path.exists(jsonl_path): with open(jsonl_path) as f: existing = sum(1 for _ in f) if existing >= max_samples * 0.9: print(f" [{name}] Already have {existing} samples, skipping") return existing print(f" [{name}] Downloading from HuggingFaceM4/A-OKVQA (max {max_samples})...") from datasets import load_dataset from collections import Counter ds = load_dataset("HuggingFaceM4/A-OKVQA", split="train", streaming=True) count = 0 with open(jsonl_path, "w") as f: for item in ds: if count >= max_samples: break image = item.get("image") if image is None: continue question = item.get("question", "") direct_answers = item.get("direct_answers", []) if not question or not direct_answers: continue # Take majority vote answer (most common in the list) if isinstance(direct_answers, list): answer_counts = Counter(str(a) for a in direct_answers if a) if answer_counts: answer = answer_counts.most_common(1)[0][0] else: continue else: answer = str(direct_answers) if not answer or len(answer) < 1: continue img_filename = f"{name}_{count:07d}.jpg" img_path = os.path.join(img_dir, img_filename) if not _save_image(image, img_path): continue f.write(json.dumps({ "question": str(question), "answer": answer, "image_path": img_path, "dataset": name, }) + "\n") count += 1 if count % 5000 == 0: print(f" [{name}] {count:,}...") print(f" [{name}] Done: {count:,}") return count def download_stage2(output_dir, max_per_dataset): """Download Stage 2 instruction tuning data (VQA with real images).""" print("\n" + "=" * 60) print("STAGE 2: Instruction Tuning Data (VQA with real images)") print("=" * 60) os.makedirs(output_dir, exist_ok=True) total = 0 datasets = [ # VQA agent: short factual answers {"name": "vqav2", "hf_repo": "merve/vqav2-small", "split": "validation", "q_key": "question", "a_key": "multiple_choice_answer", "target": 40000, "img_key": "image"}, # VQA + Reason: scene graph spatial reasoning {"name": "gqa", "hf_repo": "lmms-lab/GQA", "split": "train", "q_key": "question", "a_key": "answer", "target": 200000}, # OCR agent: text in natural images {"name": "textvqa", "hf_repo": "lmms-lab/textvqa", "split": "train", "q_key": "question", "a_key": "answers", "target": 34000}, # OCR agent: document/book text (fields are "questions" and "answers" LISTS) {"name": "ocrvqa", "hf_repo": "howard-hou/OCR-VQA", "split": "train", "target": 207000, "custom_loader": "ocrvqa"}, # Reason agent: knowledge-requiring questions (direct_answers is a LIST) {"name": "aokvqa", "hf_repo": "HuggingFaceM4/A-OKVQA", "split": "train", "target": 17000, "custom_loader": "aokvqa"}, # VQA + Reason: science questions with images {"name": "scienceqa", "hf_repo": "derek-thomas/ScienceQA", "split": "train", "target": 12000, "custom_loader": "scienceqa"}, # Caption agent: COCO detection images with FULL SENTENCE captions {"name": "coco_captions", "hf_repo": "detection-datasets/coco", "split": "train", "target": 50000, "custom_loader": "coco_captions"}, # Count agent: derived counting data from COCO {"name": "coco_count", "hf_repo": "detection-datasets/coco", "split": "train", "target": 50000, "custom_loader": "coco_count"}, # NOTE: surveillance_qa is TEXT-ONLY (no images) — moved to Stage 3 # Do NOT include text-only data in Stage 2 (zero-tolerance: crashes on missing images) ] for ds in datasets: target = min(ds["target"], max_per_dataset) if max_per_dataset else ds["target"] custom = ds.get("custom_loader") try: if custom == "scienceqa": count = _download_scienceqa(output_dir, target) elif custom == "coco": count = _download_coco_detection(output_dir, target) elif custom == "coco_captions": count = _download_coco_captions(output_dir, target) elif custom == "coco_count": count = _download_coco_count(output_dir, target) elif custom == "surveillance_qa": count = _generate_surveillance_qa(output_dir, target) elif custom == "ocrvqa": count = _download_ocrvqa(output_dir, target) elif custom == "aokvqa": count = _download_aokvqa(output_dir, target) else: count = _download_hf_dataset( name=ds["name"], hf_repo=ds["hf_repo"], split=ds["split"], output_dir=output_dir, max_samples=target, q_key=ds.get("q_key"), a_key=ds.get("a_key"), img_key=ds.get("img_key", "image"), config_name=ds.get("config_name"), ) total += count except Exception as e: print(f" [{ds['name']}] FAILED: {e}") print(f"\nStage 2 total: {total:,} samples") return total def download_stage3(output_dir, max_per_dataset): """Download Stage 3 domain fine-tuning data.""" print("\n" + "=" * 60) print("STAGE 3: Domain Fine-Tuning Data (detection/surveillance)") print("=" * 60) # Use existing download_stage3_data.py sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) try: from download_stage3_data import main as download_stage3_main # Temporarily set output dir import download_stage3_data old_main = download_stage3_data.main download_stage3_data.main = lambda: None # Prevent double execution from download_stage3_data import ( download_coco_detection, download_visdrone, download_activitynet_captions, download_ucf_crime, download_surveillance_vqa ) os.makedirs(output_dir, exist_ok=True) total = 0 cap = max_per_dataset or 200000 total += download_coco_detection(output_dir, min(118000, cap)) total += download_visdrone(output_dir, min(10000, cap)) total += download_activitynet_captions(output_dir, min(100000, cap)) total += download_ucf_crime(output_dir, min(1900, cap)) total += download_surveillance_vqa(output_dir, min(50000, cap)) print(f"\nStage 3 total: {total:,} samples") return total except ImportError: print(" [WARN] download_stage3_data.py not importable, running as subprocess") import subprocess subprocess.run([sys.executable, "scripts/download_stage3_data.py"], check=True) return 0 def validate_data(data_dir, stage_name, min_samples=100): """Validate downloaded data has real images.""" jsonl_files = list(Path(data_dir).glob("*.jsonl")) if not jsonl_files: raise RuntimeError(f"No JSONL files found in {data_dir}") # Count total samples across all JSONL files total = 0 for jf in jsonl_files: with open(jf) as f: total += sum(1 for _ in f) # Spot-check that images exist for a sample of entries images_checked = 0 images_found = 0 for jf in jsonl_files: with open(jf) as f: for line in f: item = json.loads(line) if "image_path" in item: images_checked += 1 if os.path.exists(item["image_path"]): images_found += 1 if images_checked >= 50: break if images_checked >= 50: break print(f"\n [{stage_name}] Validation: {total} total samples, {images_found}/{images_checked} spot-checked have real images") if total < min_samples: raise RuntimeError(f"FATAL: Only {total} samples in {data_dir}. Need at least {min_samples}.") if images_found == 0: raise RuntimeError(f"FATAL: No real images found in spot check of {data_dir}.") print(f" [{stage_name}] PASSED") def main(): parser = argparse.ArgumentParser(description="Download ALL ArcisVLM Training Data") parser.add_argument("--stage", type=int, choices=[1, 2, 3], default=None, help="Download specific stage only (default: all)") parser.add_argument("--max-per-dataset", type=int, default=None, help="Cap samples per dataset (for testing)") parser.add_argument("--skip-validation", action="store_true") args = parser.parse_args() start = time.time() print("=" * 60) print("ArcisVLM — Download ALL Training Data (REAL images only)") print("=" * 60) stages = [args.stage] if args.stage else [1, 2, 3] if 1 in stages: download_stage1("data/downloads/stage1", args.max_per_dataset) if not args.skip_validation: validate_data("data/downloads/stage1", "Stage 1") if 2 in stages: download_stage2("data/downloads/stage2_fullscale", args.max_per_dataset) if not args.skip_validation: validate_data("data/downloads/stage2_fullscale", "Stage 2") if 3 in stages: download_stage3("data/downloads/stage3", args.max_per_dataset) if not args.skip_validation: validate_data("data/downloads/stage3", "Stage 3") elapsed = time.time() - start print(f"\n{'=' * 60}") print(f"Download complete in {elapsed/60:.0f} minutes") print(f"{'=' * 60}") if __name__ == "__main__": main()