3Deditformer / scripts /test_gs_only_h3d_bench.py
zhaxie's picture
Upload 3DEditFormer source code
2274e38 verified
Raw
History Blame Contribute Delete
10.5 kB
#!/usr/bin/env python3
"""
GS-only eval for H3D mini139-style bench: run sparse-structure + SLAT inference, decode Gaussians,
and save multiview GS renders under a *separate* tree (`work_dirs/eval_gs_only/...` by default).
Does not write GLB/mesh, Blender `render/`, or the standard `work_dirs/eval_results/<save_name>/` layout.
Typical "h3d_only + EMA + 60k" — point checkpoint roots at your **h3d_only** training outputs:
export TRAINED_SS_DIR=/path/to/h3d_only_img_to_voxel
export TRAINED_SLAT_DIR=/path/to/h3d_only_voxel_to_texture
/root/miniconda3/envs/vinedresser3d/bin/python scripts/test_gs_only_h3d_bench.py \
--ss_latents_load_dir "${TRAINED_SS_DIR}" \
--latents_load_dir "${TRAINED_SLAT_DIR}" \
--ss_latents_load_ckpt 60000 \
--latents_load_ckpt 60000 \
--load_ema_model_for_inference \
--run_name h3d_only_ema_step60000_mini139_gs \
--end_idx 20
"""
from __future__ import annotations
import argparse
import json
import math
import os
import sys
import time
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parents[1]
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
os.chdir(_REPO_ROOT)
import tqdm # noqa: E402
from eval_3d_editing import TrellisEdititngModel, set_seed # noqa: E402
def _default_from_env(key: str, fallback: str | None) -> str | None:
v = os.environ.get(key)
return v if v else fallback
def build_argparser() -> argparse.ArgumentParser:
bench = "/mnt/zsn/zsn_workspace/PartCraft3D/H3D_v1_hf/data/test_data/h3d_manual_keep_mini139_unique_3deditformer_style"
p = argparse.ArgumentParser(description="H3D bench — GS multiview only (separate output_root).")
p.add_argument(
"--dataset_root_dir",
type=str,
default=bench,
help="Bench root (contains dataset_info.json + select json).",
)
p.add_argument("--data_info_json_path", type=str, default="dataset_info.json")
p.add_argument(
"--select_json_path",
type=str,
default="test_data_info_3deditformer.json",
help="Same as full-bench eval (`run_h3d_manual_mini140_compare_tmux.sh`).",
)
p.add_argument("--flux_edit_root_path", type=str, default="flux_edit")
p.add_argument("--alpaca_root_path", type=str, default="alpaca")
p.add_argument("--mixamo_root_path", type=str, default="mixamo")
p.add_argument(
"--output_root",
type=str,
default="./work_dirs/eval_gs_only",
help="Root for outputs (does not touch work_dirs/eval_results/).",
)
p.add_argument(
"--run_name",
type=str,
default="h3d_only_ema_step60000_mini139_gs",
help="Subfolder under output_root.",
)
p.add_argument(
"--ss_latents_config",
type=str,
default="ss_flow_img_dit_L_16l8_fp16_h3d_add_del_mod_scale_plus_3deditverse_clean_40k.json",
)
p.add_argument(
"--latents_config",
type=str,
default="slat_flow_img_dit_L_64l8p2_fp16_h3d_add_del_mod_scale_plus_3deditverse_clean_40k.json",
)
d_ss = _default_from_env("TRAINED_SS_DIR", "/root/workspace/outputs/h3d_clean_img_to_voxel_40k_bsz8x2")
d_slat = _default_from_env("TRAINED_SLAT_DIR", "/root/workspace/outputs/h3d_clean_voxel_to_texture_40k_bsz16x2")
p.add_argument(
"--ss_latents_load_dir",
type=str,
default=d_ss,
help="Sparse-structure ckpt root (contains ckpts/). Override for h3d_only training dirs.",
)
p.add_argument(
"--latents_load_dir",
type=str,
default=d_slat,
help="SLAT ckpt root (contains ckpts/). Override for h3d_only training dirs.",
)
p.add_argument("--ss_latents_load_ckpt", type=int, default=60000)
p.add_argument("--latents_load_ckpt", type=int, default=60000)
p.add_argument("--load_ema_model_for_inference", action="store_true")
p.add_argument("--trellis_pipeline_path", type=str, default="/mnt/zsn/ckpts/TRELLIS-image-large")
p.add_argument("--total_render_view_num", type=int, default=300)
p.add_argument("--eval_view_num", type=int, default=10)
p.add_argument("--gs_render_resolution", type=int, default=512)
p.add_argument("--cuda_idx", type=int, nargs="*", default=[0])
p.add_argument("--world_size", type=int, default=1)
p.add_argument("--rank", type=int, default=0)
p.add_argument("--start_idx", type=int, default=None)
p.add_argument("--end_idx", type=int, default=None)
p.add_argument("--debug", action="store_true")
p.add_argument("--print_time", action="store_true")
p.add_argument(
"--empty_structure_fallback",
type=str,
default="original",
choices=["error", "original"],
)
p.add_argument(
"--force",
action="store_true",
help="Recompute GS views even if render_gs/transforms.json exists.",
)
return p
def main() -> None:
args = build_argparser().parse_args()
args.data_info_json_path = os.path.join(args.dataset_root_dir, args.data_info_json_path)
args.select_json_path = os.path.join(args.dataset_root_dir, args.select_json_path)
args.flux_edit_root_path = os.path.join(args.dataset_root_dir, args.flux_edit_root_path)
args.alpaca_root_path = os.path.join(args.dataset_root_dir, args.alpaca_root_path)
args.mixamo_root_path = os.path.join(args.dataset_root_dir, args.mixamo_root_path)
set_seed(42)
assert len(args.cuda_idx) == 1
save_path = os.path.join(os.path.abspath(args.output_root), args.run_name)
with open(args.select_json_path, "r") as f:
select_data = json.load(f)
with open(args.data_info_json_path, "r") as f:
data_info = json.load(f)
processed_keys = []
for key, values in select_data.items():
for value in values:
processed_keys.append((key, value))
if args.start_idx is not None and args.end_idx is not None:
start_idx, end_idx = args.start_idx, args.end_idx
processed_keys = processed_keys[start_idx:end_idx]
else:
chunk_size = math.ceil(len(processed_keys) / args.world_size)
start_idx = args.rank * chunk_size
end_idx = min((args.rank + 1) * chunk_size, len(processed_keys))
processed_keys = processed_keys[start_idx:end_idx]
print("processed keys:", len(processed_keys), "->", save_path)
if args.debug:
processed_keys = processed_keys[:6]
model = TrellisEdititngModel(
ss_latents_config_path=f"./configs/editing/{args.ss_latents_config}",
ss_latents_load_dir=args.ss_latents_load_dir,
ss_latents_load_ckpt=args.ss_latents_load_ckpt,
latents_config_path=f"./configs/editing/{args.latents_config}",
latents_load_dir=args.latents_load_dir,
latents_load_ckpt=args.latents_load_ckpt,
load_ema_model_for_inference=args.load_ema_model_for_inference,
trellis_pipeline_path=args.trellis_pipeline_path,
)
print("init TrellisEdititngModel done")
for data in tqdm.tqdm(processed_keys):
dataset_type = data[0]
if dataset_type == "alpaca":
key = data[1]
ori_ss_latents_path = os.path.join(
args.alpaca_root_path, data_info[dataset_type][key]["ori_ss_latents_path"]
)
ori_latents_path = os.path.join(
args.alpaca_root_path, data_info[dataset_type][key]["ori_latents_path"]
)
ori_img_path = os.path.join(args.alpaca_root_path, data_info[dataset_type][key]["ori_img_path"])
edit_img_path = os.path.join(args.alpaca_root_path, data_info[dataset_type][key]["edit_img_path"])
elif dataset_type == "flux_edit":
key = data[1]
ori_ss_latents_path = os.path.join(
args.flux_edit_root_path, data_info[dataset_type][key]["ori_ss_latents_path"]
)
ori_latents_path = os.path.join(
args.flux_edit_root_path, data_info[dataset_type][key]["ori_latents_path"]
)
ori_img_path = os.path.join(args.flux_edit_root_path, data_info[dataset_type][key]["ori_img_path"])
edit_img_path = os.path.join(args.flux_edit_root_path, data_info[dataset_type][key]["edit_img_path"])
elif dataset_type == "mixamo":
character_name, ori_idx, edit_idx = data[1]
key = f"{character_name}_{ori_idx}_{edit_idx}"
ori_ss_latents_path = os.path.join(
args.mixamo_root_path,
data_info[dataset_type][character_name][ori_idx]["ss_latents_path"],
)
ori_latents_path = os.path.join(
args.mixamo_root_path,
data_info[dataset_type][character_name][ori_idx]["latents_path"],
)
ori_img_path = os.path.join(
args.mixamo_root_path, data_info[dataset_type][character_name][ori_idx]["img_path"]
)
edit_img_path = os.path.join(
args.mixamo_root_path, data_info[dataset_type][character_name][edit_idx]["img_path"]
)
else:
raise ValueError(f"Invalid dataset type: {dataset_type}")
mesh_save_path = os.path.join(save_path, dataset_type, key, "edit.glb")
gs_render_marker = os.path.join(os.path.dirname(mesh_save_path), "render_gs", "transforms.json")
os.makedirs(os.path.dirname(mesh_save_path), exist_ok=True)
if os.path.exists(gs_render_marker) and not args.force:
continue
t0 = time.time()
model.editing_inference(
ori_ss_latents_path=ori_ss_latents_path,
ori_latents_path=ori_latents_path,
edited_img_path=edit_img_path,
ori_img_path=ori_img_path,
output_video=False,
output_mesh=False,
render_gs_views=True,
gs_render_total_view_num=args.total_render_view_num,
gs_render_eval_view_num=args.eval_view_num,
gs_render_resolution=args.gs_render_resolution,
video_save_path=None,
mesh_save_path=mesh_save_path,
slat_save_path=None,
ori_latents_norm=True if dataset_type == "mixamo" else False,
print_time=args.print_time,
empty_structure_fallback=args.empty_structure_fallback,
)
if args.print_time:
print(f"sample {key} time: {time.time() - t0:.3f}s")
if __name__ == "__main__":
main()