BonanDing commited on
Commit
1be60e5
·
1 Parent(s): 6088be3

Align DeMemWM latent dataset loading

Browse files
datasets/video/minecraft_video_dememwm_latent_dataset.py CHANGED
@@ -10,6 +10,26 @@ from omegaconf import DictConfig
10
  from .memory_selection import SEGMENT_KEYS, cfg_get, memory_segment_lengths, select_memory_indices
11
 
12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
14
  """Load precomputed VAE latents and append causal memory frames.
15
 
@@ -23,6 +43,8 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
23
  self.cfg = cfg
24
  self.split = split
25
  self.save_dir = Path(cfg.save_dir)
 
 
26
  self.n_frames = int(cfg_get(cfg, "n_frames_valid", cfg.n_frames)) if split in {"validation", "test"} else int(cfg.n_frames)
27
  self.frame_skip = int(cfg_get(cfg, "frame_skip", 1))
28
  self.target_span = max(1, (self.n_frames - 1) * self.frame_skip + 1)
@@ -32,12 +54,23 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
32
  self._target_offsets = np.arange(self.n_frames, dtype=np.int64) * self.frame_skip
33
  self._memory_segments = memory_segment_lengths(self.n_frames, self.memory_selection)
34
  self.memory_condition_length = sum(self._memory_segments[key] for key in SEGMENT_KEYS)
 
 
35
  self.data_paths = self.get_data_paths(split)
36
  if not self.data_paths:
37
- raise FileNotFoundError(f"No latent .npz files found for split '{split}' under {self.save_dir}")
38
 
39
  self.lengths = np.asarray([self._file_length(path) for path in self.data_paths], dtype=np.int64)
40
- clips = np.clip(self.lengths - self.target_span + 1, a_min=1, a_max=None).astype(np.int64)
 
 
 
 
 
 
 
 
 
41
  if self.single_eval_clip:
42
  clips = np.ones_like(clips)
43
  self.clips_per_file = clips
@@ -49,8 +82,22 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
49
 
50
  def get_data_paths(self, split: str):
51
  split_dir = self.save_dir / split
52
- data_dir = split_dir if split_dir.exists() else self.save_dir
53
- return sorted(path for path in data_dir.glob("**/*.npz") if path.is_file())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  def __len__(self):
56
  return int(self.cum_clips_per_file[-1])
@@ -66,6 +113,7 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
66
  def load_data(self, idx: int):
67
  # === 1. Resolve clip and load arrays ===
68
  file_idx, frame_idx = self.split_idx(int(idx))
 
69
  arrays = self._load_arrays(self.data_paths[file_idx])
70
  latents = arrays["latents"]
71
  actions = arrays["actions"]
@@ -116,10 +164,13 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
116
  return int(data["latents"].shape[0])
117
 
118
  def _load_arrays(self, path: Path):
 
 
 
119
  with np.load(path, allow_pickle=False) as data:
120
  latents = np.asarray(data["latents"])
121
- actions = np.asarray(data["actions"])
122
- poses = np.asarray(data["poses"], dtype=np.float32)
123
  if "frame_indices" in data:
124
  frame_indices = np.asarray(data["frame_indices"], dtype=np.int64)
125
  else:
@@ -131,13 +182,40 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
131
  image_hw = np.asarray(cfg_get(self.cfg, "image_hw", [cfg_get(self.cfg, "resolution", 0), cfg_get(self.cfg, "resolution", 0)]), dtype=np.int64)
132
 
133
  self._validate_arrays(path, latents, actions, poses, frame_indices)
134
- return {
135
  "latents": latents,
136
  "actions": actions,
137
  "poses": poses,
138
  "frame_indices": frame_indices,
139
  "image_hw": image_hw,
140
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  def _validate_arrays(self, path: Path, latents, actions, poses, frame_indices) -> None:
143
  length = len(latents)
 
10
  from .memory_selection import SEGMENT_KEYS, cfg_get, memory_segment_lengths, select_memory_indices
11
 
12
 
13
+ _INITIAL_FRAME_OFFSET = 100
14
+ _ACTION_DIM = 25
15
+
16
+
17
+ def _convert_action_space(actions: np.ndarray) -> np.ndarray:
18
+ vec_25 = np.zeros((len(actions), _ACTION_DIM), dtype=np.float32)
19
+ vec_25[actions[:, 0] == 1, 11] = 1.0
20
+ vec_25[actions[:, 0] == 2, 12] = 1.0
21
+ vec_25[actions[:, 4] == 11, 16] = -1.0
22
+ vec_25[actions[:, 4] == 13, 16] = 1.0
23
+ vec_25[actions[:, 3] == 11, 15] = -1.0
24
+ vec_25[actions[:, 3] == 13, 15] = 1.0
25
+ vec_25[actions[:, 5] == 6, 24] = 1.0
26
+ vec_25[actions[:, 5] == 1, 24] = 1.0
27
+ vec_25[actions[:, 1] == 1, 13] = 1.0
28
+ vec_25[actions[:, 1] == 2, 14] = 1.0
29
+ vec_25[actions[:, 7] == 1, 2] = 1.0
30
+ return vec_25
31
+
32
+
33
  class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
34
  """Load precomputed VAE latents and append causal memory frames.
35
 
 
43
  self.cfg = cfg
44
  self.split = split
45
  self.save_dir = Path(cfg.save_dir)
46
+ self.wo_updown = bool(cfg_get(cfg, "wo_updown", False))
47
+ self.initial_frame_offset = _INITIAL_FRAME_OFFSET
48
  self.n_frames = int(cfg_get(cfg, "n_frames_valid", cfg.n_frames)) if split in {"validation", "test"} else int(cfg.n_frames)
49
  self.frame_skip = int(cfg_get(cfg, "frame_skip", 1))
50
  self.target_span = max(1, (self.n_frames - 1) * self.frame_skip + 1)
 
54
  self._target_offsets = np.arange(self.n_frames, dtype=np.int64) * self.frame_skip
55
  self._memory_segments = memory_segment_lengths(self.n_frames, self.memory_selection)
56
  self.memory_condition_length = sum(self._memory_segments[key] for key in SEGMENT_KEYS)
57
+ self._cached_arrays_path = None
58
+ self._cached_arrays = None
59
  self.data_paths = self.get_data_paths(split)
60
  if not self.data_paths:
61
+ raise FileNotFoundError(f"No latent .npz files found for split '{split}' under {self.save_dir / split}")
62
 
63
  self.lengths = np.asarray([self._file_length(path) for path in self.data_paths], dtype=np.int64)
64
+ required_length = self.initial_frame_offset + self.target_span
65
+ too_short = self.lengths < required_length
66
+ if bool(too_short.any()):
67
+ bad_idx = int(np.nonzero(too_short)[0][0])
68
+ raise ValueError(
69
+ f"Latent file {self.data_paths[bad_idx]} has {int(self.lengths[bad_idx])} frames, "
70
+ f"but DeMemWM requires at least {required_length} for split '{split}'"
71
+ )
72
+
73
+ clips = (self.lengths - required_length + 1).astype(np.int64)
74
  if self.single_eval_clip:
75
  clips = np.ones_like(clips)
76
  self.clips_per_file = clips
 
82
 
83
  def get_data_paths(self, split: str):
84
  split_dir = self.save_dir / split
85
+ if not split_dir.exists():
86
+ return []
87
+ paths = sorted((path for path in split_dir.glob("**/*.npz") if path.is_file()), key=lambda path: path.name)
88
+
89
+ if self.wo_updown:
90
+ paths = [path for path in paths if "w_updown" not in str(path)]
91
+
92
+ if split in {"validation", "test"} and self.wo_updown:
93
+ paths = [path for path in paths if "w_updown" not in str(path)]
94
+ elif split in {"validation", "test"}:
95
+ paths = [path for path in paths if "w_updown" in str(path)]
96
+
97
+ if not paths:
98
+ for sub_path in sorted(split_dir.iterdir(), key=lambda path: path.name):
99
+ paths += sorted((path for path in sub_path.glob("**/*.npz") if path.is_file()), key=lambda path: path.name)
100
+ return paths
101
 
102
  def __len__(self):
103
  return int(self.cum_clips_per_file[-1])
 
113
  def load_data(self, idx: int):
114
  # === 1. Resolve clip and load arrays ===
115
  file_idx, frame_idx = self.split_idx(int(idx))
116
+ frame_idx += self.initial_frame_offset
117
  arrays = self._load_arrays(self.data_paths[file_idx])
118
  latents = arrays["latents"]
119
  actions = arrays["actions"]
 
164
  return int(data["latents"].shape[0])
165
 
166
  def _load_arrays(self, path: Path):
167
+ if self._cached_arrays_path == path:
168
+ return self._cached_arrays
169
+
170
  with np.load(path, allow_pickle=False) as data:
171
  latents = np.asarray(data["latents"])
172
+ actions = self._normalize_actions(path, np.asarray(data["actions"]))
173
+ poses = self._sanitize_poses(path, np.asarray(data["poses"], dtype=np.float32), len(actions))
174
  if "frame_indices" in data:
175
  frame_indices = np.asarray(data["frame_indices"], dtype=np.int64)
176
  else:
 
182
  image_hw = np.asarray(cfg_get(self.cfg, "image_hw", [cfg_get(self.cfg, "resolution", 0), cfg_get(self.cfg, "resolution", 0)]), dtype=np.int64)
183
 
184
  self._validate_arrays(path, latents, actions, poses, frame_indices)
185
+ arrays = {
186
  "latents": latents,
187
  "actions": actions,
188
  "poses": poses,
189
  "frame_indices": frame_indices,
190
  "image_hw": image_hw,
191
  }
192
+ self._cached_arrays_path = path
193
+ self._cached_arrays = arrays
194
+ return arrays
195
+
196
+ def _normalize_actions(self, path: Path, actions: np.ndarray) -> np.ndarray:
197
+ if actions.ndim != 2:
198
+ raise ValueError(f"actions in {path} must have shape [num_frames, action_dim]")
199
+ if actions.shape[1] == _ACTION_DIM:
200
+ return actions.astype(np.float32, copy=False)
201
+ if actions.shape[1] < 8:
202
+ raise ValueError(f"raw actions in {path} must have at least 8 columns to convert to 25-D actions")
203
+ return _convert_action_space(actions)
204
+
205
+ @staticmethod
206
+ def _sanitize_poses(path: Path, poses: np.ndarray, action_length: int) -> np.ndarray:
207
+ if poses.ndim != 2 or poses.shape[1] < 5:
208
+ raise ValueError(f"poses in {path} must have shape [num_frames, >=5]")
209
+
210
+ poses = poses.astype(np.float32, copy=True)
211
+ if len(poses) > 1:
212
+ poses[0, 1] = poses[1, 1]
213
+ height_range = float(np.ptp(poses[:, 1])) if len(poses) else 0.0
214
+ if height_range >= 2:
215
+ raise ValueError(f"Pose height variation too large for {path}: {height_range}")
216
+ if len(poses) < action_length:
217
+ poses = np.pad(poses, ((1, 0), (0, 0)))
218
+ return poses
219
 
220
  def _validate_arrays(self, path: Path, latents, actions, poses, frame_indices) -> None:
221
  length = len(latents)
tests/test_dememwm_latent_dataset.py CHANGED
@@ -42,6 +42,41 @@ def _poses(num_frames):
42
  return poses
43
 
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  class MemorySelectionTests(unittest.TestCase):
46
  def test_worldmem_backend_is_explicitly_rejected_until_implemented(self):
47
  poses = _poses(8)
@@ -149,6 +184,75 @@ class MemorySelectionTests(unittest.TestCase):
149
 
150
  class DeMemWMLatentDatasetTests(unittest.TestCase):
151
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  def test_preprocess_splits_sequence_tensors_from_memory_segments(self):
153
  from algorithms.dememwm.df_video import _preprocess_dememwm_latent_batch
154
 
@@ -497,13 +601,13 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
497
  root = Path(tmp)
498
  split_dir = root / "training"
499
  split_dir.mkdir()
500
- num_frames = 12
501
  np.savez(
502
  split_dir / "sample.npz",
503
  latents=np.arange(num_frames * 2, dtype=np.float32).reshape(num_frames, 1, 1, 2),
504
  actions=np.ones((num_frames, 25), dtype=np.float32),
505
  poses=_poses(num_frames),
506
- frame_indices=np.arange(100, 100 + num_frames, dtype=np.int64),
507
  image_hw=np.array([360, 640], dtype=np.int64),
508
  )
509
  cfg = OmegaConf.create(
@@ -526,7 +630,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
526
  self.assertEqual(sample["memory_segments"], {"target": 3, "anchor": 2, "dynamic": 2, "revisit": 2})
527
  self.assertEqual(tuple(sample["latents"].shape), (9, 1, 1, 2))
528
  self.assertEqual(sample["frame_indices"][:3].tolist(), [106, 107, 108])
529
- self.assertEqual(sample["frame_indices"][3:5].tolist(), [100, 101])
530
  self.assertEqual(sample["frame_indices"][5:7].tolist(), [104, 105])
531
  self.assertTrue(all(idx < 104 for idx in sample["frame_indices"][7:9].tolist() if idx >= 0))
532
  self.assertEqual(len(sample["frame_indices"][7:9]), 2)
@@ -549,7 +653,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
549
  self.assertTrue(torch.equal(preprocessed["actions"][3:], torch.ones_like(preprocessed["actions"][3:])))
550
  self.assertEqual(tuple(preprocessed["poses"].shape), (9, 1, 5))
551
  self.assertIs(preprocessed["frame_memory_pose"], preprocessed["poses"])
552
- self.assertEqual(preprocessed["frame_memory_pose"][:7, 0, 0].tolist(), [0.0, 1.0, 2.0, -6.0, -5.0, -2.0, -1.0])
553
  self.assertEqual(tuple(preprocessed["frame_indices"].shape), (9, 1))
554
  self.assertEqual(preprocessed["memory_segments"], sample["memory_segments"])
555
  self.assertEqual(preprocessed["segment_lengths"], sample["memory_segments"])
@@ -566,7 +670,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
566
  {"anchor": (3, 5), "dynamic": (5, 7), "revisit": (7, 9)},
567
  )
568
  self.assertEqual(preprocessed["target_tensors"]["frame_indices"][:, 0].tolist(), [106, 107, 108])
569
- self.assertEqual(preprocessed["stream_tensors"]["anchor"]["frame_indices"][:, 0].tolist(), [100, 101])
570
  self.assertEqual(preprocessed["stream_tensors"]["dynamic"]["frame_indices"][:, 0].tolist(), [104, 105])
571
  self.assertEqual(preprocessed["stream_tensors"]["revisit"]["frame_indices"].shape[0], 2)
572
  self.assertEqual(tuple(preprocessed["memory_masks"]["target"].shape), (1, 3))
 
42
  return poses
43
 
44
 
45
+ def _dataset_cfg(root, **overrides):
46
+ cfg = {
47
+ "save_dir": str(root),
48
+ "n_frames": 3,
49
+ "n_frames_valid": 3,
50
+ "frame_skip": 1,
51
+ "action_cond_dim": 25,
52
+ "resolution": 128,
53
+ "image_hw": [360, 640],
54
+ "shuffle_clips": False,
55
+ "single_eval_clip": True,
56
+ "memory_selection": _selection_cfg(max_anchor_frames=0, max_dynamic_frames=0, max_revisit_frames=0),
57
+ }
58
+ cfg.update(overrides)
59
+ return OmegaConf.create(cfg)
60
+
61
+
62
+ def _write_latent_npz(path, num_frames, actions=None, poses=None, frame_indices=None):
63
+ path.parent.mkdir(parents=True, exist_ok=True)
64
+ if actions is None:
65
+ actions = np.ones((num_frames, 25), dtype=np.float32)
66
+ if poses is None:
67
+ poses = _poses(num_frames)
68
+ if frame_indices is None:
69
+ frame_indices = np.arange(num_frames, dtype=np.int64)
70
+ np.savez(
71
+ path,
72
+ latents=np.arange(num_frames * 2, dtype=np.float32).reshape(num_frames, 1, 1, 2),
73
+ actions=actions,
74
+ poses=poses,
75
+ frame_indices=frame_indices,
76
+ image_hw=np.array([360, 640], dtype=np.int64),
77
+ )
78
+
79
+
80
  class MemorySelectionTests(unittest.TestCase):
81
  def test_worldmem_backend_is_explicitly_rejected_until_implemented(self):
82
  poses = _poses(8)
 
184
 
185
  class DeMemWMLatentDatasetTests(unittest.TestCase):
186
 
187
+ def test_dataset_requires_split_directory_without_root_fallback(self):
188
+ with tempfile.TemporaryDirectory() as tmp:
189
+ root = Path(tmp)
190
+ _write_latent_npz(root / "sample.npz", 104)
191
+
192
+ with self.assertRaisesRegex(FileNotFoundError, "validation"):
193
+ MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="validation")
194
+
195
+ def test_dataset_matches_original_w_updown_split_filtering(self):
196
+ with tempfile.TemporaryDirectory() as tmp:
197
+ root = Path(tmp)
198
+ _write_latent_npz(root / "validation" / "plain.npz", 104)
199
+ _write_latent_npz(root / "validation" / "clip_w_updown.npz", 104)
200
+
201
+ dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="validation")
202
+ dataset_wo = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root, wo_updown=True), split="validation")
203
+
204
+ self.assertEqual([path.name for path in dataset.data_paths], ["clip_w_updown.npz"])
205
+ self.assertEqual([path.name for path in dataset_wo.data_paths], ["plain.npz"])
206
+
207
+ def test_dataset_rejects_files_shorter_than_original_frame_skip_window(self):
208
+ with tempfile.TemporaryDirectory() as tmp:
209
+ root = Path(tmp)
210
+ _write_latent_npz(root / "training" / "short.npz", 102)
211
+
212
+ with self.assertRaisesRegex(ValueError, "requires at least 103"):
213
+ MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
214
+
215
+ def test_dataset_applies_original_pose_height_sanity_check(self):
216
+ with tempfile.TemporaryDirectory() as tmp:
217
+ root = Path(tmp)
218
+ poses = _poses(104)
219
+ poses[:, 1] = np.linspace(0.0, 3.0, 104, dtype=np.float32)
220
+ _write_latent_npz(root / "training" / "sample.npz", 104, poses=poses)
221
+ dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
222
+
223
+ with self.assertRaisesRegex(ValueError, "Pose height variation"):
224
+ dataset[0]
225
+
226
+ def test_dataset_converts_raw_actions_to_original_25d_contract(self):
227
+ with tempfile.TemporaryDirectory() as tmp:
228
+ root = Path(tmp)
229
+ raw_actions = np.zeros((104, 8), dtype=np.int64)
230
+ raw_actions[:, 0] = 1
231
+ raw_actions[:, 3] = 11
232
+ raw_actions[:, 4] = 13
233
+ raw_actions[:, 7] = 1
234
+ _write_latent_npz(root / "training" / "sample.npz", 104, actions=raw_actions)
235
+ sample = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")[0]
236
+
237
+ self.assertEqual(tuple(sample["actions"].shape), (3, 25))
238
+ self.assertTrue(torch.equal(sample["actions"][:, 11], torch.ones(3)))
239
+ self.assertTrue(torch.equal(sample["actions"][:, 16], torch.ones(3)))
240
+ self.assertTrue(torch.equal(sample["actions"][:, 15], -torch.ones(3)))
241
+ self.assertTrue(torch.equal(sample["actions"][:, 2], torch.ones(3)))
242
+
243
+ def test_dataset_reuses_cached_npz_arrays_for_same_file(self):
244
+ with tempfile.TemporaryDirectory() as tmp:
245
+ root = Path(tmp)
246
+ _write_latent_npz(root / "training" / "sample.npz", 104)
247
+ dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
248
+ first = dataset[0]
249
+
250
+ with mock.patch("datasets.video.minecraft_video_dememwm_latent_dataset.np.load", side_effect=AssertionError("cache miss")):
251
+ second = dataset[1]
252
+
253
+ self.assertEqual(first["frame_indices"][:3].tolist(), [100, 101, 102])
254
+ self.assertEqual(second["frame_indices"][:3].tolist(), [101, 102, 103])
255
+
256
  def test_preprocess_splits_sequence_tensors_from_memory_segments(self):
257
  from algorithms.dememwm.df_video import _preprocess_dememwm_latent_batch
258
 
 
601
  root = Path(tmp)
602
  split_dir = root / "training"
603
  split_dir.mkdir()
604
+ num_frames = 112
605
  np.savez(
606
  split_dir / "sample.npz",
607
  latents=np.arange(num_frames * 2, dtype=np.float32).reshape(num_frames, 1, 1, 2),
608
  actions=np.ones((num_frames, 25), dtype=np.float32),
609
  poses=_poses(num_frames),
610
+ frame_indices=np.arange(num_frames, dtype=np.int64),
611
  image_hw=np.array([360, 640], dtype=np.int64),
612
  )
613
  cfg = OmegaConf.create(
 
630
  self.assertEqual(sample["memory_segments"], {"target": 3, "anchor": 2, "dynamic": 2, "revisit": 2})
631
  self.assertEqual(tuple(sample["latents"].shape), (9, 1, 1, 2))
632
  self.assertEqual(sample["frame_indices"][:3].tolist(), [106, 107, 108])
633
+ self.assertEqual(sample["frame_indices"][3:5].tolist(), [0, 1])
634
  self.assertEqual(sample["frame_indices"][5:7].tolist(), [104, 105])
635
  self.assertTrue(all(idx < 104 for idx in sample["frame_indices"][7:9].tolist() if idx >= 0))
636
  self.assertEqual(len(sample["frame_indices"][7:9]), 2)
 
653
  self.assertTrue(torch.equal(preprocessed["actions"][3:], torch.ones_like(preprocessed["actions"][3:])))
654
  self.assertEqual(tuple(preprocessed["poses"].shape), (9, 1, 5))
655
  self.assertIs(preprocessed["frame_memory_pose"], preprocessed["poses"])
656
+ self.assertEqual(preprocessed["frame_memory_pose"][:7, 0, 0].tolist(), [0.0, 1.0, 2.0, -106.0, -105.0, -2.0, -1.0])
657
  self.assertEqual(tuple(preprocessed["frame_indices"].shape), (9, 1))
658
  self.assertEqual(preprocessed["memory_segments"], sample["memory_segments"])
659
  self.assertEqual(preprocessed["segment_lengths"], sample["memory_segments"])
 
670
  {"anchor": (3, 5), "dynamic": (5, 7), "revisit": (7, 9)},
671
  )
672
  self.assertEqual(preprocessed["target_tensors"]["frame_indices"][:, 0].tolist(), [106, 107, 108])
673
+ self.assertEqual(preprocessed["stream_tensors"]["anchor"]["frame_indices"][:, 0].tolist(), [0, 1])
674
  self.assertEqual(preprocessed["stream_tensors"]["dynamic"]["frame_indices"][:, 0].tolist(), [104, 105])
675
  self.assertEqual(preprocessed["stream_tensors"]["revisit"]["frame_indices"].shape[0], 2)
676
  self.assertEqual(tuple(preprocessed["memory_masks"]["target"].shape), (1, 3))