#!/usr/bin/env python3 """Build a self-contained dataset_info.json from 3Deditverse_data. The generated info is compatible with the existing 3DEditFormer editing loader, while the sample set is decided only by files present in the clean encoded/condition-image tree. """ from __future__ import annotations import argparse import json import os from pathlib import Path from typing import Dict, Iterable, Optional, Tuple import numpy as np IMAGE_EXTS = {".png", ".jpg", ".jpeg", ".webp"} def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--clean-root", type=Path, default=Path("/mnt/zsn/zsn_workspace/3DEditFormer/3DEditVerse/3Deditverse_data"), help="Path to 3Deditverse_data.", ) parser.add_argument( "--output", type=Path, default=None, help="Output dataset_info.json path. Defaults to /dataset_info.json.", ) parser.add_argument( "--indent", type=int, default=2, help="JSON indentation. Use 0 for compact JSON.", ) parser.add_argument( "--voxel-count-mode", choices=("constant", "slat"), default="constant", help=( "How to fill ori_voxel_num/edit_voxel_num. " "constant is fast and sufficient for non-sparse SS training; " "slat opens every slat_latent.npz and can be slow." ), ) return parser.parse_args() def iter_dir_names(path: Path) -> Iterable[str]: if not path.is_dir(): return [] with os.scandir(path) as entries: return sorted(entry.name for entry in entries if entry.is_dir(follow_symlinks=False)) def first_existing(paths: Iterable[Path]) -> Optional[Path]: for path in paths: if path.is_file(): return path return None def first_extra_image(path: Path, exclude: set[str]) -> Optional[Path]: if not path.is_dir(): return None with os.scandir(path) as entries: candidates = sorted( Path(entry.path) for entry in entries if entry.is_file(follow_symlinks=False) and Path(entry.name).suffix.lower() in IMAGE_EXTS and entry.name not in exclude ) return candidates[0] if candidates else None def clean_condition_pair(clean_root: Path, branch: str, key: str) -> Optional[Tuple[Path, Path]]: cond_dir = clean_root / "condition-image" / branch / key ori = first_existing([cond_dir / "ori_image.png"]) if branch == "alpaca": edit = first_existing([cond_dir / "after_edited_Flux.png"]) else: edit = first_existing([cond_dir / "edited_0.png"]) if edit is None: edit = first_extra_image(cond_dir, {"ori_image.png"}) if ori is None or edit is None: return None return ori, edit def latent_pair(encoded_dir: Path, key: str, name: str) -> Optional[Tuple[Path, Path]]: ori = encoded_dir / key / "ori" / name edit = encoded_dir / key / "edit" / name if ori.is_file() and edit.is_file(): return ori, edit return None def voxel_count(slat_path: Path) -> int: with np.load(slat_path) as data: if "coords" in data: return int(data["coords"].shape[0]) if "slat_coords" in data: return int(data["slat_coords"].shape[0]) raise KeyError(f"Expected coords or slat_coords in {slat_path}") def rel(path: Path, clean_root: Path) -> str: return path.relative_to(clean_root).as_posix() def build_branch(clean_root: Path, branch: str, encoded_name: str, voxel_count_mode: str) -> Tuple[Dict[str, dict], Dict[str, int]]: encoded_dir = clean_root / "encoded_ouput" / encoded_name condition_dir = clean_root / "condition-image" / branch encoded_keys = set(iter_dir_names(encoded_dir)) condition_keys = set(iter_dir_names(condition_dir)) records: Dict[str, dict] = {} stats = { "encoded_keys": len(encoded_keys), "condition_keys": len(condition_keys), "missing_condition": 0, "missing_ss_latent": 0, "missing_slat_latent": 0, "voxel_read_errors": 0, } for key in sorted(encoded_keys & condition_keys): cond_pair = clean_condition_pair(clean_root, branch, key) if cond_pair is None: stats["missing_condition"] += 1 continue ss_pair = latent_pair(encoded_dir, key, "ss_latent.npz") if ss_pair is None: stats["missing_ss_latent"] += 1 continue slat_pair = latent_pair(encoded_dir, key, "slat_latent.npz") if slat_pair is None: stats["missing_slat_latent"] += 1 continue if voxel_count_mode == "slat": try: ori_voxel_num = voxel_count(slat_pair[0]) edit_voxel_num = voxel_count(slat_pair[1]) except Exception as exc: # keep bad files out of training info stats["voxel_read_errors"] += 1 print(f"[WARN] skip {branch}/{key}: failed to read voxel count: {exc}") continue else: ori_voxel_num = 1 edit_voxel_num = 1 ori_img, edit_img = cond_pair records[key] = { "ori_ss_latents_path": rel(ss_pair[0], clean_root), "edit_ss_latents_path": rel(ss_pair[1], clean_root), "ori_latents_path": rel(slat_pair[0], clean_root), "edit_latents_path": rel(slat_pair[1], clean_root), "ori_img_path": rel(ori_img, clean_root), "edit_img_path": rel(edit_img, clean_root), "ori_voxel_num": ori_voxel_num, "edit_voxel_num": edit_voxel_num, } stats["records"] = len(records) stats["encoded_without_condition_key"] = len(encoded_keys - condition_keys) stats["condition_without_encoded_key"] = len(condition_keys - encoded_keys) return records, stats def main() -> None: args = parse_args() clean_root = args.clean_root.resolve() output = args.output.resolve() if args.output is not None else clean_root / "dataset_info.json" if not clean_root.is_dir(): raise FileNotFoundError(f"clean root not found: {clean_root}") flux, flux_stats = build_branch(clean_root, "flux", "flux_encode", args.voxel_count_mode) alpaca, alpaca_stats = build_branch(clean_root, "alpaca", "alpaca_encode", args.voxel_count_mode) info = { "mixamo": {}, "flux_edit": flux, "alpaca": alpaca, "_meta": { "clean_root": str(clean_root), "source": "generated_from_3Deditverse_data", "voxel_count_mode": args.voxel_count_mode, "branches": { "flux_edit": flux_stats, "alpaca": alpaca_stats, }, "total_editverse_records": len(flux) + len(alpaca), }, } output.parent.mkdir(parents=True, exist_ok=True) indent = None if args.indent == 0 else args.indent output.write_text(json.dumps(info, indent=indent, sort_keys=True) + "\n") print(f"Wrote: {output}") print(f"Flux records: {len(flux)}") print(f"Alpaca records: {len(alpaca)}") print(f"Total records: {len(flux) + len(alpaca)}") print("Stats:") print(json.dumps(info["_meta"]["branches"], indent=2, sort_keys=True)) if __name__ == "__main__": main()