wlyu commited on
Commit
aa475b4
·
verified ·
1 Parent(s): 52bbc29

overwrite with WIP changes: src/evaluate.py

Browse files
Files changed (1) hide show
  1. 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
- vipe_path = test_dir / "vipe"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
702
  cmd = [
703
- "conda", "run", "-n", "vipe",
704
- "--no-capture-output",
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
- vipe_path = test_dir / "vipe"
 
 
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
- if args.pose_frames is not None:
793
- gt_c2w = gt_c2w[:args.pose_frames]
794
- pred_c2w = pred_c2w[:args.pose_frames]
 
 
 
 
 
 
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()))