| |
| """ |
| Merge multiple task-specific h=16 datasets into single training-ready dataset. |
| Creates unified manifest and symlinks to shards. |
| """ |
| import json |
| import shutil |
| from pathlib import Path |
| import argparse |
|
|
| def merge_datasets(task_dirs, output_dir): |
| """Merge multiple CIL datasets into one.""" |
| output_dir = Path(output_dir) |
| output_dir.mkdir(parents=True, exist_ok=True) |
|
|
| |
| all_groups = [] |
| all_group_index = [] |
| all_record_index = [] |
| total_records = 0 |
| shard_counter = 0 |
|
|
| for task_dir in task_dirs: |
| task_path = Path(task_dir) |
| manifest_path = task_path / "manifest.json" |
|
|
| if not manifest_path.exists(): |
| print(f"⚠️ Skipping {task_path.name} (no manifest)") |
| continue |
|
|
| print(f"Processing {task_path.name}...") |
| manifest = json.loads(manifest_path.read_text()) |
|
|
| |
| for shard_info in manifest.get("shards", []): |
| shard_file = task_path / shard_info["path"] |
| if shard_file.exists(): |
| dest = output_dir / f"shard_{shard_counter:04d}.jsonl" |
| shutil.copy2(shard_file, dest) |
| shard_counter += 1 |
|
|
| |
| if not manifest.get("shards"): |
| for shard_file in task_path.glob("*.shard"): |
| dest = output_dir / f"shard_{shard_counter:04d}.jsonl" |
| shutil.copy2(shard_file, dest) |
| shard_counter += 1 |
|
|
| |
| group_index_path = task_path / manifest.get("group_index_path", "group_index.jsonl") |
| if group_index_path.exists(): |
| with open(group_index_path) as f: |
| for line in f: |
| if line.strip(): |
| entry = json.loads(line) |
| all_groups.append(entry["group_id"]) |
| all_group_index.append(entry) |
|
|
| |
| record_index_path = task_path / manifest.get("record_index_path", "record_index.jsonl") |
| if record_index_path.exists(): |
| with open(record_index_path) as f: |
| for line in f: |
| if line.strip(): |
| all_record_index.append(json.loads(line)) |
|
|
| |
| total_records += manifest["record_count"] |
|
|
| print(f" ✅ {manifest['group_count']} groups, {manifest['record_count']} records") |
|
|
| |
| group_index_out = output_dir / "group_index.jsonl" |
| with open(group_index_out, 'w') as f: |
| for entry in all_group_index: |
| f.write(json.dumps(entry) + '\n') |
|
|
| record_index_out = output_dir / "record_index.jsonl" |
| with open(record_index_out, 'w') as f: |
| for entry in all_record_index: |
| f.write(json.dumps(entry) + '\n') |
|
|
| |
| shard_list = [{"path": f"shard_{i:04d}.jsonl"} for i in range(shard_counter)] |
|
|
| merged_manifest = { |
| "backend": "maniskill", |
| "created_at": Path(task_dirs[0]).joinpath("manifest.json").stat().st_mtime if task_dirs else 0, |
| "dataset_name": "cil_h16_merged", |
| "format": "dovla_cil", |
| "group_count": len(all_groups), |
| "group_index_path": "group_index.jsonl", |
| "index_format": "jsonl", |
| "k": 16, |
| "num_groups": len(all_groups), |
| "num_records": total_records, |
| "record_count": total_records, |
| "record_index_path": "record_index.jsonl", |
| "schema_version": "0.1", |
| "shard_count": shard_counter, |
| "shard_format": "jsonl", |
| "shards": shard_list, |
| "horizon": 16, |
| "tasks": [Path(d).name for d in task_dirs if (Path(d) / "manifest.json").exists()] |
| } |
|
|
| manifest_out = output_dir / "manifest.json" |
| manifest_out.write_text(json.dumps(merged_manifest, indent=2)) |
|
|
| print("\n" + "="*60) |
| print(f"✅ Merged dataset created: {output_dir}") |
| print(f" Total groups: {merged_manifest['group_count']}") |
| print(f" Total records: {merged_manifest['record_count']}") |
| print(f" Shards: {merged_manifest['shard_count']}") |
| print(f" Tasks: {', '.join(merged_manifest['tasks'])}") |
| print("="*60) |
|
|
| return merged_manifest |
|
|
| def main(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--input", required=True, help="Parent dir containing task subdirs") |
| parser.add_argument("--output", required=True, help="Output merged dataset dir") |
| args = parser.parse_args() |
|
|
| input_path = Path(args.input) |
| task_dirs = [d for d in input_path.iterdir() if d.is_dir() and (d / "manifest.json").exists()] |
|
|
| if not task_dirs: |
| print(f"❌ No valid task directories found in {input_path}") |
| return 1 |
|
|
| print(f"Found {len(task_dirs)} tasks to merge:") |
| for d in task_dirs: |
| print(f" - {d.name}") |
| print() |
|
|
| merge_datasets(task_dirs, args.output) |
| return 0 |
|
|
| if __name__ == "__main__": |
| import sys |
| sys.exit(main()) |
|
|