""" Generate the proof-gated planned comparison dataset. The dataset contains one calibration push per episode and a held-out query displacement target under a fixed query force. """ from __future__ import annotations import argparse import json from pathlib import Path import numpy as np from planned_comparison_common import ( OOD_CAMERAS, TRAIN_CAMERAS, generate_episode, ) def write_episode(path: Path, episode: dict) -> None: np.savez_compressed( path, rgb_pre=episode["rgb_pre"], rgb_post=episode["rgb_post"], camera_intrinsics=episode["camera_intrinsics"], camera_extrinsics=episode["camera_extrinsics"], calibration_force=episode["calibration_force"], query_force=episode["query_force"], structured_pre=episode["structured_pre"], structured_post=episode["structured_post"], structured_delta_x=episode["structured_delta_x"], structured_mobility=episode["structured_mobility"], target_query_dx=episode["target_query_dx"], detected_pre_uv=episode["detected_pre_uv"], detected_post_uv=episode["detected_post_uv"], mass=episode["mass"], damping=episode["damping"], gt_pre_world=episode["gt_pre_world"], gt_post_world=episode["gt_post_world"], gt_delta_x=episode["gt_delta_x"], ) def generate_split( rng: np.random.Generator, output_dir: Path, split: str, camera_name: str, count: int, starting_index: int, ) -> tuple[list[dict], int]: manifest_entries: list[dict] = [] index = starting_index extraction_errors = [] for _ in range(count): episode = generate_episode(rng, camera_name) file_name = f"{split}_{camera_name}_{index:05d}.npz" write_episode(output_dir / file_name, episode) extraction_errors.append(abs(float(episode["structured_delta_x"]) - float(episode["gt_delta_x"]))) manifest_entries.append( { "file": file_name, "split": split, "camera_name": camera_name, "material_name": episode["material_name"], "calibration_force": float(episode["calibration_force"]), "query_force": float(episode["query_force"]), "target_query_dx": float(episode["target_query_dx"]), } ) index += 1 if extraction_errors: avg_error = float(np.mean(extraction_errors)) max_error = float(np.max(extraction_errors)) print( f"[generate_planned_comparison_data] split={split} camera={camera_name} " f"count={count} avg_state_dx_error={avg_error:.6f} max_state_dx_error={max_error:.6f}" ) return manifest_entries, index def main() -> None: parser = argparse.ArgumentParser(description="Generate planned comparison episodes.") parser.add_argument("--output_dir", default="training/data/planned_comparison") parser.add_argument("--train_count", type=int, default=300) parser.add_argument("--val_count", type=int, default=80) parser.add_argument("--test_id_count", type=int, default=80) parser.add_argument("--test_ood_per_camera", type=int, default=120) parser.add_argument("--seed", type=int, default=0) args = parser.parse_args() output_dir = Path(args.output_dir) output_dir.mkdir(parents=True, exist_ok=True) rng = np.random.default_rng(args.seed) manifest: list[dict] = [] index = 0 for camera_name in TRAIN_CAMERAS: entries, index = generate_split(rng, output_dir, "train", camera_name, args.train_count, index) manifest.extend(entries) entries, index = generate_split(rng, output_dir, "val", camera_name, args.val_count, index) manifest.extend(entries) entries, index = generate_split(rng, output_dir, "test_id", camera_name, args.test_id_count, index) manifest.extend(entries) for camera_name in OOD_CAMERAS: entries, index = generate_split( rng, output_dir, "test_ood", camera_name, args.test_ood_per_camera, index, ) manifest.extend(entries) with open(output_dir / "manifest.json", "w", encoding="utf-8") as handle: json.dump(manifest, handle, indent=2) metadata = { "seed": args.seed, "train_cameras": list(TRAIN_CAMERAS), "ood_cameras": list(OOD_CAMERAS), "counts": { "train_per_camera": args.train_count, "val_per_camera": args.val_count, "test_id_per_camera": args.test_id_count, "test_ood_per_camera": args.test_ood_per_camera, }, } with open(output_dir / "metadata.json", "w", encoding="utf-8") as handle: json.dump(metadata, handle, indent=2) print(f"[generate_planned_comparison_data] wrote {len(manifest)} episodes to {output_dir}") if __name__ == "__main__": main()