overwrite with WIP changes: src/evaluate.py
Browse files- UCPE/src/evaluate.py +63 -13
UCPE/src/evaluate.py
CHANGED
|
@@ -44,6 +44,10 @@ class Args(BaseSettings):
|
|
| 44 |
frame_stride: Optional[int] = None
|
| 45 |
pose_frames: Optional[int] = None
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
model_config = SettingsConfigDict(
|
| 48 |
env_prefix="EVAL_",
|
| 49 |
cli_parse_args=True,
|
|
@@ -52,11 +56,12 @@ class Args(BaseSettings):
|
|
| 52 |
|
| 53 |
|
| 54 |
def get_path(args):
|
|
|
|
| 55 |
if args.evaluate_gt:
|
| 56 |
assert args.data == "PanShotDataset", "GT evaluation only supports PanShotDataset."
|
| 57 |
-
paths = {"i2v": (args.data_root / "PanShot" / "videos-test", args.data_root / "evaluate")}
|
| 58 |
elif args.test_res_path is not None:
|
| 59 |
-
paths = {args.test_res_path.name: (args.test_res_path, args.test_res_path.parent / f"evaluate_{args.test_res_path.name}")}
|
| 60 |
else:
|
| 61 |
run_id = os.environ.get('WANDB_RUN_ID', None)
|
| 62 |
assert run_id is not None, "WANDB_RUN_ID environment variable must be set."
|
|
@@ -66,7 +71,7 @@ def get_path(args):
|
|
| 66 |
for task in ["t2v", "i2v"]:
|
| 67 |
task_path = predict_dir / task
|
| 68 |
if task_path.exists():
|
| 69 |
-
paths[task] = (task_path, predict_dir / f"evaluate_{task}")
|
| 70 |
print(f"Evaluation paths: {paths}")
|
| 71 |
return paths
|
| 72 |
|
|
@@ -463,6 +468,27 @@ def video(args):
|
|
| 463 |
video = rearrange(video, "C T H W -> T C H W") # [T, C, H, W]
|
| 464 |
video = video / 2. + 0.5 # to [0, 1]
|
| 465 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 466 |
for metric_name, metric in image_metrics.items():
|
| 467 |
if metric_name == "geocalib":
|
| 468 |
if "video" not in data:
|
|
@@ -698,11 +724,27 @@ def vipe(args):
|
|
| 698 |
process.stdin.close()
|
| 699 |
process.wait()
|
| 700 |
|
| 701 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 702 |
cmd = [
|
| 703 |
-
|
| 704 |
-
"
|
| 705 |
-
"python", "/mnt/pfs/users/zhangchen/panshot/UCPE/thirdparty/vipe/run.py",
|
| 706 |
"pipeline=default",
|
| 707 |
"streams=raw_mp4_stream",
|
| 708 |
f"streams.base_path={rectify_res_path}",
|
|
@@ -710,8 +752,8 @@ def vipe(args):
|
|
| 710 |
"pipeline.output.save_artifacts=true",
|
| 711 |
"pipeline.post.depth_align_model=null",
|
| 712 |
]
|
| 713 |
-
print(f"[CMD] {' '.join(cmd)}")
|
| 714 |
-
subprocess.run(cmd, check=True)
|
| 715 |
|
| 716 |
|
| 717 |
def pose(args):
|
|
@@ -769,7 +811,9 @@ def pose(args):
|
|
| 769 |
|
| 770 |
for task, (test_res_path, test_dir) in tasks.items():
|
| 771 |
print(f"Evaluating task: {task}")
|
| 772 |
-
|
|
|
|
|
|
|
| 773 |
vipe_pose_path = vipe_path / "pose"
|
| 774 |
vipe_video_ids = set(p.stem for p in vipe_pose_path.glob("*.npz"))
|
| 775 |
# if valid_video_ids is not None:
|
|
@@ -789,9 +833,15 @@ def pose(args):
|
|
| 789 |
gt_c2w = gt_c2w[::args.frame_stride]
|
| 790 |
pred_c2w = pred_c2w[::args.frame_stride]
|
| 791 |
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 795 |
|
| 796 |
# Relative + translation normalized
|
| 797 |
gt_rel = normalize_t(relative_pose(gt_c2w.copy()))
|
|
|
|
| 44 |
frame_stride: Optional[int] = None
|
| 45 |
pose_frames: Optional[int] = None
|
| 46 |
|
| 47 |
+
# Cap pred+GT to first N frames for video metrics AND force pose_frames=N.
|
| 48 |
+
# When set, output dir gets a `_<N>f` suffix so 49/81 runs don't collide.
|
| 49 |
+
eval_frames: Optional[int] = None
|
| 50 |
+
|
| 51 |
model_config = SettingsConfigDict(
|
| 52 |
env_prefix="EVAL_",
|
| 53 |
cli_parse_args=True,
|
|
|
|
| 56 |
|
| 57 |
|
| 58 |
def get_path(args):
|
| 59 |
+
suffix = f"_{args.eval_frames}f" if args.eval_frames is not None else ""
|
| 60 |
if args.evaluate_gt:
|
| 61 |
assert args.data == "PanShotDataset", "GT evaluation only supports PanShotDataset."
|
| 62 |
+
paths = {"i2v": (args.data_root / "PanShot" / "videos-test", args.data_root / f"evaluate{suffix}")}
|
| 63 |
elif args.test_res_path is not None:
|
| 64 |
+
paths = {args.test_res_path.name: (args.test_res_path, args.test_res_path.parent / f"evaluate_{args.test_res_path.name}{suffix}")}
|
| 65 |
else:
|
| 66 |
run_id = os.environ.get('WANDB_RUN_ID', None)
|
| 67 |
assert run_id is not None, "WANDB_RUN_ID environment variable must be set."
|
|
|
|
| 71 |
for task in ["t2v", "i2v"]:
|
| 72 |
task_path = predict_dir / task
|
| 73 |
if task_path.exists():
|
| 74 |
+
paths[task] = (task_path, predict_dir / f"evaluate_{task}{suffix}")
|
| 75 |
print(f"Evaluation paths: {paths}")
|
| 76 |
return paths
|
| 77 |
|
|
|
|
| 468 |
video = rearrange(video, "C T H W -> T C H W") # [T, C, H, W]
|
| 469 |
video = video / 2. + 0.5 # to [0, 1]
|
| 470 |
|
| 471 |
+
if "video" in data:
|
| 472 |
+
# Baselines produce varying frame counts (seva/flexworld/viewcrafter=49,
|
| 473 |
+
# cf_ucpe/wan22_fun/bidir=81, gen3c=121). GT is 81. Truncate both to
|
| 474 |
+
# the common prefix so per-frame metrics (SSIM/LPIPS/PSNR/cs_image/FID)
|
| 475 |
+
# zip-align. FVD takes the truncated clip too. If args.eval_frames is
|
| 476 |
+
# set (e.g. EVAL_FRAMES=49), additionally cap to that length so 81-frame
|
| 477 |
+
# methods can be re-scored against shorter baselines on equal footing.
|
| 478 |
+
T_common = min(video.shape[0], gt_video.shape[0])
|
| 479 |
+
if args.eval_frames is not None:
|
| 480 |
+
T_common = min(T_common, args.eval_frames)
|
| 481 |
+
video = video[:T_common]
|
| 482 |
+
gt_video = gt_video[:T_common]
|
| 483 |
+
# Resize pred to GT spatial dims when they differ (flexworld 1024-wide,
|
| 484 |
+
# viewcrafter 576x1024). I3D / Inception have their own internal resize
|
| 485 |
+
# for FVD/FID but per-frame metrics (SSIM/LPIPS/PSNR/geocalib) need
|
| 486 |
+
# spatial alignment.
|
| 487 |
+
if video.shape[-2:] != gt_video.shape[-2:]:
|
| 488 |
+
import torch.nn.functional as F
|
| 489 |
+
video = F.interpolate(video, size=gt_video.shape[-2:],
|
| 490 |
+
mode="bilinear", align_corners=False)
|
| 491 |
+
|
| 492 |
for metric_name, metric in image_metrics.items():
|
| 493 |
if metric_name == "geocalib":
|
| 494 |
if "video" not in data:
|
|
|
|
| 724 |
process.stdin.close()
|
| 725 |
process.wait()
|
| 726 |
|
| 727 |
+
# vipe SLAM output is shared across eval_frames runs (49f / 81f) because
|
| 728 |
+
# SLAM operates on the full rectified mp4 — frame-count truncation happens
|
| 729 |
+
# at pose-metric time, not at SLAM time. Put it outside the suffixed
|
| 730 |
+
# test_dir so both runs reuse it (saves ~30 min × 5 baselines).
|
| 731 |
+
vipe_path = test_res_path.parent / f"vipe_{test_res_path.name}"
|
| 732 |
+
# Invoke vipe env's python directly (skipping `conda run`) so the
|
| 733 |
+
# LD_LIBRARY_PATH we set below is actually inherited — `conda run
|
| 734 |
+
# --no-capture-output` re-applies its own env logic that drops our
|
| 735 |
+
# injection on nodes whose vipe env lacks the torch_libs.sh activate
|
| 736 |
+
# hook, leading to ImportError: libnccl.so.2.
|
| 737 |
+
vipe_env = Path("/home/tiger/miniconda3/envs/vipe")
|
| 738 |
+
vipe_site = vipe_env / "lib" / "python3.10" / "site-packages"
|
| 739 |
+
ld_paths = [str(vipe_site / "torch" / "lib")]
|
| 740 |
+
nvidia_dir = vipe_site / "nvidia"
|
| 741 |
+
if nvidia_dir.is_dir():
|
| 742 |
+
ld_paths.extend(str(p / "lib") for p in nvidia_dir.iterdir() if (p / "lib").is_dir())
|
| 743 |
+
env = os.environ.copy()
|
| 744 |
+
env["LD_LIBRARY_PATH"] = ":".join(ld_paths) + ":" + env.get("LD_LIBRARY_PATH", "")
|
| 745 |
cmd = [
|
| 746 |
+
str(vipe_env / "bin" / "python"),
|
| 747 |
+
str(Path(__file__).resolve().parent.parent / "thirdparty" / "vipe" / "run.py"),
|
|
|
|
| 748 |
"pipeline=default",
|
| 749 |
"streams=raw_mp4_stream",
|
| 750 |
f"streams.base_path={rectify_res_path}",
|
|
|
|
| 752 |
"pipeline.output.save_artifacts=true",
|
| 753 |
"pipeline.post.depth_align_model=null",
|
| 754 |
]
|
| 755 |
+
print(f"[CMD] LD_LIBRARY_PATH={env['LD_LIBRARY_PATH'][:80]}... {' '.join(cmd)}")
|
| 756 |
+
subprocess.run(cmd, check=True, env=env)
|
| 757 |
|
| 758 |
|
| 759 |
def pose(args):
|
|
|
|
| 811 |
|
| 812 |
for task, (test_res_path, test_dir) in tasks.items():
|
| 813 |
print(f"Evaluating task: {task}")
|
| 814 |
+
# Read shared vipe SLAM output (see vipe() — single SLAM pass, reused by
|
| 815 |
+
# both eval_frames=49 and eval_frames=81 pose-metric runs).
|
| 816 |
+
vipe_path = test_res_path.parent / f"vipe_{test_res_path.name}"
|
| 817 |
vipe_pose_path = vipe_path / "pose"
|
| 818 |
vipe_video_ids = set(p.stem for p in vipe_pose_path.glob("*.npz"))
|
| 819 |
# if valid_video_ids is not None:
|
|
|
|
| 833 |
gt_c2w = gt_c2w[::args.frame_stride]
|
| 834 |
pred_c2w = pred_c2w[::args.frame_stride]
|
| 835 |
|
| 836 |
+
# Truncate to the common length so pred (e.g. voyager=45) and GT (=81)
|
| 837 |
+
# match before the rot/trans/cammc matmul. Then optionally cap to
|
| 838 |
+
# pose_frames_eff (eval_frames overrides pose_frames).
|
| 839 |
+
T = min(len(gt_c2w), len(pred_c2w))
|
| 840 |
+
pose_frames_eff = args.eval_frames if args.eval_frames is not None else args.pose_frames
|
| 841 |
+
if pose_frames_eff is not None:
|
| 842 |
+
T = min(T, pose_frames_eff)
|
| 843 |
+
gt_c2w = gt_c2w[:T]
|
| 844 |
+
pred_c2w = pred_c2w[:T]
|
| 845 |
|
| 846 |
# Relative + translation normalized
|
| 847 |
gt_rel = normalize_t(relative_pose(gt_c2w.copy()))
|