BonanDing commited on
Commit
bafe43d
·
1 Parent(s): b8d0bc4

Use multiview dynamic memory online

Browse files
.exp_artifact/dememwm_dynamic_multiview_memory_selection_plan.md CHANGED
@@ -707,7 +707,7 @@ Select DeMemWM dynamic memory by multiview retrieval
707
 
708
  ## Substep 5: Integrate Multiview Dynamic In Dataset And Online Rollout
709
 
710
- Status: `[ ]`
711
 
712
  Goal:
713
 
 
707
 
708
  ## Substep 5: Integrate Multiview Dynamic In Dataset And Online Rollout
709
 
710
+ Status: `[x]`
711
 
712
  Goal:
713
 
algorithms/dememwm/df_video.py CHANGED
@@ -20,6 +20,9 @@ from algorithms.common.metrics import (
20
  LearnedPerceptualImagePatchSimilarity,
21
  )
22
  from datasets.video.memory_selection import (
 
 
 
23
  _event_triggered_anchor_candidates_from_deltas,
24
  _pose_delta_values,
25
  _select_dynamic_from_stream,
@@ -1916,9 +1919,8 @@ class DeMemWMMinecraft(DiffusionForcingBase):
1916
  )
1917
  for batch_i in range(batch_size)
1918
  ]
1919
- dynamic_policy = str(_cfg_get(_cfg_get(memory_selection_cfg, "dynamic", {}), "selection_policy", "recent"))
1920
- if dynamic_policy not in {"recent", "event_triggered"}:
1921
- raise ValueError(f"memory_selection.dynamic.selection_policy must be recent or event_triggered; got {dynamic_policy!r}")
1922
  event_dynamic_caches = [_new_online_event_cache(target_length) for _ in range(batch_size)] if dynamic_policy == "event_triggered" else None
1923
  memory_sheet_targets = self._memory_sheet_target_indices(n_context_frames, target_length) if self.log_memory_selection_sheet else set()
1924
  memory_sheet_records = [[] for _ in range(batch_size)] if memory_sheet_targets else None
@@ -1960,6 +1962,7 @@ class DeMemWMMinecraft(DiffusionForcingBase):
1960
  anchor_count = int(stream_indices_by_key["anchor"].shape[1])
1961
  dynamic_count = int(stream_indices_by_key["dynamic"].shape[1])
1962
  revisit_count = int(stream_indices_by_key["revisit"].shape[1])
 
1963
  selected = {"anchor": online_anchor_indices[batch_i]}
1964
  if dynamic_policy == "recent":
1965
  selected["dynamic"] = _select_online_dynamic_indices(
@@ -1969,13 +1972,28 @@ class DeMemWMMinecraft(DiffusionForcingBase):
1969
  )
1970
  else:
1971
  selected["dynamic"] = np.empty((0,), dtype=np.int64)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1972
  selected["revisit"] = _select_revisit(
1973
- poses_np[:, batch_i, :5],
1974
  target_positions,
1975
  memory_selection_cfg,
1976
  revisit_count,
1977
  np.empty((0,), dtype=np.int64),
1978
  namespace,
 
1979
  )
1980
  if dynamic_policy == "event_triggered":
1981
  cache = event_dynamic_caches[batch_i]
@@ -1994,6 +2012,18 @@ class DeMemWMMinecraft(DiffusionForcingBase):
1994
  memory_selection_cfg,
1995
  reference_frames=selected["revisit"] if len(selected["revisit"]) > 0 else None,
1996
  )
 
 
 
 
 
 
 
 
 
 
 
 
1997
  if memory_sheet_records is not None and int(target_positions[0]) in memory_sheet_targets:
1998
  memory_sheet_records[batch_i].append(
1999
  {
 
20
  LearnedPerceptualImagePatchSimilarity,
21
  )
22
  from datasets.video.memory_selection import (
23
+ _build_shared_fov_candidate_pool,
24
+ _dynamic_multiview_selector,
25
+ _dynamic_policy,
26
  _event_triggered_anchor_candidates_from_deltas,
27
  _pose_delta_values,
28
  _select_dynamic_from_stream,
 
1919
  )
1920
  for batch_i in range(batch_size)
1921
  ]
1922
+ dynamic_policy = _dynamic_policy(memory_selection_cfg)
1923
+ multiview_selector = _dynamic_multiview_selector(memory_selection_cfg) if dynamic_policy == "multiview" else None
 
1924
  event_dynamic_caches = [_new_online_event_cache(target_length) for _ in range(batch_size)] if dynamic_policy == "event_triggered" else None
1925
  memory_sheet_targets = self._memory_sheet_target_indices(n_context_frames, target_length) if self.log_memory_selection_sheet else set()
1926
  memory_sheet_records = [[] for _ in range(batch_size)] if memory_sheet_targets else None
 
1962
  anchor_count = int(stream_indices_by_key["anchor"].shape[1])
1963
  dynamic_count = int(stream_indices_by_key["dynamic"].shape[1])
1964
  revisit_count = int(stream_indices_by_key["revisit"].shape[1])
1965
+ batch_poses = poses_np[:, batch_i, :5]
1966
  selected = {"anchor": online_anchor_indices[batch_i]}
1967
  if dynamic_policy == "recent":
1968
  selected["dynamic"] = _select_online_dynamic_indices(
 
1972
  )
1973
  else:
1974
  selected["dynamic"] = np.empty((0,), dtype=np.int64)
1975
+
1976
+ fov_pool = None
1977
+ if dynamic_policy == "multiview" and multiview_selector == "fov_greedy":
1978
+ fov_pool = _build_shared_fov_candidate_pool(
1979
+ batch_poses,
1980
+ target_positions,
1981
+ memory_selection_cfg,
1982
+ namespace,
1983
+ min_candidate_frame=0,
1984
+ dynamic_count=dynamic_count,
1985
+ revisit_count=revisit_count,
1986
+ )
1987
+
1988
+ revisit_kwargs = {"fov_pool": fov_pool} if fov_pool is not None else {}
1989
  selected["revisit"] = _select_revisit(
1990
+ batch_poses,
1991
  target_positions,
1992
  memory_selection_cfg,
1993
  revisit_count,
1994
  np.empty((0,), dtype=np.int64),
1995
  namespace,
1996
+ **revisit_kwargs,
1997
  )
1998
  if dynamic_policy == "event_triggered":
1999
  cache = event_dynamic_caches[batch_i]
 
2012
  memory_selection_cfg,
2013
  reference_frames=selected["revisit"] if len(selected["revisit"]) > 0 else None,
2014
  )
2015
+ elif dynamic_policy == "multiview":
2016
+ selected["dynamic"] = _select_dynamic_by_policy(
2017
+ int(target_positions[0]),
2018
+ dynamic_count,
2019
+ memory_selection_cfg,
2020
+ poses=batch_poses,
2021
+ reference_frames=selected["revisit"],
2022
+ excluded=selected["revisit"],
2023
+ target_positions=target_positions,
2024
+ split=namespace,
2025
+ fov_pool=fov_pool,
2026
+ )
2027
  if memory_sheet_records is not None and int(target_positions[0]) in memory_sheet_targets:
2028
  memory_sheet_records[batch_i].append(
2029
  {
configurations/dataset/video_minecraft_dememwm_latent.yaml CHANGED
@@ -34,7 +34,7 @@ memory_selection:
34
  pose_preselect_topk: 64
35
  candidate_chunk_size: 64
36
  dynamic:
37
- selection_policy: event_triggered
38
  multiview_selector: fov_greedy
39
  scene_threshold: 2.5
40
  state_threshold: 2.5
 
34
  pose_preselect_topk: 64
35
  candidate_chunk_size: 64
36
  dynamic:
37
+ selection_policy: multiview
38
  multiview_selector: fov_greedy
39
  scene_threshold: 2.5
40
  state_threshold: 2.5
datasets/video/memory_selection.py CHANGED
@@ -154,11 +154,20 @@ def _target_fov_points(target_poses: torch.Tensor) -> torch.Tensor:
154
  return points[target_inside]
155
 
156
 
157
- def _pose_preselect(candidates: np.ndarray, poses_t: torch.Tensor, target_positions: np.ndarray, cfg=None) -> np.ndarray:
 
 
 
 
 
 
 
158
  topk = cfg_get(cfg, "pose_preselect_topk", 64)
159
  if topk is None:
160
  return candidates
161
  topk = int(topk)
 
 
162
  if topk <= 0 or len(candidates) <= topk:
163
  return candidates
164
 
@@ -236,6 +245,7 @@ def _build_fov_candidate_pool(
236
  cfg,
237
  *,
238
  use_plucker: bool,
 
239
  ) -> dict[str, torch.Tensor]:
240
  """Build deterministic FOV/Plucker/coverage data for candidate frames."""
241
 
@@ -246,7 +256,8 @@ def _build_fov_candidate_pool(
246
  if len(candidates) == 0 or len(target_positions) == 0:
247
  return _empty_fov_candidate_pool(device)
248
 
249
- candidates = _pose_preselect(candidates, poses_t, target_positions, cfg)
 
250
  inside, fov_values = _candidate_fov_masks(poses_t, candidates, target_positions, cfg)
251
  candidates_t = torch.as_tensor(candidates, device=device, dtype=torch.long)
252
  candidate_poses = poses_t.index_select(0, candidates_t)
@@ -268,6 +279,115 @@ def _build_fov_candidate_pool(
268
  }
269
 
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  def _empty_selection(counts: Mapping[str, int]) -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
272
  indices = {}
273
  masks = {}
@@ -560,10 +680,16 @@ def _select_by_point_union(
560
  if count <= 0:
561
  return np.empty((0,), dtype=np.int64)
562
 
 
 
 
 
 
563
  if fov_pool is None:
564
- if len(candidates) == 0 or len(target_positions) == 0:
565
- return np.empty((0,), dtype=np.int64)
566
  fov_pool = _build_fov_candidate_pool(poses, candidates, target_positions, cfg, use_plucker=use_plucker)
 
 
 
567
 
568
  return _select_by_point_union_from_pool(
569
  fov_pool,
@@ -731,21 +857,9 @@ def _select_dynamic_multiview(
731
  if pool is None:
732
  pool = _build_fov_candidate_pool(poses, candidates, target_positions, cfg, use_plucker=True)
733
  else:
734
- pool_candidates_t = pool["candidates_t"]
735
- allowed_t = torch.as_tensor(candidates, device=pool_candidates_t.device, dtype=torch.long)
736
- keep = (pool_candidates_t[:, None] == allowed_t[None, :]).any(dim=1)
737
- if reference_frames is not None and len(reference_frames) > 0:
738
- ref_t = torch.as_tensor(np.asarray(reference_frames, dtype=np.int64), device=pool_candidates_t.device, dtype=torch.long)
739
- # Reference rows seed residual coverage below even when excluded from selection.
740
- keep |= (pool_candidates_t[:, None] == ref_t[None, :]).any(dim=1)
741
- if not bool(keep.all()):
742
- rows = torch.nonzero(keep, as_tuple=False).flatten()
743
- pool = {
744
- key: value.index_select(0, rows)
745
- if torch.is_tensor(value) and value.shape[:1] == pool_candidates_t.shape[:1]
746
- else value
747
- for key, value in pool.items()
748
- }
749
  selected = _select_dynamic_multiview_from_fov_pool(
750
  pool,
751
  count,
@@ -1145,7 +1259,13 @@ def _select_dynamic_by_policy(
1145
  min_candidate_frame: int = 0,
1146
  reference_frames=None,
1147
  excluded=None,
 
 
 
1148
  ) -> np.ndarray:
 
 
 
1149
  policy = _dynamic_policy(cfg)
1150
  if policy == "recent":
1151
  return _select_dynamic(target_start, count, min_candidate_frame)
@@ -1162,9 +1282,20 @@ def _select_dynamic_by_policy(
1162
  excluded=excluded,
1163
  )
1164
  if policy == "multiview":
1165
- selector = _dynamic_multiview_selector(cfg)
1166
- raise NotImplementedError(
1167
- f"memory_selection.dynamic.selection_policy='multiview' with multiview_selector={selector!r} is not implemented yet"
 
 
 
 
 
 
 
 
 
 
 
1168
  )
1169
  raise AssertionError(f"unhandled dynamic selection policy: {policy!r}")
1170
 
@@ -1178,6 +1309,7 @@ def _select_revisit(
1178
  split: str,
1179
  rng=None,
1180
  min_candidate_frame: int = 0,
 
1181
  ) -> np.ndarray:
1182
  if count <= 0:
1183
  return np.empty((0,), dtype=np.int64)
@@ -1213,6 +1345,7 @@ def _select_revisit(
1213
  fov_threshold=float(cfg_get(cfg, "fov_overlap_threshold", 0.6)),
1214
  min_total_coverage=float(cfg_get(cfg, "min_total_selected_coverage", 0.1)),
1215
  use_plucker=True,
 
1216
  )
1217
 
1218
 
@@ -1249,21 +1382,6 @@ def select_memory_indices(
1249
 
1250
  target_start = int(target_positions[0])
1251
  policy = _dynamic_policy(cfg)
1252
- if policy == "multiview":
1253
- selector = _dynamic_multiview_selector(cfg)
1254
- raise NotImplementedError(
1255
- f"memory_selection.dynamic.selection_policy='multiview' with multiview_selector={selector!r} is not implemented yet"
1256
- )
1257
-
1258
- if policy == "recent":
1259
- dynamic = _select_dynamic_by_policy(
1260
- target_start,
1261
- counts["dynamic"],
1262
- cfg,
1263
- min_candidate_frame=min_candidate_frame,
1264
- )
1265
- else:
1266
- dynamic = np.empty((0,), dtype=np.int64)
1267
 
1268
  anchor_start = min_candidate_frame if anchor_candidate_start is None else max(0, int(anchor_candidate_start))
1269
  if anchor_candidate_stop is None:
@@ -1273,6 +1391,18 @@ def select_memory_indices(
1273
  anchor_candidates = np.arange(anchor_start, anchor_stop, dtype=np.int64)
1274
  anchor = _select_anchor(anchor_candidates, counts["anchor"], cfg, poses=poses)
1275
 
 
 
 
 
 
 
 
 
 
 
 
 
1276
  revisit = _select_revisit(
1277
  poses,
1278
  target_positions,
@@ -1282,9 +1412,17 @@ def select_memory_indices(
1282
  split,
1283
  rng=rng,
1284
  min_candidate_frame=min_candidate_frame,
 
1285
  )
1286
 
1287
- if policy == "event_triggered":
 
 
 
 
 
 
 
1288
  dynamic_references = revisit if len(revisit) > 0 else target_positions
1289
  if dynamic_stream is not None:
1290
  candidates = _memory_candidate_frames(
@@ -1311,6 +1449,21 @@ def select_memory_indices(
1311
  min_candidate_frame=min_candidate_frame,
1312
  reference_frames=dynamic_references,
1313
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1314
 
1315
  _write_segment(indices, masks, "anchor", anchor)
1316
  _write_segment(indices, masks, "dynamic", dynamic)
 
154
  return points[target_inside]
155
 
156
 
157
+ def _pose_preselect(
158
+ candidates: np.ndarray,
159
+ poses_t: torch.Tensor,
160
+ target_positions: np.ndarray,
161
+ cfg=None,
162
+ *,
163
+ extra_topk: int = 0,
164
+ ) -> np.ndarray:
165
  topk = cfg_get(cfg, "pose_preselect_topk", 64)
166
  if topk is None:
167
  return candidates
168
  topk = int(topk)
169
+ if topk > 0:
170
+ topk += max(0, int(extra_topk))
171
  if topk <= 0 or len(candidates) <= topk:
172
  return candidates
173
 
 
245
  cfg,
246
  *,
247
  use_plucker: bool,
248
+ preselect: bool = True,
249
  ) -> dict[str, torch.Tensor]:
250
  """Build deterministic FOV/Plucker/coverage data for candidate frames."""
251
 
 
256
  if len(candidates) == 0 or len(target_positions) == 0:
257
  return _empty_fov_candidate_pool(device)
258
 
259
+ if preselect:
260
+ candidates = _pose_preselect(candidates, poses_t, target_positions, cfg)
261
  inside, fov_values = _candidate_fov_masks(poses_t, candidates, target_positions, cfg)
262
  candidates_t = torch.as_tensor(candidates, device=device, dtype=torch.long)
263
  candidate_poses = poses_t.index_select(0, candidates_t)
 
279
  }
280
 
281
 
282
+ def _select_fov_pool_rows(pool: dict[str, torch.Tensor], rows: torch.Tensor) -> dict[str, torch.Tensor]:
283
+ candidates_t = pool["candidates_t"]
284
+ return {
285
+ key: value.index_select(0, rows)
286
+ if torch.is_tensor(value) and value.shape[:1] == candidates_t.shape[:1]
287
+ else value
288
+ for key, value in pool.items()
289
+ }
290
+
291
+
292
+ def _unique_ordered_frames(*arrays: np.ndarray | None) -> np.ndarray:
293
+ values = []
294
+ seen = set()
295
+ for array in arrays:
296
+ if array is None:
297
+ continue
298
+ for value in np.asarray(array, dtype=np.int64):
299
+ frame = int(value)
300
+ if frame not in seen:
301
+ seen.add(frame)
302
+ values.append(frame)
303
+ return np.asarray(values, dtype=np.int64)
304
+
305
+
306
+ def _filter_fov_candidate_pool(
307
+ pool: dict[str, torch.Tensor],
308
+ candidates: np.ndarray,
309
+ *,
310
+ extra_frames: np.ndarray | None = None,
311
+ ) -> dict[str, torch.Tensor]:
312
+ candidates_t = pool["candidates_t"]
313
+ allowed = _unique_ordered_frames(candidates, extra_frames)
314
+ if len(allowed) == 0:
315
+ rows = torch.empty((0,), device=candidates_t.device, dtype=torch.long)
316
+ return _select_fov_pool_rows(pool, rows)
317
+
318
+ if candidates_t.numel() == 0:
319
+ rows = torch.empty((0,), device=candidates_t.device, dtype=torch.long)
320
+ return _select_fov_pool_rows(pool, rows)
321
+
322
+ allowed_t = torch.as_tensor(allowed, device=candidates_t.device, dtype=torch.long)
323
+ matches = allowed_t[:, None] == candidates_t[None, :]
324
+ keep_allowed = matches.any(dim=1)
325
+ rows = matches.to(dtype=torch.long).argmax(dim=1).index_select(0, torch.nonzero(keep_allowed, as_tuple=False).flatten())
326
+ if rows.numel() == candidates_t.numel() and bool(torch.equal(rows, torch.arange(candidates_t.numel(), device=candidates_t.device))):
327
+ return pool
328
+ return _select_fov_pool_rows(pool, rows)
329
+
330
+
331
+ def _build_shared_fov_candidate_pool(
332
+ poses: np.ndarray,
333
+ target_positions: np.ndarray,
334
+ cfg,
335
+ split: str,
336
+ *,
337
+ min_candidate_frame: int = 0,
338
+ dynamic_count: int = 0,
339
+ revisit_count: int = 0,
340
+ revisit_excluded: np.ndarray | None = None,
341
+ ) -> dict[str, torch.Tensor] | None:
342
+ """Build one exact FOV pool over the candidates needed by both selectors."""
343
+
344
+ target_positions = np.asarray(target_positions, dtype=np.int64)
345
+ if len(target_positions) == 0:
346
+ return None
347
+
348
+ base_candidates = _memory_candidate_frames(
349
+ len(poses),
350
+ target_positions,
351
+ cfg,
352
+ split,
353
+ min_candidate_frame=min_candidate_frame,
354
+ )
355
+ if len(base_candidates) == 0:
356
+ return None
357
+
358
+ poses_t = _as_pose_tensor(poses)
359
+ dynamic_candidates = None
360
+ if int(dynamic_count) > 0:
361
+ # Revisit frames are excluded after selection; include replacement rows
362
+ # that can enter dynamic's pose-preselected top-k after that exclusion.
363
+ dynamic_candidates = _pose_preselect(
364
+ base_candidates,
365
+ poses_t,
366
+ target_positions,
367
+ cfg,
368
+ extra_topk=max(0, int(revisit_count)),
369
+ )
370
+
371
+ revisit_candidates = None
372
+ if split != "training" and int(revisit_count) > 0:
373
+ revisit_candidates = _exclude_revisit_local_context(base_candidates, target_positions, cfg)
374
+ if revisit_excluded is not None and len(revisit_excluded) > 0 and len(revisit_candidates) > 0:
375
+ revisit_candidates = revisit_candidates[~np.isin(revisit_candidates, np.asarray(revisit_excluded, dtype=np.int64))]
376
+ revisit_candidates = _pose_preselect(revisit_candidates, poses_t, target_positions, cfg)
377
+
378
+ pool_candidates = _unique_ordered_frames(dynamic_candidates, revisit_candidates)
379
+ if len(pool_candidates) == 0:
380
+ return None
381
+ return _build_fov_candidate_pool(
382
+ poses,
383
+ pool_candidates,
384
+ target_positions,
385
+ cfg,
386
+ use_plucker=True,
387
+ preselect=False,
388
+ )
389
+
390
+
391
  def _empty_selection(counts: Mapping[str, int]) -> Tuple[Dict[str, np.ndarray], Dict[str, np.ndarray]]:
392
  indices = {}
393
  masks = {}
 
680
  if count <= 0:
681
  return np.empty((0,), dtype=np.int64)
682
 
683
+ candidates = np.asarray(candidates, dtype=np.int64)
684
+ target_positions = np.asarray(target_positions, dtype=np.int64)
685
+ if len(candidates) == 0 or len(target_positions) == 0:
686
+ return np.empty((0,), dtype=np.int64)
687
+
688
  if fov_pool is None:
 
 
689
  fov_pool = _build_fov_candidate_pool(poses, candidates, target_positions, cfg, use_plucker=use_plucker)
690
+ else:
691
+ candidates = _pose_preselect(candidates, _as_pose_tensor(poses), target_positions, cfg)
692
+ fov_pool = _filter_fov_candidate_pool(fov_pool, candidates)
693
 
694
  return _select_by_point_union_from_pool(
695
  fov_pool,
 
857
  if pool is None:
858
  pool = _build_fov_candidate_pool(poses, candidates, target_positions, cfg, use_plucker=True)
859
  else:
860
+ candidates = _pose_preselect(candidates, _as_pose_tensor(poses), target_positions, cfg)
861
+ # Reference rows seed residual coverage below even when excluded from selection.
862
+ pool = _filter_fov_candidate_pool(pool, candidates, extra_frames=reference_frames)
 
 
 
 
 
 
 
 
 
 
 
 
863
  selected = _select_dynamic_multiview_from_fov_pool(
864
  pool,
865
  count,
 
1259
  min_candidate_frame: int = 0,
1260
  reference_frames=None,
1261
  excluded=None,
1262
+ target_positions=None,
1263
+ split: str = "training",
1264
+ fov_pool: dict[str, torch.Tensor] | None = None,
1265
  ) -> np.ndarray:
1266
+ if count <= 0:
1267
+ return np.empty((0,), dtype=np.int64)
1268
+
1269
  policy = _dynamic_policy(cfg)
1270
  if policy == "recent":
1271
  return _select_dynamic(target_start, count, min_candidate_frame)
 
1282
  excluded=excluded,
1283
  )
1284
  if policy == "multiview":
1285
+ if poses is None:
1286
+ raise ValueError("multiview dynamic selection requires poses")
1287
+ if target_positions is None:
1288
+ target_positions = np.asarray([target_start], dtype=np.int64)
1289
+ return _select_dynamic_multiview(
1290
+ poses,
1291
+ target_positions,
1292
+ cfg,
1293
+ count,
1294
+ excluded=excluded,
1295
+ reference_frames=reference_frames,
1296
+ split=split,
1297
+ min_candidate_frame=min_candidate_frame,
1298
+ fov_pool=fov_pool,
1299
  )
1300
  raise AssertionError(f"unhandled dynamic selection policy: {policy!r}")
1301
 
 
1309
  split: str,
1310
  rng=None,
1311
  min_candidate_frame: int = 0,
1312
+ fov_pool: dict[str, torch.Tensor] | None = None,
1313
  ) -> np.ndarray:
1314
  if count <= 0:
1315
  return np.empty((0,), dtype=np.int64)
 
1345
  fov_threshold=float(cfg_get(cfg, "fov_overlap_threshold", 0.6)),
1346
  min_total_coverage=float(cfg_get(cfg, "min_total_selected_coverage", 0.1)),
1347
  use_plucker=True,
1348
+ fov_pool=fov_pool,
1349
  )
1350
 
1351
 
 
1382
 
1383
  target_start = int(target_positions[0])
1384
  policy = _dynamic_policy(cfg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1385
 
1386
  anchor_start = min_candidate_frame if anchor_candidate_start is None else max(0, int(anchor_candidate_start))
1387
  if anchor_candidate_stop is None:
 
1391
  anchor_candidates = np.arange(anchor_start, anchor_stop, dtype=np.int64)
1392
  anchor = _select_anchor(anchor_candidates, counts["anchor"], cfg, poses=poses)
1393
 
1394
+ fov_pool = None
1395
+ if policy == "multiview" and _dynamic_multiview_selector(cfg) == "fov_greedy":
1396
+ fov_pool = _build_shared_fov_candidate_pool(
1397
+ poses,
1398
+ target_positions,
1399
+ cfg,
1400
+ split,
1401
+ min_candidate_frame=min_candidate_frame,
1402
+ dynamic_count=counts["dynamic"],
1403
+ revisit_count=counts["revisit"],
1404
+ )
1405
+
1406
  revisit = _select_revisit(
1407
  poses,
1408
  target_positions,
 
1412
  split,
1413
  rng=rng,
1414
  min_candidate_frame=min_candidate_frame,
1415
+ fov_pool=fov_pool,
1416
  )
1417
 
1418
+ if policy == "recent":
1419
+ dynamic = _select_dynamic_by_policy(
1420
+ target_start,
1421
+ counts["dynamic"],
1422
+ cfg,
1423
+ min_candidate_frame=min_candidate_frame,
1424
+ )
1425
+ elif policy == "event_triggered":
1426
  dynamic_references = revisit if len(revisit) > 0 else target_positions
1427
  if dynamic_stream is not None:
1428
  candidates = _memory_candidate_frames(
 
1449
  min_candidate_frame=min_candidate_frame,
1450
  reference_frames=dynamic_references,
1451
  )
1452
+ elif policy == "multiview":
1453
+ dynamic = _select_dynamic_by_policy(
1454
+ target_start,
1455
+ counts["dynamic"],
1456
+ cfg,
1457
+ poses=poses,
1458
+ min_candidate_frame=min_candidate_frame,
1459
+ reference_frames=revisit,
1460
+ excluded=revisit,
1461
+ target_positions=target_positions,
1462
+ split=split,
1463
+ fov_pool=fov_pool,
1464
+ )
1465
+ else:
1466
+ raise AssertionError(f"unhandled dynamic selection policy: {policy!r}")
1467
 
1468
  _write_segment(indices, masks, "anchor", anchor)
1469
  _write_segment(indices, masks, "dynamic", dynamic)
tests/test_dememwm_latent_dataset.py CHANGED
@@ -209,7 +209,7 @@ class MemorySelectionTests(unittest.TestCase):
209
  cfg = OmegaConf.load(config_path)
210
 
211
  self.assertIs(cfg.memory_selection.causal, True)
212
- self.assertEqual(cfg.memory_selection.dynamic.selection_policy, "event_triggered")
213
  self.assertEqual(cfg.memory_selection.dynamic.multiview_selector, "fov_greedy")
214
  self.assertEqual(cfg.memory_selection.pose_preselect_topk, 64)
215
  self.assertEqual(cfg.memory_selection.candidate_chunk_size, 64)
@@ -1030,19 +1030,125 @@ class MemorySelectionTests(unittest.TestCase):
1030
  with self.assertRaisesRegex(ValueError, "fov_greedy, pose_plucker_fps"):
1031
  select_memory_indices(poses, np.array([6]), cfg)
1032
 
1033
- def test_multiview_policy_selection_fails_explicitly_until_implemented(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1034
  poses = np.zeros((8, 5), dtype=np.float32)
1035
- for selector in ("fov_greedy", "pose_plucker_fps"):
1036
- with self.subTest(selector=selector):
1037
- cfg = _selection_cfg(
1038
- max_anchor_frames=0,
1039
- max_dynamic_frames=2,
1040
- max_revisit_frames=0,
1041
- dynamic=_dynamic_cfg("multiview", multiview_selector=selector),
1042
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1043
 
1044
- with self.assertRaisesRegex(NotImplementedError, "multiview"):
1045
- select_memory_indices(poses, np.array([6]), cfg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1046
 
1047
  def test_event_dynamic_uses_nearest_anchors_per_revisit_frame(self):
1048
  import datasets.video.memory_selection as memory_selection
@@ -1701,6 +1807,167 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
1701
  self.assertEqual(video_calls[0][1]["namespace"], "test_vis")
1702
  self.assertEqual(video_calls[0][1]["step"], 0)
1703
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1704
  def test_validation_event_policy_uses_committed_history_before_local_span(self):
1705
  from algorithms.dememwm.df_video import DeMemWMMinecraft, _preprocess_dememwm_latent_batch
1706
 
 
209
  cfg = OmegaConf.load(config_path)
210
 
211
  self.assertIs(cfg.memory_selection.causal, True)
212
+ self.assertEqual(cfg.memory_selection.dynamic.selection_policy, "multiview")
213
  self.assertEqual(cfg.memory_selection.dynamic.multiview_selector, "fov_greedy")
214
  self.assertEqual(cfg.memory_selection.pose_preselect_topk, 64)
215
  self.assertEqual(cfg.memory_selection.candidate_chunk_size, 64)
 
1030
  with self.assertRaisesRegex(ValueError, "fov_greedy, pose_plucker_fps"):
1031
  select_memory_indices(poses, np.array([6]), cfg)
1032
 
1033
+ def test_multiview_policy_selection_uses_shared_fov_pool_and_segments(self):
1034
+ import datasets.video.memory_selection as memory_selection
1035
+
1036
+ poses = _poses(10)
1037
+ cfg = _selection_cfg(
1038
+ max_anchor_frames=1,
1039
+ max_dynamic_frames=2,
1040
+ max_revisit_frames=1,
1041
+ local_context_exclusion_frames=2,
1042
+ fov_overlap_threshold=0.0,
1043
+ min_total_selected_coverage=0.0,
1044
+ dynamic=_dynamic_cfg("multiview", multiview_selector="fov_greedy"),
1045
+ )
1046
+ pool = _fov_pool(
1047
+ [0, 1, 2, 3, 4, 5],
1048
+ [
1049
+ [True, False, False],
1050
+ [False, True, False],
1051
+ [False, False, True],
1052
+ [True, True, False],
1053
+ [False, False, False],
1054
+ [True, True, True],
1055
+ ],
1056
+ gaps=[6, 5, 4, 3, 2, 1],
1057
+ poses=poses,
1058
+ )
1059
+ dynamic_calls = []
1060
+
1061
+ def fake_dynamic(
1062
+ poses_arg,
1063
+ target_positions,
1064
+ cfg_arg,
1065
+ count,
1066
+ *,
1067
+ excluded=None,
1068
+ reference_frames=None,
1069
+ split="training",
1070
+ min_candidate_frame=0,
1071
+ fov_pool=None,
1072
+ ):
1073
+ dynamic_calls.append(
1074
+ {
1075
+ "target_positions": target_positions.copy(),
1076
+ "excluded": np.asarray(excluded, dtype=np.int64).copy(),
1077
+ "reference_frames": np.asarray(reference_frames, dtype=np.int64).copy(),
1078
+ "split": split,
1079
+ "fov_pool": fov_pool,
1080
+ }
1081
+ )
1082
+ return np.asarray([1, 2], dtype=np.int64)[:count]
1083
+
1084
+ with mock.patch.object(memory_selection, "_build_fov_candidate_pool", return_value=pool) as build_mock, mock.patch.object(
1085
+ memory_selection, "_select_dynamic_multiview", side_effect=fake_dynamic
1086
+ ):
1087
+ indices, masks = select_memory_indices(poses, np.array([6, 7]), cfg, split="validation")
1088
+
1089
+ self.assertEqual(set(indices), {"anchor", "dynamic", "revisit"})
1090
+ self.assertEqual({key: len(indices[key]) for key in ("anchor", "dynamic", "revisit")}, {"anchor": 1, "dynamic": 2, "revisit": 1})
1091
+ self.assertEqual({key: len(masks[key]) for key in ("anchor", "dynamic", "revisit")}, {"anchor": 1, "dynamic": 2, "revisit": 1})
1092
+ self.assertEqual(build_mock.call_count, 1)
1093
+ self.assertEqual(build_mock.call_args.args[2].tolist(), [6, 7])
1094
+ selected_revisit = indices["revisit"][masks["revisit"]]
1095
+ self.assertTrue(np.all(selected_revisit < 4))
1096
+ self.assertEqual(len(dynamic_calls), 1)
1097
+ self.assertIs(dynamic_calls[0]["fov_pool"], pool)
1098
+ self.assertEqual(dynamic_calls[0]["target_positions"].tolist(), [6, 7])
1099
+ self.assertEqual(dynamic_calls[0]["excluded"].tolist(), selected_revisit.tolist())
1100
+ self.assertEqual(dynamic_calls[0]["reference_frames"].tolist(), selected_revisit.tolist())
1101
+ self.assertEqual(indices["dynamic"].tolist(), [1, 2])
1102
+ self.assertEqual(masks["dynamic"].tolist(), [True, True])
1103
+
1104
+ def test_multiview_shared_fov_pool_preselects_after_revisit_local_exclusion(self):
1105
  poses = np.zeros((8, 5), dtype=np.float32)
1106
+ poses[:, 0] = 1000.0
1107
+ poses[:, 4] = 180.0
1108
+ poses[0] = np.asarray([0, 0, 0, 0, 0], dtype=np.float32)
1109
+ poses[5] = np.asarray([0, 0, 0, 0, 0], dtype=np.float32)
1110
+ poses[6] = np.asarray([0, 0, 0, 0, 0], dtype=np.float32)
1111
+ cfg = _selection_cfg(
1112
+ max_anchor_frames=0,
1113
+ max_dynamic_frames=1,
1114
+ max_revisit_frames=1,
1115
+ local_context_exclusion_frames=2,
1116
+ pose_preselect_topk=1,
1117
+ fov_overlap_threshold=0.0,
1118
+ min_total_selected_coverage=0.0,
1119
+ dynamic=_dynamic_cfg("multiview", multiview_selector="fov_greedy"),
1120
+ )
1121
+
1122
+ indices, masks = select_memory_indices(poses, np.asarray([6], dtype=np.int64), cfg, split="validation")
1123
+
1124
+ self.assertEqual(indices["revisit"].tolist(), [0])
1125
+ self.assertEqual(masks["revisit"].tolist(), [True])
1126
+
1127
+ def test_multiview_policy_dispatcher_calls_selector_with_target_positions(self):
1128
+ import datasets.video.memory_selection as memory_selection
1129
 
1130
+ poses = _poses(8)
1131
+ cfg = _selection_cfg(
1132
+ max_anchor_frames=0,
1133
+ max_dynamic_frames=1,
1134
+ max_revisit_frames=0,
1135
+ dynamic=_dynamic_cfg("multiview", multiview_selector="fov_greedy"),
1136
+ )
1137
+ target_positions = np.asarray([6, 7], dtype=np.int64)
1138
+
1139
+ with mock.patch.object(memory_selection, "_select_dynamic_multiview", return_value=np.asarray([2], dtype=np.int64)) as select_mock:
1140
+ selected = memory_selection._select_dynamic_by_policy(
1141
+ 6,
1142
+ 1,
1143
+ cfg,
1144
+ poses=poses,
1145
+ target_positions=target_positions,
1146
+ split="validation",
1147
+ )
1148
+
1149
+ self.assertEqual(selected.tolist(), [2])
1150
+ self.assertEqual(select_mock.call_args.args[1].tolist(), [6, 7])
1151
+ self.assertEqual(select_mock.call_args.kwargs["split"], "validation")
1152
 
1153
  def test_event_dynamic_uses_nearest_anchors_per_revisit_frame(self):
1154
  import datasets.video.memory_selection as memory_selection
 
1807
  self.assertEqual(video_calls[0][1]["namespace"], "test_vis")
1808
  self.assertEqual(video_calls[0][1]["step"], 0)
1809
 
1810
+ def test_validation_multiview_policy_uses_current_query_pool_without_event_cache(self):
1811
+ import algorithms.dememwm.df_video as df_video
1812
+ from algorithms.dememwm.df_video import DeMemWMMinecraft, _preprocess_dememwm_latent_batch
1813
+
1814
+ build_calls = []
1815
+ revisit_calls = []
1816
+ dynamic_calls = []
1817
+
1818
+ def fake_build_shared_fov_pool(
1819
+ poses,
1820
+ target_positions,
1821
+ cfg,
1822
+ split,
1823
+ *,
1824
+ min_candidate_frame=0,
1825
+ dynamic_count=0,
1826
+ revisit_count=0,
1827
+ revisit_excluded=None,
1828
+ ):
1829
+ pool = {"query": tuple(int(v) for v in target_positions)}
1830
+ build_calls.append(
1831
+ {
1832
+ "target_positions": target_positions.tolist(),
1833
+ "split": split,
1834
+ "min_candidate_frame": min_candidate_frame,
1835
+ "dynamic_count": dynamic_count,
1836
+ "revisit_count": revisit_count,
1837
+ "pool": pool,
1838
+ }
1839
+ )
1840
+ return pool
1841
+
1842
+ def fake_select_revisit(poses, target_positions, cfg, count, excluded, split, rng=None, min_candidate_frame=0, fov_pool=None):
1843
+ revisit_calls.append(
1844
+ {
1845
+ "target_positions": target_positions.tolist(),
1846
+ "split": split,
1847
+ "fov_pool": fov_pool,
1848
+ }
1849
+ )
1850
+ return np.asarray([0], dtype=np.int64)[:count]
1851
+
1852
+ def fake_select_dynamic_by_policy(
1853
+ target_start,
1854
+ count,
1855
+ cfg,
1856
+ poses=None,
1857
+ latents=None,
1858
+ actions=None,
1859
+ min_candidate_frame=0,
1860
+ reference_frames=None,
1861
+ excluded=None,
1862
+ target_positions=None,
1863
+ split="training",
1864
+ fov_pool=None,
1865
+ ):
1866
+ dynamic_calls.append(
1867
+ {
1868
+ "target_start": int(target_start),
1869
+ "target_positions": np.asarray(target_positions, dtype=np.int64).tolist(),
1870
+ "excluded": np.asarray(excluded, dtype=np.int64).tolist(),
1871
+ "reference_frames": np.asarray(reference_frames, dtype=np.int64).tolist(),
1872
+ "split": split,
1873
+ "fov_pool": fov_pool,
1874
+ }
1875
+ )
1876
+ return np.asarray([1], dtype=np.int64)[:count]
1877
+
1878
+ class FakeDiffusion:
1879
+ stabilization_level = 0
1880
+
1881
+ def __init__(self):
1882
+ self.calls = []
1883
+
1884
+ def sample_step(self, x, action_cond, pose_cond, curr_noise_level, next_noise_level, **kwargs):
1885
+ self.calls.append({"x": x.detach().clone(), "kwargs": kwargs})
1886
+ return x[: kwargs["frame_memory_segments"]["target"]]
1887
+
1888
+ class Harness:
1889
+ def __init__(self):
1890
+ self.memory_condition_length, self.clip_noise = 2, 0.0
1891
+ self.context_frames, self.frame_stack, self.chunk_size, self.n_tokens = 2, 1, 2, 2
1892
+ self.cfg = OmegaConf.create(
1893
+ {
1894
+ "memory_selection": {
1895
+ "enabled": True,
1896
+ "max_anchor_frames": 0,
1897
+ "max_dynamic_frames": 1,
1898
+ "max_revisit_frames": 1,
1899
+ "dynamic": _dynamic_cfg("multiview", multiview_selector="fov_greedy"),
1900
+ }
1901
+ }
1902
+ )
1903
+ self.diffusion_model = FakeDiffusion()
1904
+ self.horizons = []
1905
+ self.logged = []
1906
+ self.logger = None
1907
+ self.log_video = False
1908
+ self.log_memory_selection_sheet = False
1909
+ self.save_local = False
1910
+ self.local_save_dir = None
1911
+
1912
+ def _preprocess_batch(self, batch):
1913
+ return _preprocess_dememwm_latent_batch(batch)
1914
+
1915
+ def _generate_scheduling_matrix(self, horizon):
1916
+ self.horizons.append(horizon)
1917
+ return np.stack([np.full(horizon, level, dtype=np.int64) for level in (1, 0)])
1918
+
1919
+ def decode(self, xs):
1920
+ return xs
1921
+
1922
+ def _update_metric_accumulators(self, xs_pred, xs_gt, valid_mask=None, eval_start=0):
1923
+ pass
1924
+
1925
+ def _update_per_frame_metric_accumulators(self, xs_pred, xs_gt, mask, eval_start):
1926
+ pass
1927
+
1928
+ def _update_revisit_metric_accumulators(self, xs_pred, xs_gt, poses, frame_indices, mask, eval_start, batch_idx, namespace):
1929
+ pass
1930
+
1931
+ def log(self, name, value):
1932
+ self.logged.append((name, value))
1933
+
1934
+ batch = {
1935
+ "latents": torch.arange(7, dtype=torch.float32).view(1, 7, 1, 1, 1),
1936
+ "actions": torch.zeros((1, 7, 25), dtype=torch.float32),
1937
+ "poses": torch.arange(7, dtype=torch.float32).view(1, 7, 1).repeat(1, 1, 5),
1938
+ "frame_indices": (10 + torch.arange(7, dtype=torch.long)).view(1, 7),
1939
+ "memory_segments": {"target": torch.tensor([5]), "anchor": torch.tensor([0]), "dynamic": torch.tensor([1]), "revisit": torch.tensor([1])},
1940
+ "memory_masks": {
1941
+ "target": torch.ones((1, 5), dtype=torch.bool),
1942
+ "anchor": torch.zeros((1, 0), dtype=torch.bool),
1943
+ "dynamic": torch.ones((1, 1), dtype=torch.bool),
1944
+ "revisit": torch.ones((1, 1), dtype=torch.bool),
1945
+ },
1946
+ "image_hw": torch.tensor([[360, 640]], dtype=torch.long),
1947
+ }
1948
+ harness = Harness()
1949
+
1950
+ with mock.patch.object(df_video, "_new_online_event_cache", side_effect=AssertionError("unexpected event cache")), mock.patch.object(
1951
+ df_video, "_extend_online_event_cache", side_effect=AssertionError("unexpected event cache extension")
1952
+ ), mock.patch.object(df_video, "_build_shared_fov_candidate_pool", side_effect=fake_build_shared_fov_pool), mock.patch.object(
1953
+ df_video, "_select_revisit", side_effect=fake_select_revisit
1954
+ ), mock.patch.object(df_video, "_select_dynamic_by_policy", side_effect=fake_select_dynamic_by_policy):
1955
+ DeMemWMMinecraft.validation_step(harness, batch, 0, namespace="test")
1956
+
1957
+ self.assertEqual(harness.horizons, [2, 1])
1958
+ self.assertEqual([call["target_positions"] for call in build_calls], [[2, 3], [4]])
1959
+ self.assertEqual([call["split"] for call in build_calls], ["test", "test"])
1960
+ self.assertEqual([call["dynamic_count"] for call in build_calls], [1, 1])
1961
+ self.assertEqual([call["revisit_count"] for call in build_calls], [1, 1])
1962
+ self.assertEqual([call["target_positions"] for call in revisit_calls], [[2, 3], [4]])
1963
+ self.assertEqual([call["target_positions"] for call in dynamic_calls], [[2, 3], [4]])
1964
+ for built, revisit, dynamic in zip(build_calls, revisit_calls, dynamic_calls):
1965
+ self.assertIs(revisit["fov_pool"], built["pool"])
1966
+ self.assertIs(dynamic["fov_pool"], built["pool"])
1967
+ self.assertEqual([call["excluded"] for call in dynamic_calls], [[0], [0]])
1968
+ self.assertEqual([call["reference_frames"] for call in dynamic_calls], [[0], [0]])
1969
+ self.assertEqual([name for name, _ in harness.logged], ["test/latent_mse"])
1970
+
1971
  def test_validation_event_policy_uses_committed_history_before_local_span(self):
1972
  from algorithms.dememwm.df_video import DeMemWMMinecraft, _preprocess_dememwm_latent_batch
1973