Download scripts/run_generate.py from jimchen2/jimchen2-pendulum-111: direct link, hf CLI and curl.
- Browser
- Download file 5.84 kB
-
https://huggingface.co/jimchen2/jimchen2-pendulum-111/resolve/main/scripts/run_generate.py
- Command line
-
hf download hf://jimchen2/jimchen2-pendulum-111/scripts/run_generate.py
-
curl -L -o run_generate.py https://huggingface.co/jimchen2/jimchen2-pendulum-111/resolve/main/scripts/run_generate.py
5.84 kB
| #!/usr/bin/env python3 | |
| """Generate the 3000-video pendulum dataset + CLEVRER-style questions. | |
| Usage: python run_generate.py --root OUT --n-train 2400 --val-per-regime 100 --workers 2 | |
| Resume: re-running the same command skips videos/CSVs that already exist. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import json | |
| import sys | |
| import time | |
| from multiprocessing import Pool | |
| from pathlib import Path | |
| import numpy as np | |
| sys.path.insert(0, str(Path(__file__).parent)) | |
| from pendulum_gen import (FRAMES, FPS, SIZE_PX, REGIMES_VAL, REGIME_DESCRIPTIONS, | |
| generate_one) | |
| from questions import build_questions | |
| ROOT = None | |
| def worker(job): | |
| row, cfg, A = generate_one(job, str(ROOT)) | |
| rng = np.random.default_rng(np.random.SeedSequence([job["seed"], job["index"], 777])) | |
| qs = build_questions(cfg, A, rng) | |
| entry = { | |
| "video_id": job["video_id"], | |
| "video_filename": row["video_path"], | |
| "split": job["split"], | |
| "regime": job["regime"], | |
| "questions": qs, | |
| } | |
| return row, entry | |
| def init_root(root): | |
| global ROOT | |
| ROOT = root | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--root", required=True) | |
| ap.add_argument("--n-train", type=int, default=2400) | |
| ap.add_argument("--val-per-regime", type=int, default=100) | |
| ap.add_argument("--seed", type=int, default=20261014) | |
| ap.add_argument("--workers", type=int, default=2) | |
| args = ap.parse_args() | |
| root = Path(args.root) | |
| root.mkdir(parents=True, exist_ok=True) | |
| jobs = [] | |
| for i in range(args.n_train): | |
| jobs.append({"seed": args.seed, "index": i, "split": "train", | |
| "regime": "train", "video_id": f"video_{i:05d}"}) | |
| k = 0 | |
| for regime in REGIMES_VAL: | |
| for j in range(args.val_per_regime): | |
| jobs.append({"seed": args.seed, "index": 100000 + k, "split": "val", | |
| "regime": regime, "video_id": f"video_{10000 + k:05d}"}) | |
| k += 1 | |
| print(f"{len(jobs)} videos -> {root}", flush=True) | |
| t0 = time.time() | |
| rows, q_entries = {}, {} | |
| done = 0 | |
| with Pool(args.workers, initializer=init_root, initargs=(root,)) as pool: | |
| for row, entry in pool.imap_unordered(worker, jobs, chunksize=4): | |
| rows[row["video_id"]] = row | |
| q_entries[entry["video_id"]] = entry | |
| done += 1 | |
| if done % 25 == 0 or done == len(jobs): | |
| rate = done / (time.time() - t0) | |
| eta = (len(jobs) - done) / max(rate, 1e-9) | |
| print(f"[{done}/{len(jobs)}] {rate:.2f} vid/s eta {eta/60:.1f} min", | |
| flush=True) | |
| # ---- index.csv (train first, then val; same column order as original + extras) | |
| cols = ["video_id", "split", "regime", "video_path", "csv_path", "frames", | |
| "duration_s", "fps", "size_px", "gravity_m_s2", "length_m", "name", | |
| "theta0_deg", "omega0_rad_s", "damping_s_inv", "damping_model", | |
| "quad_drag_beta", "drive_amp_rad_s2", "drive_freq_hz", "drive_phase_rad", | |
| "light_mean_deg", "light_amplitude_deg", "light_frequency_hz", | |
| "light_phase_rad"] | |
| order = sorted(rows, key=lambda v: (0 if rows[v]["split"] == "train" else 1, v)) | |
| with (root / "index.csv").open("w", newline="") as f: | |
| w = csv.DictWriter(f, fieldnames=cols) | |
| w.writeheader() | |
| for v in order: | |
| w.writerow(rows[v]) | |
| # ---- questions | |
| qroot = root / "questions" | |
| qroot.mkdir(exist_ok=True) | |
| for split in ("train", "val"): | |
| entries = [q_entries[v] for v in order if q_entries[v]["split"] == split] | |
| payload = { | |
| "info": { | |
| "description": "CLEVRER-style questions for the C-JEPA pendulum dataset", | |
| "split": split, | |
| "num_videos": len(entries), | |
| "num_questions": sum(len(e["questions"]) for e in entries), | |
| "question_types": ["descriptive", "explanatory", "predictive", | |
| "counterfactual"], | |
| "answer_convention": "descriptive: free-form string; others: index into 'choices'", | |
| "answers_derived_from": "ground-truth RK4 simulation (28 s; videos show first 13.33 s)", | |
| }, | |
| "scene_annotation": entries, | |
| } | |
| with (qroot / f"questions_{split}.json").open("w") as f: | |
| json.dump(payload, f, indent=1) | |
| # ---- metadata.json | |
| meta = { | |
| "dataset": "pendulum_cjepa_clevrer_3k", | |
| "seed": args.seed, | |
| "generation_mode": "random within per-regime bounds", | |
| "num_videos": len(jobs), | |
| "train_videos": args.n_train, | |
| "val_videos": args.val_per_regime * len(REGIMES_VAL), | |
| "val_design": "out-of-distribution physics regimes (NOT just a held-out seed)", | |
| "val_regimes": REGIME_DESCRIPTIONS, | |
| "frames_per_video": FRAMES, | |
| "duration_s": FRAMES / FPS, | |
| "fps": FPS, | |
| "image_size": [SIZE_PX, SIZE_PX], | |
| "codec": "H.264", "pixel_format": "yuv420p", "audio": False, | |
| "ode": "theta_ddot = -(g/L) sin(theta) - gamma*theta_dot - beta*theta_dot*|theta_dot| + A*cos(2*pi*f_d*t+phi)", | |
| "integrator": "RK4", "substeps_per_frame": 8, | |
| "gravity_m_s2": "9.81 (train); {1.62, 3.71, 24.79} (val_gravity)", | |
| "length_m": 1.0, | |
| "split_unit": "whole video", | |
| "normalized_labels": False, | |
| "simulated_seconds_for_questions": 28.0, | |
| "questions_per_video_target": 8, | |
| } | |
| with (root / "metadata.json").open("w") as f: | |
| json.dump(meta, f, indent=2) | |
| n_q = sum(len(e["questions"]) for e in q_entries.values()) | |
| print(f"DONE {len(jobs)} videos, {n_q} questions, " | |
| f"{(time.time()-t0)/60:.1f} min", flush=True) | |
| if __name__ == "__main__": | |
| main() | |