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

Select DeMemWM dynamic memory by multiview retrieval

Browse files
.exp_artifact/dememwm_dynamic_multiview_memory_selection_plan.md CHANGED
@@ -604,7 +604,7 @@ Add DeMemWM multiview selector backends
604
 
605
  ## Substep 4: Implement Dynamic-As-Multiview Selection
606
 
607
- Status: `[ ]`
608
 
609
  Goal:
610
 
 
604
 
605
  ## Substep 4: Implement Dynamic-As-Multiview Selection
606
 
607
+ Status: `[x]`
608
 
609
  Goal:
610
 
datasets/video/memory_selection.py CHANGED
@@ -691,6 +691,83 @@ def _dynamic_multiview_selector(cfg) -> str:
691
  return selector
692
 
693
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
694
  def _latent_frame_vectors(latents, start: int, stop: int):
695
  if latents is None or stop <= start:
696
  return None
 
691
  return selector
692
 
693
 
694
+ def _select_dynamic_multiview(
695
+ poses: np.ndarray,
696
+ target_positions: np.ndarray,
697
+ cfg,
698
+ count: int,
699
+ *,
700
+ excluded: np.ndarray | None = None,
701
+ reference_frames: np.ndarray | None = None,
702
+ split: str = "training",
703
+ min_candidate_frame: int = 0,
704
+ fov_pool: dict[str, torch.Tensor] | None = None,
705
+ ) -> np.ndarray:
706
+ """Select dynamic-as-multiview memory frames."""
707
+
708
+ if count <= 0:
709
+ return np.empty((0,), dtype=np.int64)
710
+
711
+ poses = _as_pose_array(poses)
712
+ target_positions = np.asarray(target_positions, dtype=np.int64)
713
+ if len(target_positions) == 0:
714
+ return np.empty((0,), dtype=np.int64)
715
+
716
+ candidates = _memory_candidate_frames(
717
+ len(poses),
718
+ target_positions,
719
+ cfg,
720
+ split,
721
+ min_candidate_frame=min_candidate_frame,
722
+ )
723
+ if excluded is not None and len(excluded) > 0 and len(candidates) > 0:
724
+ candidates = candidates[~np.isin(candidates, np.asarray(excluded, dtype=np.int64))]
725
+ if len(candidates) == 0:
726
+ return np.empty((0,), dtype=np.int64)
727
+
728
+ selector = _dynamic_multiview_selector(cfg)
729
+ if selector == "fov_greedy":
730
+ pool = fov_pool
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,
752
+ excluded=excluded,
753
+ reference_frames=reference_frames,
754
+ all_poses=poses,
755
+ )
756
+ elif selector == "pose_plucker_fps":
757
+ selected = _select_dynamic_multiview_pose_plucker_fps(
758
+ poses,
759
+ candidates,
760
+ target_positions,
761
+ cfg,
762
+ count,
763
+ seed_ids=reference_frames,
764
+ )
765
+ else:
766
+ raise AssertionError(f"unhandled dynamic multiview selector: {selector!r}")
767
+
768
+ return np.sort(np.asarray(selected, dtype=np.int64))
769
+
770
+
771
  def _latent_frame_vectors(latents, start: int, stop: int):
772
  if latents is None or stop <= start:
773
  return None
tests/test_dememwm_latent_dataset.py CHANGED
@@ -542,6 +542,113 @@ class MemorySelectionTests(unittest.TestCase):
542
 
543
  self.assertEqual(selected.tolist(), [2])
544
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
545
  def test_training_default_causal_revisit_does_not_select_future(self):
546
  poses = _poses(8)
547
  poses[6] = poses[4]
 
542
 
543
  self.assertEqual(selected.tolist(), [2])
544
 
545
+ def test_dynamic_multiview_provided_fov_pool_keeps_excluded_reference_coverage(self):
546
+ import datasets.video.memory_selection as memory_selection
547
+
548
+ poses = _poses(6)
549
+ cfg = _selection_cfg(dynamic=_dynamic_cfg("multiview", multiview_selector="fov_greedy"))
550
+ pool = _fov_pool(
551
+ [0, 2, 3],
552
+ [[True, False], [True, False], [False, True]],
553
+ fov_values=[0.5, 1.0, 0.5],
554
+ plucker=[0.0, 0.0, 0.0],
555
+ gaps=[5, 3, 2],
556
+ poses=poses,
557
+ )
558
+
559
+ with mock.patch.object(
560
+ memory_selection,
561
+ "_build_fov_candidate_pool",
562
+ side_effect=AssertionError("unexpected FOV pool build"),
563
+ ):
564
+ selected = memory_selection._select_dynamic_multiview(
565
+ poses,
566
+ np.asarray([5], dtype=np.int64),
567
+ cfg,
568
+ count=1,
569
+ excluded=np.asarray([0], dtype=np.int64),
570
+ reference_frames=np.asarray([0], dtype=np.int64),
571
+ split="training",
572
+ fov_pool=pool,
573
+ )
574
+
575
+ self.assertEqual(selected.tolist(), [3])
576
+
577
+ def test_dynamic_multiview_fov_greedy_candidates_pool_exclusion_and_determinism(self):
578
+ import datasets.video.memory_selection as memory_selection
579
+
580
+ poses = _poses(8)
581
+ default_cfg = _selection_cfg(dynamic=_dynamic_cfg("multiview", multiview_selector="fov_greedy"))
582
+ noncausal_cfg = _selection_cfg(causal=False, dynamic=_dynamic_cfg("multiview", multiview_selector="fov_greedy"))
583
+ cases = [
584
+ (default_cfg, "training", np.asarray([5], dtype=np.int64), [1, 2, 3, 4], [3, 4]),
585
+ (noncausal_cfg, "training", np.asarray([3], dtype=np.int64), [1, 2, 3, 4, 5, 6, 7], [6, 7]),
586
+ (noncausal_cfg, "validation", np.asarray([3], dtype=np.int64), [1, 2], [1, 2]),
587
+ (noncausal_cfg, "test", np.asarray([3], dtype=np.int64), [1, 2], [1, 2]),
588
+ ]
589
+
590
+ def fake_select(pool, count, **kwargs):
591
+ return pool["candidates_t"].cpu().numpy()[-count:]
592
+
593
+ for cfg, split, targets, expected_candidates, expected_selected in cases:
594
+ seen_candidates = []
595
+
596
+ def fake_build(poses_arg, candidates, target_positions, cfg_arg, *, use_plucker):
597
+ seen_candidates.append((candidates.copy(), use_plucker))
598
+ return _fov_pool(candidates, np.ones((len(candidates), 1), dtype=bool), poses=poses_arg)
599
+
600
+ with self.subTest(split=split), mock.patch.object(memory_selection, "_build_fov_candidate_pool", side_effect=fake_build), mock.patch.object(
601
+ memory_selection, "_select_dynamic_multiview_from_fov_pool", side_effect=fake_select
602
+ ):
603
+ selected = memory_selection._select_dynamic_multiview(poses, targets, cfg, count=2, split=split, min_candidate_frame=1)
604
+
605
+ self.assertEqual(seen_candidates[0][0].tolist(), expected_candidates)
606
+ self.assertTrue(seen_candidates[0][1])
607
+ self.assertEqual(selected.tolist(), expected_selected)
608
+
609
+ provided_poses = _poses(6)
610
+ pool = _fov_pool([0, 1, 2, 3], [[True, False], [False, True], [True, False], [False, True]], poses=provided_poses)
611
+ kwargs = dict(excluded=np.asarray([0, 1], dtype=np.int64), reference_frames=np.asarray([0, 1], dtype=np.int64), split="training", fov_pool=pool)
612
+ with mock.patch.object(memory_selection, "_build_fov_candidate_pool", side_effect=AssertionError("unexpected FOV pool build")):
613
+ first = memory_selection._select_dynamic_multiview(provided_poses, np.asarray([5], dtype=np.int64), default_cfg, count=2, **kwargs)
614
+ second = memory_selection._select_dynamic_multiview(provided_poses, np.asarray([5], dtype=np.int64), default_cfg, count=2, **kwargs)
615
+
616
+ self.assertEqual(first.tolist(), [2, 3])
617
+ self.assertEqual(second.tolist(), first.tolist())
618
+ self.assertTrue(set(first.tolist()).isdisjoint({0, 1}))
619
+
620
+ def test_dynamic_multiview_pose_plucker_fps_uses_pose_path_without_fov_masks(self):
621
+ import datasets.video.memory_selection as memory_selection
622
+
623
+ poses = _poses(8)
624
+ cfg = _selection_cfg(
625
+ pose_similarity_threshold=0.0,
626
+ training_use_plucker=False,
627
+ pose_preselect_topk=3,
628
+ dynamic=_dynamic_cfg("multiview", multiview_selector="pose_plucker_fps"),
629
+ )
630
+ reference = np.asarray([0], dtype=np.int64)
631
+
632
+ with mock.patch.object(memory_selection, "_candidate_fov_masks", side_effect=AssertionError("unexpected exact FOV masks")), mock.patch.object(
633
+ memory_selection, "_build_fov_candidate_pool", side_effect=AssertionError("unexpected FOV pool build")
634
+ ), mock.patch.object(memory_selection, "_rank_pose_plucker_candidates", wraps=memory_selection._rank_pose_plucker_candidates) as rank_mock, mock.patch.object(
635
+ memory_selection, "_select_pose_fps", wraps=memory_selection._select_pose_fps
636
+ ) as fps_mock:
637
+ selected = memory_selection._select_dynamic_multiview(
638
+ poses,
639
+ np.asarray([6], dtype=np.int64),
640
+ cfg,
641
+ count=2,
642
+ reference_frames=reference,
643
+ split="training",
644
+ )
645
+
646
+ self.assertTrue(rank_mock.called)
647
+ self.assertTrue(fps_mock.called)
648
+ self.assertEqual(fps_mock.call_args.kwargs["seed_ids"].tolist(), [0])
649
+ self.assertEqual(selected.tolist(), sorted(selected.tolist()))
650
+ self.assertLessEqual(len(selected), 2)
651
+
652
  def test_training_default_causal_revisit_does_not_select_future(self):
653
  poses = _poses(8)
654
  poses[6] = poses[4]