BonanDing commited on
Commit
9699eb4
·
1 Parent(s): 915a235

Align DeMemWM latent memory frame offsets

Browse files
configurations/dataset/video_minecraft_dememwm_latent.yaml CHANGED
@@ -1,7 +1,8 @@
1
  defaults:
2
  - base_dataset
3
 
4
- save_dir: data/minecraft_simple_backforward_latents
 
5
  metadata: null
6
  n_frames: 8
7
  n_frames_valid: 500
 
1
  defaults:
2
  - base_dataset
3
 
4
+ save_dir: data/minecraft_simple_backforward
5
+ precomputed_feature_dir: ${dataset.save_dir}/vae_features
6
  metadata: null
7
  n_frames: 8
8
  n_frames_valid: 500
datasets/video/memory_selection.py CHANGED
@@ -336,10 +336,10 @@ def _select_anchor(candidates: np.ndarray, count: int, cfg) -> np.ndarray:
336
  return np.asarray(selected[:count], dtype=np.int64)
337
 
338
 
339
- def _select_dynamic(target_start: int, count: int) -> np.ndarray:
340
- if count <= 0 or target_start <= 0:
341
  return np.empty((0,), dtype=np.int64)
342
- start = max(0, target_start - count)
343
  return np.arange(start, target_start, dtype=np.int64)[-count:]
344
 
345
 
@@ -351,6 +351,7 @@ def _select_revisit(
351
  excluded: np.ndarray,
352
  split: str,
353
  rng=None,
 
354
  ) -> np.ndarray:
355
  if count <= 0:
356
  return np.empty((0,), dtype=np.int64)
@@ -358,7 +359,7 @@ def _select_revisit(
358
  target_start = int(target_positions[0])
359
  exclusion = max(0, int(cfg_get(cfg, "local_context_exclusion_frames", 8)))
360
  causal_stop = max(0, target_start - exclusion)
361
- candidates = np.arange(0, causal_stop, dtype=np.int64)
362
  if len(candidates) == 0:
363
  return np.empty((0,), dtype=np.int64)
364
 
@@ -382,13 +383,14 @@ def _select_revisit(
382
  )
383
 
384
 
385
- def select_memory_indices(poses, target_positions, cfg=None, split: str = "training", rng=None):
386
  """Select causal memory indices and masks for [anchor][dynamic][revisit]."""
387
 
388
  poses = _as_pose_array(poses)
389
  target_positions = np.asarray(target_positions, dtype=np.int64)
390
  target_positions = target_positions[(target_positions >= 0) & (target_positions < len(poses))]
391
  enabled = bool(cfg_get(cfg, "enabled", True))
 
392
 
393
  counts = {
394
  "anchor": int(cfg_get(cfg, "max_anchor_frames", 0)) if enabled else 0,
@@ -402,12 +404,21 @@ def select_memory_indices(poses, target_positions, cfg=None, split: str = "train
402
 
403
  target_start = int(target_positions[0])
404
 
405
- dynamic = _select_dynamic(target_start, counts["dynamic"])
406
  anchor_stop = max(0, target_start - counts["dynamic"])
407
- anchor_candidates = np.arange(0, anchor_stop, dtype=np.int64)
408
  anchor = _select_anchor(anchor_candidates, counts["anchor"], cfg)
409
  excluded = np.concatenate([anchor, dynamic]) if len(anchor) or len(dynamic) else np.empty((0,), dtype=np.int64)
410
- revisit = _select_revisit(poses, target_positions, cfg, counts["revisit"], excluded, split, rng=rng)
 
 
 
 
 
 
 
 
 
411
 
412
  _write_segment(indices, masks, "anchor", anchor)
413
  _write_segment(indices, masks, "dynamic", dynamic)
 
336
  return np.asarray(selected[:count], dtype=np.int64)
337
 
338
 
339
+ def _select_dynamic(target_start: int, count: int, min_candidate_frame: int = 0) -> np.ndarray:
340
+ if count <= 0 or target_start <= min_candidate_frame:
341
  return np.empty((0,), dtype=np.int64)
342
+ start = max(min_candidate_frame, target_start - count)
343
  return np.arange(start, target_start, dtype=np.int64)[-count:]
344
 
345
 
 
351
  excluded: np.ndarray,
352
  split: str,
353
  rng=None,
354
+ min_candidate_frame: int = 0,
355
  ) -> np.ndarray:
356
  if count <= 0:
357
  return np.empty((0,), dtype=np.int64)
 
359
  target_start = int(target_positions[0])
360
  exclusion = max(0, int(cfg_get(cfg, "local_context_exclusion_frames", 8)))
361
  causal_stop = max(0, target_start - exclusion)
362
+ candidates = np.arange(min_candidate_frame, causal_stop, dtype=np.int64)
363
  if len(candidates) == 0:
364
  return np.empty((0,), dtype=np.int64)
365
 
 
383
  )
384
 
385
 
386
+ def select_memory_indices(poses, target_positions, cfg=None, split: str = "training", rng=None, min_candidate_frame: int = 0):
387
  """Select causal memory indices and masks for [anchor][dynamic][revisit]."""
388
 
389
  poses = _as_pose_array(poses)
390
  target_positions = np.asarray(target_positions, dtype=np.int64)
391
  target_positions = target_positions[(target_positions >= 0) & (target_positions < len(poses))]
392
  enabled = bool(cfg_get(cfg, "enabled", True))
393
+ min_candidate_frame = max(0, int(min_candidate_frame))
394
 
395
  counts = {
396
  "anchor": int(cfg_get(cfg, "max_anchor_frames", 0)) if enabled else 0,
 
404
 
405
  target_start = int(target_positions[0])
406
 
407
+ dynamic = _select_dynamic(target_start, counts["dynamic"], min_candidate_frame)
408
  anchor_stop = max(0, target_start - counts["dynamic"])
409
+ anchor_candidates = np.arange(min_candidate_frame, anchor_stop, dtype=np.int64)
410
  anchor = _select_anchor(anchor_candidates, counts["anchor"], cfg)
411
  excluded = np.concatenate([anchor, dynamic]) if len(anchor) or len(dynamic) else np.empty((0,), dtype=np.int64)
412
+ revisit = _select_revisit(
413
+ poses,
414
+ target_positions,
415
+ cfg,
416
+ counts["revisit"],
417
+ excluded,
418
+ split,
419
+ rng=rng,
420
+ min_candidate_frame=min_candidate_frame,
421
+ )
422
 
423
  _write_segment(indices, masks, "anchor", anchor)
424
  _write_segment(indices, masks, "dynamic", dynamic)
datasets/video/minecraft_video_dememwm_latent_dataset.py CHANGED
@@ -2,7 +2,9 @@
2
 
3
  from __future__ import annotations
4
 
5
- import warnings
 
 
6
  from pathlib import Path
7
  import numpy as np
8
  import torch
@@ -11,32 +13,60 @@ from omegaconf import DictConfig
11
  from .memory_selection import SEGMENT_KEYS, cfg_get, memory_segment_lengths, select_memory_indices
12
 
13
 
14
- _INITIAL_FRAME_OFFSET = 100
15
- _ACTION_DIM = 25
16
-
17
-
18
- def _convert_action_space(actions: np.ndarray) -> np.ndarray:
19
- vec_25 = np.zeros((len(actions), _ACTION_DIM), dtype=np.float32)
20
- vec_25[actions[:, 0] == 1, 11] = 1.0
21
- vec_25[actions[:, 0] == 2, 12] = 1.0
22
- vec_25[actions[:, 4] == 11, 16] = -1.0
23
- vec_25[actions[:, 4] == 13, 16] = 1.0
24
- vec_25[actions[:, 3] == 11, 15] = -1.0
25
- vec_25[actions[:, 3] == 13, 15] = 1.0
26
- vec_25[actions[:, 5] == 6, 24] = 1.0
27
- vec_25[actions[:, 5] == 1, 24] = 1.0
28
- vec_25[actions[:, 1] == 1, 13] = 1.0
29
- vec_25[actions[:, 1] == 2, 14] = 1.0
30
- vec_25[actions[:, 7] == 1, 2] = 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  return vec_25
32
 
33
 
 
 
 
 
34
  class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
35
  """Load precomputed VAE latents and append causal memory frames.
36
 
37
- Expected files are ``.npz`` archives under ``save_dir/<split>`` with
38
- ``latents``, ``actions``, and ``poses`` arrays. Optional ``frame_indices``
39
- and ``image_hw`` arrays are propagated when present.
40
  """
41
 
42
  def __init__(self, cfg: DictConfig, split: str = "training"):
@@ -44,8 +74,13 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
44
  self.cfg = cfg
45
  self.split = split
46
  self.save_dir = Path(cfg.save_dir)
 
 
47
  self.wo_updown = bool(cfg_get(cfg, "wo_updown", False))
48
  self.initial_frame_offset = _INITIAL_FRAME_OFFSET
 
 
 
49
  self.n_frames = int(cfg_get(cfg, "n_frames_valid", cfg.n_frames)) if split in {"validation", "test"} else int(cfg.n_frames)
50
  self.frame_skip = int(cfg_get(cfg, "frame_skip", 1))
51
  self.target_span = max(1, (self.n_frames - 1) * self.frame_skip + 1)
@@ -59,10 +94,10 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
59
  self._cached_arrays = None
60
  self.data_paths = self.get_data_paths(split)
61
  if not self.data_paths:
62
- raise FileNotFoundError(f"No latent .npz files found for split '{split}' under {self.save_dir / split}")
63
 
64
  self.lengths = np.asarray([self._file_length(path) for path in self.data_paths], dtype=np.int64)
65
- required_length = self.initial_frame_offset + self.target_span
66
  too_short = self.lengths < required_length
67
  if bool(too_short.any()):
68
  bad_idx = int(np.nonzero(too_short)[0][0])
@@ -72,44 +107,47 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
72
  )
73
 
74
  clips = (self.lengths - required_length + 1).astype(np.int64)
 
 
 
 
 
75
  if self.single_eval_clip:
76
  clips = np.ones_like(clips)
77
  self.clips_per_file = clips
78
  self.cum_clips_per_file = np.cumsum(self.clips_per_file)
79
  self.idx_remap = list(range(self.__len__()))
80
- if split == "training" and bool(cfg_get(cfg, "shuffle_clips", True)):
81
- rng = np.random.default_rng(int(cfg_get(cfg, "seed", 0)))
82
  rng.shuffle(self.idx_remap)
83
 
84
  def get_data_paths(self, split: str):
85
- split_dir = self.save_dir / split
86
- if not split_dir.exists():
87
- return []
88
- paths = sorted((path for path in split_dir.glob("**/*.npz") if path.is_file()), key=lambda path: path.name)
89
- num_candidates = len(paths)
90
-
91
- split_filter = None
92
- if split in {"validation", "test"}:
93
- split_filter = "wo_updown" if self.wo_updown else "w_updown"
94
- if self.wo_updown:
95
- paths = [path for path in paths if "w_updown" not in str(path)]
96
- else:
97
- paths = [path for path in paths if "w_updown" in str(path)]
98
- elif self.wo_updown:
99
- paths = [path for path in paths if "w_updown" not in str(path)]
 
 
 
 
 
100
 
101
  if not paths:
102
- if split_filter is not None and num_candidates:
103
- message = (
104
- f"Selected split '{split}' under {split_dir} is empty because the "
105
- f"{split_filter} split filter removed all {num_candidates} candidate .npz file(s)."
106
- )
107
- warnings.warn(message, stacklevel=2)
108
- raise FileNotFoundError(message)
109
- if split_filter is not None:
110
- return paths
111
- for sub_path in sorted(split_dir.iterdir(), key=lambda path: path.name):
112
- paths += sorted((path for path in sub_path.glob("**/*.npz") if path.is_file()), key=lambda path: path.name)
113
  return paths
114
 
115
  def __len__(self):
@@ -126,7 +164,7 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
126
  def load_data(self, idx: int):
127
  # === 1. Resolve clip and load arrays ===
128
  file_idx, frame_idx = self.split_idx(int(idx))
129
- frame_idx += self.initial_frame_offset
130
  arrays = self._load_arrays(self.data_paths[file_idx])
131
  latents = arrays["latents"]
132
  actions = arrays["actions"]
@@ -140,6 +178,7 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
140
  target_positions,
141
  self.memory_selection,
142
  split=self.split,
 
143
  )
144
  ordered_memory = [memory_indices[key] for key in SEGMENT_KEYS]
145
  source_positions = np.concatenate([target_positions, *ordered_memory]).astype(np.int64)
@@ -172,39 +211,63 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
172
  "image_hw": torch.as_tensor(arrays["image_hw"], dtype=torch.long),
173
  }
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  def _file_length(self, path: Path) -> int:
176
- with np.load(path, allow_pickle=False) as data:
177
- return int(data["latents"].shape[0])
 
 
178
 
179
  def _load_arrays(self, path: Path):
180
  if self._cached_arrays_path == path:
181
  return self._cached_arrays
182
 
183
- with np.load(path, allow_pickle=False) as data:
184
- latents = np.asarray(data["latents"])
185
- actions = self._normalize_actions(path, np.asarray(data["actions"]))
186
- poses = self._sanitize_poses(path, np.asarray(data["poses"], dtype=np.float32), len(actions))
187
- if "frame_indices" in data:
188
- frame_indices = np.asarray(data["frame_indices"], dtype=np.int64)
189
- else:
190
- frame_indices = np.arange(len(latents), dtype=np.int64)
191
-
192
- if "image_hw" in data:
193
- image_hw = np.asarray(data["image_hw"], dtype=np.int64).reshape(-1)[:2]
194
- else:
195
- 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)
196
-
197
- self._validate_arrays(path, latents, actions, poses, frame_indices)
198
- arrays = {
 
 
 
199
  "latents": latents,
200
  "actions": actions,
201
  "poses": poses,
202
- "frame_indices": frame_indices,
203
- "image_hw": image_hw,
204
  }
205
- self._cached_arrays_path = path
206
- self._cached_arrays = arrays
207
- return arrays
208
 
209
  def _normalize_actions(self, path: Path, actions: np.ndarray) -> np.ndarray:
210
  if actions.ndim != 2:
@@ -213,7 +276,7 @@ class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
213
  return actions.astype(np.float32, copy=False)
214
  if actions.shape[1] < 8:
215
  raise ValueError(f"raw actions in {path} must have at least 8 columns to convert to 25-D actions")
216
- return _convert_action_space(actions)
217
 
218
  @staticmethod
219
  def _sanitize_poses(path: Path, poses: np.ndarray, action_length: int) -> np.ndarray:
 
2
 
3
  from __future__ import annotations
4
 
5
+ import json
6
+ import os
7
+ import random
8
  from pathlib import Path
9
  import numpy as np
10
  import torch
 
13
  from .memory_selection import SEGMENT_KEYS, cfg_get, memory_segment_lengths, select_memory_indices
14
 
15
 
16
+ ACTION_KEYS = [
17
+ "inventory",
18
+ "ESC",
19
+ "hotbar.1",
20
+ "hotbar.2",
21
+ "hotbar.3",
22
+ "hotbar.4",
23
+ "hotbar.5",
24
+ "hotbar.6",
25
+ "hotbar.7",
26
+ "hotbar.8",
27
+ "hotbar.9",
28
+ "forward",
29
+ "back",
30
+ "left",
31
+ "right",
32
+ "cameraY",
33
+ "cameraX",
34
+ "jump",
35
+ "sneak",
36
+ "sprint",
37
+ "swapHands",
38
+ "attack",
39
+ "use",
40
+ "pickItem",
41
+ "drop",
42
+ ]
43
+
44
+ def convert_action_space(actions):
45
+ vec_25 = torch.zeros(len(actions), len(ACTION_KEYS))
46
+ vec_25[actions[:,0]==1, 11] = 1
47
+ vec_25[actions[:,0]==2, 12] = 1
48
+ vec_25[actions[:,4]==11, 16] = -1
49
+ vec_25[actions[:,4]==13, 16] = 1
50
+ vec_25[actions[:,3]==11, 15] = -1
51
+ vec_25[actions[:,3]==13, 15] = 1
52
+ vec_25[actions[:,5]==6, 24] = 1
53
+ vec_25[actions[:,5]==1, 24] = 1
54
+ vec_25[actions[:,1]==1, 13] = 1
55
+ vec_25[actions[:,1]==2, 14] = 1
56
+ vec_25[actions[:,7]==1, 2] = 1
57
  return vec_25
58
 
59
 
60
+ _INITIAL_FRAME_OFFSET = 100
61
+ _ACTION_DIM = len(ACTION_KEYS)
62
+
63
+
64
  class MinecraftVideoDeMemWMLatentDataset(torch.utils.data.Dataset):
65
  """Load precomputed VAE latents and append causal memory frames.
66
 
67
+ The index comes from the raw Minecraft ``.mp4`` tree, matching
68
+ ``MinecraftVideoDataset``. Latents are read from the original DeMemWM
69
+ ``vae_features`` cache at the same relative path.
70
  """
71
 
72
  def __init__(self, cfg: DictConfig, split: str = "training"):
 
74
  self.cfg = cfg
75
  self.split = split
76
  self.save_dir = Path(cfg.save_dir)
77
+ precomputed_feature_dir = cfg_get(cfg, "precomputed_feature_dir")
78
+ self.feature_root = Path(precomputed_feature_dir) if precomputed_feature_dir is not None else self.save_dir / "vae_features"
79
  self.wo_updown = bool(cfg_get(cfg, "wo_updown", False))
80
  self.initial_frame_offset = _INITIAL_FRAME_OFFSET
81
+ context_window = cfg_get(cfg, "context_window", cfg_get(cfg, "context_length", 0))
82
+ self.context_window = max(0, int(context_window)) if split == "training" else 0
83
+ self.target_start_offset = self.initial_frame_offset + self.context_window
84
  self.n_frames = int(cfg_get(cfg, "n_frames_valid", cfg.n_frames)) if split in {"validation", "test"} else int(cfg.n_frames)
85
  self.frame_skip = int(cfg_get(cfg, "frame_skip", 1))
86
  self.target_span = max(1, (self.n_frames - 1) * self.frame_skip + 1)
 
94
  self._cached_arrays = None
95
  self.data_paths = self.get_data_paths(split)
96
  if not self.data_paths:
97
+ raise FileNotFoundError(f"No raw .mp4 files found for split '{split}' under {self.save_dir / split}")
98
 
99
  self.lengths = np.asarray([self._file_length(path) for path in self.data_paths], dtype=np.int64)
100
+ required_length = self.target_start_offset + self.target_span
101
  too_short = self.lengths < required_length
102
  if bool(too_short.any()):
103
  bad_idx = int(np.nonzero(too_short)[0][0])
 
107
  )
108
 
109
  clips = (self.lengths - required_length + 1).astype(np.int64)
110
+ if split == "training":
111
+ # Keep targets inside the original 1300-frame training window after
112
+ # reserving the eval-style context prefix before each target clip.
113
+ original_training_clips = max(1, 1300 - self.context_window - self.target_span + 1)
114
+ clips = np.minimum(clips, original_training_clips)
115
  if self.single_eval_clip:
116
  clips = np.ones_like(clips)
117
  self.clips_per_file = clips
118
  self.cum_clips_per_file = np.cumsum(self.clips_per_file)
119
  self.idx_remap = list(range(self.__len__()))
120
+ if bool(cfg_get(cfg, "shuffle_clips", True)):
121
+ rng = random.Random(int(cfg_get(cfg, "seed", 0)))
122
  rng.shuffle(self.idx_remap)
123
 
124
  def get_data_paths(self, split: str):
125
+ """
126
+ Retrieve all video file paths for the given split.
127
+
128
+ Args:
129
+ split (str): Dataset split ("training" or "validation").
130
+
131
+ Returns:
132
+ List[Path]: List of video file paths.
133
+ """
134
+ data_dir = self.save_dir / split
135
+ paths = sorted(list(data_dir.glob("**/*.mp4")), key=lambda x: x.name)
136
+
137
+ if self.wo_updown:
138
+ # Filter out paths containing "w_updown"
139
+ paths = [p for p in paths if "w_updown" not in str(p)]
140
+
141
+ if (split == "validation" or split == "test") and self.wo_updown:
142
+ paths = [p for p in paths if "w_updown" not in str(p)]
143
+ elif split == "validation" or split == "test":
144
+ paths = [p for p in paths if "w_updown" in str(p)]
145
 
146
  if not paths:
147
+ sub_dirs = os.listdir(data_dir)
148
+ for sub_dir in sub_dirs:
149
+ sub_path = data_dir / sub_dir
150
+ paths += sorted(list(sub_path.glob("**/*.mp4")), key=lambda x: x.name)
 
 
 
 
 
 
 
151
  return paths
152
 
153
  def __len__(self):
 
164
  def load_data(self, idx: int):
165
  # === 1. Resolve clip and load arrays ===
166
  file_idx, frame_idx = self.split_idx(int(idx))
167
+ frame_idx += self.target_start_offset
168
  arrays = self._load_arrays(self.data_paths[file_idx])
169
  latents = arrays["latents"]
170
  actions = arrays["actions"]
 
178
  target_positions,
179
  self.memory_selection,
180
  split=self.split,
181
+ min_candidate_frame=self.initial_frame_offset,
182
  )
183
  ordered_memory = [memory_indices[key] for key in SEGMENT_KEYS]
184
  source_positions = np.concatenate([target_positions, *ordered_memory]).astype(np.int64)
 
211
  "image_hw": torch.as_tensor(arrays["image_hw"], dtype=torch.long),
212
  }
213
 
214
+ def _feature_cache_paths(self, video_path: Path) -> tuple[Path, Path]:
215
+ relative_video_path = video_path.relative_to(self.save_dir)
216
+ feature_dir = self.feature_root / relative_video_path.parent
217
+ video_index = relative_video_path.stem
218
+ return (
219
+ feature_dir / f"{video_index}_vae_feature.npy",
220
+ feature_dir / f"{video_index}_vae_feature_meta.json",
221
+ )
222
+
223
+ def _default_image_hw(self) -> np.ndarray:
224
+ return np.asarray(
225
+ cfg_get(self.cfg, "image_hw", [cfg_get(self.cfg, "resolution", 0), cfg_get(self.cfg, "resolution", 0)]),
226
+ dtype=np.int64,
227
+ ).reshape(-1)[:2]
228
+
229
+ def _feature_image_hw(self, meta_path: Path) -> np.ndarray:
230
+ if not meta_path.exists():
231
+ return self._default_image_hw()
232
+ with open(meta_path, "r", encoding="utf-8") as handle:
233
+ meta = json.load(handle)
234
+ return np.asarray([int(meta["image_height"]), int(meta["image_width"])], dtype=np.int64)
235
+
236
  def _file_length(self, path: Path) -> int:
237
+ feature_path, _ = self._feature_cache_paths(path)
238
+ if not feature_path.exists():
239
+ raise FileNotFoundError(f"Missing precomputed VAE features: {feature_path}")
240
+ return int(np.load(feature_path, mmap_mode="r").shape[0])
241
 
242
  def _load_arrays(self, path: Path):
243
  if self._cached_arrays_path == path:
244
  return self._cached_arrays
245
 
246
+ arrays = self._load_feature_arrays(path)
247
+
248
+ self._validate_arrays(path, arrays["latents"], arrays["actions"], arrays["poses"], arrays["frame_indices"])
249
+ self._cached_arrays_path = path
250
+ self._cached_arrays = arrays
251
+ return arrays
252
+
253
+ def _load_feature_arrays(self, video_path: Path):
254
+ feature_path, meta_path = self._feature_cache_paths(video_path)
255
+ if not feature_path.exists():
256
+ raise FileNotFoundError(f"Missing precomputed VAE features: {feature_path}")
257
+
258
+ action_path = video_path.with_suffix(".npz")
259
+ with np.load(action_path, allow_pickle=False) as data:
260
+ latents = np.load(feature_path, mmap_mode="r")
261
+ actions = self._normalize_actions(action_path, np.asarray(data["actions"]))
262
+ poses = self._sanitize_poses(action_path, np.asarray(data["poses"], dtype=np.float32), len(actions))
263
+
264
+ return {
265
  "latents": latents,
266
  "actions": actions,
267
  "poses": poses,
268
+ "frame_indices": np.arange(len(latents), dtype=np.int64),
269
+ "image_hw": self._feature_image_hw(meta_path),
270
  }
 
 
 
271
 
272
  def _normalize_actions(self, path: Path, actions: np.ndarray) -> np.ndarray:
273
  if actions.ndim != 2:
 
276
  return actions.astype(np.float32, copy=False)
277
  if actions.shape[1] < 8:
278
  raise ValueError(f"raw actions in {path} must have at least 8 columns to convert to 25-D actions")
279
+ return convert_action_space(actions).numpy()
280
 
281
  @staticmethod
282
  def _sanitize_poses(path: Path, poses: np.ndarray, action_length: int) -> np.ndarray:
tests/test_dememwm_latent_dataset.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import tempfile
2
  import unittest
3
  from unittest import mock
@@ -43,6 +44,7 @@ def _poses(num_frames):
43
  def _dataset_cfg(root, **overrides):
44
  cfg = {
45
  "save_dir": str(root),
 
46
  "n_frames": 3,
47
  "n_frames_valid": 3,
48
  "frame_skip": 1,
@@ -57,22 +59,21 @@ def _dataset_cfg(root, **overrides):
57
  return OmegaConf.create(cfg)
58
 
59
 
60
- def _write_latent_npz(path, num_frames, actions=None, poses=None, frame_indices=None):
61
- path.parent.mkdir(parents=True, exist_ok=True)
 
 
62
  if actions is None:
63
- actions = np.ones((num_frames, 25), dtype=np.float32)
64
  if poses is None:
65
  poses = _poses(num_frames)
66
- if frame_indices is None:
67
- frame_indices = np.arange(num_frames, dtype=np.int64)
68
- np.savez(
69
- path,
70
- latents=np.arange(num_frames * 2, dtype=np.float32).reshape(num_frames, 1, 1, 2),
71
- actions=actions,
72
- poses=poses,
73
- frame_indices=frame_indices,
74
- image_hw=np.array([360, 640], dtype=np.int64),
75
- )
76
 
77
 
78
  class MemorySelectionTests(unittest.TestCase):
@@ -178,7 +179,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
178
  def test_dataset_requires_split_directory_without_root_fallback(self):
179
  with tempfile.TemporaryDirectory() as tmp:
180
  root = Path(tmp)
181
- _write_latent_npz(root / "sample.npz", 104)
182
 
183
  with self.assertRaisesRegex(FileNotFoundError, "validation"):
184
  MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="validation")
@@ -186,46 +187,44 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
186
  def test_dataset_matches_original_w_updown_split_filtering(self):
187
  with tempfile.TemporaryDirectory() as tmp:
188
  root = Path(tmp)
189
- _write_latent_npz(root / "validation" / "plain.npz", 104)
190
- _write_latent_npz(root / "validation" / "clip_w_updown.npz", 104)
191
 
192
  dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="validation")
193
  dataset_wo = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root, wo_updown=True), split="validation")
194
 
195
- self.assertEqual([path.name for path in dataset.data_paths], ["clip_w_updown.npz"])
196
- self.assertEqual([path.name for path in dataset_wo.data_paths], ["plain.npz"])
197
 
198
  def test_dataset_filters_nested_validation_files_by_selected_split(self):
199
  with tempfile.TemporaryDirectory() as tmp:
200
  root = Path(tmp)
201
- _write_latent_npz(root / "validation" / "nested" / "plain.npz", 104)
202
- _write_latent_npz(root / "validation" / "nested" / "clip_w_updown.npz", 104)
203
 
204
  dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="validation")
205
  dataset_wo = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root, wo_updown=True), split="validation")
206
 
207
- self.assertEqual([path.name for path in dataset.data_paths], ["clip_w_updown.npz"])
208
- self.assertEqual([path.name for path in dataset_wo.data_paths], ["plain.npz"])
209
 
210
- def test_dataset_rejects_nested_validation_test_files_removed_by_split_filter(self):
211
  cases = [
212
- ("validation", False, "plain.npz", "w_updown"),
213
- ("test", True, "clip_w_updown.npz", "wo_updown"),
214
  ]
215
- for split, wo_updown, filename, split_filter in cases:
216
- with self.subTest(split=split, split_filter=split_filter), tempfile.TemporaryDirectory() as tmp:
217
  root = Path(tmp)
218
- _write_latent_npz(root / split / "nested" / filename, 104)
219
- message = f"Selected split '{split}'.*{split_filter} split filter removed all"
220
 
221
- with self.assertWarnsRegex(UserWarning, message):
222
- with self.assertRaisesRegex(FileNotFoundError, message):
223
- MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root, wo_updown=wo_updown), split=split)
224
 
225
  def test_dataset_rejects_files_shorter_than_original_frame_skip_window(self):
226
  with tempfile.TemporaryDirectory() as tmp:
227
  root = Path(tmp)
228
- _write_latent_npz(root / "training" / "short.npz", 102)
229
 
230
  with self.assertRaisesRegex(ValueError, "requires at least 103"):
231
  MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
@@ -235,7 +234,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
235
  root = Path(tmp)
236
  poses = _poses(104)
237
  poses[:, 1] = np.linspace(0.0, 3.0, 104, dtype=np.float32)
238
- _write_latent_npz(root / "training" / "sample.npz", 104, poses=poses)
239
  dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
240
 
241
  with self.assertRaisesRegex(ValueError, "Pose height variation"):
@@ -249,7 +248,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
249
  raw_actions[:, 3] = 11
250
  raw_actions[:, 4] = 13
251
  raw_actions[:, 7] = 1
252
- _write_latent_npz(root / "training" / "sample.npz", 104, actions=raw_actions)
253
  sample = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")[0]
254
 
255
  self.assertEqual(tuple(sample["actions"].shape), (3, 25))
@@ -258,10 +257,40 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
258
  self.assertTrue(torch.equal(sample["actions"][:, 15], -torch.ones(3)))
259
  self.assertTrue(torch.equal(sample["actions"][:, 2], torch.ones(3)))
260
 
261
- def test_dataset_reuses_cached_npz_arrays_for_same_file(self):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  with tempfile.TemporaryDirectory() as tmp:
263
  root = Path(tmp)
264
- _write_latent_npz(root / "training" / "sample.npz", 104)
 
 
 
 
 
 
 
 
265
  dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
266
  first = dataset[0]
267
 
@@ -714,7 +743,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
714
  def test_disabled_memory_selection_does_not_allocate_memory_slots(self):
715
  with tempfile.TemporaryDirectory() as tmp:
716
  root = Path(tmp)
717
- _write_latent_npz(root / "training" / "sample.npz", 104)
718
  dataset = MinecraftVideoDeMemWMLatentDataset(
719
  _dataset_cfg(root, memory_selection=_selection_cfg(enabled=False)),
720
  split="training",
@@ -729,44 +758,97 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
729
  for key in ("anchor", "dynamic", "revisit"):
730
  self.assertEqual(tuple(sample["memory_masks"][key].shape), (0,))
731
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
732
  def test_dataset_returns_target_anchor_dynamic_revisit_contract(self):
733
  with tempfile.TemporaryDirectory() as tmp:
734
  root = Path(tmp)
735
- split_dir = root / "training"
736
- split_dir.mkdir()
737
  num_frames = 112
738
- np.savez(
739
- split_dir / "sample.npz",
740
- latents=np.arange(num_frames * 2, dtype=np.float32).reshape(num_frames, 1, 1, 2),
 
 
741
  actions=np.ones((num_frames, 25), dtype=np.float32),
742
- poses=_poses(num_frames),
743
- frame_indices=np.arange(num_frames, dtype=np.int64),
744
- image_hw=np.array([360, 640], dtype=np.int64),
745
  )
746
- cfg = OmegaConf.create(
747
- {
748
- "save_dir": str(root),
749
- "n_frames": 3,
750
- "n_frames_valid": 3,
751
- "frame_skip": 1,
752
- "action_cond_dim": 25,
753
- "resolution": 128,
754
- "image_hw": [360, 640],
755
- "shuffle_clips": False,
756
- "single_eval_clip": True,
757
- "memory_selection": _selection_cfg(),
758
- }
759
  )
760
- dataset = MinecraftVideoDeMemWMLatentDataset(cfg, split="training")
761
  sample = dataset[6]
762
 
763
  self.assertEqual(sample["memory_segments"], {"target": 3, "anchor": 2, "dynamic": 2, "revisit": 2})
764
  self.assertEqual(tuple(sample["latents"].shape), (9, 1, 1, 2))
765
  self.assertEqual(sample["frame_indices"][:3].tolist(), [106, 107, 108])
766
- self.assertEqual(sample["frame_indices"][3:5].tolist(), [0, 1])
767
  self.assertEqual(sample["frame_indices"][5:7].tolist(), [104, 105])
768
- self.assertTrue(all(idx < 104 for idx in sample["frame_indices"][7:9].tolist() if idx >= 0))
769
- self.assertEqual(len(sample["frame_indices"][7:9]), 2)
770
  self.assertTrue(sample["memory_masks"]["target"].all().item())
771
  self.assertTrue(sample["memory_masks"]["anchor"].all().item())
772
  self.assertTrue(sample["memory_masks"]["dynamic"].all().item())
@@ -786,7 +868,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
786
  self.assertTrue(torch.equal(preprocessed["actions"][3:], torch.ones_like(preprocessed["actions"][3:])))
787
  self.assertEqual(tuple(preprocessed["poses"].shape), (9, 1, 5))
788
  self.assertIs(preprocessed["frame_memory_pose"], preprocessed["poses"])
789
- self.assertEqual(preprocessed["frame_memory_pose"][:7, 0, 0].tolist(), [0.0, 1.0, 2.0, -106.0, -105.0, -2.0, -1.0])
790
  self.assertEqual(tuple(preprocessed["frame_indices"].shape), (9, 1))
791
  self.assertEqual(preprocessed["memory_segments"], sample["memory_segments"])
792
  self.assertEqual(preprocessed["segment_lengths"], sample["memory_segments"])
@@ -803,7 +885,7 @@ class DeMemWMLatentDatasetTests(unittest.TestCase):
803
  {"anchor": (3, 5), "dynamic": (5, 7), "revisit": (7, 9)},
804
  )
805
  self.assertEqual(preprocessed["target_tensors"]["frame_indices"][:, 0].tolist(), [106, 107, 108])
806
- self.assertEqual(preprocessed["stream_tensors"]["anchor"]["frame_indices"][:, 0].tolist(), [0, 1])
807
  self.assertEqual(preprocessed["stream_tensors"]["dynamic"]["frame_indices"][:, 0].tolist(), [104, 105])
808
  self.assertEqual(preprocessed["stream_tensors"]["revisit"]["frame_indices"].shape[0], 2)
809
  self.assertEqual(tuple(preprocessed["memory_masks"]["target"].shape), (1, 3))
 
1
+ import json
2
  import tempfile
3
  import unittest
4
  from unittest import mock
 
44
  def _dataset_cfg(root, **overrides):
45
  cfg = {
46
  "save_dir": str(root),
47
+ "precomputed_feature_dir": str(Path(root) / "vae_features"),
48
  "n_frames": 3,
49
  "n_frames_valid": 3,
50
  "frame_skip": 1,
 
59
  return OmegaConf.create(cfg)
60
 
61
 
62
+ def _write_vae_feature_clip(root, feature_root, split="training", subdir="scene", stem="000001", num_frames=104, actions=None, poses=None):
63
+ video_path = Path(root) / split / subdir / f"{stem}.mp4"
64
+ video_path.parent.mkdir(parents=True, exist_ok=True)
65
+ video_path.write_bytes(b"")
66
  if actions is None:
67
+ actions = np.ones((num_frames, 8), dtype=np.int32)
68
  if poses is None:
69
  poses = _poses(num_frames)
70
+ np.savez(video_path.with_suffix(".npz"), actions=actions, poses=poses)
71
+
72
+ feature_dir = Path(feature_root) / split / subdir
73
+ feature_dir.mkdir(parents=True, exist_ok=True)
74
+ np.save(feature_dir / f"{stem}_vae_feature.npy", np.arange(num_frames * 2, dtype=np.float16).reshape(num_frames, 1, 1, 2))
75
+ with open(feature_dir / f"{stem}_vae_feature_meta.json", "w", encoding="utf-8") as handle:
76
+ json.dump({"image_height": 360, "image_width": 640}, handle)
 
 
 
77
 
78
 
79
  class MemorySelectionTests(unittest.TestCase):
 
179
  def test_dataset_requires_split_directory_without_root_fallback(self):
180
  with tempfile.TemporaryDirectory() as tmp:
181
  root = Path(tmp)
182
+ (root / "sample.mp4").write_bytes(b"")
183
 
184
  with self.assertRaisesRegex(FileNotFoundError, "validation"):
185
  MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="validation")
 
187
  def test_dataset_matches_original_w_updown_split_filtering(self):
188
  with tempfile.TemporaryDirectory() as tmp:
189
  root = Path(tmp)
190
+ _write_vae_feature_clip(root, root / "vae_features", split="validation", stem="plain")
191
+ _write_vae_feature_clip(root, root / "vae_features", split="validation", stem="clip_w_updown")
192
 
193
  dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="validation")
194
  dataset_wo = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root, wo_updown=True), split="validation")
195
 
196
+ self.assertEqual([path.name for path in dataset.data_paths], ["clip_w_updown.mp4"])
197
+ self.assertEqual([path.name for path in dataset_wo.data_paths], ["plain.mp4"])
198
 
199
  def test_dataset_filters_nested_validation_files_by_selected_split(self):
200
  with tempfile.TemporaryDirectory() as tmp:
201
  root = Path(tmp)
202
+ _write_vae_feature_clip(root, root / "vae_features", split="validation", subdir="nested", stem="plain")
203
+ _write_vae_feature_clip(root, root / "vae_features", split="validation", subdir="nested", stem="clip_w_updown")
204
 
205
  dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="validation")
206
  dataset_wo = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root, wo_updown=True), split="validation")
207
 
208
+ self.assertEqual([path.name for path in dataset.data_paths], ["clip_w_updown.mp4"])
209
+ self.assertEqual([path.name for path in dataset_wo.data_paths], ["plain.mp4"])
210
 
211
+ def test_dataset_matches_original_fallback_when_split_filter_has_no_matches(self):
212
  cases = [
213
+ ("validation", False, "plain"),
214
+ ("test", True, "clip_w_updown"),
215
  ]
216
+ for split, wo_updown, stem in cases:
217
+ with self.subTest(split=split, wo_updown=wo_updown), tempfile.TemporaryDirectory() as tmp:
218
  root = Path(tmp)
219
+ _write_vae_feature_clip(root, root / "vae_features", split=split, subdir="nested", stem=stem)
220
+ dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root, wo_updown=wo_updown), split=split)
221
 
222
+ self.assertEqual([path.name for path in dataset.data_paths], [f"{stem}.mp4"])
 
 
223
 
224
  def test_dataset_rejects_files_shorter_than_original_frame_skip_window(self):
225
  with tempfile.TemporaryDirectory() as tmp:
226
  root = Path(tmp)
227
+ _write_vae_feature_clip(root, root / "vae_features", stem="short", num_frames=102)
228
 
229
  with self.assertRaisesRegex(ValueError, "requires at least 103"):
230
  MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
 
234
  root = Path(tmp)
235
  poses = _poses(104)
236
  poses[:, 1] = np.linspace(0.0, 3.0, 104, dtype=np.float32)
237
+ _write_vae_feature_clip(root, root / "vae_features", stem="sample", poses=poses)
238
  dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
239
 
240
  with self.assertRaisesRegex(ValueError, "Pose height variation"):
 
248
  raw_actions[:, 3] = 11
249
  raw_actions[:, 4] = 13
250
  raw_actions[:, 7] = 1
251
+ _write_vae_feature_clip(root, root / "vae_features", stem="sample", actions=raw_actions)
252
  sample = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")[0]
253
 
254
  self.assertEqual(tuple(sample["actions"].shape), (3, 25))
 
257
  self.assertTrue(torch.equal(sample["actions"][:, 15], -torch.ones(3)))
258
  self.assertTrue(torch.equal(sample["actions"][:, 2], torch.ones(3)))
259
 
260
+ def test_dataset_reads_dememwm_vae_feature_layout(self):
261
+ with tempfile.TemporaryDirectory() as tmp:
262
+ root = Path(tmp) / "minecraft"
263
+ feature_root = root / "vae_features"
264
+ raw_actions = np.zeros((104, 8), dtype=np.int64)
265
+ raw_actions[:, 0] = 1
266
+ raw_actions[:, 3] = 11
267
+ raw_actions[:, 4] = 13
268
+ raw_actions[:, 7] = 1
269
+ _write_vae_feature_clip(root, feature_root, actions=raw_actions)
270
+ dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
271
+ sample = dataset[0]
272
+
273
+ self.assertEqual([path.suffix for path in dataset.data_paths], [".mp4"])
274
+ self.assertEqual(tuple(sample["latents"].shape), (3, 1, 1, 2))
275
+ self.assertEqual(tuple(sample["actions"].shape), (3, 25))
276
+ self.assertEqual(sample["image_hw"].tolist(), [360, 640])
277
+ self.assertTrue(torch.equal(sample["actions"][:, 11], torch.ones(3)))
278
+ self.assertTrue(torch.equal(sample["actions"][:, 16], torch.ones(3)))
279
+ self.assertTrue(torch.equal(sample["actions"][:, 15], -torch.ones(3)))
280
+ self.assertTrue(torch.equal(sample["actions"][:, 2], torch.ones(3)))
281
+
282
+ def test_dataset_uses_original_training_clip_window(self):
283
  with tempfile.TemporaryDirectory() as tmp:
284
  root = Path(tmp)
285
+ _write_vae_feature_clip(root, root / "vae_features", stem="sample", num_frames=1500)
286
+ dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root, n_frames=8), split="training")
287
+
288
+ self.assertEqual(len(dataset), 1300 - 8 + 1)
289
+
290
+ def test_dataset_reuses_cached_feature_arrays_for_same_file(self):
291
+ with tempfile.TemporaryDirectory() as tmp:
292
+ root = Path(tmp)
293
+ _write_vae_feature_clip(root, root / "vae_features", stem="sample")
294
  dataset = MinecraftVideoDeMemWMLatentDataset(_dataset_cfg(root), split="training")
295
  first = dataset[0]
296
 
 
743
  def test_disabled_memory_selection_does_not_allocate_memory_slots(self):
744
  with tempfile.TemporaryDirectory() as tmp:
745
  root = Path(tmp)
746
+ _write_vae_feature_clip(root, root / "vae_features", stem="sample")
747
  dataset = MinecraftVideoDeMemWMLatentDataset(
748
  _dataset_cfg(root, memory_selection=_selection_cfg(enabled=False)),
749
  split="training",
 
758
  for key in ("anchor", "dynamic", "revisit"):
759
  self.assertEqual(tuple(sample["memory_masks"][key].shape), (0,))
760
 
761
+ def test_dataset_excludes_initial_offset_from_memory_candidates(self):
762
+ with tempfile.TemporaryDirectory() as tmp:
763
+ root = Path(tmp)
764
+ _write_vae_feature_clip(
765
+ root,
766
+ root / "vae_features",
767
+ stem="sample",
768
+ num_frames=112,
769
+ actions=np.ones((112, 25), dtype=np.float32),
770
+ )
771
+ dataset = MinecraftVideoDeMemWMLatentDataset(
772
+ _dataset_cfg(root, memory_selection=_selection_cfg()),
773
+ split="training",
774
+ )
775
+ sample = dataset[0]
776
+
777
+ self.assertEqual(sample["frame_indices"][:3].tolist(), [100, 101, 102])
778
+ self.assertEqual(sample["frame_indices"][3:].tolist(), [-1, -1, -1, -1, -1, -1])
779
+ for key in ("anchor", "dynamic", "revisit"):
780
+ self.assertFalse(sample["memory_masks"][key].any().item())
781
+
782
+ def test_training_context_window_shifts_target_start_but_not_memory_floor(self):
783
+ with tempfile.TemporaryDirectory() as tmp:
784
+ root = Path(tmp)
785
+ _write_vae_feature_clip(
786
+ root,
787
+ root / "vae_features",
788
+ stem="sample",
789
+ num_frames=112,
790
+ actions=np.ones((112, 25), dtype=np.float32),
791
+ )
792
+ dataset = MinecraftVideoDeMemWMLatentDataset(
793
+ _dataset_cfg(
794
+ root,
795
+ context_length=4,
796
+ memory_selection=_selection_cfg(max_anchor_frames=2, max_dynamic_frames=2, max_revisit_frames=0),
797
+ ),
798
+ split="training",
799
+ )
800
+ sample = dataset[0]
801
+
802
+ self.assertEqual(dataset.context_window, 4)
803
+ self.assertEqual(len(dataset), 112 - (100 + 4 + 3) + 1)
804
+ self.assertEqual(sample["frame_indices"].tolist(), [104, 105, 106, 100, 101, 102, 103])
805
+ self.assertTrue(sample["memory_masks"]["anchor"].all().item())
806
+ self.assertTrue(sample["memory_masks"]["dynamic"].all().item())
807
+ self.assertEqual(tuple(sample["memory_masks"]["revisit"].shape), (0,))
808
+
809
+ def test_validation_ignores_training_context_window_shift(self):
810
+ with tempfile.TemporaryDirectory() as tmp:
811
+ root = Path(tmp)
812
+ _write_vae_feature_clip(
813
+ root,
814
+ root / "vae_features",
815
+ split="validation",
816
+ stem="clip_w_updown",
817
+ num_frames=112,
818
+ actions=np.ones((112, 25), dtype=np.float32),
819
+ )
820
+ dataset = MinecraftVideoDeMemWMLatentDataset(
821
+ _dataset_cfg(root, context_length=4),
822
+ split="validation",
823
+ )
824
+ sample = dataset[0]
825
+
826
+ self.assertEqual(dataset.context_window, 0)
827
+ self.assertEqual(sample["frame_indices"].tolist(), [100, 101, 102])
828
+
829
  def test_dataset_returns_target_anchor_dynamic_revisit_contract(self):
830
  with tempfile.TemporaryDirectory() as tmp:
831
  root = Path(tmp)
 
 
832
  num_frames = 112
833
+ _write_vae_feature_clip(
834
+ root,
835
+ root / "vae_features",
836
+ stem="sample",
837
+ num_frames=num_frames,
838
  actions=np.ones((num_frames, 25), dtype=np.float32),
 
 
 
839
  )
840
+ dataset = MinecraftVideoDeMemWMLatentDataset(
841
+ _dataset_cfg(root, memory_selection=_selection_cfg()),
842
+ split="training",
 
 
 
 
 
 
 
 
 
 
843
  )
 
844
  sample = dataset[6]
845
 
846
  self.assertEqual(sample["memory_segments"], {"target": 3, "anchor": 2, "dynamic": 2, "revisit": 2})
847
  self.assertEqual(tuple(sample["latents"].shape), (9, 1, 1, 2))
848
  self.assertEqual(sample["frame_indices"][:3].tolist(), [106, 107, 108])
849
+ self.assertEqual(sample["frame_indices"][3:5].tolist(), [100, 101])
850
  self.assertEqual(sample["frame_indices"][5:7].tolist(), [104, 105])
851
+ self.assertEqual(sample["frame_indices"][7:9].tolist(), [102, 103])
 
852
  self.assertTrue(sample["memory_masks"]["target"].all().item())
853
  self.assertTrue(sample["memory_masks"]["anchor"].all().item())
854
  self.assertTrue(sample["memory_masks"]["dynamic"].all().item())
 
868
  self.assertTrue(torch.equal(preprocessed["actions"][3:], torch.ones_like(preprocessed["actions"][3:])))
869
  self.assertEqual(tuple(preprocessed["poses"].shape), (9, 1, 5))
870
  self.assertIs(preprocessed["frame_memory_pose"], preprocessed["poses"])
871
+ self.assertEqual(preprocessed["frame_memory_pose"][:7, 0, 0].tolist(), [0.0, 1.0, 2.0, -6.0, -5.0, -2.0, -1.0])
872
  self.assertEqual(tuple(preprocessed["frame_indices"].shape), (9, 1))
873
  self.assertEqual(preprocessed["memory_segments"], sample["memory_segments"])
874
  self.assertEqual(preprocessed["segment_lengths"], sample["memory_segments"])
 
885
  {"anchor": (3, 5), "dynamic": (5, 7), "revisit": (7, 9)},
886
  )
887
  self.assertEqual(preprocessed["target_tensors"]["frame_indices"][:, 0].tolist(), [106, 107, 108])
888
+ self.assertEqual(preprocessed["stream_tensors"]["anchor"]["frame_indices"][:, 0].tolist(), [100, 101])
889
  self.assertEqual(preprocessed["stream_tensors"]["dynamic"]["frame_indices"][:, 0].tolist(), [104, 105])
890
  self.assertEqual(preprocessed["stream_tensors"]["revisit"]["frame_indices"].shape[0], 2)
891
  self.assertEqual(tuple(preprocessed["memory_masks"]["target"].shape), (1, 3))