BonanDing commited on
Commit
e35cf13
·
1 Parent(s): 513e9b6

Add DeMemWM long-horizon eval metrics

Browse files
Files changed (1) hide show
  1. algorithms/dememwm/df_video.py +404 -4
algorithms/dememwm/df_video.py CHANGED
@@ -1,7 +1,9 @@
1
  from collections.abc import Mapping
 
2
  import os
3
  import random
4
  import time
 
5
  import numpy as np
6
  import torch
7
  import torch.distributed as dist
@@ -24,6 +26,7 @@ from datasets.video.memory_selection import (
24
  _select_revisit,
25
  )
26
  from utils.logging_utils import log_video, get_validation_metrics_for_videos
 
27
  from .df_base import DiffusionForcingBase
28
  from .models.vae import VAE_models
29
  from .models.diffusion import Diffusion
@@ -670,6 +673,19 @@ class DeMemWMMinecraft(DiffusionForcingBase):
670
  self.lpips_batch_size = getattr(cfg, "lpips_batch_size", 16)
671
  self.next_frame_length = getattr(cfg, "next_frame_length", 1)
672
  self.require_pose_prediction = getattr(cfg, "require_pose_prediction", False)
 
 
 
 
 
 
 
 
 
 
 
 
 
673
 
674
  super().__init__(cfg)
675
 
@@ -864,6 +880,372 @@ class DeMemWMMinecraft(DiffusionForcingBase):
864
  self._lpips_sum = torch.tensor(0.0, device=self._metric_device)
865
  self._lpips_count = torch.tensor(0.0, device=self._metric_device)
866
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867
  def _update_metric_accumulators(self, xs_pred: torch.Tensor, xs_gt: torch.Tensor) -> None:
868
  if not hasattr(self, "_metric_device"):
869
  self._reset_metric_accumulators()
@@ -915,6 +1297,11 @@ class DeMemWMMinecraft(DiffusionForcingBase):
915
  sync_dist=False,
916
  )
917
 
 
 
 
 
 
918
  self.validation_step_outputs.clear()
919
 
920
  def _preprocess_batch(self, batch):
@@ -1185,24 +1572,37 @@ class DeMemWMMinecraft(DiffusionForcingBase):
1185
  xs_decode,
1186
  step=getattr(self, "global_step", 0),
1187
  namespace=namespace + "_vis",
 
1188
  context_frames=self.context_frames,
1189
  logger=self.logger.experiment,
1190
  save_local=self.save_local,
1191
  local_save_dir=self.local_save_dir,
1192
  )
1193
 
 
1194
  if target_mask is None:
1195
  self._update_metric_accumulators(xs_pred_decode, xs_decode)
1196
  else:
1197
- valid_mask = rearrange(
1198
  target_mask[:, eval_start:target_length].to(device=xs_pred_decode.device),
1199
  "b t -> t b",
1200
  )
1201
- if valid_mask.any():
1202
  self._update_metric_accumulators(
1203
- xs_pred_decode[valid_mask].unsqueeze(1),
1204
- xs_decode[valid_mask].unsqueeze(1),
1205
  )
 
 
 
 
 
 
 
 
 
 
 
1206
  return latent_loss
1207
 
1208
  def test_step(self, batch, batch_idx) -> STEP_OUTPUT:
 
1
  from collections.abc import Mapping
2
+ import csv
3
  import os
4
  import random
5
  import time
6
+ from pathlib import Path
7
  import numpy as np
8
  import torch
9
  import torch.distributed as dist
 
26
  _select_revisit,
27
  )
28
  from utils.logging_utils import log_video, get_validation_metrics_for_videos
29
+ from algorithms.worldmem.revisit_consistency import RevisitPair, compute_frame_metric_rows
30
  from .df_base import DiffusionForcingBase
31
  from .models.vae import VAE_models
32
  from .models.diffusion import Diffusion
 
673
  self.lpips_batch_size = getattr(cfg, "lpips_batch_size", 16)
674
  self.next_frame_length = getattr(cfg, "next_frame_length", 1)
675
  self.require_pose_prediction = getattr(cfg, "require_pose_prediction", False)
676
+ self.log_per_frame_metrics = bool(getattr(cfg, "log_per_frame_metrics", False))
677
+ self.log_revisit_metrics = bool(getattr(cfg, "log_revisit_metrics", False))
678
+ self.revisit_metrics_min_gap = int(getattr(cfg, "revisit_metrics_min_gap", 8))
679
+ self.revisit_metrics_gap_bands = tuple(int(x) for x in getattr(cfg, "revisit_metrics_gap_bands", (32, 128)))
680
+ self.revisit_metrics_self_crop_fraction = float(getattr(cfg, "revisit_metrics_self_crop_fraction", 0.5))
681
+ memory_selection_cfg = _cfg_get(cfg, "memory_selection", {})
682
+ self.revisit_metrics_fov_overlap_threshold = float(
683
+ getattr(
684
+ cfg,
685
+ "revisit_metrics_fov_overlap_threshold",
686
+ _cfg_get(memory_selection_cfg, "fov_overlap_threshold", 0.6),
687
+ )
688
+ )
689
 
690
  super().__init__(cfg)
691
 
 
880
  self._lpips_sum = torch.tensor(0.0, device=self._metric_device)
881
  self._lpips_count = torch.tensor(0.0, device=self._metric_device)
882
 
883
+ if self.log_per_frame_metrics:
884
+ self._frame_metric_eval_start = None
885
+ for attr in ("mse_sum", "mse_count", "psnr_sum", "psnr_count", "lpips_sum", "lpips_count"):
886
+ setattr(self, f"_frame_{attr}", torch.empty(0, device=self._metric_device))
887
+ if self.log_revisit_metrics:
888
+ self._reset_revisit_metric_accumulators()
889
+
890
+ def _eval_artifact_dir(self) -> Path:
891
+ base = Path(self.local_save_dir) if self.local_save_dir is not None else Path.cwd() / "eval_artifacts"
892
+ return base / "metrics"
893
+
894
+ def _is_global_rank_zero(self) -> bool:
895
+ return not (dist.is_available() and dist.is_initialized()) or dist.get_rank() == 0
896
+
897
+ def _mean_or_nan_tensor(self, total: torch.Tensor, count: torch.Tensor) -> torch.Tensor:
898
+ return torch.where(count > 0, total / count.clamp_min(1.0), torch.full_like(total, float("nan")))
899
+
900
+ def _pad_frame_metric_accumulators(self, length: int) -> None:
901
+ current = int(self._frame_mse_sum.numel())
902
+ if current >= int(length):
903
+ return
904
+ pad = int(length) - current
905
+ for attr in ("mse_sum", "mse_count", "psnr_sum", "psnr_count", "lpips_sum", "lpips_count"):
906
+ name = f"_frame_{attr}"
907
+ setattr(self, name, torch.cat([getattr(self, name), torch.zeros(pad, device=self._metric_device)]))
908
+
909
+ def _sync_frame_metric_accumulators(self) -> None:
910
+ if not (dist.is_available() and dist.is_initialized()):
911
+ return
912
+ length = torch.tensor(int(self._frame_mse_sum.numel()), device=self._metric_device, dtype=torch.long)
913
+ dist.all_reduce(length, op=dist.ReduceOp.MAX)
914
+ self._pad_frame_metric_accumulators(int(length.item()))
915
+ for attr in ("mse_sum", "mse_count", "psnr_sum", "psnr_count", "lpips_sum", "lpips_count"):
916
+ dist.all_reduce(getattr(self, f"_frame_{attr}"), op=dist.ReduceOp.SUM)
917
+
918
+ def _update_per_frame_metric_accumulators(
919
+ self,
920
+ xs_pred: torch.Tensor,
921
+ xs_gt: torch.Tensor,
922
+ valid_mask: torch.Tensor | None,
923
+ eval_start: int,
924
+ ) -> None:
925
+ if not self.log_per_frame_metrics or xs_pred.numel() == 0:
926
+ return
927
+ if not hasattr(self, "_frame_mse_sum"):
928
+ self._reset_metric_accumulators()
929
+
930
+ frames = int(xs_pred.shape[0])
931
+ self._pad_frame_metric_accumulators(frames)
932
+ if self._frame_metric_eval_start is None:
933
+ self._frame_metric_eval_start = int(eval_start)
934
+
935
+ pred = torch.clamp(xs_pred.to(self._metric_device).float(), 0.0, 1.0)
936
+ gt = torch.clamp(xs_gt.to(self._metric_device).float(), 0.0, 1.0)
937
+ mask = torch.ones(pred.shape[:2], device=self._metric_device, dtype=torch.bool)
938
+ if valid_mask is not None:
939
+ mask = valid_mask.to(device=self._metric_device, dtype=torch.bool)
940
+
941
+ frame_mse = (pred - gt).square().flatten(start_dim=2).mean(dim=2)
942
+ frame_psnr = 10.0 * torch.log10(1.0 / frame_mse.clamp_min(torch.finfo(frame_mse.dtype).eps))
943
+ counts = mask.sum(dim=1).to(dtype=frame_mse.dtype)
944
+ mask_f = mask.to(dtype=frame_mse.dtype)
945
+ self._frame_mse_sum[:frames] += (frame_mse * mask_f).sum(dim=1)
946
+ self._frame_mse_count[:frames] += counts
947
+ self._frame_psnr_sum[:frames] += (frame_psnr * mask_f).sum(dim=1)
948
+ self._frame_psnr_count[:frames] += counts
949
+
950
+ with torch.no_grad():
951
+ for frame_idx in range(frames):
952
+ frame_mask = mask[frame_idx]
953
+ count = int(frame_mask.sum().item())
954
+ if count == 0:
955
+ continue
956
+ self.validation_lpips_model.reset()
957
+ self.validation_lpips_model.update(pred[frame_idx, frame_mask], gt[frame_idx, frame_mask])
958
+ value = self.validation_lpips_model.compute().detach().to(self._metric_device)
959
+ self.validation_lpips_model.reset()
960
+ self._frame_lpips_sum[frame_idx] += value * count
961
+ self._frame_lpips_count[frame_idx] += count
962
+
963
+ def _log_per_frame_metrics(self) -> None:
964
+ if not hasattr(self, "_frame_mse_sum"):
965
+ return
966
+ self._sync_frame_metric_accumulators()
967
+ valid = self._frame_mse_count > 0
968
+ if not bool(valid.any()):
969
+ return
970
+
971
+ eval_start = 0 if self._frame_metric_eval_start is None else int(self._frame_metric_eval_start)
972
+ rows = []
973
+ log_dict = {}
974
+ for frame_idx in torch.nonzero(valid, as_tuple=False).flatten().tolist():
975
+ mse = self._frame_mse_sum[frame_idx] / self._frame_mse_count[frame_idx].clamp_min(1.0)
976
+ psnr = self._frame_psnr_sum[frame_idx] / self._frame_psnr_count[frame_idx].clamp_min(1.0)
977
+ lpips = self._frame_lpips_sum[frame_idx] / self._frame_lpips_count[frame_idx].clamp_min(1.0)
978
+ generated_frame = int(frame_idx) + 1
979
+ log_dict[f"per_frame/mse_{generated_frame:04d}"] = mse
980
+ log_dict[f"per_frame/psnr_{generated_frame:04d}"] = psnr
981
+ log_dict[f"per_frame/lpips_{generated_frame:04d}"] = lpips
982
+ rows.append(
983
+ {
984
+ "generated_frame": generated_frame,
985
+ "absolute_frame": eval_start + int(frame_idx),
986
+ "mse": float(mse.detach().cpu().item()),
987
+ "psnr": float(psnr.detach().cpu().item()),
988
+ "lpips": float(lpips.detach().cpu().item()),
989
+ "count": float(self._frame_mse_count[frame_idx].detach().cpu().item()),
990
+ }
991
+ )
992
+ self.log_dict(log_dict, sync_dist=False)
993
+ if self._is_global_rank_zero():
994
+ output_path = self._eval_artifact_dir() / f"per_frame_metrics_step{int(getattr(self, 'global_step', 0)):08d}.csv"
995
+ output_path.parent.mkdir(parents=True, exist_ok=True)
996
+ with output_path.open("w", newline="", encoding="utf-8") as handle:
997
+ writer = csv.DictWriter(handle, fieldnames=list(rows[0].keys()))
998
+ writer.writeheader()
999
+ writer.writerows(rows)
1000
+
1001
+ def _revisit_gap_labels(self) -> list[tuple[str, int, int | None]]:
1002
+ edges = [self.revisit_metrics_min_gap]
1003
+ edges.extend(edge for edge in sorted(set(self.revisit_metrics_gap_bands)) if edge > self.revisit_metrics_min_gap)
1004
+ edges.append(None)
1005
+ return [
1006
+ (f"{int(lo)}_{'inf' if hi is None else int(hi)}", int(lo), None if hi is None else int(hi))
1007
+ for lo, hi in zip(edges[:-1], edges[1:])
1008
+ ]
1009
+
1010
+ def _reset_revisit_metric_accumulators(self) -> None:
1011
+ names = (
1012
+ "num_pairs",
1013
+ "num_revisit_targets",
1014
+ "num_revisit_frames",
1015
+ "num_non_revisit_frames",
1016
+ "revisit_lpips_sum",
1017
+ "revisit_lpips_count",
1018
+ "non_revisit_lpips_sum",
1019
+ "non_revisit_lpips_count",
1020
+ "revisit_psnr_sum",
1021
+ "revisit_psnr_count",
1022
+ "non_revisit_psnr_sum",
1023
+ "non_revisit_psnr_count",
1024
+ "self_lpips_sum",
1025
+ "self_lpips_count",
1026
+ "self_psnr_sum",
1027
+ "self_psnr_count",
1028
+ )
1029
+ for name in names:
1030
+ setattr(self, f"_revisit_{name}", torch.tensor(0.0, device=self._metric_device))
1031
+ self._revisit_gap_stats = {
1032
+ label: {
1033
+ "count": torch.tensor(0.0, device=self._metric_device),
1034
+ "self_lpips_sum": torch.tensor(0.0, device=self._metric_device),
1035
+ "self_lpips_count": torch.tensor(0.0, device=self._metric_device),
1036
+ "self_psnr_sum": torch.tensor(0.0, device=self._metric_device),
1037
+ "self_psnr_count": torch.tensor(0.0, device=self._metric_device),
1038
+ "revisit_lpips_sum": torch.tensor(0.0, device=self._metric_device),
1039
+ "revisit_lpips_count": torch.tensor(0.0, device=self._metric_device),
1040
+ }
1041
+ for label, _, _ in self._revisit_gap_labels()
1042
+ }
1043
+
1044
+ def _add_revisit_value(self, sum_attr: str, count_attr: str, value) -> None:
1045
+ try:
1046
+ value = float(value)
1047
+ except (TypeError, ValueError):
1048
+ return
1049
+ if not np.isfinite(value):
1050
+ return
1051
+ getattr(self, sum_attr).add_(value)
1052
+ getattr(self, count_attr).add_(1.0)
1053
+
1054
+ def _add_revisit_value_for_dict(self, stats: dict[str, torch.Tensor], name: str, value) -> None:
1055
+ try:
1056
+ value = float(value)
1057
+ except (TypeError, ValueError):
1058
+ return
1059
+ if not np.isfinite(value):
1060
+ return
1061
+ stats[f"{name}_sum"] += value
1062
+ stats[f"{name}_count"] += 1.0
1063
+
1064
+ def _mine_revisit_pairs_with_validation_selector(
1065
+ self,
1066
+ poses: torch.Tensor,
1067
+ frame_indices: torch.Tensor,
1068
+ memory_selection_cfg,
1069
+ eval_start: int,
1070
+ split: str,
1071
+ clip_id: str,
1072
+ ) -> list[RevisitPair]:
1073
+ poses_np = poses.detach().cpu().numpy()
1074
+ frame_indices_cpu = frame_indices.detach().cpu()
1075
+ excluded = np.empty((0,), dtype=np.int64)
1076
+ pairs: list[RevisitPair] = []
1077
+ for target_index in range(int(eval_start), int(poses.shape[0])):
1078
+ selected = _select_revisit(
1079
+ poses_np,
1080
+ np.asarray([target_index], dtype=np.int64),
1081
+ memory_selection_cfg,
1082
+ 1,
1083
+ excluded,
1084
+ split,
1085
+ )
1086
+ if len(selected) == 0:
1087
+ continue
1088
+ source_index = int(selected[0])
1089
+ source_frame = int(frame_indices_cpu[source_index].item())
1090
+ target_frame = int(frame_indices_cpu[target_index].item())
1091
+ if source_index >= target_index:
1092
+ continue
1093
+ source_pose = poses[source_index].detach().float().cpu()
1094
+ target_pose = poses[target_index].detach().float().cpu()
1095
+ position_distance = float(torch.linalg.vector_norm(target_pose[:3] - source_pose[:3]).item())
1096
+ yaw_delta = float(abs(((target_pose[4] - source_pose[4] + 180.0) % 360.0) - 180.0).item())
1097
+ pitch_delta = float(abs(((target_pose[3] - source_pose[3] + 180.0) % 360.0) - 180.0).item())
1098
+ pairs.append(
1099
+ RevisitPair(
1100
+ clip_id=str(clip_id),
1101
+ source_index=source_index,
1102
+ target_index=target_index,
1103
+ source_frame=source_frame,
1104
+ target_frame=target_frame,
1105
+ gap=int(target_frame - source_frame),
1106
+ fov_overlap=1.0,
1107
+ plucker_overlap=0.0,
1108
+ position_distance=position_distance,
1109
+ yaw_delta_deg=yaw_delta,
1110
+ pitch_delta_deg=pitch_delta,
1111
+ positional=bool(position_distance <= 2.0),
1112
+ )
1113
+ )
1114
+ return pairs
1115
+
1116
+ def _update_revisit_metric_accumulators(
1117
+ self,
1118
+ xs_pred: torch.Tensor,
1119
+ xs_gt: torch.Tensor,
1120
+ target_poses: torch.Tensor,
1121
+ target_frame_indices: torch.Tensor,
1122
+ valid_mask: torch.Tensor | None,
1123
+ eval_start: int,
1124
+ batch_idx: int,
1125
+ namespace: str,
1126
+ ) -> None:
1127
+ if not self.log_revisit_metrics or xs_pred.numel() == 0:
1128
+ return
1129
+ if not hasattr(self, "_revisit_num_pairs"):
1130
+ self._reset_revisit_metric_accumulators()
1131
+
1132
+ valid_mask_cpu = None if valid_mask is None else valid_mask.detach().cpu()
1133
+ batch_size = int(xs_pred.shape[1])
1134
+ for batch_i in range(batch_size):
1135
+ clip_id = f"batch{batch_idx:06d}_sample{batch_i:02d}"
1136
+ poses = target_poses[:, batch_i, :5].detach()
1137
+ frame_indices = target_frame_indices[:, batch_i].detach()
1138
+ pairs = self._mine_revisit_pairs_with_validation_selector(
1139
+ poses,
1140
+ frame_indices,
1141
+ _cfg_get(self.cfg, "memory_selection", {}),
1142
+ eval_start,
1143
+ namespace,
1144
+ clip_id,
1145
+ )
1146
+ self._revisit_num_pairs += float(len(pairs))
1147
+ self._revisit_num_revisit_targets += float(len({int(pair.target_index) for pair in pairs}))
1148
+ rows = compute_frame_metric_rows(
1149
+ branch="validation",
1150
+ clip_id=clip_id,
1151
+ pred=xs_pred[:, batch_i],
1152
+ gt=xs_gt[:, batch_i],
1153
+ poses=poses,
1154
+ pairs=pairs,
1155
+ context_frames=int(eval_start),
1156
+ lpips_model=self.validation_lpips_model,
1157
+ self_crop_fraction=self.revisit_metrics_self_crop_fraction,
1158
+ compute_plucker_similarity=False,
1159
+ )
1160
+ for row in rows:
1161
+ output_index = int(row["output_index"])
1162
+ if valid_mask_cpu is not None and not bool(valid_mask_cpu[output_index, batch_i].item()):
1163
+ continue
1164
+ if bool(row["is_revisit"]):
1165
+ self._revisit_num_revisit_frames += 1.0
1166
+ self._add_revisit_value("_revisit_revisit_lpips_sum", "_revisit_revisit_lpips_count", row["gt_lpips"])
1167
+ self._add_revisit_value("_revisit_revisit_psnr_sum", "_revisit_revisit_psnr_count", row["gt_psnr"])
1168
+ self._add_revisit_value("_revisit_self_lpips_sum", "_revisit_self_lpips_count", row["self_lpips"])
1169
+ self._add_revisit_value("_revisit_self_psnr_sum", "_revisit_self_psnr_count", row["self_psnr"])
1170
+ try:
1171
+ gap = int(row["gap"])
1172
+ except (TypeError, ValueError):
1173
+ gap = None
1174
+ if gap is not None:
1175
+ for label, lo, hi in self._revisit_gap_labels():
1176
+ if gap >= lo and (hi is None or gap < hi):
1177
+ stats = self._revisit_gap_stats[label]
1178
+ stats["count"] += 1.0
1179
+ self._add_revisit_value_for_dict(stats, "revisit_lpips", row["gt_lpips"])
1180
+ self._add_revisit_value_for_dict(stats, "self_lpips", row["self_lpips"])
1181
+ self._add_revisit_value_for_dict(stats, "self_psnr", row["self_psnr"])
1182
+ break
1183
+ else:
1184
+ self._revisit_num_non_revisit_frames += 1.0
1185
+ self._add_revisit_value("_revisit_non_revisit_lpips_sum", "_revisit_non_revisit_lpips_count", row["gt_lpips"])
1186
+ self._add_revisit_value("_revisit_non_revisit_psnr_sum", "_revisit_non_revisit_psnr_count", row["gt_psnr"])
1187
+
1188
+ def _sync_revisit_metric_accumulators(self) -> None:
1189
+ if not (dist.is_available() and dist.is_initialized()):
1190
+ return
1191
+ attrs = (
1192
+ "num_pairs",
1193
+ "num_revisit_targets",
1194
+ "num_revisit_frames",
1195
+ "num_non_revisit_frames",
1196
+ "revisit_lpips_sum",
1197
+ "revisit_lpips_count",
1198
+ "non_revisit_lpips_sum",
1199
+ "non_revisit_lpips_count",
1200
+ "revisit_psnr_sum",
1201
+ "revisit_psnr_count",
1202
+ "non_revisit_psnr_sum",
1203
+ "non_revisit_psnr_count",
1204
+ "self_lpips_sum",
1205
+ "self_lpips_count",
1206
+ "self_psnr_sum",
1207
+ "self_psnr_count",
1208
+ )
1209
+ for attr in attrs:
1210
+ dist.all_reduce(getattr(self, f"_revisit_{attr}"), op=dist.ReduceOp.SUM)
1211
+ for stats in self._revisit_gap_stats.values():
1212
+ for value in stats.values():
1213
+ dist.all_reduce(value, op=dist.ReduceOp.SUM)
1214
+
1215
+ def _log_revisit_metrics(self) -> None:
1216
+ if not hasattr(self, "_revisit_num_pairs"):
1217
+ return
1218
+ self._sync_revisit_metric_accumulators()
1219
+ revisit_lpips = self._mean_or_nan_tensor(self._revisit_revisit_lpips_sum, self._revisit_revisit_lpips_count)
1220
+ non_revisit_lpips = self._mean_or_nan_tensor(self._revisit_non_revisit_lpips_sum, self._revisit_non_revisit_lpips_count)
1221
+ log_dict = {
1222
+ "revisit_metrics/num_pairs": self._revisit_num_pairs,
1223
+ "revisit_metrics/num_revisit_targets": self._revisit_num_revisit_targets,
1224
+ "revisit_metrics/num_revisit_frames": self._revisit_num_revisit_frames,
1225
+ "revisit_metrics/num_non_revisit_frames": self._revisit_num_non_revisit_frames,
1226
+ "revisit_metrics/revisit_lpips": revisit_lpips,
1227
+ "revisit_metrics/non_revisit_lpips": non_revisit_lpips,
1228
+ "revisit_metrics/revisit_psnr": self._mean_or_nan_tensor(self._revisit_revisit_psnr_sum, self._revisit_revisit_psnr_count),
1229
+ "revisit_metrics/non_revisit_psnr": self._mean_or_nan_tensor(self._revisit_non_revisit_psnr_sum, self._revisit_non_revisit_psnr_count),
1230
+ "revisit_metrics/self_consistency_lpips": self._mean_or_nan_tensor(self._revisit_self_lpips_sum, self._revisit_self_lpips_count),
1231
+ "revisit_metrics/self_consistency_psnr": self._mean_or_nan_tensor(self._revisit_self_psnr_sum, self._revisit_self_psnr_count),
1232
+ "revisit_metrics/revisit_minus_non_revisit_lpips": revisit_lpips - non_revisit_lpips,
1233
+ }
1234
+ for label, stats in self._revisit_gap_stats.items():
1235
+ log_dict[f"revisit_metrics/num_gap_{label}"] = stats["count"]
1236
+ log_dict[f"revisit_metrics/self_consistency_lpips_gap_{label}"] = self._mean_or_nan_tensor(stats["self_lpips_sum"], stats["self_lpips_count"])
1237
+ log_dict[f"revisit_metrics/self_consistency_psnr_gap_{label}"] = self._mean_or_nan_tensor(stats["self_psnr_sum"], stats["self_psnr_count"])
1238
+ log_dict[f"revisit_metrics/revisit_lpips_gap_{label}"] = self._mean_or_nan_tensor(stats["revisit_lpips_sum"], stats["revisit_lpips_count"])
1239
+ self.log_dict(log_dict, sync_dist=False)
1240
+ if self._is_global_rank_zero():
1241
+ rows = [{"metric": key, "value": float(value.detach().cpu().item())} for key, value in log_dict.items()]
1242
+ output_path = self._eval_artifact_dir() / f"revisit_metrics_step{int(getattr(self, 'global_step', 0)):08d}.csv"
1243
+ output_path.parent.mkdir(parents=True, exist_ok=True)
1244
+ with output_path.open("w", newline="", encoding="utf-8") as handle:
1245
+ writer = csv.DictWriter(handle, fieldnames=["metric", "value"])
1246
+ writer.writeheader()
1247
+ writer.writerows(rows)
1248
+
1249
  def _update_metric_accumulators(self, xs_pred: torch.Tensor, xs_gt: torch.Tensor) -> None:
1250
  if not hasattr(self, "_metric_device"):
1251
  self._reset_metric_accumulators()
 
1297
  sync_dist=False,
1298
  )
1299
 
1300
+ if self.log_per_frame_metrics:
1301
+ self._log_per_frame_metrics()
1302
+ if self.log_revisit_metrics:
1303
+ self._log_revisit_metrics()
1304
+
1305
  self.validation_step_outputs.clear()
1306
 
1307
  def _preprocess_batch(self, batch):
 
1572
  xs_decode,
1573
  step=getattr(self, "global_step", 0),
1574
  namespace=namespace + "_vis",
1575
+ prefix=f"batch{batch_idx:06d}",
1576
  context_frames=self.context_frames,
1577
  logger=self.logger.experiment,
1578
  save_local=self.save_local,
1579
  local_save_dir=self.local_save_dir,
1580
  )
1581
 
1582
+ metric_mask = None
1583
  if target_mask is None:
1584
  self._update_metric_accumulators(xs_pred_decode, xs_decode)
1585
  else:
1586
+ metric_mask = rearrange(
1587
  target_mask[:, eval_start:target_length].to(device=xs_pred_decode.device),
1588
  "b t -> t b",
1589
  )
1590
+ if metric_mask.any():
1591
  self._update_metric_accumulators(
1592
+ xs_pred_decode[metric_mask].unsqueeze(1),
1593
+ xs_decode[metric_mask].unsqueeze(1),
1594
  )
1595
+ self._update_per_frame_metric_accumulators(xs_pred_decode, xs_decode, metric_mask, eval_start)
1596
+ self._update_revisit_metric_accumulators(
1597
+ xs_pred_decode,
1598
+ xs_decode,
1599
+ target_poses,
1600
+ target_frame_indices,
1601
+ metric_mask,
1602
+ eval_start,
1603
+ batch_idx,
1604
+ namespace,
1605
+ )
1606
  return latent_loss
1607
 
1608
  def test_step(self, batch, batch_idx) -> STEP_OUTPUT: