| |
| """ |
| 按 visual_eval_config 中的 prompt 与首 chunk 预设,批量跑固定首帧 2chunk/4chunk, |
| 输出按 prompt_id 与 first_chunk_id 分目录,便于肉眼对比查看。 |
| 用法见 VISUAL_EVAL_DESIGN.md。 |
| """ |
| from __future__ import annotations |
|
|
| import argparse |
| import os |
| import subprocess |
| import sys |
|
|
| _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
| _EXP_DIR = os.path.dirname(_SCRIPT_DIR) |
| _DEFAULT_FIRST_FRAME = os.path.join(_EXP_DIR, "train", "ctx_5_20_per_frame_vae", "image.png") |
| _RUN_GEN_SCRIPT = os.path.join(_EXP_DIR, "run_generalization_fixed_first_frame.py") |
|
|
|
|
| def _load_config(path: str) -> dict: |
| try: |
| import yaml |
| with open(path, "r", encoding="utf-8") as f: |
| return yaml.safe_load(f) or {} |
| except ImportError: |
| |
| with open(path, "r", encoding="utf-8") as f: |
| text = f.read() |
| |
| import json |
| |
| raise SystemExit("pip install PyYAML 后重试,或使用 --prompts/--first_chunks 手动指定。") |
|
|
|
|
| def _export_dataset_frame(dataset_base: str, video_name: str, start_frame: int, out_path: str, w: int = 640, h: int = 352) -> bool: |
| from PIL import Image |
| for suf in (f"{start_frame:04d}.png", f"{start_frame}.png"): |
| img_p = os.path.join(dataset_base, "frames", video_name.strip(), suf) |
| if os.path.isfile(img_p): |
| img = Image.open(img_p).convert("RGB") |
| try: |
| img = img.resize((w, h), Image.Resampling.LANCZOS) |
| except AttributeError: |
| img = img.resize((w, h), Image.LANCZOS) |
| os.makedirs(os.path.dirname(out_path), exist_ok=True) |
| img.save(out_path) |
| return True |
| return False |
|
|
|
|
| def main(): |
| p = argparse.ArgumentParser(description="按 config 跑多组 prompt × 首帧,便于可视化对比") |
| p.add_argument("--ckpt", required=True, help="权重路径") |
| p.add_argument("--config", default=None, help="YAML 配置,默认 eval_metrics/visual_eval_config.yaml") |
| p.add_argument("--output_root", required=True, help="输出根目录,下建 prompt_<id>_first_<id>") |
| p.add_argument("--dataset_base", default=None, help="数据集根目录,用于 dataset_frame 首帧导出") |
| p.add_argument("--prompts", nargs="*", default=None, help="只跑这些 prompt id,默认全部") |
| p.add_argument("--first_chunks", nargs="*", default=None, help="只跑这些 first_chunk id,默认全部") |
| p.add_argument("--use_recommended", action="store_true", help="仅跑 config 里 recommended_pairs 列出的 (prompt_id, first_chunk_id)") |
| p.add_argument("--action_dir", default=None, help="action_rotation_*.json 目录,默认 exp 目录") |
| p.add_argument("--context_frames", type=int, default=1) |
| p.add_argument("--no_camera_encoder_separate_t_r", action="store_true") |
| args = p.parse_args() |
|
|
| config_path = args.config or os.path.join(_SCRIPT_DIR, "visual_eval_config.yaml") |
| if not os.path.isfile(config_path): |
| print(f"Config 不存在: {config_path}", file=sys.stderr) |
| sys.exit(1) |
| config = _load_config(config_path) |
|
|
| prompts_cfg = config.get("prompts") or [] |
| first_chunk_cfg = config.get("first_chunk_presets") or [] |
| prompt_map = {x["id"]: x for x in prompts_cfg if x.get("id")} |
| first_map = {x["id"]: x for x in first_chunk_cfg if x.get("id")} |
|
|
| if args.use_recommended and config.get("recommended_pairs"): |
| pairs = config["recommended_pairs"] |
| prompt_ids = list({p[0] for p in pairs if len(p) >= 2}) |
| first_ids = list({p[1] for p in pairs if len(p) >= 2}) |
| run_pairs = [(p[0], p[1]) for p in pairs if len(p) >= 2 and p[0] in prompt_map and p[1] in first_map] |
| else: |
| prompt_ids = args.prompts or list(prompt_map.keys()) |
| first_ids = args.first_chunks or list(first_map.keys()) |
| run_pairs = None |
|
|
| |
| first_frames_dir = os.path.join(args.output_root, "first_frames") |
| first_chunk_to_path = {} |
| for fid in first_ids: |
| fc = first_map.get(fid) |
| if not fc: |
| continue |
| t = (fc.get("type") or "").strip() |
| if t == "fixed_image": |
| path = (fc.get("path") or "").strip() |
| if not path: |
| path = _DEFAULT_FIRST_FRAME |
| if os.path.isfile(path): |
| first_chunk_to_path[fid] = os.path.abspath(path) |
| else: |
| print(f"[skip] first_chunk {fid}: 图片不存在 {path}", file=sys.stderr) |
| elif t == "dataset_frame": |
| if not args.dataset_base: |
| print(f"[skip] first_chunk {fid}: dataset_frame 需提供 --dataset_base", file=sys.stderr) |
| continue |
| vn = (fc.get("video_name") or "").strip() |
| sf = int(fc.get("start_frame") or 0) |
| out_path = os.path.join(first_frames_dir, f"{fid}.png") |
| if _export_dataset_frame(args.dataset_base, vn, sf, out_path): |
| first_chunk_to_path[fid] = out_path |
| else: |
| print(f"[skip] first_chunk {fid}: 无法导出帧 {vn} frame {sf}", file=sys.stderr) |
| else: |
| print(f"[skip] first_chunk {fid}: 未知 type {t}", file=sys.stderr) |
|
|
| if not first_chunk_to_path: |
| print("没有可用的首帧预设。", file=sys.stderr) |
| sys.exit(1) |
|
|
| action_dir = args.action_dir or _EXP_DIR |
| if not os.path.isfile(_RUN_GEN_SCRIPT): |
| print(f"未找到 {_RUN_GEN_SCRIPT}", file=sys.stderr) |
| sys.exit(1) |
|
|
| for pid in prompt_ids: |
| pr = prompt_map.get(pid) |
| if not pr: |
| print(f"[skip] prompt {pid} 不在 config 中", file=sys.stderr) |
| continue |
| text = (pr.get("text") or "A scene.").strip() |
| for fid, first_path in first_chunk_to_path.items(): |
| if run_pairs is not None and (pid, fid) not in run_pairs: |
| continue |
| out_dir = os.path.join(args.output_root, f"prompt_{pid}_first_{fid}") |
| os.makedirs(out_dir, exist_ok=True) |
| cmd = [ |
| sys.executable, |
| _RUN_GEN_SCRIPT, |
| "--ckpt", args.ckpt, |
| "--first_frame_image", first_path, |
| "--prompt", text, |
| "--output_dir", out_dir, |
| "--sampling_action_dir", action_dir, |
| "--context_frames", str(args.context_frames), |
| ] |
| if args.no_camera_encoder_separate_t_r: |
| cmd.append("--no_camera_encoder_separate_t_r") |
| print(f"[run] prompt={pid} first={fid} -> {out_dir}", file=sys.stderr) |
| ret = subprocess.run(cmd, cwd=_EXP_DIR) |
| if ret.returncode != 0: |
| print(f"[warn] 退出码 {ret.returncode} prompt={pid} first={fid}", file=sys.stderr) |
|
|
| print(f"Done. 输出根目录: {args.output_root}", file=sys.stderr) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|