Add DeMemWM long-horizon eval metrics
Browse files- 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 |
-
|
| 1198 |
target_mask[:, eval_start:target_length].to(device=xs_pred_decode.device),
|
| 1199 |
"b t -> t b",
|
| 1200 |
)
|
| 1201 |
-
if
|
| 1202 |
self._update_metric_accumulators(
|
| 1203 |
-
xs_pred_decode[
|
| 1204 |
-
xs_decode[
|
| 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:
|