kimodo-motion-api / benchmark /embed_folder.py
cs686's picture
Deploy Kimodo ZeroGPU motion API
a550c4e verified
Raw
History Blame Contribute Delete
5.58 kB
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""
Step (3) of evaluation pipeline.
This script recursively embeds generated motions, ground-truth motions, and text prompts from a test suite folder tree with the pre-trained TMR model.
"""
import argparse
from pathlib import Path
import numpy as np
import torch
from tqdm import tqdm
from kimodo.meta import parse_prompts_from_meta
from kimodo.model.load_model import load_model
from kimodo.tools import load_json
def discover_motion_folders(root: Path) -> list[Path]:
root = root.resolve()
if not root.is_dir():
raise FileNotFoundError(f"Folder does not exist: {root}")
out: list[Path] = []
for meta_path in root.rglob("meta.json"):
src_dir = meta_path.parent
if (src_dir / "motion.npz").is_file() or (src_dir / "gt_motion.npz").is_file():
out.append(src_dir)
return sorted(out)
def _load_posed_joints(npz_path: Path, device: str) -> torch.Tensor:
data = np.load(npz_path)
if "posed_joints" not in data:
raise SystemExit(f"NPZ must contain 'posed_joints': {npz_path}")
posed_joints = data["posed_joints"]
if posed_joints.ndim == 4:
if posed_joints.shape[0] != 1:
raise SystemExit(f"Expected batch size 1 for posed_joints, got {posed_joints.shape[0]} in {npz_path}")
posed_joints = posed_joints[0]
if posed_joints.ndim != 3:
raise SystemExit(f"Expected posed_joints shape [T, J, 3], got {posed_joints.shape} in {npz_path}")
return torch.from_numpy(posed_joints).float().to(device)
def main():
parser = argparse.ArgumentParser(
description="Recursively embed motion, gt_motion, and text; save motion_embedding.npy, gt_motion_embedding.npy, and text_embedding.npy when present.",
)
parser.add_argument(
"folder",
type=Path,
help="Root folder to search recursively for meta.json and motion.npz and/or gt_motion.npz",
)
parser.add_argument(
"--model",
default="tmr-soma-rp",
help="Model for encoding (e.g. TMR-SOMA-RP-v1, tmr-soma-rp). Default: tmr-soma-rp",
)
parser.add_argument(
"--device",
default=None,
help="Device (default: cuda if available else cpu)",
)
parser.add_argument(
"--overwrite",
action="store_true",
help="Re-embed even if embedding files already exist",
)
parser.add_argument(
"--text_encoder_fp32",
action="store_true",
help="Uses fp32 for the text encoder rather than default bfloat16.",
)
args = parser.parse_args()
folder = args.folder.resolve()
if not folder.is_dir():
raise SystemExit(f"Folder does not exist or is not a directory: {folder}")
device = args.device or ("cuda" if torch.cuda.is_available() else "cpu")
model = load_model(modelname=args.model, device=device, default_family="TMR", text_encoder_fp32=args.text_encoder_fp32)
dirs = discover_motion_folders(folder)
if not dirs:
raise SystemExit(f"No directories with meta.json and (motion.npz or gt_motion.npz) found under {folder}")
print(f"Discovered {len(dirs)} motion folders.")
skipped_motion = 0
skipped_gt = 0
skipped_text = 0
for sample_dir in tqdm(dirs, desc="Embedding"):
meta_path = sample_dir / "meta.json"
meta = load_json(meta_path)
texts, _ = parse_prompts_from_meta(meta)
if len(texts) != 1:
raise SystemExit(f"Expected exactly one text per motion; got {len(texts)} in {meta_path}")
text = texts[0]
# Embed motion.npz -> motion_embedding.npy
if (sample_dir / "motion.npz").is_file():
if not args.overwrite and (sample_dir / "motion_embedding.npy").is_file():
skipped_motion += 1
else:
npz_path = sample_dir / "motion.npz"
posed_joints = _load_posed_joints(npz_path, device)
with torch.inference_mode():
motion_emb = model.encode_motion(posed_joints, unit_vector=True)
np.save(sample_dir / "motion_embedding.npy", motion_emb.cpu().numpy())
# Embed gt_motion.npz -> gt_motion_embedding.npy
if (sample_dir / "gt_motion.npz").is_file():
if not args.overwrite and (sample_dir / "gt_motion_embedding.npy").is_file():
skipped_gt += 1
else:
npz_path = sample_dir / "gt_motion.npz"
posed_joints = _load_posed_joints(npz_path, device)
with torch.inference_mode():
gt_motion_emb = model.encode_motion(posed_joints, unit_vector=True)
np.save(sample_dir / "gt_motion_embedding.npy", gt_motion_emb.cpu().numpy())
# Embed text -> text_embedding.npy
if not args.overwrite and (sample_dir / "text_embedding.npy").is_file():
skipped_text += 1
else:
with torch.inference_mode():
text_emb = model.encode_raw_text([text], unit_vector=True)
np.save(sample_dir / "text_embedding.npy", text_emb.cpu().numpy())
total_skipped = skipped_motion + skipped_gt + skipped_text
if total_skipped:
print(f"Embedded {len(dirs)} folders; skipped some existing files (use --overwrite to re-embed).")
else:
print(f"Saved motion_embedding.npy, gt_motion_embedding.npy, and text_embedding.npy in {len(dirs)} folders.")
if __name__ == "__main__":
main()