aabyaneh commited on
Commit
dbdf2ca
·
verified ·
1 Parent(s): 870cf97

Make corrected-data epoch-30 FasterWAM current; move previous run under old

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. LATEST_RESULTS.md +10 -0
  2. README.md +6 -0
  3. hf_local_checkpoint_runs.json +76 -0
  4. joint/fasterwam/README.md +4 -4
  5. joint/fasterwam/old/ARCHIVE_PROVENANCE.json +724 -0
  6. joint/fasterwam/old/README.md +5 -0
  7. joint/fasterwam/train90_joint_job4247/README.md +0 -9
  8. joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_029310.pt +0 -3
  9. joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_058620.pt +0 -3
  10. joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_087930.pt +0 -3
  11. joint/fasterwam/train90_joint_job4247/conditioning/dish_utensil/tasks.jsonl +0 -1
  12. joint/fasterwam/train90_joint_job4247/conditioning/fruit_pick/tasks.jsonl +0 -1
  13. joint/fasterwam/train90_joint_job4247/conditioning/index.json +0 -22
  14. joint/fasterwam/train90_joint_job4247/conditioning/place_cube/tasks.jsonl +0 -1
  15. joint/fasterwam/train90_joint_job4247/conditioning/prompts.json +0 -6
  16. joint/fasterwam/train90_joint_job4247/conditioning/push_t/tasks.jsonl +0 -1
  17. joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/3331746089cf26d56af486af1ef7035e746c2e823cc957e2e3b7a0c3baf347ef.t5_len128.wan22ti2v5b.pt +0 -3
  18. joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/531e2910f4efdc285bcefbeca24a8a1ff789d6c4365dfd7c15de4eda9eb89f6b.t5_len128.wan22ti2v5b.pt +0 -3
  19. joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/6bbe3e745d0bcd06a540d3d895af431a769ae466d0cf1102bcee7979ba41e95b.t5_len128.wan22ti2v5b.pt +0 -3
  20. joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/71cb088954da46da4e3cb6c6f73ac812690e9bccf886c7858377c61918b4e3e8.t5_len128.wan22ti2v5b.pt +0 -3
  21. joint/fasterwam/train90_joint_job4247/config.yaml +0 -344
  22. joint/fasterwam/train90_joint_job4247/dataset_stats.json +0 -1938
  23. joint/fasterwam/train90_joint_job4247/manifest.json +0 -45
  24. joint/fasterwam/train90_joint_job4247/provenance/code.json +0 -22
  25. joint/fasterwam/train90_joint_job4247/provenance/implementation.json +0 -364
  26. joint/fasterwam/train90_joint_job4247/provenance/training.log +0 -0
  27. joint/fasterwam/train90_joint_job4247/provenance/training_summary.json +0 -3
  28. joint/fasterwam/train90_joint_job4247/requirements.txt +0 -226
  29. joint/fasterwam/train90_joint_job4247/splits/dish_utensil_lerobot_v21.json +0 -118
  30. joint/fasterwam/train90_joint_job4247/splits/fruit_pick_lerobot_v21.json +0 -118
  31. joint/fasterwam/train90_joint_job4247/splits/place_cube_lerobot_v21.json +0 -118
  32. joint/fasterwam/train90_joint_job4247/splits/push_t_lerobot_v21.json +0 -118
  33. joint/fasterwam/train90_joint_job4247/training_code/fastwam/LICENSE +0 -24
  34. joint/fasterwam/train90_joint_job4247/training_code/fastwam/pyproject.toml +0 -53
  35. joint/fasterwam/train90_joint_job4247/training_code/fastwam/scripts/train.py +0 -24
  36. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/__init__.py +0 -1
  37. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/__init__.py +0 -0
  38. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/aligned_3dpft.py +0 -91
  39. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/dataset_utils.py +0 -109
  40. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/eef_anchors.py +0 -310
  41. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/base_lerobot_dataset.py +0 -517
  42. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/constants.py +0 -54
  43. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/compute_stats.py +0 -179
  44. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/utils.py +0 -965
  45. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/video_utils.py +0 -527
  46. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/lerobot_dataset.py +0 -1404
  47. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/processors/base_processor.py +0 -263
  48. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/processors/fastwam_processor.py +0 -362
  49. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/robot_video_dataset.py +0 -446
  50. joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/transforms/action_state_merger.py +0 -78
LATEST_RESULTS.md CHANGED
@@ -1,3 +1,13 @@
 
 
 
 
 
 
 
 
 
 
1
  # Latest completed training results — September 16, 2026
2
 
3
  These five runs completed on the canonical 90/10 episode split. Each was trained on four H200 GPUs with global batch 32. This update publishes three inference checkpoints spread across each run, including the final checkpoint. Optimizer state remains local; the tables below report the final checkpoint metrics.
 
1
+ # Latest publication — September 19, 2026
2
+
3
+ Current joint FasterWAM: [job 4414, epoch 30](joint/fasterwam/train90_joint_job4414/README.md), native step 87,930, trained jointly on fruit_pick, dish_utensil, place_cube and push_t. Dataset revision `4ad42310b4408ff08370124e680e3a20ad267b1e` contains the validated corrected place_cube spatial crops. All four task embeddings and joint normalization are included. Sampled validation: loss 0.2390, action L1 0.0042, action L2/MSE 0.0017, PSNR 30.0401, SSIM 0.8902 (rounded log values, not robot success measurements).
4
+
5
+ This checkpoint was saved before the user-requested time limit. The run was configured for 50 epochs but stopped later at that limit; this publication contains epoch 30 only. The previous job 4247 run is archived under [joint/fasterwam/old/](joint/fasterwam/old/README.md), with its original files preserved.
6
+
7
+ The following September 16 results are historical and predate the place_cube correction.
8
+
9
+ ---
10
+
11
  # Latest completed training results — September 16, 2026
12
 
13
  These five runs completed on the canonical 90/10 episode split. Each was trained on four H200 GPUs with global batch 32. This update publishes three inference checkpoints spread across each run, including the final checkpoint. Optimizer state remains local; the tables below report the final checkpoint metrics.
README.md CHANGED
@@ -20,6 +20,12 @@ Each run includes its original checkpoint format and inference/training metadata
20
 
21
  See [CHECKPOINT_UPLOADS.md](CHECKPOINT_UPLOADS.md) for the upload contract and commands, [checkpoint_layout.json](checkpoint_layout.json) for the path schema, and [hf_checkpoint_imports.json](hf_checkpoint_imports.json) for pinned source revisions and file hashes. Imported run READMEs retain their original training and evaluation limitations; these historical runs are not asserted to use the current benchmark split.
22
 
 
 
 
 
 
 
23
  ## Imported archives
24
 
25
  | Source | Run folder |
 
20
 
21
  See [CHECKPOINT_UPLOADS.md](CHECKPOINT_UPLOADS.md) for the upload contract and commands, [checkpoint_layout.json](checkpoint_layout.json) for the path schema, and [hf_checkpoint_imports.json](hf_checkpoint_imports.json) for pinned source revisions and file hashes. Imported run READMEs retain their original training and evaluation limitations; these historical runs are not asserted to use the current benchmark split.
22
 
23
+ ## Current joint FasterWAM — September 19, 2026
24
+
25
+ [Job 4414, epoch 30](joint/fasterwam/train90_joint_job4414/README.md) is the current joint FasterWAM checkpoint, trained on all four datasets with corrected place_cube crops (dataset revision `4ad42310b4408ff08370124e680e3a20ad267b1e`). Native checkpoint: `step_087930.pt`; 8 H200 GPUs; 360 train / 40 held-out episodes. Inference weights and matching config, normalization, task embeddings, source and validation provenance are included. The run stopped at its requested time limit after saving epoch 30; it did not complete the planned 50 epochs.
26
+
27
+ The previous joint FasterWAM run, job 4247, is preserved under [joint/fasterwam/old/](joint/fasterwam/old/README.md). Its place_cube crops were invalid and it is superseded by job 4414.
28
+
29
  ## Imported archives
30
 
31
  | Source | Run folder |
hf_local_checkpoint_runs.json CHANGED
@@ -116,6 +116,82 @@
116
  "task": "place_cube",
117
  "training_status": "complete",
118
  "wandb_run": "https://wandb.ai/aabyaneh/fasterwam-real/runs/ilkye0g0"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  }
120
  ]
121
  }
 
116
  "task": "place_cube",
117
  "training_status": "complete",
118
  "wandb_run": "https://wandb.ai/aabyaneh/fasterwam-real/runs/ilkye0g0"
119
+ },
120
+ {
121
+ "bytes": 31086341628,
122
+ "checkpoint_contents": "three inference checkpoints and matching metadata",
123
+ "checkpoint_epochs": [
124
+ 10,
125
+ 20,
126
+ 30
127
+ ],
128
+ "completed_updates": 94160,
129
+ "ended_utc": "2026-09-17T15:38:29Z",
130
+ "final_checkpoint_step": 87930,
131
+ "final_metrics": {},
132
+ "includes_training_state": false,
133
+ "last_logged_training_step": 94160,
134
+ "model": "fasterwam",
135
+ "native_checkpoint_steps": [
136
+ 29310,
137
+ 58620,
138
+ 87930
139
+ ],
140
+ "planned_epochs": 50,
141
+ "prepared_bundle": "train_out/hf_uploads/fasterwam_joint_train90_joint_job4247",
142
+ "run_id": "train90_joint_job4247",
143
+ "source_dir": "fastwam_train/runs/joint_all4_fasterwam/2026-09-17_04-15-26_job4247",
144
+ "task": "joint",
145
+ "training_status": "cancelled_by_user",
146
+ "wandb_run": "https://wandb.ai/aabyaneh/fasterwam-real/runs/pp7o49m6",
147
+ "verified_commit": "68669a419e3ab75fe9841daebb8642c31ce9ba06",
148
+ "publication_status": "archived",
149
+ "repo_prefix": "joint/fasterwam/old/train90_joint_job4247",
150
+ "archive_reason": "Superseded: trained with invalid place_cube spatial crops",
151
+ "superseded_by": "joint/fasterwam/train90_joint_job4414"
152
+ },
153
+ {
154
+ "schema_version": 1,
155
+ "task": "joint",
156
+ "model": "fasterwam",
157
+ "run_id": "train90_joint_job4414",
158
+ "slurm_job": 4414,
159
+ "native_checkpoint_steps": [
160
+ 87930
161
+ ],
162
+ "checkpoint_epochs": [
163
+ 30
164
+ ],
165
+ "final_checkpoint_step": 87930,
166
+ "published_checkpoint_updates": 87930,
167
+ "last_logged_training_step": 100800,
168
+ "planned_epochs": 50,
169
+ "training_status": "time_limit_at_user_requested_deadline",
170
+ "ended_utc": "2026-09-19T12:01:03Z",
171
+ "checkpoint_saved_utc": "2026-09-19T10:26:15Z",
172
+ "includes_training_state": false,
173
+ "training_gpus": 8,
174
+ "global_batch_size": 32,
175
+ "train_episodes": 360,
176
+ "validation_episodes": 40,
177
+ "dataset_revision": "4ad42310b4408ff08370124e680e3a20ad267b1e",
178
+ "code_revision": "19483b8b1b6a72e697091a07e7df7777aa09fd85",
179
+ "base_model": "Wan-AI/Wan2.2-TI2V-5B",
180
+ "prompts": {
181
+ "fruit_pick": "Lift the lid, put it aside, and pick the black plum.",
182
+ "dish_utensil": "pick the dishes out of the rack and place them on the table, then pick and place the spoon to the gray dish and fork to the pink dish",
183
+ "place_cube": "place the cube in the bowl",
184
+ "push_t": "push the T block to the target outline"
185
+ },
186
+ "source_dir": "fastwam_train/runs/joint_all4_fasterwam/2026-09-18_23-28-49_job4414",
187
+ "wandb_run": "https://wandb.ai/aabyaneh/fasterwam-real/runs/bct4y3en",
188
+ "integrity_inventory": "upload_manifest.json",
189
+ "robot_evaluation": "not performed",
190
+ "repo_prefix": "joint/fasterwam/train90_joint_job4414",
191
+ "publication_status": "published_verified",
192
+ "prepared_bundle": "train_out/hf_uploads/fasterwam_joint_train90_joint_job4414",
193
+ "checkpoint_contents": "epoch-30 inference weights, config, normalization, all four task embeddings, code and corrected-data provenance",
194
+ "verified_upload_commit": "870cf97d4d8ca9bf5c9c6a71eb7f903559617cb6"
195
  }
196
  ]
197
  }
joint/fasterwam/README.md CHANGED
@@ -1,7 +1,7 @@
1
- # joint / fasterwam
2
 
3
- Checkpoints trained jointly on all four tasks belong here. Publish each joint run once.
4
 
5
- Store each run under `<run_id>/`, with weights under `checkpoints/` and the matching config, normalization, conditioning and run metadata alongside. This README reserves the folder; it does not indicate that weights are available.
6
 
7
- See the [upload guide](../../CHECKPOINT_UPLOADS.md).
 
1
+ # Joint FasterWAM
2
 
3
+ Current: [train90_joint_job4414 — epoch 30](train90_joint_job4414/README.md).
4
 
5
+ Native weights: [step_087930.pt](train90_joint_job4414/checkpoints/weights/step_087930.pt). Trained jointly on fruit_pick, dish_utensil, place_cube and push_t, with the corrected and validated place_cube dataset revision `4ad42310b4408ff08370124e680e3a20ad267b1e`.
6
 
7
+ [Old runs](old/README.md) preserve the previous job 4247 weights and metadata. Those weights used invalid place_cube crops and are superseded.
joint/fasterwam/old/ARCHIVE_PROVENANCE.json ADDED
@@ -0,0 +1,724 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "commit": "e73433f58cd3b4e2266fc6b759ca0f54ceae1af7",
3
+ "source_revision": "a99226d389334830c413f8a419dff39560ee8b89",
4
+ "source_prefix": "joint/fasterwam/train90_joint_job4247/",
5
+ "archive_prefix": "joint/fasterwam/old/train90_joint_job4247/",
6
+ "verified_files": 119,
7
+ "reason": "Invalid place_cube spatial crops; superseded by job4414",
8
+ "files": [
9
+ {
10
+ "path": "joint/fasterwam/train90_joint_job4247/README.md",
11
+ "size": 1082,
12
+ "sha256": null,
13
+ "blob_id": "e8ea7671120730ba22d5c4fa1ddc6f137546bdc1"
14
+ },
15
+ {
16
+ "path": "joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_029310.pt",
17
+ "size": 10360250873,
18
+ "sha256": "714b360d124faa1eccef880ef97916250bf9b267f84276f9c5235f239fb55ab3",
19
+ "blob_id": "7bc1b60d59bd1830ae034645b582de3cf16a5e7c"
20
+ },
21
+ {
22
+ "path": "joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_058620.pt",
23
+ "size": 10360250873,
24
+ "sha256": "229abf320895323b87f42dae740a0b7c9cd8d03860b3a313d3d7e7c9a98fba91",
25
+ "blob_id": "7f32e0496b6e7f64c32fc8af496e9cf3c12f013e"
26
+ },
27
+ {
28
+ "path": "joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_087930.pt",
29
+ "size": 10360250873,
30
+ "sha256": "0a2e5a3592cfb6d02682183b746e6009300e080d753d22b400b08820d4e1d8c4",
31
+ "blob_id": "284bfd8f838ae78a8fe25071ed6dfcb84212e709"
32
+ },
33
+ {
34
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/dish_utensil/tasks.jsonl",
35
+ "size": 163,
36
+ "sha256": null,
37
+ "blob_id": "4889ce572a4fba78e9b0c0db49e2e3139895057e"
38
+ },
39
+ {
40
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/fruit_pick/tasks.jsonl",
41
+ "size": 82,
42
+ "sha256": null,
43
+ "blob_id": "1cf9cc95bcf3b7cd93dd6c318c9e5266df517aee"
44
+ },
45
+ {
46
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/index.json",
47
+ "size": 1651,
48
+ "sha256": null,
49
+ "blob_id": "a08f8e4eb5d5765b5ffa7fb33d11180b0e366121"
50
+ },
51
+ {
52
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/place_cube/tasks.jsonl",
53
+ "size": 56,
54
+ "sha256": null,
55
+ "blob_id": "2f1a8277b173ab2a3a0e7321a424921f32b5ee5c"
56
+ },
57
+ {
58
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/prompts.json",
59
+ "size": 330,
60
+ "sha256": null,
61
+ "blob_id": "c4d04fa855239969c2853d2a54ed4188d1549db9"
62
+ },
63
+ {
64
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/push_t/tasks.jsonl",
65
+ "size": 68,
66
+ "sha256": null,
67
+ "blob_id": "1dedddb57ce3fb271987779e8b688f196a06ffd8"
68
+ },
69
+ {
70
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/3331746089cf26d56af486af1ef7035e746c2e823cc957e2e3b7a0c3baf347ef.t5_len128.wan22ti2v5b.pt",
71
+ "size": 1051869,
72
+ "sha256": "7ddfc1a78ca521bc8793e4a58e8c1f4fa55d83274ae0026483888d21521e5855",
73
+ "blob_id": "ff38e930fb2171e06a66a825b02c59071a9d5991"
74
+ },
75
+ {
76
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/531e2910f4efdc285bcefbeca24a8a1ff789d6c4365dfd7c15de4eda9eb89f6b.t5_len128.wan22ti2v5b.pt",
77
+ "size": 1051869,
78
+ "sha256": "c676ff34af5f0fd05efa441912d0b94569a824caafa5778847b9a885a79c1e23",
79
+ "blob_id": "40c9e1771938248f3a82f545d8a212f501372235"
80
+ },
81
+ {
82
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/6bbe3e745d0bcd06a540d3d895af431a769ae466d0cf1102bcee7979ba41e95b.t5_len128.wan22ti2v5b.pt",
83
+ "size": 1051869,
84
+ "sha256": "f4d0befa15744dccbba163d380fd62e8c0ca8bffc6b5271f200d527abc23d200",
85
+ "blob_id": "b2aab59f6efd38faebc888e4561bebabf897c2f5"
86
+ },
87
+ {
88
+ "path": "joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/71cb088954da46da4e3cb6c6f73ac812690e9bccf886c7858377c61918b4e3e8.t5_len128.wan22ti2v5b.pt",
89
+ "size": 1051869,
90
+ "sha256": "85b8468de72c7e7c481fe968d6ab5164d3b3c694f64b7037ec2d05fa706c73c6",
91
+ "blob_id": "897035aef7e7c67b5888ba6ac7e4bc7f3db82f60"
92
+ },
93
+ {
94
+ "path": "joint/fasterwam/train90_joint_job4247/config.yaml",
95
+ "size": 8668,
96
+ "sha256": null,
97
+ "blob_id": "7c71304ca5cdeb48c17118080c7c6719b1819a2e"
98
+ },
99
+ {
100
+ "path": "joint/fasterwam/train90_joint_job4247/dataset_stats.json",
101
+ "size": 40087,
102
+ "sha256": null,
103
+ "blob_id": "700f9c798742497fbe3b831a5e7f708234f22961"
104
+ },
105
+ {
106
+ "path": "joint/fasterwam/train90_joint_job4247/manifest.json",
107
+ "size": 1737,
108
+ "sha256": null,
109
+ "blob_id": "385eea812f62fc92e0b89255c4a6ead3770a491b"
110
+ },
111
+ {
112
+ "path": "joint/fasterwam/train90_joint_job4247/provenance/code.json",
113
+ "size": 814,
114
+ "sha256": null,
115
+ "blob_id": "0f4e4c5550b1c1efeeaa760d3b7e6b87ad87f5e2"
116
+ },
117
+ {
118
+ "path": "joint/fasterwam/train90_joint_job4247/provenance/implementation.json",
119
+ "size": 42911,
120
+ "sha256": null,
121
+ "blob_id": "8c655b29d8501a4ad76f034bc66d1c2b7711f6bb"
122
+ },
123
+ {
124
+ "path": "joint/fasterwam/train90_joint_job4247/provenance/training.log",
125
+ "size": 4024180,
126
+ "sha256": null,
127
+ "blob_id": "6aa04b0e02ac670b53ddadfcde85e16fd38bdce3"
128
+ },
129
+ {
130
+ "path": "joint/fasterwam/train90_joint_job4247/provenance/training_summary.json",
131
+ "size": 66,
132
+ "sha256": null,
133
+ "blob_id": "89dc1ccdf4dd67a320bba366f9805bd86f9d4519"
134
+ },
135
+ {
136
+ "path": "joint/fasterwam/train90_joint_job4247/requirements.txt",
137
+ "size": 4136,
138
+ "sha256": null,
139
+ "blob_id": "9170e47ace8e1b0b2c76374d13fdb864e9a5825b"
140
+ },
141
+ {
142
+ "path": "joint/fasterwam/train90_joint_job4247/splits/dish_utensil_lerobot_v21.json",
143
+ "size": 1340,
144
+ "sha256": null,
145
+ "blob_id": "dceff5f8f43f54d6fe4f011848210a0c01276820"
146
+ },
147
+ {
148
+ "path": "joint/fasterwam/train90_joint_job4247/splits/fruit_pick_lerobot_v21.json",
149
+ "size": 1336,
150
+ "sha256": null,
151
+ "blob_id": "c079faa8f98ae706d44fe91c105a24a88f320eec"
152
+ },
153
+ {
154
+ "path": "joint/fasterwam/train90_joint_job4247/splits/place_cube_lerobot_v21.json",
155
+ "size": 1333,
156
+ "sha256": null,
157
+ "blob_id": "50da50bfb6f4e1bf0a0a68313388c91e59ac140f"
158
+ },
159
+ {
160
+ "path": "joint/fasterwam/train90_joint_job4247/splits/push_t_lerobot_v21.json",
161
+ "size": 1328,
162
+ "sha256": null,
163
+ "blob_id": "521fe4a4f96b004d0ddc54c1b8340fc5e8955319"
164
+ },
165
+ {
166
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/LICENSE",
167
+ "size": 1210,
168
+ "sha256": null,
169
+ "blob_id": "90124d129334cd5de11aa0fd606182b3ffd442cb"
170
+ },
171
+ {
172
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/pyproject.toml",
173
+ "size": 1152,
174
+ "sha256": null,
175
+ "blob_id": "853bc3e032f6cc85ff20f2338f8418976b69a615"
176
+ },
177
+ {
178
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/scripts/train.py",
179
+ "size": 543,
180
+ "sha256": null,
181
+ "blob_id": "f0a3b5e2467eef86eb7deac5305a33d1cc00bcf7"
182
+ },
183
+ {
184
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/__init__.py",
185
+ "size": 23,
186
+ "sha256": null,
187
+ "blob_id": "f40dcb36ffb7cb5999600c8d9520cd72e3f60af0"
188
+ },
189
+ {
190
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/__init__.py",
191
+ "size": 0,
192
+ "sha256": null,
193
+ "blob_id": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
194
+ },
195
+ {
196
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/aligned_3dpft.py",
197
+ "size": 4311,
198
+ "sha256": null,
199
+ "blob_id": "e18d746558f8cda87966946151f5a425d47d6363"
200
+ },
201
+ {
202
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/dataset_utils.py",
203
+ "size": 3735,
204
+ "sha256": null,
205
+ "blob_id": "e7d5830119dd42c492ec3ba5c01081c26b836d71"
206
+ },
207
+ {
208
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/eef_anchors.py",
209
+ "size": 12039,
210
+ "sha256": null,
211
+ "blob_id": "d1a2b97271c6c10a9514430605fdac99315d5dea"
212
+ },
213
+ {
214
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/base_lerobot_dataset.py",
215
+ "size": 23105,
216
+ "sha256": null,
217
+ "blob_id": "227fb9ee707369162bc1cc2c16fa1b96a1729a7a"
218
+ },
219
+ {
220
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/constants.py",
221
+ "size": 1915,
222
+ "sha256": null,
223
+ "blob_id": "30777239efdfa096bfefbc0f2d729e8f36da90ca"
224
+ },
225
+ {
226
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/compute_stats.py",
227
+ "size": 7067,
228
+ "sha256": null,
229
+ "blob_id": "df26818f654de1410ccdab2306a85ca4070b761b"
230
+ },
231
+ {
232
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/utils.py",
233
+ "size": 36236,
234
+ "sha256": null,
235
+ "blob_id": "dd382df529058e4edcf202cde1eef54170e21863"
236
+ },
237
+ {
238
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/video_utils.py",
239
+ "size": 19421,
240
+ "sha256": null,
241
+ "blob_id": "9dd6fee91c9d867d5a0f4540e5ae0713b7f753dc"
242
+ },
243
+ {
244
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/lerobot_dataset.py",
245
+ "size": 60778,
246
+ "sha256": null,
247
+ "blob_id": "ef5d5e50235f051c8919c9a635c25a3289215c02"
248
+ },
249
+ {
250
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/processors/base_processor.py",
251
+ "size": 10811,
252
+ "sha256": null,
253
+ "blob_id": "39d1665b6f0da68060fad7a120241577301a6139"
254
+ },
255
+ {
256
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/processors/fastwam_processor.py",
257
+ "size": 15064,
258
+ "sha256": null,
259
+ "blob_id": "e06eb0246a6443f7dcd1052582f3cad41dd05f37"
260
+ },
261
+ {
262
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/robot_video_dataset.py",
263
+ "size": 20540,
264
+ "sha256": null,
265
+ "blob_id": "b7b682a760a2671104c5cbcae7e63e077a68a249"
266
+ },
267
+ {
268
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/transforms/action_state_merger.py",
269
+ "size": 2522,
270
+ "sha256": null,
271
+ "blob_id": "0a35644a4fbc65a5cb6abe3f642f4af35a4e438b"
272
+ },
273
+ {
274
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/transforms/image.py",
275
+ "size": 725,
276
+ "sha256": null,
277
+ "blob_id": "8dbeda01c09dd57bc772ad4d53cf8b49586a2d06"
278
+ },
279
+ {
280
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/transforms/misc.py",
281
+ "size": 422,
282
+ "sha256": null,
283
+ "blob_id": "49279d6888f565078aafbcc3a830584bb34515a5"
284
+ },
285
+ {
286
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/transforms/relative_action.py",
287
+ "size": 5423,
288
+ "sha256": null,
289
+ "blob_id": "5e4df6dbf0e131e9093ae7e2e149dcdc9823801f"
290
+ },
291
+ {
292
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/transforms/rotation.py",
293
+ "size": 5603,
294
+ "sha256": null,
295
+ "blob_id": "4f19924703f4274401f01537180aae11f9bc84c7"
296
+ },
297
+ {
298
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/utils/normalizer.py",
299
+ "size": 11274,
300
+ "sha256": null,
301
+ "blob_id": "1c674b0234bcf3cf1a1bef0f8fc0523020449b4a"
302
+ },
303
+ {
304
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/utils/rotation.py",
305
+ "size": 21110,
306
+ "sha256": null,
307
+ "blob_id": "f468cd4f24092bfe8a601a85e2cb8e5e6b84cbbb"
308
+ },
309
+ {
310
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/robotwin_aligned_3dpft.py",
311
+ "size": 4506,
312
+ "sha256": null,
313
+ "blob_id": "e601786919c6f4723924c6ef0bc16984348a3c7e"
314
+ },
315
+ {
316
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/video_latent_cache.py",
317
+ "size": 19138,
318
+ "sha256": null,
319
+ "blob_id": "ce28d1193029023b115d3a63b8cd4480c3f1b7a0"
320
+ },
321
+ {
322
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/__init__.py",
323
+ "size": 3140,
324
+ "sha256": null,
325
+ "blob_id": "a710f4c422f5b6c459c1404112b173f60569dc20"
326
+ },
327
+ {
328
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/bearing.py",
329
+ "size": 7646,
330
+ "sha256": null,
331
+ "blob_id": "3491b9a307ab752fe0005e6daa3410f4eeb9ca2e"
332
+ },
333
+ {
334
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/calibration.py",
335
+ "size": 15987,
336
+ "sha256": null,
337
+ "blob_id": "177eac6a5383929a16be17ccfb8283512c270992"
338
+ },
339
+ {
340
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/camera_source.py",
341
+ "size": 5050,
342
+ "sha256": null,
343
+ "blob_id": "9cab1f86c82968633b4efef4df5ce001c7808f60"
344
+ },
345
+ {
346
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/control_rope.py",
347
+ "size": 4168,
348
+ "sha256": null,
349
+ "blob_id": "6bb696b07899fcd4c0b49771be8aca39b9c726ac"
350
+ },
351
+ {
352
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/eef_projector.py",
353
+ "size": 41068,
354
+ "sha256": null,
355
+ "blob_id": "eb813ce6bbd97aef8fc6893c7b24e25de05504c8"
356
+ },
357
+ {
358
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/flow_context.py",
359
+ "size": 4745,
360
+ "sha256": null,
361
+ "blob_id": "dfc3ce73445ca45a74f6d8e42d2973dbc3d0056f"
362
+ },
363
+ {
364
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/flow_trajectory.py",
365
+ "size": 12820,
366
+ "sha256": null,
367
+ "blob_id": "aa2a7bae6aca961f5cfa536b72416708422a8400"
368
+ },
369
+ {
370
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/identity.py",
371
+ "size": 5463,
372
+ "sha256": null,
373
+ "blob_id": "7322cadc38c3b9524f23d0270a8b5f7cb5b8b103"
374
+ },
375
+ {
376
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/plus_anchors.py",
377
+ "size": 17643,
378
+ "sha256": null,
379
+ "blob_id": "155e35f69a054c091fae01cb82591f6b436fa9ee"
380
+ },
381
+ {
382
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/prox_pinhole.py",
383
+ "size": 3570,
384
+ "sha256": null,
385
+ "blob_id": "63525b5f4a848216b057e047aa4c8e5e9d611bf5"
386
+ },
387
+ {
388
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/robotwin_flow.py",
389
+ "size": 21052,
390
+ "sha256": null,
391
+ "blob_id": "1ae33b25208913cf44e85f195429c173bf0fd194"
392
+ },
393
+ {
394
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/geometry/weak_perspective.py",
395
+ "size": 5089,
396
+ "sha256": null,
397
+ "blob_id": "7f16f51493fdc5c1957c6a4efa5b2d9b20c6d953"
398
+ },
399
+ {
400
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/lr_schedules.py",
401
+ "size": 7988,
402
+ "sha256": null,
403
+ "blob_id": "df51864620d37bc7988f17e6e358a3a58189405b"
404
+ },
405
+ {
406
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/__init__.py",
407
+ "size": 35,
408
+ "sha256": null,
409
+ "blob_id": "f45b7d25cd81883bbc40b37fd6366225d1e84105"
410
+ },
411
+ {
412
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/__init__.py",
413
+ "size": 0,
414
+ "sha256": null,
415
+ "blob_id": "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
416
+ },
417
+ {
418
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/action_dit.py",
419
+ "size": 52467,
420
+ "sha256": null,
421
+ "blob_id": "cd095affb62d566b059ad381b5454d943a00e307"
422
+ },
423
+ {
424
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/action_mlp.py",
425
+ "size": 9289,
426
+ "sha256": null,
427
+ "blob_id": "5417e8e8874304c0609436e1af17ca9d84a65032"
428
+ },
429
+ {
430
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/action_proprio_jitter.py",
431
+ "size": 7732,
432
+ "sha256": null,
433
+ "blob_id": "e2852c5faf003256cd851c1d022c806820889166"
434
+ },
435
+ {
436
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/aligned_3dpft.py",
437
+ "size": 14891,
438
+ "sha256": null,
439
+ "blob_id": "876e508f059e13cab19f760b8f5ab2f2f0cff4e7"
440
+ },
441
+ {
442
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/aligned_3dpft_x0.py",
443
+ "size": 11619,
444
+ "sha256": null,
445
+ "blob_id": "a0e589a4cf955e223116b1e668b01ef05741f34e"
446
+ },
447
+ {
448
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/attn_offset_head_anchor.py",
449
+ "size": 10748,
450
+ "sha256": null,
451
+ "blob_id": "1a58ca9cdcdbc333e4cae94898e08b530c346ea6"
452
+ },
453
+ {
454
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/control_rope.py",
455
+ "size": 7260,
456
+ "sha256": null,
457
+ "blob_id": "3149d1a5360bd2c3d5ced44a0750d7ef3c279ec3"
458
+ },
459
+ {
460
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/controlnet_pp.py",
461
+ "size": 10714,
462
+ "sha256": null,
463
+ "blob_id": "5bb451df634f1fd2469f9e901bbd75d9d848454c"
464
+ },
465
+ {
466
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/fasterwam_decoupled.py",
467
+ "size": 83098,
468
+ "sha256": null,
469
+ "blob_id": "8c9791ef4925fbed2976c4febd5309b72b4b98dc"
470
+ },
471
+ {
472
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/fastwam.py",
473
+ "size": 89934,
474
+ "sha256": null,
475
+ "blob_id": "a7adf6a70cf493de36e96ed8de627362d902dc7c"
476
+ },
477
+ {
478
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/fastwam_action_mlp.py",
479
+ "size": 21755,
480
+ "sha256": null,
481
+ "blob_id": "28daaff52e33cc945c11461a7bab52c13406d6cb"
482
+ },
483
+ {
484
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/fastwam_decoupled.py",
485
+ "size": 59696,
486
+ "sha256": null,
487
+ "blob_id": "f12470812b4c8db6e4d6983573370df5b330093e"
488
+ },
489
+ {
490
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/fastwam_idm.py",
491
+ "size": 20353,
492
+ "sha256": null,
493
+ "blob_id": "d21d69b3ac9a83f931e6d1018b17202d49e1b788"
494
+ },
495
+ {
496
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/fastwam_joint.py",
497
+ "size": 10088,
498
+ "sha256": null,
499
+ "blob_id": "c0604f15536c5f433ea68faa02284e56088d9260"
500
+ },
501
+ {
502
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/ha_content_pp.py",
503
+ "size": 3395,
504
+ "sha256": null,
505
+ "blob_id": "1893e772fdd9fda4e2f01848e69cd06c973a341c"
506
+ },
507
+ {
508
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/helpers/__init__.py",
509
+ "size": 415,
510
+ "sha256": null,
511
+ "blob_id": "35bac73c6b329a83d34b02e39a12012305879347"
512
+ },
513
+ {
514
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/helpers/gradient.py",
515
+ "size": 565,
516
+ "sha256": null,
517
+ "blob_id": "636ef5c2e29a96553bfc6d21e47c70506a3831f4"
518
+ },
519
+ {
520
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/helpers/io.py",
521
+ "size": 7751,
522
+ "sha256": null,
523
+ "blob_id": "26d65d08cfa52a40c6e41d4e6b1d9fd4b65843b6"
524
+ },
525
+ {
526
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/helpers/loader.py",
527
+ "size": 8279,
528
+ "sha256": null,
529
+ "blob_id": "99d92ff409fb104c6ebf8cc29905baa12d80e28f"
530
+ },
531
+ {
532
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/helpers/state_dict_converters.py",
533
+ "size": 5226,
534
+ "sha256": null,
535
+ "blob_id": "3a83b5c554d79278c3b73859eea092a78f3ec528"
536
+ },
537
+ {
538
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/iha.py",
539
+ "size": 7474,
540
+ "sha256": null,
541
+ "blob_id": "65e167e35f562e80544ba49178ac7f59871502b9"
542
+ },
543
+ {
544
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/kv_fusion.py",
545
+ "size": 5917,
546
+ "sha256": null,
547
+ "blob_id": "021c5cb471cf7756bb42cf8f00948ae6b8b9ba90"
548
+ },
549
+ {
550
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/learnable_head_anchor.py",
551
+ "size": 4046,
552
+ "sha256": null,
553
+ "blob_id": "866a7779829795a2a6bbc1d32c4fdd2efaf284de"
554
+ },
555
+ {
556
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/mot.py",
557
+ "size": 23212,
558
+ "sha256": null,
559
+ "blob_id": "dfb1a7cfe4b299c3d99d5a118f7728f24069ed0c"
560
+ },
561
+ {
562
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/mot_decoupled.py",
563
+ "size": 89831,
564
+ "sha256": null,
565
+ "blob_id": "2ec4f3931f07ddf3fd5977e1b95f1c8d847a7b15"
566
+ },
567
+ {
568
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/offset_head_anchor.py",
569
+ "size": 10467,
570
+ "sha256": null,
571
+ "blob_id": "1ee79a69c81c082f1b9c01e9310995e49d3c9d22"
572
+ },
573
+ {
574
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/piha.py",
575
+ "size": 4158,
576
+ "sha256": null,
577
+ "blob_id": "a55946e05d6595bd56e4a30f2fc8661e4e6c92a9"
578
+ },
579
+ {
580
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/piha_post_rope_kv.py",
581
+ "size": 6922,
582
+ "sha256": null,
583
+ "blob_id": "47e8fcab87fc3bed9a8f86b94d3ff4fd0cb13523"
584
+ },
585
+ {
586
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/proprio_offset_head_anchor.py",
587
+ "size": 7182,
588
+ "sha256": null,
589
+ "blob_id": "99404ba80dff7aa5372726052f188ac7aae8d0b8"
590
+ },
591
+ {
592
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/robotwin_aligned_3dpft.py",
593
+ "size": 9908,
594
+ "sha256": null,
595
+ "blob_id": "6c4bd2baa2b3d7676e5ee9ff00a6372e12f0e54d"
596
+ },
597
+ {
598
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/schedulers/scheduler_continuous.py",
599
+ "size": 4068,
600
+ "sha256": null,
601
+ "blob_id": "9028f6c86e2e35bc9efd75dda26d9cf88ff898bb"
602
+ },
603
+ {
604
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/text_ca_dropout.py",
605
+ "size": 15007,
606
+ "sha256": null,
607
+ "blob_id": "389bda2abf6c557801f7a2bbbc7527ee950cb69e"
608
+ },
609
+ {
610
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/viewlocal_ha_pp.py",
611
+ "size": 13486,
612
+ "sha256": null,
613
+ "blob_id": "698ea3c6f3b6afd3865e70c6566b19cf9d0fc88f"
614
+ },
615
+ {
616
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/wan22.py",
617
+ "size": 16945,
618
+ "sha256": null,
619
+ "blob_id": "0a7f5a6bccb07193e2fcaf0329a27b28ddda20f0"
620
+ },
621
+ {
622
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/wan_video_dit.py",
623
+ "size": 33084,
624
+ "sha256": null,
625
+ "blob_id": "afb9a25b3ca6c8e3819997e26fd5a9ddad6b9a6c"
626
+ },
627
+ {
628
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/wan_video_text_encoder.py",
629
+ "size": 11459,
630
+ "sha256": null,
631
+ "blob_id": "24f89f49c971d3798a2eed8e20faa98ec01cba20"
632
+ },
633
+ {
634
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/models/wan22/wan_video_vae.py",
635
+ "size": 50058,
636
+ "sha256": null,
637
+ "blob_id": "0386bdedb6a532781eb81509e5f574011113a706"
638
+ },
639
+ {
640
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/runtime.py",
641
+ "size": 34288,
642
+ "sha256": null,
643
+ "blob_id": "cdf15830cc1109a0f1361cf97aa2d69ed6f58d2e"
644
+ },
645
+ {
646
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/trainer.py",
647
+ "size": 66389,
648
+ "sha256": null,
649
+ "blob_id": "ea6e07fb54072e50f98c085395c3344bbcc116f3"
650
+ },
651
+ {
652
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/__init__.py",
653
+ "size": 96,
654
+ "sha256": null,
655
+ "blob_id": "3d0eb927e25fab27c8dae7daa3d52a31c2d532b2"
656
+ },
657
+ {
658
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/config_resolvers.py",
659
+ "size": 2526,
660
+ "sha256": null,
661
+ "blob_id": "4b1273b96b716ffa1028dfce42474376930a19f1"
662
+ },
663
+ {
664
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/fs.py",
665
+ "size": 76,
666
+ "sha256": null,
667
+ "blob_id": "b210bde5a81689be2969db6059ab71833958f43a"
668
+ },
669
+ {
670
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/logging_config.py",
671
+ "size": 4679,
672
+ "sha256": null,
673
+ "blob_id": "eba39b2fcff02c4d8488cf099d414eca19fbbc47"
674
+ },
675
+ {
676
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/misc.py",
677
+ "size": 1223,
678
+ "sha256": null,
679
+ "blob_id": "9981aa8913c2b565c3b6cf5e317eed12d6a7222c"
680
+ },
681
+ {
682
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/pytorch_utils.py",
683
+ "size": 5049,
684
+ "sha256": null,
685
+ "blob_id": "87180a30f9edbba85cc1c94a3fd604b79fd15b1a"
686
+ },
687
+ {
688
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/samplers.py",
689
+ "size": 1339,
690
+ "sha256": null,
691
+ "blob_id": "8b4f52a26a749fadc78a5c4ae7fa9068570be000"
692
+ },
693
+ {
694
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/video_io.py",
695
+ "size": 819,
696
+ "sha256": null,
697
+ "blob_id": "74898b9c02a79c6b34b36af215a32743a8541d5d"
698
+ },
699
+ {
700
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/utils/video_metrics.py",
701
+ "size": 3446,
702
+ "sha256": null,
703
+ "blob_id": "6b2837fa1d7b3c7c2532e99121ee46d68eb6be6e"
704
+ },
705
+ {
706
+ "path": "joint/fasterwam/train90_joint_job4247/training_code/fastwam_joint.py",
707
+ "size": 3994,
708
+ "sha256": null,
709
+ "blob_id": "bcd1ba2657cb2c6ea920a48439feddbb6319ccbf"
710
+ },
711
+ {
712
+ "path": "joint/fasterwam/train90_joint_job4247/upload_manifest.json",
713
+ "size": 36140,
714
+ "sha256": null,
715
+ "blob_id": "aae0341b5ba11619cab14934d870442b604b3fa5"
716
+ },
717
+ {
718
+ "path": "joint/fasterwam/train90_joint_job4247/validation/final_metrics.json",
719
+ "size": 3,
720
+ "sha256": null,
721
+ "blob_id": "0967ef424bce6791893e9a57bb952f80fd536e93"
722
+ }
723
+ ]
724
+ }
joint/fasterwam/old/README.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Old joint FasterWAM checkpoints
2
+
3
+ [train90_joint_job4247](train90_joint_job4247/README.md) preserves all three historical checkpoints (epochs 10, 20 and 30) and all matching metadata, unchanged. This run used the invalid place_cube spatial crops and is superseded by [job 4414](../train90_joint_job4414/README.md).
4
+
5
+ Original prefix: `joint/fasterwam/train90_joint_job4247/`. Archived prefix: `joint/fasterwam/old/train90_joint_job4247/`. Original manifests retain their original paths as historical provenance; apply this prefix mapping when inspecting them. `ARCHIVE_PROVENANCE.json` records the pinned source revision and original file hashes.
joint/fasterwam/train90_joint_job4247/README.md DELETED
@@ -1,9 +0,0 @@
1
- # Joint FasterWAM — job 4247
2
-
3
- Trained jointly on fruit_pick, dish_utensil, place_cube, and push_t using eight H200 GPUs, global batch 32, and canonical 90/10 episode splits. The user stopped the originally planned 50-epoch run during epoch 33.
4
-
5
- Published checkpoints: epoch 10 (29310), epoch 20 (58620), epoch 30 (87930). These retain the original 50-epoch cosine schedule; this was not a separate 30-epoch training schedule. Optimizer and resume state are excluded.
6
-
7
- Use checkpoints/weights with the bundled runtime, config.yaml, dataset_stats.json, and all four task embeddings under conditioning/. Remap training paths locally. The Wan2.2 VAE/base components in the config remain external dependencies. Source snapshots are export-time copies; provenance/implementation.json records training-time source hashes.
8
-
9
- The W&B summary was not finalized after cancellation. Training-time validation is recorded in provenance/training.log; no robot evaluation is claimed. See manifest.json for provenance and upload_manifest.json for checksums. Bundled code retains upstream licenses.
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_029310.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:714b360d124faa1eccef880ef97916250bf9b267f84276f9c5235f239fb55ab3
3
- size 10360250873
 
 
 
 
joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_058620.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:229abf320895323b87f42dae740a0b7c9cd8d03860b3a313d3d7e7c9a98fba91
3
- size 10360250873
 
 
 
 
joint/fasterwam/train90_joint_job4247/checkpoints/weights/step_087930.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:0a2e5a3592cfb6d02682183b746e6009300e080d753d22b400b08820d4e1d8c4
3
- size 10360250873
 
 
 
 
joint/fasterwam/train90_joint_job4247/conditioning/dish_utensil/tasks.jsonl DELETED
@@ -1 +0,0 @@
1
- {"task_index": 0, "task": "pick the dishes out of the rack and place them on the table, then pick and place the spoon to the gray dish and fork to the pink dish"}
 
 
joint/fasterwam/train90_joint_job4247/conditioning/fruit_pick/tasks.jsonl DELETED
@@ -1 +0,0 @@
1
- {"task_index": 0, "task": "Lift the lid, put it aside, and pick the black plum."}
 
 
joint/fasterwam/train90_joint_job4247/conditioning/index.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "dish_utensil": {
3
- "embedding": "conditioning/text_embeds_cache/6bbe3e745d0bcd06a540d3d895af431a769ae466d0cf1102bcee7979ba41e95b.t5_len128.wan22ti2v5b.pt",
4
- "encoded_prompt": "A video recorded from a robot's point of view executing the following instruction: pick the dishes out of the rack and place them on the table, then pick and place the spoon to the gray dish and fork to the pink dish",
5
- "task": "pick the dishes out of the rack and place them on the table, then pick and place the spoon to the gray dish and fork to the pink dish"
6
- },
7
- "fruit_pick": {
8
- "embedding": "conditioning/text_embeds_cache/3331746089cf26d56af486af1ef7035e746c2e823cc957e2e3b7a0c3baf347ef.t5_len128.wan22ti2v5b.pt",
9
- "encoded_prompt": "A video recorded from a robot's point of view executing the following instruction: Lift the lid, put it aside, and pick the black plum.",
10
- "task": "Lift the lid, put it aside, and pick the black plum."
11
- },
12
- "place_cube": {
13
- "embedding": "conditioning/text_embeds_cache/531e2910f4efdc285bcefbeca24a8a1ff789d6c4365dfd7c15de4eda9eb89f6b.t5_len128.wan22ti2v5b.pt",
14
- "encoded_prompt": "A video recorded from a robot's point of view executing the following instruction: place the cube in the bowl",
15
- "task": "place the cube in the bowl"
16
- },
17
- "push_t": {
18
- "embedding": "conditioning/text_embeds_cache/71cb088954da46da4e3cb6c6f73ac812690e9bccf886c7858377c61918b4e3e8.t5_len128.wan22ti2v5b.pt",
19
- "encoded_prompt": "A video recorded from a robot's point of view executing the following instruction: push the T block to the target outline",
20
- "task": "push the T block to the target outline"
21
- }
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/conditioning/place_cube/tasks.jsonl DELETED
@@ -1 +0,0 @@
1
- {"task_index": 0, "task": "place the cube in the bowl"}
 
 
joint/fasterwam/train90_joint_job4247/conditioning/prompts.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "dish_utensil": "pick the dishes out of the rack and place them on the table, then pick and place the spoon to the gray dish and fork to the pink dish",
3
- "fruit_pick": "Lift the lid, put it aside, and pick the black plum.",
4
- "place_cube": "place the cube in the bowl",
5
- "push_t": "push the T block to the target outline"
6
- }
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/conditioning/push_t/tasks.jsonl DELETED
@@ -1 +0,0 @@
1
- {"task_index": 0, "task": "push the T block to the target outline"}
 
 
joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/3331746089cf26d56af486af1ef7035e746c2e823cc957e2e3b7a0c3baf347ef.t5_len128.wan22ti2v5b.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:7ddfc1a78ca521bc8793e4a58e8c1f4fa55d83274ae0026483888d21521e5855
3
- size 1051869
 
 
 
 
joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/531e2910f4efdc285bcefbeca24a8a1ff789d6c4365dfd7c15de4eda9eb89f6b.t5_len128.wan22ti2v5b.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c676ff34af5f0fd05efa441912d0b94569a824caafa5778847b9a885a79c1e23
3
- size 1051869
 
 
 
 
joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/6bbe3e745d0bcd06a540d3d895af431a769ae466d0cf1102bcee7979ba41e95b.t5_len128.wan22ti2v5b.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:f4d0befa15744dccbba163d380fd62e8c0ca8bffc6b5271f200d527abc23d200
3
- size 1051869
 
 
 
 
joint/fasterwam/train90_joint_job4247/conditioning/text_embeds_cache/71cb088954da46da4e3cb6c6f73ac812690e9bccf886c7858377c61918b4e3e8.t5_len128.wan22ti2v5b.pt DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:85b8468de72c7e7c481fe968d6ab5164d3b3c694f64b7037ec2d05fa706c73c6
3
- size 1051869
 
 
 
 
joint/fasterwam/train90_joint_job4247/config.yaml DELETED
@@ -1,344 +0,0 @@
1
- output_dir: /shared_work/amin-a/realworld-wam/fastwam_train/runs/joint_all4_fasterwam/2026-09-17_04-15-26_job4247
2
- batch_size: 4
3
- num_workers: 4
4
- lr_scheduler_type: cosine
5
- learning_rate: 0.0001
6
- num_epochs: 50
7
- max_steps: null
8
- wsd_decay_fraction: 0.1
9
- wsd_decay_shape: linear
10
- wsd_branch_decay_steps: null
11
- log_every: 10
12
- save_every: 29310
13
- save_steps: []
14
- eval_every: 2931
15
- eval_num_inference_steps: 10
16
- gradient_accumulation_steps: 1
17
- mixed_precision: bf16
18
- distributed_timeout_minutes: 60
19
- seed: 42
20
- max_grad_norm: 1.0
21
- weight_decay: 0.01
22
- resume: null
23
- allow_unverified_full_state_resume: false
24
- freeze_video_backbone: false
25
- hf_upload:
26
- enabled: false
27
- dry_run: false
28
- fail_on_error: false
29
- script_path: huggingface/upload_to_hf.sh
30
- bucket_run_name: null
31
- wandb:
32
- enabled: true
33
- workspace: aabyaneh
34
- project: fasterwam-real
35
- name: joint_all4_fasterwam_2026-09-17_04-15-26_job4247
36
- group: fasterwam
37
- mode: online
38
- data:
39
- dataset_name: joint_all4
40
- train:
41
- _target_: fastwam.datasets.lerobot.robot_video_dataset.RobotVideoDataset
42
- dataset_dirs:
43
- - /tmp/amin_fastwam/fruit_pick_lerobot_v21
44
- - /tmp/amin_fastwam/dish_utensil_lerobot_v21
45
- - /tmp/amin_fastwam/place_cube_lerobot_v21
46
- - /tmp/amin_fastwam/push_t_lerobot_v21
47
- shape_meta:
48
- images:
49
- - key: image
50
- raw_shape:
51
- - 3
52
- - 256
53
- - 256
54
- shape:
55
- - 3
56
- - 224
57
- - 224
58
- - key: wrist_image
59
- raw_shape:
60
- - 3
61
- - 256
62
- - 256
63
- shape:
64
- - 3
65
- - 224
66
- - 224
67
- action:
68
- - key: default
69
- raw_shape: 7
70
- shape: 7
71
- state:
72
- - key: default
73
- raw_shape: 8
74
- shape: 8
75
- num_frames: 33
76
- global_sample_stride: 1
77
- action_video_freq_ratio: 4
78
- video_size:
79
- - 224
80
- - 448
81
- camera_key: null
82
- val_set_proportion: 0.0
83
- is_training_set: true
84
- episode_indices_path: /shared_work/amin-a/realworld-wam/splits/fruit_pick_lerobot_v21.json
85
- episode_indices_key: train
86
- skip_padding_as_possible: false
87
- concat_multi_camera: horizontal
88
- processor:
89
- _target_: fastwam.datasets.lerobot.processors.fastwam_processor.FastWAMProcessor
90
- shape_meta:
91
- images:
92
- - key: image
93
- raw_shape:
94
- - 3
95
- - 256
96
- - 256
97
- shape:
98
- - 3
99
- - 224
100
- - 224
101
- - key: wrist_image
102
- raw_shape:
103
- - 3
104
- - 256
105
- - 256
106
- shape:
107
- - 3
108
- - 224
109
- - 224
110
- action:
111
- - key: default
112
- raw_shape: 7
113
- shape: 7
114
- state:
115
- - key: default
116
- raw_shape: 8
117
- shape: 8
118
- num_obs_steps: 33
119
- num_output_cameras: 2
120
- action_output_dim: 7
121
- proprio_output_dim: 8
122
- delta_action_dim_mask:
123
- default:
124
- - true
125
- - true
126
- - true
127
- - true
128
- - true
129
- - true
130
- - false
131
- action_state_transforms: null
132
- use_stepwise_action_norm: false
133
- norm_default_mode: min/max
134
- norm_exception_mode: null
135
- action_state_merger:
136
- _target_: fastwam.datasets.lerobot.transforms.action_state_merger.ConcatLeftAlign
137
- train_transforms:
138
- - _target_: fastwam.datasets.lerobot.transforms.image.ToTensor
139
- - _target_: torchvision.transforms.Resize
140
- size:
141
- - 224
142
- - 224
143
- val_transforms:
144
- - _target_: fastwam.datasets.lerobot.transforms.image.ToTensor
145
- - _target_: torchvision.transforms.Resize
146
- size:
147
- - 224
148
- - 224
149
- text_embedding_cache_dir: /tmp/amin_fastwam/joint_all4_text_embeds_cache
150
- context_len: 128
151
- val:
152
- _target_: fastwam.datasets.lerobot.robot_video_dataset.RobotVideoDataset
153
- dataset_dirs:
154
- - /tmp/amin_fastwam/fruit_pick_lerobot_v21
155
- - /tmp/amin_fastwam/dish_utensil_lerobot_v21
156
- - /tmp/amin_fastwam/place_cube_lerobot_v21
157
- - /tmp/amin_fastwam/push_t_lerobot_v21
158
- shape_meta:
159
- images:
160
- - key: image
161
- raw_shape:
162
- - 3
163
- - 256
164
- - 256
165
- shape:
166
- - 3
167
- - 224
168
- - 224
169
- - key: wrist_image
170
- raw_shape:
171
- - 3
172
- - 256
173
- - 256
174
- shape:
175
- - 3
176
- - 224
177
- - 224
178
- action:
179
- - key: default
180
- raw_shape: 7
181
- shape: 7
182
- state:
183
- - key: default
184
- raw_shape: 8
185
- shape: 8
186
- num_frames: 33
187
- global_sample_stride: 1
188
- action_video_freq_ratio: 4
189
- video_size:
190
- - 224
191
- - 448
192
- camera_key: null
193
- val_set_proportion: 0.0
194
- is_training_set: false
195
- episode_indices_path: /shared_work/amin-a/realworld-wam/splits/fruit_pick_lerobot_v21.json
196
- episode_indices_key: val
197
- skip_padding_as_possible: false
198
- concat_multi_camera: horizontal
199
- processor:
200
- _target_: fastwam.datasets.lerobot.processors.fastwam_processor.FastWAMProcessor
201
- shape_meta:
202
- images:
203
- - key: image
204
- raw_shape:
205
- - 3
206
- - 256
207
- - 256
208
- shape:
209
- - 3
210
- - 224
211
- - 224
212
- - key: wrist_image
213
- raw_shape:
214
- - 3
215
- - 256
216
- - 256
217
- shape:
218
- - 3
219
- - 224
220
- - 224
221
- action:
222
- - key: default
223
- raw_shape: 7
224
- shape: 7
225
- state:
226
- - key: default
227
- raw_shape: 8
228
- shape: 8
229
- num_obs_steps: 33
230
- num_output_cameras: 2
231
- action_output_dim: 7
232
- proprio_output_dim: 8
233
- delta_action_dim_mask:
234
- default:
235
- - true
236
- - true
237
- - true
238
- - true
239
- - true
240
- - true
241
- - false
242
- action_state_transforms: null
243
- use_stepwise_action_norm: false
244
- norm_default_mode: min/max
245
- norm_exception_mode: null
246
- action_state_merger:
247
- _target_: fastwam.datasets.lerobot.transforms.action_state_merger.ConcatLeftAlign
248
- train_transforms:
249
- - _target_: fastwam.datasets.lerobot.transforms.image.ToTensor
250
- - _target_: torchvision.transforms.Resize
251
- size:
252
- - 224
253
- - 224
254
- val_transforms:
255
- - _target_: fastwam.datasets.lerobot.transforms.image.ToTensor
256
- - _target_: torchvision.transforms.Resize
257
- size:
258
- - 224
259
- - 224
260
- text_embedding_cache_dir: /tmp/amin_fastwam/joint_all4_text_embeds_cache
261
- context_len: 128
262
- model:
263
- _target_: fastwam.models.wan22.piha.create_piha
264
- model_id: Wan-AI/Wan2.2-TI2V-5B
265
- tokenizer_model_id: Wan-AI/Wan2.1-T2V-1.3B
266
- tokenizer_max_len: 128
267
- load_text_encoder: false
268
- proprio_dim: 8
269
- redirect_common_files: true
270
- mot_checkpoint_mixed_attn: false
271
- action_dit_pretrained_path: null
272
- skip_dit_load_from_pretrain: false
273
- decoupled: true
274
- kv_source_mode: new_fused_kv
275
- fusion_hidden_dim: 64
276
- fusion_use_norm: true
277
- fixed_rope: true
278
- new_fused_kv_rope_mode: aligned_3d
279
- aligned_3d_action_spatial_anchor_layout: horizontal
280
- new_fused_kv_projection_mode: simple_head_softmax
281
- new_fused_kv_pos_embed_dim: 128
282
- new_fused_kv_mlp_mixer_num_blocks: 1
283
- new_fused_kv_mlp_mixer_token_mlp_ratio: 4.0
284
- new_fused_kv_mlp_mixer_channel_mlp_ratio: 4.0
285
- new_fused_kv_head_fused_kv_sin2d_pe_mlp_hidden_dim: 512
286
- new_fused_kv_simple_head_softmax_fuse_mode: all
287
- new_fused_kv_head_fused_kv_fuse_mode: all
288
- video_dit_config:
289
- has_image_input: false
290
- patch_size:
291
- - 1
292
- - 2
293
- - 2
294
- in_dim: 48
295
- hidden_dim: 3072
296
- ffn_dim: 14336
297
- freq_dim: 256
298
- text_dim: 4096
299
- out_dim: 48
300
- num_heads: 24
301
- attn_head_dim: 128
302
- num_layers: 30
303
- eps: 1.0e-06
304
- seperated_timestep: true
305
- require_clip_embedding: false
306
- require_vae_embedding: false
307
- fuse_vae_embedding_in_latents: true
308
- use_gradient_checkpointing: false
309
- video_attention_mask_mode: first_frame_causal
310
- action_conditioned: false
311
- action_dim: 7
312
- action_group_causal_mask_mode: group_diagonal
313
- action_dit_config:
314
- action_dim: 7
315
- hidden_dim: 3072
316
- ffn_dim: 12288
317
- num_heads: 24
318
- attn_head_dim: 128
319
- num_layers: 1
320
- layer_selected: null
321
- text_dim: 4096
322
- freq_dim: 256
323
- eps: 1.0e-06
324
- use_gradient_checkpointing: false
325
- action_text_cross_attn: false
326
- video_scheduler:
327
- train_shift: 5.0
328
- infer_shift: 5.0
329
- num_train_timesteps: 1000
330
- action_scheduler:
331
- train_shift: 5.0
332
- infer_shift: 5.0
333
- num_train_timesteps: 1000
334
- loss:
335
- lambda_video: 1.0
336
- lambda_action: 1.0
337
- piha_permutation_mode: cyclic
338
- action_text_ca_dropout_p: 0.0
339
- method: fasterwam
340
- implementation:
341
- repository: https://github.com/rhyang6/fastwam
342
- branch: new_fused_kv
343
- commit: 19483b8b1b6a72e697091a07e7df7777aa09fd85
344
- reference_config: scripts_liheng/final_version/libero.yaml
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/dataset_stats.json DELETED
@@ -1,1938 +0,0 @@
1
- {
2
- "state": {
3
- "default": {
4
- "stepwise_min": [
5
- [
6
- 0.3257139027118683,
7
- -0.19808503985404968,
8
- 0.11773434281349182,
9
- 2.8797452449798584,
10
- -1.348800778388977,
11
- 0.1602623164653778,
12
- 5.3190000471659005e-05,
13
- -0.03993518278002739
14
- ]
15
- ],
16
- "stepwise_max": [
17
- [
18
- 0.6804016828536987,
19
- 0.31247836351394653,
20
- 0.45249679684638977,
21
- 3.012880325317383,
22
- -1.1482024192810059,
23
- 0.589073657989502,
24
- 0.03993518278002739,
25
- -5.3190000471659005e-05
26
- ]
27
- ],
28
- "global_min": [
29
- 0.3257139027118683,
30
- -0.19808503985404968,
31
- 0.11773434281349182,
32
- 2.8797452449798584,
33
- -1.348800778388977,
34
- 0.1602623164653778,
35
- 5.3190000471659005e-05,
36
- -0.03993518278002739
37
- ],
38
- "global_max": [
39
- 0.6804016828536987,
40
- 0.31247836351394653,
41
- 0.45249679684638977,
42
- 3.012880325317383,
43
- -1.1482024192810059,
44
- 0.589073657989502,
45
- 0.03993518278002739,
46
- -5.3190000471659005e-05
47
- ],
48
- "stepwise_q01": [
49
- [
50
- 0.326332151889801,
51
- -0.19770283997058868,
52
- 0.1177695095539093,
53
- 2.881575584411621,
54
- -1.346077799797058,
55
- 0.1623862236738205,
56
- 0.0001012185966828838,
57
- -0.039905305951833725
58
- ]
59
- ],
60
- "stepwise_q99": [
61
- [
62
- 0.6752662658691406,
63
- 0.3044644892215729,
64
- 0.450180321931839,
65
- 3.0091538429260254,
66
- -1.149796485900879,
67
- 0.5569735765457153,
68
- 0.039905305951833725,
69
- -0.00010121861123479903
70
- ]
71
- ],
72
- "global_q01": [
73
- 0.326332151889801,
74
- -0.19770283997058868,
75
- 0.1177695095539093,
76
- 2.881575584411621,
77
- -1.346077799797058,
78
- 0.1623862236738205,
79
- 0.0001012185966828838,
80
- -0.039905305951833725
81
- ],
82
- "global_q99": [
83
- 0.6752662658691406,
84
- 0.3044644892215729,
85
- 0.450180321931839,
86
- 3.0091538429260254,
87
- -1.149796485900879,
88
- 0.5569735765457153,
89
- 0.039905305951833725,
90
- -0.00010121861123479903
91
- ],
92
- "stepwise_mean": [
93
- [
94
- 0.4861578345298767,
95
- 0.02602844499051571,
96
- 0.25201132893562317,
97
- 2.952380657196045,
98
- -1.2511029243469238,
99
- 0.37992680072784424,
100
- 0.021428851410746574,
101
- -0.021428851410746574
102
- ]
103
- ],
104
- "stepwise_std": [
105
- [
106
- 0.04675422981381416,
107
- 0.1213567927479744,
108
- 0.06022870913147926,
109
- 0.017529325559735298,
110
- 0.03535432368516922,
111
- 0.09873220324516296,
112
- 0.01631387695670128,
113
- 0.01631387695670128
114
- ]
115
- ],
116
- "global_mean": [
117
- 0.4861578345298767,
118
- 0.02602844499051571,
119
- 0.25201132893562317,
120
- 2.952380657196045,
121
- -1.2511029243469238,
122
- 0.37992680072784424,
123
- 0.021428851410746574,
124
- -0.021428851410746574
125
- ],
126
- "global_std": [
127
- 0.04675422981381416,
128
- 0.1213567927479744,
129
- 0.06022870913147926,
130
- 0.017529325559735298,
131
- 0.03535432368516922,
132
- 0.09873220324516296,
133
- 0.01631387695670128,
134
- 0.01631387695670128
135
- ]
136
- }
137
- },
138
- "action": {
139
- "default": {
140
- "stepwise_min": [
141
- [
142
- -0.023571429774165154,
143
- -0.03100000135600567,
144
- -0.025142857804894447,
145
- 0.0,
146
- -0.0,
147
- -0.0,
148
- -1.0
149
- ],
150
- [
151
- -0.023571429774165154,
152
- -0.03100000135600567,
153
- -0.025142857804894447,
154
- 0.0,
155
- -0.0,
156
- -0.0,
157
- -1.0
158
- ],
159
- [
160
- -0.023571429774165154,
161
- -0.03100000135600567,
162
- -0.025142857804894447,
163
- 0.0,
164
- -0.0,
165
- -0.0,
166
- -1.0
167
- ],
168
- [
169
- -0.023571429774165154,
170
- -0.03100000135600567,
171
- -0.025142857804894447,
172
- 0.0,
173
- -0.0,
174
- -0.0,
175
- -1.0
176
- ],
177
- [
178
- -0.023571429774165154,
179
- -0.03100000135600567,
180
- -0.025142857804894447,
181
- 0.0,
182
- -0.0,
183
- -0.0,
184
- -1.0
185
- ],
186
- [
187
- -0.023571429774165154,
188
- -0.03100000135600567,
189
- -0.025142857804894447,
190
- 0.0,
191
- -0.0,
192
- -0.0,
193
- -1.0
194
- ],
195
- [
196
- -0.023571429774165154,
197
- -0.03100000135600567,
198
- -0.025142857804894447,
199
- 0.0,
200
- -0.0,
201
- -0.0,
202
- -1.0
203
- ],
204
- [
205
- -0.023571429774165154,
206
- -0.03100000135600567,
207
- -0.025142857804894447,
208
- 0.0,
209
- -0.0,
210
- -0.0,
211
- -1.0
212
- ],
213
- [
214
- -0.023571429774165154,
215
- -0.03100000135600567,
216
- -0.025142857804894447,
217
- 0.0,
218
- -0.0,
219
- -0.0,
220
- -1.0
221
- ],
222
- [
223
- -0.023571429774165154,
224
- -0.03100000135600567,
225
- -0.025142857804894447,
226
- 0.0,
227
- -0.0,
228
- -0.0,
229
- -1.0
230
- ],
231
- [
232
- -0.023571429774165154,
233
- -0.03100000135600567,
234
- -0.025142857804894447,
235
- 0.0,
236
- -0.0,
237
- -0.0,
238
- -1.0
239
- ],
240
- [
241
- -0.023571429774165154,
242
- -0.03100000135600567,
243
- -0.025142857804894447,
244
- 0.0,
245
- -0.0,
246
- -0.0,
247
- -1.0
248
- ],
249
- [
250
- -0.023571429774165154,
251
- -0.03100000135600567,
252
- -0.025142857804894447,
253
- 0.0,
254
- -0.0,
255
- -0.0,
256
- -1.0
257
- ],
258
- [
259
- -0.023571429774165154,
260
- -0.03100000135600567,
261
- -0.025142857804894447,
262
- 0.0,
263
- -0.0,
264
- -0.0,
265
- -1.0
266
- ],
267
- [
268
- -0.023571429774165154,
269
- -0.03100000135600567,
270
- -0.025142857804894447,
271
- 0.0,
272
- -0.0,
273
- -0.0,
274
- -1.0
275
- ],
276
- [
277
- -0.023571429774165154,
278
- -0.03100000135600567,
279
- -0.025142857804894447,
280
- 0.0,
281
- -0.0,
282
- -0.0,
283
- -1.0
284
- ],
285
- [
286
- -0.023571429774165154,
287
- -0.03100000135600567,
288
- -0.025142857804894447,
289
- 0.0,
290
- -0.0,
291
- -0.0,
292
- -1.0
293
- ],
294
- [
295
- -0.023571429774165154,
296
- -0.03100000135600567,
297
- -0.025142857804894447,
298
- 0.0,
299
- -0.0,
300
- -0.0,
301
- -1.0
302
- ],
303
- [
304
- -0.023571429774165154,
305
- -0.03100000135600567,
306
- -0.025142857804894447,
307
- 0.0,
308
- -0.0,
309
- -0.0,
310
- -1.0
311
- ],
312
- [
313
- -0.023571429774165154,
314
- -0.03100000135600567,
315
- -0.025142857804894447,
316
- 0.0,
317
- -0.0,
318
- -0.0,
319
- -1.0
320
- ],
321
- [
322
- -0.023571429774165154,
323
- -0.03100000135600567,
324
- -0.025142857804894447,
325
- 0.0,
326
- -0.0,
327
- -0.0,
328
- -1.0
329
- ],
330
- [
331
- -0.023571429774165154,
332
- -0.03100000135600567,
333
- -0.025142857804894447,
334
- 0.0,
335
- -0.0,
336
- -0.0,
337
- -1.0
338
- ],
339
- [
340
- -0.023571429774165154,
341
- -0.03100000135600567,
342
- -0.025142857804894447,
343
- 0.0,
344
- -0.0,
345
- -0.0,
346
- -1.0
347
- ],
348
- [
349
- -0.023571429774165154,
350
- -0.03100000135600567,
351
- -0.025142857804894447,
352
- 0.0,
353
- -0.0,
354
- -0.0,
355
- -1.0
356
- ],
357
- [
358
- -0.023571429774165154,
359
- -0.03100000135600567,
360
- -0.025142857804894447,
361
- 0.0,
362
- -0.0,
363
- -0.0,
364
- -1.0
365
- ],
366
- [
367
- -0.023571429774165154,
368
- -0.03100000135600567,
369
- -0.025142857804894447,
370
- 0.0,
371
- -0.0,
372
- -0.0,
373
- -1.0
374
- ],
375
- [
376
- -0.023571429774165154,
377
- -0.03100000135600567,
378
- -0.025142857804894447,
379
- 0.0,
380
- -0.0,
381
- -0.0,
382
- -1.0
383
- ],
384
- [
385
- -0.023571429774165154,
386
- -0.03100000135600567,
387
- -0.025142857804894447,
388
- 0.0,
389
- -0.0,
390
- -0.0,
391
- -1.0
392
- ],
393
- [
394
- -0.023571429774165154,
395
- -0.03100000135600567,
396
- -0.025142857804894447,
397
- 0.0,
398
- -0.0,
399
- -0.0,
400
- -1.0
401
- ],
402
- [
403
- -0.023571429774165154,
404
- -0.03100000135600567,
405
- -0.025142857804894447,
406
- 0.0,
407
- -0.0,
408
- -0.0,
409
- -1.0
410
- ],
411
- [
412
- -0.023571429774165154,
413
- -0.03100000135600567,
414
- -0.025142857804894447,
415
- 0.0,
416
- -0.0,
417
- -0.0,
418
- -1.0
419
- ],
420
- [
421
- -0.023571429774165154,
422
- -0.03100000135600567,
423
- -0.025142857804894447,
424
- 0.0,
425
- -0.0,
426
- -0.0,
427
- -1.0
428
- ]
429
- ],
430
- "stepwise_max": [
431
- [
432
- 0.022142857313156128,
433
- 0.021714285016059875,
434
- 0.03257143124938011,
435
- 0.0,
436
- -0.0,
437
- -0.0,
438
- 1.0
439
- ],
440
- [
441
- 0.022142857313156128,
442
- 0.021714285016059875,
443
- 0.03257143124938011,
444
- 0.0,
445
- -0.0,
446
- -0.0,
447
- 1.0
448
- ],
449
- [
450
- 0.022142857313156128,
451
- 0.021714285016059875,
452
- 0.03257143124938011,
453
- 0.0,
454
- -0.0,
455
- -0.0,
456
- 1.0
457
- ],
458
- [
459
- 0.022142857313156128,
460
- 0.021714285016059875,
461
- 0.03257143124938011,
462
- 0.0,
463
- -0.0,
464
- -0.0,
465
- 1.0
466
- ],
467
- [
468
- 0.022142857313156128,
469
- 0.021714285016059875,
470
- 0.03257143124938011,
471
- 0.0,
472
- -0.0,
473
- -0.0,
474
- 1.0
475
- ],
476
- [
477
- 0.022142857313156128,
478
- 0.021714285016059875,
479
- 0.03257143124938011,
480
- 0.0,
481
- -0.0,
482
- -0.0,
483
- 1.0
484
- ],
485
- [
486
- 0.022142857313156128,
487
- 0.021714285016059875,
488
- 0.03257143124938011,
489
- 0.0,
490
- -0.0,
491
- -0.0,
492
- 1.0
493
- ],
494
- [
495
- 0.022142857313156128,
496
- 0.021714285016059875,
497
- 0.03257143124938011,
498
- 0.0,
499
- -0.0,
500
- -0.0,
501
- 1.0
502
- ],
503
- [
504
- 0.022142857313156128,
505
- 0.021714285016059875,
506
- 0.03257143124938011,
507
- 0.0,
508
- -0.0,
509
- -0.0,
510
- 1.0
511
- ],
512
- [
513
- 0.022142857313156128,
514
- 0.021714285016059875,
515
- 0.03257143124938011,
516
- 0.0,
517
- -0.0,
518
- -0.0,
519
- 1.0
520
- ],
521
- [
522
- 0.022142857313156128,
523
- 0.021714285016059875,
524
- 0.03257143124938011,
525
- 0.0,
526
- -0.0,
527
- -0.0,
528
- 1.0
529
- ],
530
- [
531
- 0.022142857313156128,
532
- 0.021714285016059875,
533
- 0.03257143124938011,
534
- 0.0,
535
- -0.0,
536
- -0.0,
537
- 1.0
538
- ],
539
- [
540
- 0.022142857313156128,
541
- 0.021714285016059875,
542
- 0.03257143124938011,
543
- 0.0,
544
- -0.0,
545
- -0.0,
546
- 1.0
547
- ],
548
- [
549
- 0.022142857313156128,
550
- 0.021714285016059875,
551
- 0.03257143124938011,
552
- 0.0,
553
- -0.0,
554
- -0.0,
555
- 1.0
556
- ],
557
- [
558
- 0.022142857313156128,
559
- 0.01842857152223587,
560
- 0.03257143124938011,
561
- 0.0,
562
- -0.0,
563
- -0.0,
564
- 1.0
565
- ],
566
- [
567
- 0.022142857313156128,
568
- 0.01842857152223587,
569
- 0.03257143124938011,
570
- 0.0,
571
- -0.0,
572
- -0.0,
573
- 1.0
574
- ],
575
- [
576
- 0.022142857313156128,
577
- 0.01842857152223587,
578
- 0.03257143124938011,
579
- 0.0,
580
- -0.0,
581
- -0.0,
582
- 1.0
583
- ],
584
- [
585
- 0.022142857313156128,
586
- 0.01842857152223587,
587
- 0.03257143124938011,
588
- 0.0,
589
- -0.0,
590
- -0.0,
591
- 1.0
592
- ],
593
- [
594
- 0.022142857313156128,
595
- 0.01842857152223587,
596
- 0.03257143124938011,
597
- 0.0,
598
- -0.0,
599
- -0.0,
600
- 1.0
601
- ],
602
- [
603
- 0.022142857313156128,
604
- 0.01842857152223587,
605
- 0.03257143124938011,
606
- 0.0,
607
- -0.0,
608
- -0.0,
609
- 1.0
610
- ],
611
- [
612
- 0.022142857313156128,
613
- 0.01842857152223587,
614
- 0.03257143124938011,
615
- 0.0,
616
- -0.0,
617
- -0.0,
618
- 1.0
619
- ],
620
- [
621
- 0.022142857313156128,
622
- 0.017999999225139618,
623
- 0.03257143124938011,
624
- 0.0,
625
- -0.0,
626
- -0.0,
627
- 1.0
628
- ],
629
- [
630
- 0.022142857313156128,
631
- 0.017999999225139618,
632
- 0.03257143124938011,
633
- 0.0,
634
- -0.0,
635
- -0.0,
636
- 1.0
637
- ],
638
- [
639
- 0.022142857313156128,
640
- 0.017999999225139618,
641
- 0.03257143124938011,
642
- 0.0,
643
- -0.0,
644
- -0.0,
645
- 1.0
646
- ],
647
- [
648
- 0.022142857313156128,
649
- 0.017999999225139618,
650
- 0.03257143124938011,
651
- 0.0,
652
- -0.0,
653
- -0.0,
654
- 1.0
655
- ],
656
- [
657
- 0.022142857313156128,
658
- 0.017999999225139618,
659
- 0.03257143124938011,
660
- 0.0,
661
- -0.0,
662
- -0.0,
663
- 1.0
664
- ],
665
- [
666
- 0.022142857313156128,
667
- 0.017999999225139618,
668
- 0.03257143124938011,
669
- 0.0,
670
- -0.0,
671
- -0.0,
672
- 1.0
673
- ],
674
- [
675
- 0.022142857313156128,
676
- 0.017999999225139618,
677
- 0.03257143124938011,
678
- 0.0,
679
- -0.0,
680
- -0.0,
681
- 1.0
682
- ],
683
- [
684
- 0.022142857313156128,
685
- 0.017999999225139618,
686
- 0.03257143124938011,
687
- 0.0,
688
- -0.0,
689
- -0.0,
690
- 1.0
691
- ],
692
- [
693
- 0.022142857313156128,
694
- 0.017999999225139618,
695
- 0.03257143124938011,
696
- 0.0,
697
- -0.0,
698
- -0.0,
699
- 1.0
700
- ],
701
- [
702
- 0.022142857313156128,
703
- 0.017999999225139618,
704
- 0.03257143124938011,
705
- 0.0,
706
- -0.0,
707
- -0.0,
708
- 1.0
709
- ],
710
- [
711
- 0.022142857313156128,
712
- 0.017999999225139618,
713
- 0.03257143124938011,
714
- 0.0,
715
- -0.0,
716
- -0.0,
717
- 1.0
718
- ]
719
- ],
720
- "global_min": [
721
- -0.023571429774165154,
722
- -0.03100000135600567,
723
- -0.025142857804894447,
724
- 0.0,
725
- -0.0,
726
- -0.0,
727
- -1.0
728
- ],
729
- "global_max": [
730
- 0.022142857313156128,
731
- 0.021714285016059875,
732
- 0.03257143124938011,
733
- 0.0,
734
- -0.0,
735
- -0.0,
736
- 1.0
737
- ],
738
- "stepwise_q01": [
739
- [
740
- -0.017999999225139618,
741
- -0.028668571263551712,
742
- -0.020428571850061417,
743
- 0.0,
744
- -0.0,
745
- -0.0,
746
- -1.0
747
- ],
748
- [
749
- -0.017999999225139618,
750
- -0.028668571263551712,
751
- -0.020428571850061417,
752
- 0.0,
753
- -0.0,
754
- -0.0,
755
- -1.0
756
- ],
757
- [
758
- -0.017999999225139618,
759
- -0.028668571263551712,
760
- -0.020428571850061417,
761
- 0.0,
762
- -0.0,
763
- -0.0,
764
- -1.0
765
- ],
766
- [
767
- -0.017999999225139618,
768
- -0.028668571263551712,
769
- -0.020428571850061417,
770
- 0.0,
771
- -0.0,
772
- -0.0,
773
- -1.0
774
- ],
775
- [
776
- -0.017999999225139618,
777
- -0.028668571263551712,
778
- -0.020428571850061417,
779
- 0.0,
780
- -0.0,
781
- -0.0,
782
- -1.0
783
- ],
784
- [
785
- -0.017999999225139618,
786
- -0.028668571263551712,
787
- -0.020428571850061417,
788
- 0.0,
789
- 0.0,
790
- -0.0,
791
- -1.0
792
- ],
793
- [
794
- -0.017999999225139618,
795
- -0.028668571263551712,
796
- -0.020428571850061417,
797
- 0.0,
798
- 0.0,
799
- -0.0,
800
- -1.0
801
- ],
802
- [
803
- -0.017999999225139618,
804
- -0.028668571263551712,
805
- -0.020428571850061417,
806
- 0.0,
807
- -0.0,
808
- -0.0,
809
- -1.0
810
- ],
811
- [
812
- -0.017999999225139618,
813
- -0.028668571263551712,
814
- -0.020428571850061417,
815
- 0.0,
816
- -0.0,
817
- -0.0,
818
- -1.0
819
- ],
820
- [
821
- -0.017999999225139618,
822
- -0.028668571263551712,
823
- -0.020428571850061417,
824
- 0.0,
825
- -0.0,
826
- -0.0,
827
- -1.0
828
- ],
829
- [
830
- -0.017999999225139618,
831
- -0.028668571263551712,
832
- -0.020428571850061417,
833
- 0.0,
834
- -0.0,
835
- 0.0,
836
- -1.0
837
- ],
838
- [
839
- -0.017999999225139618,
840
- -0.028668571263551712,
841
- -0.020428571850061417,
842
- 0.0,
843
- -0.0,
844
- 0.0,
845
- -1.0
846
- ],
847
- [
848
- -0.017999999225139618,
849
- -0.028668571263551712,
850
- -0.020428571850061417,
851
- 0.0,
852
- -0.0,
853
- -0.0,
854
- -1.0
855
- ],
856
- [
857
- -0.017999999225139618,
858
- -0.028668571263551712,
859
- -0.020428571850061417,
860
- 0.0,
861
- -0.0,
862
- -0.0,
863
- -1.0
864
- ],
865
- [
866
- -0.017999999225139618,
867
- -0.028668571263551712,
868
- -0.020428571850061417,
869
- 0.0,
870
- -0.0,
871
- -0.0,
872
- -1.0
873
- ],
874
- [
875
- -0.017999999225139618,
876
- -0.028668571263551712,
877
- -0.020428571850061417,
878
- 0.0,
879
- -0.0,
880
- -0.0,
881
- -1.0
882
- ],
883
- [
884
- -0.017999999225139618,
885
- -0.028668571263551712,
886
- -0.020428571850061417,
887
- 0.0,
888
- -0.0,
889
- -0.0,
890
- -1.0
891
- ],
892
- [
893
- -0.017999999225139618,
894
- -0.028668571263551712,
895
- -0.020428571850061417,
896
- 0.0,
897
- -0.0,
898
- -0.0,
899
- -1.0
900
- ],
901
- [
902
- -0.017999999225139618,
903
- -0.028668571263551712,
904
- -0.020428571850061417,
905
- 0.0,
906
- -0.0,
907
- -0.0,
908
- -1.0
909
- ],
910
- [
911
- -0.017999999225139618,
912
- -0.028668571263551712,
913
- -0.020428571850061417,
914
- 0.0,
915
- -0.0,
916
- -0.0,
917
- -1.0
918
- ],
919
- [
920
- -0.017999999225139618,
921
- -0.028668571263551712,
922
- -0.020428571850061417,
923
- 0.0,
924
- -0.0,
925
- -0.0,
926
- -1.0
927
- ],
928
- [
929
- -0.017999999225139618,
930
- -0.028668571263551712,
931
- -0.020428571850061417,
932
- 0.0,
933
- -0.0,
934
- -0.0,
935
- -1.0
936
- ],
937
- [
938
- -0.017999999225139618,
939
- -0.028668571263551712,
940
- -0.020428571850061417,
941
- 0.0,
942
- -0.0,
943
- -0.0,
944
- -1.0
945
- ],
946
- [
947
- -0.017999999225139618,
948
- -0.028668571263551712,
949
- -0.020428571850061417,
950
- 0.0,
951
- -0.0,
952
- -0.0,
953
- -1.0
954
- ],
955
- [
956
- -0.017999999225139618,
957
- -0.028668571263551712,
958
- -0.020428571850061417,
959
- 0.0,
960
- -0.0,
961
- -0.0,
962
- -1.0
963
- ],
964
- [
965
- -0.017999999225139618,
966
- -0.028668571263551712,
967
- -0.020428571850061417,
968
- 0.0,
969
- -0.0,
970
- -0.0,
971
- -1.0
972
- ],
973
- [
974
- -0.017999999225139618,
975
- -0.028668571263551712,
976
- -0.020428571850061417,
977
- 0.0,
978
- -0.0,
979
- -0.0,
980
- -1.0
981
- ],
982
- [
983
- -0.017999999225139618,
984
- -0.028668571263551712,
985
- -0.020428571850061417,
986
- 0.0,
987
- -0.0,
988
- -0.0,
989
- -1.0
990
- ],
991
- [
992
- -0.017999999225139618,
993
- -0.028668571263551712,
994
- -0.020428571850061417,
995
- 0.0,
996
- -0.0,
997
- -0.0,
998
- -1.0
999
- ],
1000
- [
1001
- -0.017999999225139618,
1002
- -0.028668571263551712,
1003
- -0.020428571850061417,
1004
- 0.0,
1005
- -0.0,
1006
- -0.0,
1007
- -1.0
1008
- ],
1009
- [
1010
- -0.017999999225139618,
1011
- -0.028668571263551712,
1012
- -0.020428571850061417,
1013
- 0.0,
1014
- -0.0,
1015
- -0.0,
1016
- -1.0
1017
- ],
1018
- [
1019
- -0.017999999225139618,
1020
- -0.028668571263551712,
1021
- -0.020428571850061417,
1022
- 0.0,
1023
- -0.0,
1024
- -0.0,
1025
- -1.0
1026
- ]
1027
- ],
1028
- "stepwise_q99": [
1029
- [
1030
- 0.01874857023358345,
1031
- 0.017999999225139618,
1032
- 0.032182857394218445,
1033
- 0.0,
1034
- 0.0,
1035
- 0.0,
1036
- 1.0
1037
- ],
1038
- [
1039
- 0.01874857023358345,
1040
- 0.017999999225139618,
1041
- 0.032182857394218445,
1042
- 0.0,
1043
- 0.0,
1044
- 0.0,
1045
- 1.0
1046
- ],
1047
- [
1048
- 0.01874857023358345,
1049
- 0.017999999225139618,
1050
- 0.032182857394218445,
1051
- 0.0,
1052
- 0.0,
1053
- 0.0,
1054
- 1.0
1055
- ],
1056
- [
1057
- 0.01874857023358345,
1058
- 0.017999999225139618,
1059
- 0.032182857394218445,
1060
- 0.0,
1061
- 0.0,
1062
- 0.0,
1063
- 1.0
1064
- ],
1065
- [
1066
- 0.01874857023358345,
1067
- 0.017999999225139618,
1068
- 0.032182857394218445,
1069
- 0.0,
1070
- 0.0,
1071
- 0.0,
1072
- 1.0
1073
- ],
1074
- [
1075
- 0.01874857023358345,
1076
- 0.017999999225139618,
1077
- 0.032182857394218445,
1078
- 0.0,
1079
- 0.0,
1080
- 0.0,
1081
- 1.0
1082
- ],
1083
- [
1084
- 0.01874857023358345,
1085
- 0.017999999225139618,
1086
- 0.032182857394218445,
1087
- 0.0,
1088
- 0.0,
1089
- 0.0,
1090
- 1.0
1091
- ],
1092
- [
1093
- 0.01874857023358345,
1094
- 0.017999999225139618,
1095
- 0.032182857394218445,
1096
- 0.0,
1097
- 0.0,
1098
- 0.0,
1099
- 1.0
1100
- ],
1101
- [
1102
- 0.01874857023358345,
1103
- 0.017999999225139618,
1104
- 0.032182857394218445,
1105
- 0.0,
1106
- 0.0,
1107
- 0.0,
1108
- 1.0
1109
- ],
1110
- [
1111
- 0.01874857023358345,
1112
- 0.017999999225139618,
1113
- 0.032182857394218445,
1114
- 0.0,
1115
- 0.0,
1116
- 0.0,
1117
- 1.0
1118
- ],
1119
- [
1120
- 0.01874857023358345,
1121
- 0.017999999225139618,
1122
- 0.032182857394218445,
1123
- 0.0,
1124
- 0.0,
1125
- 0.0,
1126
- 1.0
1127
- ],
1128
- [
1129
- 0.01874857023358345,
1130
- 0.017999999225139618,
1131
- 0.032182857394218445,
1132
- 0.0,
1133
- 0.0,
1134
- 0.0,
1135
- 1.0
1136
- ],
1137
- [
1138
- 0.01811428926885128,
1139
- 0.017999999225139618,
1140
- 0.032182857394218445,
1141
- 0.0,
1142
- 0.0,
1143
- 0.0,
1144
- 1.0
1145
- ],
1146
- [
1147
- 0.01811428926885128,
1148
- 0.017999999225139618,
1149
- 0.032182857394218445,
1150
- 0.0,
1151
- 0.0,
1152
- 0.0,
1153
- 1.0
1154
- ],
1155
- [
1156
- 0.01811428926885128,
1157
- 0.017999999225139618,
1158
- 0.032182857394218445,
1159
- 0.0,
1160
- 0.0,
1161
- 0.0,
1162
- 1.0
1163
- ],
1164
- [
1165
- 0.01811428926885128,
1166
- 0.017999999225139618,
1167
- 0.032182857394218445,
1168
- 0.0,
1169
- 0.0,
1170
- 0.0,
1171
- 1.0
1172
- ],
1173
- [
1174
- 0.01811428926885128,
1175
- 0.017999999225139618,
1176
- 0.032182857394218445,
1177
- 0.0,
1178
- 0.0,
1179
- 0.0,
1180
- 1.0
1181
- ],
1182
- [
1183
- 0.01811428926885128,
1184
- 0.017999999225139618,
1185
- 0.032182857394218445,
1186
- 0.0,
1187
- 0.0,
1188
- 0.0,
1189
- 1.0
1190
- ],
1191
- [
1192
- 0.01811428926885128,
1193
- 0.017999999225139618,
1194
- 0.032182857394218445,
1195
- 0.0,
1196
- 0.0,
1197
- 0.0,
1198
- 1.0
1199
- ],
1200
- [
1201
- 0.01811428926885128,
1202
- 0.017999999225139618,
1203
- 0.032182857394218445,
1204
- 0.0,
1205
- 0.0,
1206
- 0.0,
1207
- 1.0
1208
- ],
1209
- [
1210
- 0.01811428926885128,
1211
- 0.017999999225139618,
1212
- 0.032182857394218445,
1213
- 0.0,
1214
- 0.0,
1215
- 0.0,
1216
- 1.0
1217
- ],
1218
- [
1219
- 0.01811428926885128,
1220
- 0.017999999225139618,
1221
- 0.032182857394218445,
1222
- 0.0,
1223
- 0.0,
1224
- 0.0,
1225
- 1.0
1226
- ],
1227
- [
1228
- 0.01811428926885128,
1229
- 0.017999999225139618,
1230
- 0.032182857394218445,
1231
- 0.0,
1232
- 0.0,
1233
- 0.0,
1234
- 1.0
1235
- ],
1236
- [
1237
- 0.01811428926885128,
1238
- 0.017999999225139618,
1239
- 0.032182857394218445,
1240
- 0.0,
1241
- 0.0,
1242
- 0.0,
1243
- 1.0
1244
- ],
1245
- [
1246
- 0.01811428926885128,
1247
- 0.017999999225139618,
1248
- 0.032182857394218445,
1249
- 0.0,
1250
- 0.0,
1251
- 0.0,
1252
- 1.0
1253
- ],
1254
- [
1255
- 0.01811428926885128,
1256
- 0.017999999225139618,
1257
- 0.032182857394218445,
1258
- 0.0,
1259
- 0.0,
1260
- 0.0,
1261
- 1.0
1262
- ],
1263
- [
1264
- 0.01811428926885128,
1265
- 0.017999999225139618,
1266
- 0.032182857394218445,
1267
- 0.0,
1268
- 0.0,
1269
- 0.0,
1270
- 1.0
1271
- ],
1272
- [
1273
- 0.01811428926885128,
1274
- 0.017999999225139618,
1275
- 0.032182857394218445,
1276
- 0.0,
1277
- 0.0,
1278
- 0.0,
1279
- 1.0
1280
- ],
1281
- [
1282
- 0.01811428926885128,
1283
- 0.017999999225139618,
1284
- 0.032182857394218445,
1285
- 0.0,
1286
- 0.0,
1287
- 0.0,
1288
- 1.0
1289
- ],
1290
- [
1291
- 0.01811428926885128,
1292
- 0.017999999225139618,
1293
- 0.032182857394218445,
1294
- 0.0,
1295
- 0.0,
1296
- 0.0,
1297
- 1.0
1298
- ],
1299
- [
1300
- 0.01811428926885128,
1301
- 0.017999999225139618,
1302
- 0.032182857394218445,
1303
- 0.0,
1304
- 0.0,
1305
- 0.0,
1306
- 1.0
1307
- ],
1308
- [
1309
- 0.01811428926885128,
1310
- 0.017999999225139618,
1311
- 0.032182857394218445,
1312
- 0.0,
1313
- 0.0,
1314
- 0.0,
1315
- 1.0
1316
- ]
1317
- ],
1318
- "global_q01": [
1319
- -0.017999999225139618,
1320
- -0.028668571263551712,
1321
- -0.020428571850061417,
1322
- 0.0,
1323
- -0.0,
1324
- -0.0,
1325
- -1.0
1326
- ],
1327
- "global_q99": [
1328
- 0.01874857023358345,
1329
- 0.017999999225139618,
1330
- 0.032182857394218445,
1331
- 0.0,
1332
- 0.0,
1333
- 0.0,
1334
- 1.0
1335
- ],
1336
- "stepwise_mean": [
1337
- [
1338
- 0.00010599139204714447,
1339
- -0.0003520426689647138,
1340
- 0.00014291385014075786,
1341
- 0.0,
1342
- 0.0,
1343
- 0.0,
1344
- 0.2183247208595276
1345
- ],
1346
- [
1347
- 0.00010662782733561471,
1348
- -0.00035185267915949225,
1349
- 0.00014307233504951,
1350
- 0.0,
1351
- 0.0,
1352
- 0.0,
1353
- 0.22085411846637726
1354
- ],
1355
- [
1356
- 0.00010768464562715963,
1357
- -0.0003513394040055573,
1358
- 0.0001433969009667635,
1359
- 0.0,
1360
- 0.0,
1361
- 0.0,
1362
- 0.22338351607322693
1363
- ],
1364
- [
1365
- 0.0001071520455298014,
1366
- -0.0003502023755572736,
1367
- 0.0001435836311429739,
1368
- 0.0,
1369
- 0.0,
1370
- 0.0,
1371
- 0.22591295838356018
1372
- ],
1373
- [
1374
- 0.00010332007514080033,
1375
- -0.00034813376259990036,
1376
- 0.00014470236783381552,
1377
- 0.0,
1378
- 0.0,
1379
- 0.0,
1380
- 0.22844235599040985
1381
- ],
1382
- [
1383
- 9.708326979307458e-05,
1384
- -0.0003456552803982049,
1385
- 0.0001498961355537176,
1386
- 0.0,
1387
- 0.0,
1388
- 0.0,
1389
- 0.23097175359725952
1390
- ],
1391
- [
1392
- 8.982994040707126e-05,
1393
- -0.00034212053287774324,
1394
- 0.00015701490337960422,
1395
- 0.0,
1396
- 0.0,
1397
- 0.0,
1398
- 0.23350116610527039
1399
- ],
1400
- [
1401
- 8.295998850371689e-05,
1402
- -0.00033426276058889925,
1403
- 0.00016626888827886432,
1404
- 0.0,
1405
- 0.0,
1406
- 0.0,
1407
- 0.23603059351444244
1408
- ],
1409
- [
1410
- 7.580933743156493e-05,
1411
- -0.00032417502370662987,
1412
- 0.00018232851289212704,
1413
- 0.0,
1414
- 0.0,
1415
- 0.0,
1416
- 0.23855999112129211
1417
- ],
1418
- [
1419
- 6.725409912178293e-05,
1420
- -0.00031425998895429075,
1421
- 0.00019788365170825273,
1422
- 0.0,
1423
- 0.0,
1424
- 0.0,
1425
- 0.24108940362930298
1426
- ],
1427
- [
1428
- 5.8587662351783365e-05,
1429
- -0.0003024389152415097,
1430
- 0.0002139062125934288,
1431
- 0.0,
1432
- 0.0,
1433
- 0.0,
1434
- 0.24361883103847504
1435
- ],
1436
- [
1437
- 4.8913931095739827e-05,
1438
- -0.00029040378285571933,
1439
- 0.00023386623070109636,
1440
- 0.0,
1441
- 0.0,
1442
- 0.0,
1443
- 0.2461482435464859
1444
- ],
1445
- [
1446
- 3.9833157643442973e-05,
1447
- -0.0002796816115733236,
1448
- 0.0002501923299860209,
1449
- 0.0,
1450
- 0.0,
1451
- 0.0,
1452
- 0.24867764115333557
1453
- ],
1454
- [
1455
- 3.252932947361842e-05,
1456
- -0.0002679046301636845,
1457
- 0.00026465911651030183,
1458
- 0.0,
1459
- 0.0,
1460
- 0.0,
1461
- 0.25120702385902405
1462
- ],
1463
- [
1464
- 2.6598059776006266e-05,
1465
- -0.0002585168113000691,
1466
- 0.00028005309286527336,
1467
- 0.0,
1468
- 0.0,
1469
- 0.0,
1470
- 0.2537364661693573
1471
- ],
1472
- [
1473
- 1.9946868633269332e-05,
1474
- -0.00025132004520855844,
1475
- 0.00029630353674292564,
1476
- 0.0,
1477
- 0.0,
1478
- 0.0,
1479
- 0.25626587867736816
1480
- ],
1481
- [
1482
- 1.303988574363757e-05,
1483
- -0.00024510364164598286,
1484
- 0.0003103403723798692,
1485
- 0.0,
1486
- 0.0,
1487
- 0.0,
1488
- 0.25880569219589233
1489
- ],
1490
- [
1491
- 5.248548404779285e-06,
1492
- -0.00024028636107686907,
1493
- 0.00032640460995025933,
1494
- 0.0,
1495
- 0.0,
1496
- 0.0,
1497
- 0.2613455355167389
1498
- ],
1499
- [
1500
- -2.630091103128507e-06,
1501
- -0.00023644340399187058,
1502
- 0.0003419383428990841,
1503
- 0.0,
1504
- 0.0,
1505
- 0.0,
1506
- 0.2638853192329407
1507
- ],
1508
- [
1509
- -1.1612431080720853e-05,
1510
- -0.00023524471907876432,
1511
- 0.00035437088808976114,
1512
- 0.0,
1513
- 0.0,
1514
- 0.0,
1515
- 0.2664651870727539
1516
- ],
1517
- [
1518
- -2.4109574951580726e-05,
1519
- -0.00023595777747686952,
1520
- 0.0003668583813123405,
1521
- 0.0,
1522
- 0.0,
1523
- 0.0,
1524
- 0.26908746361732483
1525
- ],
1526
- [
1527
- -3.409310011193156e-05,
1528
- -0.00023765936202835292,
1529
- 0.0003780024708248675,
1530
- 0.0,
1531
- 0.0,
1532
- 0.0,
1533
- 0.2717488706111908
1534
- ],
1535
- [
1536
- -4.41225383838173e-05,
1537
- -0.00024006325111258775,
1538
- 0.0003892174281645566,
1539
- 0.0,
1540
- 0.0,
1541
- 0.0,
1542
- 0.2744331955909729
1543
- ],
1544
- [
1545
- -5.318918192642741e-05,
1546
- -0.00024481830769218504,
1547
- 0.0003998273750767112,
1548
- 0.0,
1549
- 0.0,
1550
- 0.0,
1551
- 0.27719661593437195
1552
- ],
1553
- [
1554
- -6.362147541949525e-05,
1555
- -0.00025020778411999345,
1556
- 0.0004108487628400326,
1557
- 0.0,
1558
- 0.0,
1559
- 0.0,
1560
- 0.27999645471572876
1561
- ],
1562
- [
1563
- -7.399872993119061e-05,
1564
- -0.0002569758507888764,
1565
- 0.00042012453195638955,
1566
- 0.0,
1567
- 0.0,
1568
- 0.0,
1569
- 0.28288376331329346
1570
- ],
1571
- [
1572
- -8.447915024589747e-05,
1573
- -0.00026511374744586647,
1574
- 0.00042987134656868875,
1575
- 0.0,
1576
- 0.0,
1577
- 0.0,
1578
- 0.28581443428993225
1579
- ],
1580
- [
1581
- -9.251773008145392e-05,
1582
- -0.00027324925758875906,
1583
- 0.00043566094245761633,
1584
- 0.0,
1585
- 0.0,
1586
- 0.0,
1587
- 0.28880083560943604
1588
- ],
1589
- [
1590
- -0.0001004255173029378,
1591
- -0.00028087335522286594,
1592
- 0.00044270692160353065,
1593
- 0.0,
1594
- 0.0,
1595
- 0.0,
1596
- 0.2919144332408905
1597
- ],
1598
- [
1599
- -0.00010663300781743601,
1600
- -0.0002883909037336707,
1601
- 0.0004484386881813407,
1602
- 0.0,
1603
- 0.0,
1604
- 0.0,
1605
- 0.29495498538017273
1606
- ],
1607
- [
1608
- -0.00011239774903515354,
1609
- -0.0002963065926451236,
1610
- 0.000453940243460238,
1611
- 0.0,
1612
- 0.0,
1613
- 0.0,
1614
- 0.2980213463306427
1615
- ],
1616
- [
1617
- -0.00011664993508020416,
1618
- -0.0003038592985831201,
1619
- 0.00045629238593392074,
1620
- 0.0,
1621
- 0.0,
1622
- 0.0,
1623
- 0.3011433184146881
1624
- ]
1625
- ],
1626
- "stepwise_std": [
1627
- [
1628
- 0.003859531134366989,
1629
- 0.005849294830113649,
1630
- 0.005096182227134705,
1631
- 0.0,
1632
- 0.0,
1633
- 0.0,
1634
- 0.6930140852928162
1635
- ],
1636
- [
1637
- 0.003858074778690934,
1638
- 0.005849248263984919,
1639
- 0.005096119828522205,
1640
- 0.0,
1641
- 0.0,
1642
- 0.0,
1643
- 0.692183256149292
1644
- ],
1645
- [
1646
- 0.00385070638731122,
1647
- 0.005849029868841171,
1648
- 0.005095901433378458,
1649
- 0.0,
1650
- 0.0,
1651
- 0.0,
1652
- 0.6913419961929321
1653
- ],
1654
- [
1655
- 0.003835494164377451,
1656
- 0.005848364904522896,
1657
- 0.0050956266932189465,
1658
- 0.0,
1659
- 0.0,
1660
- 0.0,
1661
- 0.6904901266098022
1662
- ],
1663
- [
1664
- 0.0038173042703419924,
1665
- 0.0058466228656470776,
1666
- 0.005094226449728012,
1667
- 0.0,
1668
- 0.0,
1669
- 0.0,
1670
- 0.6896276473999023
1671
- ],
1672
- [
1673
- 0.003798459656536579,
1674
- 0.005840709898620844,
1675
- 0.005085964687168598,
1676
- 0.0,
1677
- 0.0,
1678
- 0.0,
1679
- 0.6887545585632324
1680
- ],
1681
- [
1682
- 0.003779440186917782,
1683
- 0.0058306735008955,
1684
- 0.0050780801102519035,
1685
- 0.0,
1686
- 0.0,
1687
- 0.0,
1688
- 0.6878708600997925
1689
- ],
1690
- [
1691
- 0.0037602565716952085,
1692
- 0.005817433353513479,
1693
- 0.005068507045507431,
1694
- 0.0,
1695
- 0.0,
1696
- 0.0,
1697
- 0.686976432800293
1698
- ],
1699
- [
1700
- 0.0037413816899061203,
1701
- 0.005803139880299568,
1702
- 0.00504791084676981,
1703
- 0.0,
1704
- 0.0,
1705
- 0.0,
1706
- 0.6860713362693787
1707
- ],
1708
- [
1709
- 0.003722784109413624,
1710
- 0.005785479210317135,
1711
- 0.005028924439102411,
1712
- 0.0,
1713
- 0.0,
1714
- 0.0,
1715
- 0.6851553320884705
1716
- ],
1717
- [
1718
- 0.003704249393194914,
1719
- 0.005768594332039356,
1720
- 0.005011409055441618,
1721
- 0.0,
1722
- 0.0,
1723
- 0.0,
1724
- 0.6842285990715027
1725
- ],
1726
- [
1727
- 0.003684787079691887,
1728
- 0.005749644245952368,
1729
- 0.004986910615116358,
1730
- 0.0,
1731
- 0.0,
1732
- 0.0,
1733
- 0.683290958404541
1734
- ],
1735
- [
1736
- 0.003669159021228552,
1737
- 0.005727663636207581,
1738
- 0.004969390109181404,
1739
- 0.0,
1740
- 0.0,
1741
- 0.0,
1742
- 0.6823424696922302
1743
- ],
1744
- [
1745
- 0.0036586951464414597,
1746
- 0.005707941949367523,
1747
- 0.00495464401319623,
1748
- 0.0,
1749
- 0.0,
1750
- 0.0,
1751
- 0.681382954120636
1752
- ],
1753
- [
1754
- 0.0036503244191408157,
1755
- 0.005686633288860321,
1756
- 0.0049393498338758945,
1757
- 0.0,
1758
- 0.0,
1759
- 0.0,
1760
- 0.6804124116897583
1761
- ],
1762
- [
1763
- 0.0036399371456354856,
1764
- 0.005666919052600861,
1765
- 0.004921806510537863,
1766
- 0.0,
1767
- 0.0,
1768
- 0.0,
1769
- 0.6794309020042419
1770
- ],
1771
- [
1772
- 0.0036300860811024904,
1773
- 0.005649130791425705,
1774
- 0.004907391965389252,
1775
- 0.0,
1776
- 0.0,
1777
- 0.0,
1778
- 0.6784419417381287
1779
- ],
1780
- [
1781
- 0.003619162132963538,
1782
- 0.005632068496197462,
1783
- 0.004889165051281452,
1784
- 0.0,
1785
- 0.0,
1786
- 0.0,
1787
- 0.6774417757987976
1788
- ],
1789
- [
1790
- 0.0036096996627748013,
1791
- 0.005616484675556421,
1792
- 0.00487248320132494,
1793
- 0.0,
1794
- 0.0,
1795
- 0.0,
1796
- 0.6764302849769592
1797
- ],
1798
- [
1799
- 0.0035996257793158293,
1800
- 0.005601839628070593,
1801
- 0.004860007204115391,
1802
- 0.0,
1803
- 0.0,
1804
- 0.0,
1805
- 0.675421416759491
1806
- ],
1807
- [
1808
- 0.003584834048524499,
1809
- 0.0055876681581139565,
1810
- 0.004846835974603891,
1811
- 0.0,
1812
- 0.0,
1813
- 0.0,
1814
- 0.6744153499603271
1815
- ],
1816
- [
1817
- 0.0035738751757889986,
1818
- 0.00557406060397625,
1819
- 0.004837150685489178,
1820
- 0.0,
1821
- 0.0,
1822
- 0.0,
1823
- 0.6734107136726379
1824
- ],
1825
- [
1826
- 0.0035626839380711317,
1827
- 0.005561233498156071,
1828
- 0.00482589565217495,
1829
- 0.0,
1830
- 0.0,
1831
- 0.0,
1832
- 0.6724013686180115
1833
- ],
1834
- [
1835
- 0.0035529215820133686,
1836
- 0.005548112094402313,
1837
- 0.004816059954464436,
1838
- 0.0,
1839
- 0.0,
1840
- 0.0,
1841
- 0.6714058518409729
1842
- ],
1843
- [
1844
- 0.0035408444236963987,
1845
- 0.005534714087843895,
1846
- 0.0048048244789242744,
1847
- 0.0,
1848
- 0.0,
1849
- 0.0,
1850
- 0.670409083366394
1851
- ],
1852
- [
1853
- 0.0035294988192617893,
1854
- 0.005521110724657774,
1855
- 0.004796639084815979,
1856
- 0.0,
1857
- 0.0,
1858
- 0.0,
1859
- 0.669427216053009
1860
- ],
1861
- [
1862
- 0.003518011188134551,
1863
- 0.0055075655691325665,
1864
- 0.0047874050214886665,
1865
- 0.0,
1866
- 0.0,
1867
- 0.0,
1868
- 0.6684450507164001
1869
- ],
1870
- [
1871
- 0.003509625094011426,
1872
- 0.005495153367519379,
1873
- 0.004781167954206467,
1874
- 0.0,
1875
- 0.0,
1876
- 0.0,
1877
- 0.6674659252166748
1878
- ],
1879
- [
1880
- 0.003499576123431325,
1881
- 0.005482127424329519,
1882
- 0.0047730510123074055,
1883
- 0.0,
1884
- 0.0,
1885
- 0.0,
1886
- 0.6665111780166626
1887
- ],
1888
- [
1889
- 0.0034931653644889593,
1890
- 0.0054698726162314415,
1891
- 0.004765379708260298,
1892
- 0.0,
1893
- 0.0,
1894
- 0.0,
1895
- 0.665599524974823
1896
- ],
1897
- [
1898
- 0.0034858165308833122,
1899
- 0.005457438062876463,
1900
- 0.0047576818615198135,
1901
- 0.0,
1902
- 0.0,
1903
- 0.0,
1904
- 0.6647313833236694
1905
- ],
1906
- [
1907
- 0.0034800912253558636,
1908
- 0.005446141120046377,
1909
- 0.004752232693135738,
1910
- 0.0,
1911
- 0.0,
1912
- 0.0,
1913
- 0.6638643145561218
1914
- ]
1915
- ],
1916
- "global_mean": [
1917
- 8.372806405532174e-06,
1918
- -0.00028733949875459075,
1919
- 0.0002960901183541864,
1920
- 0.0,
1921
- 0.0,
1922
- 0.0,
1923
- 0.2582257091999054
1924
- ],
1925
- "global_std": [
1926
- 0.0036533514503389597,
1927
- 0.005667832214385271,
1928
- 0.004929195158183575,
1929
- 0.0,
1930
- 0.0,
1931
- 0.0,
1932
- 0.6791926622390747
1933
- ]
1934
- }
1935
- },
1936
- "num_episodes": 360,
1937
- "num_transition": 93771
1938
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/manifest.json DELETED
@@ -1,45 +0,0 @@
1
- {
2
- "base_model": "Wan-AI/Wan2.2-TI2V-5B",
3
- "checkpoint_contents": "three inference checkpoints and matching metadata",
4
- "checkpoint_epochs": [
5
- 10,
6
- 20,
7
- 30
8
- ],
9
- "checkpoint_selection": "epochs 10, 20, and 30; stopped early at user request",
10
- "completed_updates": 94160,
11
- "ended_utc": "2026-09-17T15:38:29Z",
12
- "epochs": 50,
13
- "final_checkpoint": "checkpoints/weights/step_087930.pt",
14
- "final_checkpoint_step": 87930,
15
- "global_batch_size": 32,
16
- "includes_training_state": false,
17
- "integrity_inventory": "upload_manifest.json",
18
- "last_logged_training_step": 94160,
19
- "model": "fasterwam",
20
- "native_checkpoint_steps": [
21
- 29310,
22
- 58620,
23
- 87930
24
- ],
25
- "planned_epochs": 50,
26
- "prompts": {
27
- "dish_utensil": "pick the dishes out of the rack and place them on the table, then pick and place the spoon to the gray dish and fork to the pink dish",
28
- "fruit_pick": "Lift the lid, put it aside, and pick the black plum.",
29
- "place_cube": "place the cube in the bowl",
30
- "push_t": "push the T block to the target outline"
31
- },
32
- "retained_through_epoch": 30,
33
- "robot_evaluation": "not performed for these runs",
34
- "run_id": "train90_joint_job4247",
35
- "schema_version": 1,
36
- "slurm_job": 4247,
37
- "source_dir": "fastwam_train/runs/joint_all4_fasterwam/2026-09-17_04-15-26_job4247",
38
- "stop_reason": "User requested cancellation and publication of epochs 10, 20, 30 only",
39
- "task": "joint",
40
- "training_gpus": 8,
41
- "training_mode": "joint",
42
- "training_status": "cancelled_by_user",
43
- "validation_note": "No finalized W&B summary was available after cancellation. See training log for validation history; no robot evaluation claimed.",
44
- "wandb_run": "https://wandb.ai/aabyaneh/fasterwam-real/runs/pp7o49m6"
45
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/provenance/code.json DELETED
@@ -1,22 +0,0 @@
1
- {
2
- "args": [
3
- "--config-dir",
4
- "/shared_work/amin-a/realworld-wam/fastwam_train/configs",
5
- "task=fasterwam_joint_split90",
6
- "output_dir=/shared_work/amin-a/realworld-wam/fastwam_train/runs/joint_all4_fasterwam/2026-09-17_04-15-26_job4247",
7
- "wandb.name=joint_all4_fasterwam_2026-09-17_04-15-26_job4247",
8
- "wandb.workspace=aabyaneh",
9
- "wandb.project=fasterwam-real",
10
- "wandb.mode=online",
11
- "hydra/job_logging=stdout",
12
- "data.dataset_name=joint_all4",
13
- "batch_size=4",
14
- "gradient_accumulation_steps=1",
15
- "num_epochs=50",
16
- "save_every=29310",
17
- "eval_every=2931"
18
- ],
19
- "code_snapshot_note": "Bundled source files are export-time snapshots; saved run config and checkpoint are authoritative.",
20
- "python": "CPython 3.10.20",
21
- "startedAt": "2026-09-17T04:17:33.873343Z"
22
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/provenance/implementation.json DELETED
@@ -1,364 +0,0 @@
1
- {
2
- "repository": "https://github.com/rhyang6/fastwam",
3
- "branch": "new_fused_kv",
4
- "commit": "19483b8b1b6a72e697091a07e7df7777aa09fd85",
5
- "reference_config": "scripts_liheng/final_version/libero.yaml",
6
- "method": "fasterwam",
7
- "files": {
8
- "LICENSE": "8379dddfbbb1154ad9cc57f008f1f8d4a8d26c3de99bd6b36aa4d8067888aa92",
9
- "configs/calibration/libero_camera_calibration_v1.json": "dfd4faa7ed6ccbf27b3ef69a682377b9e839a75e757caaa16f41d86c3faa685e",
10
- "configs/calibration/libero_plus_anchors_v1.json": "67abf6f8c5b6067d3cc93a806299947de47f8b8393d895757dbf2880757349c1",
11
- "configs/data/libero_2cam.yaml": "67a6b911352cef43bb2e5da4a60083a6c94af0143bfbf811e9d612b72bfa5960",
12
- "configs/data/libero_2cam_aligned_3dpft.yaml": "3f8f0448440db7b0cdb5c5e1cc408e3b19dc8b4b564d4e196e781d6327152ab2",
13
- "configs/data/roboreal.yaml": "ed35d3f0cf25a3939a703fedcc547015fcb5415d683df57ae39243447efbb944",
14
- "configs/data/robotwin.yaml": "f1ba509fac21fc59d5a210d6f68bcf94640729ce00dce695b7ca6df24c7b5aa7",
15
- "configs/data/robotwin_clean_random.yaml": "53ff96c11d41c47ac137109d53c7f5ae6f73abc9042607dcccbd3a7d8e0a1387",
16
- "configs/data/robotwin_v30.yaml": "72b59238d3699d41eda61af3871419505ac6b7c0adda58af66c33bdfa403db8f",
17
- "configs/data/robotwin_v30_aligned_3dpft.yaml": "8646fba876beb63b9ac79cf54628016b2fd60596eee77a73d9fc808b1ed46174",
18
- "configs/data/robotwin_v30_delta.yaml": "4a4fa6d059c5f5c90a1bb464b4d76bad06e67a1f0c934bb20e5b001fe8621c92",
19
- "configs/model/faster_wam.yaml": "4376e8b8e5fde662d3722404819f4bd5247236e8dae93857871f09b2d587d059",
20
- "configs/model/fasterwam_decoupled_action_rope.yaml": "35b34784bf44ca502b691c294e6835ac5a4b171bcaeb7b205b845b456af0064c",
21
- "configs/model/fasterwam_decoupled_aligned_3dpft.yaml": "e02fcba5a4edfbbe3d369b046fd956c6329e5efdf7df48dd19697580321c0de0",
22
- "configs/model/fasterwam_decoupled_aligned_3dpft_weakpersp.yaml": "38c0a978291c47e65e135f8a98a7080919a83e7ca276b5eb47329df9f2599596",
23
- "configs/model/fasterwam_decoupled_aligned_3dpft_x0.yaml": "c6aa72fcf8ffae0390216f2cc7614c54f94b1ba1d2796e1cc8ecf05b0431dcf7",
24
- "configs/model/fasterwam_decoupled_aligned_3dpft_x0_proxpinhole.yaml": "8e71445d25266dba71a923dfb436bebd0e9d751d5cc7bad20a1eab1eff575e94",
25
- "configs/model/fasterwam_decoupled_aligned_3dpft_x0_weakpersp.yaml": "fe99f4df2febd7251b70179367135969215ceb9fb8b71ebcc18842ce453c130a",
26
- "configs/model/fasterwam_decoupled_attn_offset_head_anchor.yaml": "6806230f3272c222c47d66e3e7ab06b70d31038fad945649254059e7be0b1d11",
27
- "configs/model/fasterwam_decoupled_control_rope.yaml": "46af59125fbd223540c3180553681094e1bdf12e384c703bb11d352793ed3daf",
28
- "configs/model/fasterwam_decoupled_controlnet_pp.yaml": "bd56cb94c5252b1eadf580b2b8c323f5b0f284120a4e55a5f7cc8caf03fb4e24",
29
- "configs/model/fasterwam_decoupled_ha_content_pp.yaml": "88c894f3d7092b09a73a72767ec230cbfdee48a23a443446afc47dd7b51f6cd4",
30
- "configs/model/fasterwam_decoupled_ha_origami.yaml": "723f97e6bc43e3110564af570f05cdb6b4d55d7c817c3f0c81b8aaa4517c686b",
31
- "configs/model/fasterwam_decoupled_head_anchor_h3072.yaml": "802896b938dfec82fbb1e1c4c79cde8d48cce474c8017095fe0faefd657f51cf",
32
- "configs/model/fasterwam_decoupled_iha.yaml": "478b106254f15562e72d4e669d7171a397ea968018a0766183d37eb24d92e2af",
33
- "configs/model/fasterwam_decoupled_learnable_head_anchor.yaml": "820e603a7a303950f5bb1db63d1f33cd47ad43ce2f0684e5cfa5d3f04ffe19e9",
34
- "configs/model/fasterwam_decoupled_learnable_head_anchor_h3072.yaml": "635214d40f122009807edbb255815367ecb382e4b6185466aeff9d75dde3e4f7",
35
- "configs/model/fasterwam_decoupled_non_fixed_rope.yaml": "c18df423fe45ca49682511d46194a3b823938b4d045260580fada97f50f74763",
36
- "configs/model/fasterwam_decoupled_offset_head_anchor.yaml": "a1cc2b511321031fe2533bad45936fb082fb47f6203231af781e6a3def58854a",
37
- "configs/model/fasterwam_decoupled_piha.yaml": "8c92c8c77af46dacdc6a3b42ed5e70cafff1be65efae075b0c98008da8d8cd2f",
38
- "configs/model/fasterwam_decoupled_piha_post_rope_kv_simplepad_wide_newdrop05.yaml": "f94784772f55edfd5463b642f2b1e01bbf50f94f862b38543360a1d328d09749",
39
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero.yaml": "7738bab2cf88206c13dca23a828899edd61d6b1062b85554042acc0a26f91892",
40
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_cfgdrop01.yaml": "c775698ea84f8a4ede6b68a043fee6bdce31e1f45b8bdb7e6f931ba6f6a627b9",
41
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_drop02.yaml": "3a4b6d439c1ba523b7a2a0d01f96a1327ffc6de79ee04a5fb7ed0c43c73ab990",
42
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_jitter05.yaml": "a518782be1d7840d97e96c92c055b5affcd57b7dfa15cbf3e38a37f9df2b425d",
43
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_newdrop01.yaml": "1d37eee2d374798fed73dbbca610a0efcd24f4cf2534082198216d93821c1845",
44
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_newdrop05.yaml": "117db49eadf95e8ae708ba29c8c9ca9a76725a7f6eab5e92c0f1681d89e9fe1f",
45
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_simple_cfgdrop01.yaml": "0d5ee96e2a370a0dbc5b9d99851fe28642cf9f5a4d0f06fe1bc69d9636e07627",
46
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_simple_wide_newdrop01.yaml": "e5a1c21b7dd751872ba4975fcbbf002b13e5220d37add2b05b200a680d803983",
47
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_simple_wide_newdrop05.yaml": "900d8de2a7e89a0732942b93187b409bca96d4f63df5a311aba2763af22c738c",
48
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_simple_wide_nulldrop0.yaml": "661cf1ae45c855829631ca6727e612a8510a70f297954e124e3fcf69603d6cdf",
49
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_tgate_drop02.yaml": "7cef525d1debc26fbb0a9d4524e710898a834f1d44c3ca351ed7aa5d50799c84",
50
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_wide_cfgdrop01.yaml": "68b49e4dea0f3b0282d3708c2b843f4a7872d2d947d8238f8103e3a1fb82d340",
51
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_wide_newdrop01.yaml": "20ec0e1e879739d845fac9a20255fc5f87ce42a67eb60e5ae78e992d49adefee",
52
- "configs/model/fasterwam_decoupled_piha_proprio_adaln_zero_wide_newdrop05.yaml": "51ff20d87979292fe5e26d97a7a23bb7ecad2b9f1f6f21082cc712bcd1b036b2",
53
- "configs/model/fasterwam_decoupled_piha_proprioca_pre_piha_adaln_zero.yaml": "203dab8186358b7e00d3ec29548bdcbe7689d9833d88d5b656cd20ec91885405",
54
- "configs/model/fasterwam_decoupled_piha_s2_notextca_wide.yaml": "428f1cd7e15c7f73de8a902a922d1901f589bb8776dd43514d0e250a17c7027a",
55
- "configs/model/fasterwam_decoupled_piha_textca_cfgdrop01.yaml": "ad068306feb58b79e78c35d7df2404212aacf1c532ad00f5d5e732e69a036ad9",
56
- "configs/model/fasterwam_decoupled_piha_textca_post_droppath02.yaml": "af9e3c11ad60b6b421953a8578fcdaed7e303463e357bee052b7d1a267fdda6e",
57
- "configs/model/fasterwam_decoupled_piha_textca_post_piha_adaln_zero.yaml": "d3bd137c793d81c550e27da27d5d112adaf8ca1254e51f1856be9abdde0f4efe",
58
- "configs/model/fasterwam_decoupled_piha_textca_post_piha_adaln_zero_cfgdrop01.yaml": "b935f7b017640b1921b6933bf4db47d0552166fde149b0d143935fcc4743fadf",
59
- "configs/model/fasterwam_decoupled_piha_textca_pre_piha_adaln_zero.yaml": "c7db20a11dbcdb7a7e5eb3d8016ec6ae388642e3be50f9d01e7cade8f12ab468",
60
- "configs/model/fasterwam_decoupled_piha_textca_pre_piha_adaln_zero_drop01.yaml": "0decdda84bacafcdaf8a6d0b9240b799d62ad6fee7544ff83636087dcefaf199",
61
- "configs/model/fasterwam_decoupled_piha_textca_pre_piha_adaln_zero_drop02.yaml": "0c7bed756eacf69ff48a7642708fde7c45a002bff0080c630e20ec789d7a0f8a",
62
- "configs/model/fasterwam_decoupled_piha_textca_pre_piha_adaln_zero_newdp01.yaml": "e4edb493c653218fd3242f5427897b9ee40e22c7484295b783df0673d26c08a8",
63
- "configs/model/fasterwam_decoupled_piha_textca_proprio_jitter.yaml": "2f546b9609b6fa4b396962a84837f0f45bbc35122c59e0dc7d5c24195fd3baf5",
64
- "configs/model/fasterwam_decoupled_piha_textca_separate_null_drop01.yaml": "4039be28e1ae9c67c683b27ee62014a4b2e91400e88eed7333594be5539500aa",
65
- "configs/model/fasterwam_decoupled_piha_textca_separate_null_drop01_wide.yaml": "80443ead682fb85e943307ab18ede4e78cffe1d2d97b7c6c9268721de13588c5",
66
- "configs/model/fasterwam_decoupled_piha_textca_separate_null_drop02.yaml": "731d5ad71209f77578e6b68e7a43dabfc614c34fe5f512128dfdadcd3d116694",
67
- "configs/model/fasterwam_decoupled_piha_textca_text_only.yaml": "054c236c5c7560f4c09b37c53031fd75e72530fa5cc3d63cde1ceca5d4efcb5a",
68
- "configs/model/fasterwam_decoupled_proprio_offset_head_anchor.yaml": "5a991a2a83c3aca15b6505f94fc7b16cf989e21796915ad7e59e2e2995be503b",
69
- "configs/model/fasterwam_decoupled_robotwin_aligned_3dpft.yaml": "1ad5b5203967f4b699b1dcd452b9517b0fb18cf22bd2596a031d9f7bba287cc5",
70
- "configs/model/fasterwam_decoupled_robotwin_piha.yaml": "589d760a4bdeff1462c11473091d904e14e6fee364eb19f28abe82a8cd789a3f",
71
- "configs/model/fasterwam_decoupled_robotwin_piha_cyclic_notextca_wide.yaml": "7d83328f3115ecb382fb08a304d838ca73e917fe307577ef33d33f586eb9a238",
72
- "configs/model/fasterwam_decoupled_robotwin_piha_cyclic_notextca_wide_m2.yaml": "033ed8c5ba8df6dba7babe8614010fd964cdfc794da491afe48f65772be2ee57",
73
- "configs/model/fasterwam_decoupled_robotwin_piha_cyclic_proprio_adaln_zero_simple_wide_newdrop01.yaml": "ecd85b1b781a5c701f2e30ac5048113a0c29d2575a704e382e9d2acffb5d84ef",
74
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_notextca_wide.yaml": "65c04fc915408e51161a56910b2ce867718125c67608e8946c49217893cf292a",
75
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_notextca_wide_m2.yaml": "bb6b3c81557db91d781c9194bf17bc8ccacd1e143dbf526c1e0c96cd6d1ac93c",
76
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_post_rope_kv_simplepad_wide_newdrop01.yaml": "64b2d3128b2e1132ccbcb7952f91e31e5080acf0cea820455cc99174eeae7b06",
77
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_proprio_adaln_zero.yaml": "2835c16bc27a1861ece3111ad2bbcc0ca6c8c163888269d658fee93116298716",
78
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_proprio_adaln_zero_cfgdrop01.yaml": "1d52f29de3d8137eaef46016b83813e4e739f55574fbcdb722e2f5b4dd4fe5a1",
79
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_proprio_adaln_zero_jitter05.yaml": "9709256567b7eb05cbf6a601f602878a94e0f34b43f58a52375c193810bf68c3",
80
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_proprio_adaln_zero_simple_wide_newdrop01.yaml": "522d322b47eb9bff865b77dae1626596cbf3105ec1b22141d42ce7c8ee36f835",
81
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_proprio_adaln_zero_simple_wide_nulldrop0.yaml": "ed9421ab8c79fb242626fd0bf7b86bcabfafed1b83269bbeecf9ed11d9db4410",
82
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_proprio_adaln_zero_wide_cfgdrop01.yaml": "7df67a49dba60c481f3acf8633f73f62f8088615f5402a9d939ef4dfbe66e873",
83
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_textca.yaml": "23b28a10d06b5711a6f3ac71a93b278525845a0868c6d1c2e8f55bac16cd4c2c",
84
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_textca_cfgdrop01.yaml": "dd681117e2d45190138e7713de107d57b6af158be77b5bcf49fa40433556f0f9",
85
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_textca_post_droppath01_wide.yaml": "71f561f22621b36924a11fb2b23b290f8a22e622f6b23265fe6bdc8d7d708226",
86
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_textca_pre_piha_adaln_zero.yaml": "f1bd375a3eb80505646796ebb9578215b8ae3330da1a004e83819b8a9e9d9197",
87
- "configs/model/fasterwam_decoupled_robotwin_piha_s3_textca_separate_null_drop01_wide.yaml": "04ce7e36b50dafc99cb1e75190b97d32d45bbb9fceb51d8e2a86123ae6d3c7d5",
88
- "configs/model/fasterwam_decoupled_viewlocal_ha_pp.yaml": "809db6d01a4f9640be616d8e1b8ce44f16b0bd8ab3d27c6c62d3cee3d88e319c",
89
- "configs/model/fastwam.yaml": "e19fd569ca168f9621567bd273cba544251f57609972c3ab95d9125b3ab7146d",
90
- "configs/model/fastwam_action_mlp.yaml": "c5090b5e1ef85f2e82df82844fef4a3d0ed6d69a3d1715471399c1b0fcbcc08b",
91
- "configs/model/fastwam_decoupled.yaml": "32ca8b9d20ad26add7de7a3ab0a564de7ee7f785567f9f35d5716617e2171067",
92
- "configs/model/fastwam_idm.yaml": "951c508dfee6a3b4066489a57d633ee83743d2ecb96ba695c61c38cded7528f2",
93
- "configs/model/fastwam_joint.yaml": "4bb1ed76ff315d11ee4de8804740393c06e9736517fb232117f58005f100c3bb",
94
- "configs/sim_libero.yaml": "aaf0ca1c9fa847b5084e0b849520a1bafec406914b75ad409432b142f43ac5cb",
95
- "configs/task/libero_idm_2cam224_1e-4.yaml": "5df6cf62843806b0d9d2fb48b873945865bbfa74d135c58032651d54d6992c4e",
96
- "configs/task/libero_joint_2cam224_1e-4.yaml": "1c46f34058c9c5363bab6814a0b86eb13be3a0077e944db7d39cf151da07636e",
97
- "configs/task/libero_uncond_2cam224_1e-4.yaml": "27bdc30545aa06bca81137690a241d469d5a67a433881976bd79da3d4171d1a0",
98
- "configs/task/roboreal_uncond_3cam_384_1e-4.yaml": "654614f5a146dc7d02aae59b9a184efdf6f88f1a49ca76d48b4eb97847a7bcfa",
99
- "configs/task/robotwin_action_mlp_3cam_384_1e-4_v30.yaml": "26d29ac6cba793ccda1e659e6dc495b88f140ebc97257975dc95cedc146c5b76",
100
- "configs/task/robotwin_decoupled_3cam_384_1e-4.yaml": "fb0b79ddcfcaf1f04030477d2dda04b0b931d4a85a3b5a7ff253883865ce198f",
101
- "configs/task/robotwin_decoupled_3cam_384_1e-4_clean_random.yaml": "7c1273a424a6df2b7407ce692756186d9e42e8cd54451912f200a12a8de02cf7",
102
- "configs/task/robotwin_decoupled_3cam_384_1e-4_v30.yaml": "f7f5d3d06b596ce0fc42d44a95d394bf0d1a081c313e2e6336c43aaf7d198642",
103
- "configs/task/robotwin_fasterwam_3cam_384_1e-4_v30.yaml": "e03f812c18296d3d9ca9dc6006d18a0db8316dae89d37eb5d87918424fc238b8",
104
- "configs/task/robotwin_fasterwam_3cam_384_1e-4_v30_delta.yaml": "11442397245491db776b70bdab6e0c53de8193e340a5d687f158de5be0f9e46f",
105
- "configs/task/robotwin_fasterwam_aligned_3dpft_3cam_384_1e-4_v30.yaml": "b5d4e77f862163bb5e5198347a970f1a1c80598062682217e1aac459ae879b40",
106
- "configs/task/robotwin_fasterwam_fixed_rope_starclean_holdout.yaml": "4ed28ff0d2749008aabe0eb7768ac42870ff516adbc1fb24cc76ed114abe17c8",
107
- "configs/task/robotwin_fasterwam_non_fixed_rope_starclean_holdout.yaml": "72b8e49fa073e9b21f9fb6e2c97cbf10fa93dfaff224726e90a82fed1fb4eb52",
108
- "configs/task/robotwin_fastwam_30layer_starclean_holdout.yaml": "7c23c35d1dea094aae9adee6449dc207d4cf6f910ee91528e2aaa2ce3a4bcb31",
109
- "configs/task/robotwin_fused_kv_3cam_384_1e-4_v30.yaml": "6ed7b018130805be3d29c7f298697288ba7f95bc9dabf8bec372574897ae4859",
110
- "configs/task/robotwin_idm_3cam_384_1e-4.yaml": "2e67c9f301105752962af825e57e5b46ef5e6ba4290e397e5efc7abb88652619",
111
- "configs/task/robotwin_joint_3cam_384_1e-4.yaml": "daf4cb5246728ba60ae6903fbfd9d5e029d7172f30916b8143f81783450fa874",
112
- "configs/task/robotwin_uncond_3cam_384_1e-4.yaml": "2f78a207415b452b05e0238f3e00eb9942d7960413076b5b3cdcf1ce09caef9f",
113
- "configs/task/robotwin_uncond_3cam_384_1e-4_v30.yaml": "f64f830ce7331b8fb6925cd275f23cf34ed7a848a584f2fa5176c7b5aa720e69",
114
- "configs/train.yaml": "3d1294b83b7de598fa65e29e96988694be7f4b4c211f5f0890418313421c659c",
115
- "pyproject.toml": "cd7abcb7b19b3ab6e2b2f3dd57e3ac7f8f329b07a7f1ff4f819b6627e9045a1a",
116
- "scripts/__init__.py": "5a71e51c716ea7579151f36996bebb1ff82c6fedd0c45c68f2918cf5812f1053",
117
- "scripts/accelerate_configs/accelerate_zero0.yaml": "dacdf1cee3675246567f1f61e943c6762a4700b8b40c888af8fdf89773adc35b",
118
- "scripts/accelerate_configs/accelerate_zero1_ds.yaml": "5b9e24f38c199fdff6bbd17981d86e2624f855f707660094c99f711efc8e0833",
119
- "scripts/consolidate_parquets.py": "aeea6ee5c4536843daa5a192280d8f4b126e70a0b5b17d67927b6fb64da2b23e",
120
- "scripts/convert_multitask_to_v30.py": "989dfb32c6e2869d8ce6003f3ade338035dcddf6102723dfb3f190cb77f850d8",
121
- "scripts/convert_to_v30.py": "55a0685d75287c687759f75ba66faa4c5f2e9f4131e33386c308e4ab5f453dca",
122
- "scripts/ds_configs/ds_zero1_config.json": "8b65ebcb8de8c3c89113695a61783cdb97c2aa5003477f0ca743aa511bc68312",
123
- "scripts/find_lowest_inference_time.py": "09060fec278c0e107dc1a84ff326ecb0b25dbfd58f83b7bf544d5a6a404e4fe3",
124
- "scripts/fix_step5.py": "c7d1a306f3714d48a384e162a1b5c1473e3d7fc6a73d68816ccefc928cd6da5f",
125
- "scripts/nccl_smoke_test.py": "207c69bdbacc8fc837372866bd9cf9e50049985d633988b10d972119216c9f0a",
126
- "scripts/precompute_clean2rand_text_embeds.sh": "ba39e5506ec920d78f707cb4bf14af44ac0013b7928a130de44f63b088abf59a",
127
- "scripts/precompute_libero_plus_eval_prompt_caches.sh": "77d7fd49bca0794a20fb3cbf09102726eb43dae59675c89af6c17d3e5fd7839f",
128
- "scripts/precompute_text_embeds.py": "a1c0b63a0cac37394bfb4d023ae5dd916ca64ffbb87b8a086b34c5e4c903ba1e",
129
- "scripts/precompute_video_latents.py": "25191edbc21862b87549d613d2d9590772adc67fbbdd3aeee354cc146126c141",
130
- "scripts/preprocess_action_dit_backbone.py": "0fb11f04756d71e6b1b1d26cdf9e5831dfead339da38aa25c02f171d7f484a94",
131
- "scripts/run_eval.sh": "0f95ef66bf81cbb12857805a96a7d560f7bd56efbe36be4a29b65c55b3497472",
132
- "scripts/run_train.sh": "bf470e305d1f663372e7a966bfac2ec8fd5a9bdf0c32f5f028dc135b24b5f610",
133
- "scripts/slurm_train.sh": "54a212c718c6b199a9d5e67e726a390adacbde70eec32e0933a6029aaae7f333",
134
- "scripts/test_find_lowest_inference_time.py": "23354d0b3fa5b29928597dc9df8ca5ae2e38675d5c59001a212d90c242fa3a54",
135
- "scripts/train.py": "0b137862ae1a269863960508d282a27dbaee3caae90c9989403fef977fbbe8b8",
136
- "scripts/train_zero1.sh": "3eb17cb89605954acf5652768b1fc174be17c9776d9178c4a82d7483e6a8934c",
137
- "scripts_liheng/final_version/README.md": "eefa3d83dd86d0d2b2b90eb8f2e05d553a9f7f66708aebd5f3a0d08cb02cf736",
138
- "scripts_liheng/final_version/libero.yaml": "3593f63981c929d21c3f9f61d802d9ede835d6fe9767bf71b9774d00fed26e65",
139
- "scripts_liheng/final_version/robotwin.yaml": "4b2e79381fcb82ed39097dc21f6f40c169cbb4f3c766080e851c930ff3bafb40",
140
- "src/fastwam/__init__.py": "c4149c5ff0f9abd1517360daf761e692feb8570eb7a0caa2dfde52d4412c3e23",
141
- "src/fastwam/datasets/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
142
- "src/fastwam/datasets/aligned_3dpft.py": "48a439a71ca5e7687f76304d50c3c8b401fce6c255feb60fbab4bd24affbfa7f",
143
- "src/fastwam/datasets/dataset_utils.py": "3bc17590049c7cb54061acf8d731b07892e7f530c5d7eeac2613fa77a285a392",
144
- "src/fastwam/datasets/eef_anchors.py": "2e0cdd77ab4072a0355f379bbd3c1aaaa845c5ba1098b59b652b8f08495ea961",
145
- "src/fastwam/datasets/lerobot/base_lerobot_dataset.py": "4d264f9f96e067883053843c7711644cd129353a9be9a5b0e41c4ab9099174f6",
146
- "src/fastwam/datasets/lerobot/constants.py": "11ac9260341e1560c5eb0dd7acb79ed63c74180b8ab4113c27622ff213db417f",
147
- "src/fastwam/datasets/lerobot/lerobot/datasets/compute_stats.py": "7d7daab219332b5abdfd5ec67ccaf3502e8eb583a17a4e482c3f3d4f7a691e89",
148
- "src/fastwam/datasets/lerobot/lerobot/datasets/utils.py": "f2cfe722464cbe2144efa42eab48e92c4db06cfe0301217c12e03bc9b9a6cee4",
149
- "src/fastwam/datasets/lerobot/lerobot/datasets/video_utils.py": "cb647a1e955c3279e39ce8d7af7a37ea7432bbd71534c183f9ceebc8557dd89f",
150
- "src/fastwam/datasets/lerobot/lerobot/lerobot_dataset.py": "ef054e5b0dfb3a26e392376699e31808174afd620298f5977e1eb88385d93963",
151
- "src/fastwam/datasets/lerobot/processors/base_processor.py": "1f47bc85fee11e3d16714b2f1e3a2dc874f4609a1706a788617156d34b48b58e",
152
- "src/fastwam/datasets/lerobot/processors/fastwam_processor.py": "f61b2ed19e199f0e063e727878b17fce70ba451b3ee32b036f0b19e1b2d74384",
153
- "src/fastwam/datasets/lerobot/robot_video_dataset.py": "6a2538f41567cc86435d36a90e09c819a82eb0e1ce8897ee10b817758cc0dada",
154
- "src/fastwam/datasets/lerobot/transforms/action_state_merger.py": "669d6991415fc5eca44b32b129d15fee1e06e2f17d59d90350c252598033b427",
155
- "src/fastwam/datasets/lerobot/transforms/image.py": "4d7ab61d0844918525244db653831f0f632730046b2714a6163ff57c441a16fa",
156
- "src/fastwam/datasets/lerobot/transforms/misc.py": "8b505bd9e3a54ab2db7efcd64f0929d2e0303f5db1123de1074fd01fa9887e2b",
157
- "src/fastwam/datasets/lerobot/transforms/relative_action.py": "ebd0a50b91b44aac2959b0903d22b197490355ebf1371fb906d424cd1ed4fbb0",
158
- "src/fastwam/datasets/lerobot/transforms/rotation.py": "0de34b252d60b956cda07c0c19baebbd3c8a28916face700c6664c5815e11e91",
159
- "src/fastwam/datasets/lerobot/utils/normalizer.py": "5ac89a646cd6395e98feaa832f8fdf7c7e00857080af3d9c15710e243c43ad43",
160
- "src/fastwam/datasets/lerobot/utils/rotation.py": "6169162f98faf02a3fe5201e2b8a1069fe299840d036ee7cad8af5fa8f561542",
161
- "src/fastwam/datasets/robotwin_aligned_3dpft.py": "a25697adce91bb50bc647b5a2750cc6c06a734a853a18e73c7c45ff3575b2aa4",
162
- "src/fastwam/datasets/video_latent_cache.py": "029685a2c508093d4103e91b4cfe4752cab02c8a339854c54996ef410d932860",
163
- "src/fastwam/geometry/__init__.py": "6dca1e525b0309aa1dc652bc358da8bbffdf6a1b897d550f94e3b6475af25bdb",
164
- "src/fastwam/geometry/bearing.py": "b532e46f4ee370512bbb85c8ec6953984d60f45bc7023b25d50b0a7c715af367",
165
- "src/fastwam/geometry/calibration.py": "27fd1ac432e2f84e4f93a81c44212e43242935e8e740e4cbe62cc369ce3c040d",
166
- "src/fastwam/geometry/camera_source.py": "c1ed83f4ca07906a93591e0909273e9c11e8ae557869a10f2710a40fcdb09389",
167
- "src/fastwam/geometry/control_rope.py": "faf4b8161d551bf2f0e04d50863997972e8b3838547309052c02877f65370d53",
168
- "src/fastwam/geometry/eef_projector.py": "4573508ee3f4dbe5f73a99267cf8b3e5c039ca8067a8a3060d6653b81e052964",
169
- "src/fastwam/geometry/flow_context.py": "176f1f5bec816d79de152a914e6b438dff2f6aa6b86e0cd61b078a0d80f7bd04",
170
- "src/fastwam/geometry/flow_trajectory.py": "eb886b53e1c5efe333bbf3dcf7437235168c5d685739a5c12ae9cb487bf45c01",
171
- "src/fastwam/geometry/identity.py": "5ce73d68a505f9ade3a607ccb3f87f7c5498acdc64b5f9669b80fb9a2a695102",
172
- "src/fastwam/geometry/plus_anchors.py": "0819cfcedfa19a20345c2107e292a5c70bac3ac806054f647ca41581b17fef5f",
173
- "src/fastwam/geometry/prox_pinhole.py": "c2e5924c93b6628f22a3609b01a84403821f4ff43e9ebc09fcec1477987908bd",
174
- "src/fastwam/geometry/robotwin_flow.py": "ed872b88878961c434ae3cee4d9e36bcf802371c8b5b3e66d3b9784d797c35d2",
175
- "src/fastwam/geometry/weak_perspective.py": "5dda53e80d8d69eafb5b6b391c38115f60e263da20f014bede43bc4c38a170a5",
176
- "src/fastwam/lr_schedules.py": "a55094b749307d792f10bd8dcee7fec6e64767592fd11ca609efe6bcf2b64ab2",
177
- "src/fastwam/models/__init__.py": "ae4c5733f4bbf7caf31df2ae6f789516f6a10cd84a321f7d0375a7034253ac6d",
178
- "src/fastwam/models/wan22/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
179
- "src/fastwam/models/wan22/action_dit.py": "b44dbb467bbe4bdb7aecd3d62398887e969d2322f9b7fa5ba4d1f0d7512f076b",
180
- "src/fastwam/models/wan22/action_mlp.py": "cae2696f55eb3ad6a9e0ad57808670e4f5daa2c3cfab779849adeafbf8f64a0f",
181
- "src/fastwam/models/wan22/action_proprio_jitter.py": "5e0514d8b6c4b7055ef3d1cbdee7ef8c48f01b68c74ca05f55c757ffece3d359",
182
- "src/fastwam/models/wan22/aligned_3dpft.py": "dadf2af499fd1c885c4208eda3d99d9359b9c9e1c8117019886161fdec7ff3c1",
183
- "src/fastwam/models/wan22/aligned_3dpft_x0.py": "6457955b5d917b19a887903ef40d1c99c4279aacf8bc1bb04900ba77fa1959aa",
184
- "src/fastwam/models/wan22/attn_offset_head_anchor.py": "72db2485774b4092e14b7277a49e4e7faed1afa4b319f7921e5dea27b44795e3",
185
- "src/fastwam/models/wan22/control_rope.py": "dc38bac44b7255333db11c51f342b4dada4b1a8583f8c664cc7d905bec7e886b",
186
- "src/fastwam/models/wan22/controlnet_pp.py": "9705427f6210b11c6180bd61e7079c3c7d1a06f8c564d1358f63a28f69ead4b6",
187
- "src/fastwam/models/wan22/fasterwam_decoupled.py": "1c75e685f0a930dd318f6be2c8440f07ad70c97e23bb889b82272df065d6712c",
188
- "src/fastwam/models/wan22/fastwam.py": "22ca5d635f84205f820a9bb2bad618aaa0a2cf0ac34e6296d9209c8e8c59b28e",
189
- "src/fastwam/models/wan22/fastwam_action_mlp.py": "6900181ba52cd469d6b67059d1ed1877776bd4f6eaf8e95305467153e2c7b3e2",
190
- "src/fastwam/models/wan22/fastwam_decoupled.py": "2e2b1ef09c60a497e778cb8dfde72bb9cc0853651c072c9fa9c9bd910f2134ae",
191
- "src/fastwam/models/wan22/fastwam_idm.py": "0f380cef6363123531b116712f7b46dafa16961f50de822881e76fc8b1313b1d",
192
- "src/fastwam/models/wan22/fastwam_joint.py": "35bf9bceba78b5c49b6655e5e103fce3566595fce31974a5da54a35ddc3a2ffb",
193
- "src/fastwam/models/wan22/ha_content_pp.py": "14419e79fb5bc42f8c3c63385bb6c7d943844fcc5a069ed9baecbfa16241431b",
194
- "src/fastwam/models/wan22/helpers/__init__.py": "f26116de229557a37a4812dc4ee75971f5f9e8b80240786ea4d0e5b0ff703cbf",
195
- "src/fastwam/models/wan22/helpers/gradient.py": "ba5d8f7272eb029dc6cd2849ca99b70f6ad5abb838d21c818beb0590620dc793",
196
- "src/fastwam/models/wan22/helpers/io.py": "1c6c1bc8a975b47092912833194fce25e2e3ac886fbab72d73af3ff5a00b3c62",
197
- "src/fastwam/models/wan22/helpers/loader.py": "792f66bd0609c1365256aa24d43301340f81260c1651d89b04d2ae8224a08726",
198
- "src/fastwam/models/wan22/helpers/state_dict_converters.py": "81d213373e5056a8d8bf31768b9b04164138ea78d0b306e316334841499f4bf9",
199
- "src/fastwam/models/wan22/iha.py": "9ebd044a7a1bc27192425c1ddaee52f6d009b176f632c99b01f5993dd8b6e38a",
200
- "src/fastwam/models/wan22/kv_fusion.py": "6d0923ac3c13b003d80c532c33f2f072c88a4a407b631da9f751bbbea8491f49",
201
- "src/fastwam/models/wan22/learnable_head_anchor.py": "e4535aa748092bd63786bdcb078655d3fc79cc4260dc69c15f946657a7cbc676",
202
- "src/fastwam/models/wan22/mot.py": "e4572d5a63e8a0329a365208161a923116f3d49fe4fe9992998de26abc83c09d",
203
- "src/fastwam/models/wan22/mot_decoupled.py": "ecb2feefbb15f0f4c5081f2ed7eed8e0c16dc6ad5fbe3e5c3323d54997f27ca7",
204
- "src/fastwam/models/wan22/offset_head_anchor.py": "ded0371aa5e376f2ba54886d3200bdff09d41223d58846ec10d13f25f59c139a",
205
- "src/fastwam/models/wan22/piha.py": "671de66cbd53af8af281fd8fa2f3e3eeb7b499d2bc2da30c70c1a0ee5bdb0b99",
206
- "src/fastwam/models/wan22/piha_post_rope_kv.py": "98aeffc362e21fb9417264c837ae9a8fc47165ad0aa4421aa0690643c8a46201",
207
- "src/fastwam/models/wan22/proprio_offset_head_anchor.py": "46030d28f3beee04900d5ea52c59bed0cf25f2e65efa228671ef69cdea7e4805",
208
- "src/fastwam/models/wan22/robotwin_aligned_3dpft.py": "db4ea90fe871746c5fa6afe27bee3d7ee4a9064b3f390d79eecb617906574360",
209
- "src/fastwam/models/wan22/schedulers/scheduler_continuous.py": "2ececce284f7b9fe876965fbf5e11ca7b69caaf693979266b0f63062a589f181",
210
- "src/fastwam/models/wan22/text_ca_dropout.py": "65d621fb0e3c0c4f10db1d58c7cd53eb53078e44d9ad3266a9f2b1fee8cd2f7a",
211
- "src/fastwam/models/wan22/viewlocal_ha_pp.py": "350b06631f6b98056f2e0a9221776e8715614284894e8f28c380c232c5598f04",
212
- "src/fastwam/models/wan22/wan22.py": "b72998999b4b2c5096773092339b8ca81e9e7d96f398b81c57d3d22d14972ac4",
213
- "src/fastwam/models/wan22/wan_video_dit.py": "9fe059b41fa8248bee5d0e6b49fd22c2d2fe5ef48553a7f1cb06f769a06e6feb",
214
- "src/fastwam/models/wan22/wan_video_text_encoder.py": "aedf7f131556a39b99a273deec3a85d67f41b5367cef41f310f33a807ae60a87",
215
- "src/fastwam/models/wan22/wan_video_vae.py": "31b3fb9f844a76f5db5af4854e14802aae640b85b61d0de03a4b53b52515abb7",
216
- "src/fastwam/runtime.py": "253454009a5defad0f4138875304999ea36195dd59ce4745ab79149880204887",
217
- "src/fastwam/trainer.py": "80afaf3dd3d3bffaec81023bc6bd3b7efc822bf5e836f983c2e97e9bc2d96fa9",
218
- "src/fastwam/utils/__init__.py": "2893c05b99b17162941c70a41859c2808f8ae390be367a26cac7d5ab93ee197b",
219
- "src/fastwam/utils/config_resolvers.py": "01c48cbd42b86e636b70ccc3e193a81147162ba890777102b2c61086fa415340",
220
- "src/fastwam/utils/fs.py": "69e6df236b43360428a613686c8f37c531c0d16a3a2c5b06a725c46b49d657c5",
221
- "src/fastwam/utils/logging_config.py": "cadc4f6ad487c3f37171a6defad36d23d9ab8bf262f73a461cd9ee9b03b757e7",
222
- "src/fastwam/utils/misc.py": "05aa51247936a8d3faac27f90b78b65ff83a688259bed55d114b6a617ced532d",
223
- "src/fastwam/utils/pytorch_utils.py": "ffc483e7fb435cd654326750eaffdbfc9243837c3140dcc74e45b24abb751a33",
224
- "src/fastwam/utils/samplers.py": "4d3bd9b2d498f4ad8f2cf9765d60c4bf5368fa6fa2a7d42d0db6210f66899652",
225
- "src/fastwam/utils/video_io.py": "9e9898b021a44e87aa1b4e6d13e3755a9869a2e27a7eb53c72a6802808d5bd80",
226
- "src/fastwam/utils/video_metrics.py": "aaa37e458cc9e4bb79a17923b5ff5900c97b4309f7abd50db44cf76e9143ffd6",
227
- "tests/print_eval_results.py": "34590e8ce1baa7a93a453e0ac3af0e1be38392e20d8e19330932f1e598ac5f80",
228
- "tests/print_real_dimensions.py": "bf718b8feb599a9e7f52d3227171079b6acee5c4dbc8d520d6567bfd0da0e3c2",
229
- "tests/test_action_dit_selective_init.py": "0f8e8ab3ed9366b797a51a092e5eec57a516462105137e207dc52cea93ae8ff1",
230
- "tests/test_action_mlp.py": "6880720c982d1350ee207c21c32e74af4ffc89fd6c4673a9fe7a5fef74984005",
231
- "tests/test_action_proprio_condition_jitter.py": "99877f4f7f5256fec08c3043b3abadc90bc89e8d411db5c7f30d5e754595978b",
232
- "tests/test_action_proprio_null_dropout.py": "1bfa23472c4493b6e5d8c046a988e3d98ea9e2de7f48591bba968a623374eff3",
233
- "tests/test_action_text_ca_dropout.py": "ffd1bd7c0cab920bef709f18fb3cca7d31e88fcc8c4542b4c1436e65aa38f624",
234
- "tests/test_action_text_ca_post_droppath.py": "ffc4f40854b2a21260a4d6ebf8250599ef88fe5972974e77eb29c9fbe4a9bc13",
235
- "tests/test_action_text_ca_proprio_jitter.py": "1256041d8c86a4a9ecdc3be51e6ff4c494ea13fbbc5c95849049fce5c30d0a1e",
236
- "tests/test_action_text_cross_attn.py": "58a2f441f666ebef0d755c9c894b4fd55720600899db00f47e4b2901c4fb0487",
237
- "tests/test_aligned_3dpft.py": "be54483206fed5410491116487975bc5626712c9bfa9b9ed6116ec21b8ad3a85",
238
- "tests/test_aligned_3dpft_eval_context.py": "9c7097662669a1122d2f39fbcb5f758e1e79d5e80c34f7c6ae84a682e27f3b13",
239
- "tests/test_aligned_3dpft_factory.py": "fe052ecfdc71c84c7747e49e28a7537e776751f874443d2fbf75fc19ef425941",
240
- "tests/test_aligned_3dpft_live_camera.py": "0ff58b5ecc6a67d9eb8602499c0c24206c60cbe2b1a676b07ba58eea13ea369b",
241
- "tests/test_aligned_3dpft_native_launchers.py": "408b9efc6f1727f030e03ba244d20066fd5cb950d6d5a91bc4475570175244c5",
242
- "tests/test_aligned_3dpft_x0.py": "097aff5b5e3094999d95ca63b5cc9ffaabec98fe45646c15bfa2b17d2d98d18c",
243
- "tests/test_attn_head_softmax_libero.py": "ef1d4a23aa160c328d57afae626cd509f193041cf5b5507a762d2792c8119e77",
244
- "tests/test_attn_offset_head_anchor.py": "08d86090997ac6f8c0564e50664c7b8a0f5541c8d92ee5acc5bec8f8d9b057e1",
245
- "tests/test_bearing_projection.py": "84b2bad0bc07d8b936b78d30ac72bd0c750db8f0e72b0816b1e9e6fec8f1c190",
246
- "tests/test_checkpoint_identity_metadata.py": "4f89721f55275c1d0dfac8156ba5aed8618f6c5b02194ef1565f714ffc5b418c",
247
- "tests/test_config_resolvers.py": "f3a3686cf924797c4b24caa97dcad12d65eb5de950a6a449483109c94fb2da05",
248
- "tests/test_control_rope.py": "6281fc66eea8b5c1340a8549b77d3928f276e1fffa2635072d1118dd4d5b2d83",
249
- "tests/test_controlnet_pp.py": "eac6a8e177f68421035b6d6f062686ab15cdb0c051b6f3432311baf2daeb8d1d",
250
- "tests/test_decoupled_full_integration.py": "671a9e8caea55b9532f9637b4030664d9e141576c99a8ce112ea279711585d54",
251
- "tests/test_decoupled_prelaunch_gate.py": "bea2f1e5834183b6143b16b8683c7433aa3250760b4f2d18120bf568e6b2e916",
252
- "tests/test_decoupled_shapes.py": "560de0e0593933284e27c418b8a9776e2ac613d9e13dadbeb086e5dfc86954d5",
253
- "tests/test_decoupled_stress_edge_cases.py": "d2b9ca8ede032e64ddfb925a0beacbc2ba8425c25048ed515d8cd3442b93ad55",
254
- "tests/test_decoupled_stress_mutation.py": "ed17b747c1db8467ac75407180ad32f5d840b41fdc3c9b099a03a18e987364fb",
255
- "tests/test_decoupled_stress_pipeline.py": "9b90e942fb935d7e6d96b9a5a34f62e75a991bb6603499a92bd0853a11482b52",
256
- "tests/test_decoupled_vs_baseline_comparison.py": "f004729b97c47224f62539ef1f8256315f6544c0109cde952b6712b1192fd1fc",
257
- "tests/test_eef_anchor_dataset_threading.py": "8608c1fdf80ed434bd1e389dd9f6132bb39d077f3574e328d4955fc49bcece12",
258
- "tests/test_eef_anchor_tooling.py": "0e16f0dac6bf17dd9ef50cc622fd0a786f120b7fbaa36c399e99eeede7126958",
259
- "tests/test_eef_calibration_artifact.py": "32c8b89e0531d3cda947aee57841de68bdf44125af34ce0e92538681e4501422",
260
- "tests/test_eef_calibration_parity.py": "80f4d3dac2056428629868cc2c8e61bcbab71aa45ea2ae70b92c06aacbdb9e3a",
261
- "tests/test_eef_projector.py": "c564ea2b643a78407307b47828f4beee56b3750a838df0f71fcaa6b39d080edf",
262
- "tests/test_explicit_checkpoint_steps.py": "feaeb15ee0aee6a43c473d586ebaf6fc8d321fe7cec571ab643870298495d68a",
263
- "tests/test_fasterwam_fused_inference.py": "db548954165f610c066b643cab72617c2fe78661f2214b3e4a56bebf9f42a2f0",
264
- "tests/test_fasterwam_fused_kv_rope_gate.py": "54362f3121f98031a5b46490ac81c67b563cd6a480ef49bcfc2062d9475f2f06",
265
- "tests/test_fasterwam_fused_mlp.py": "6bef5c70b3e2e48524420f34f36578c12041ed9eff37d1f6fac0beea3e907da3",
266
- "tests/test_fasterwam_fused_mlp_gate.py": "277aaf626e82618586583dca51b04a759de94b2ce2247e58362d40d4be911931",
267
- "tests/test_fastwam_decoupled.py": "3d1139dc08434a20767abd34c42557335dac2323e04517243a555e09bde8ef7a",
268
- "tests/test_fastwam_idm.py": "9c265791e9bb0ac010e793de2febb57cffae8296a168959a26b4d3b132da4b9c",
269
- "tests/test_fastwam_joint.py": "9b739c632f32f48e170b71acd6804654e732f02809d10e61cd738b3d65009b5c",
270
- "tests/test_freeze_integration_gpu.py": "4716b14ae346d032147ec4c59a9611080835728c6d83005705a28c0e9e82c18b",
271
- "tests/test_freeze_trainer_wiring.py": "2398d3b91e004931f60ee1695a932dba1a3195c360cb1daf0c697b11e2b813b7",
272
- "tests/test_freeze_video_backbone.py": "a703963d4ba806cac1d022ef398919eb9db892f68d8fea213be3eb71b1724a7f",
273
- "tests/test_fused_kv_checkpoint_guard.py": "916c878cce9aedaf68e4390606c317398cc3ac9604a0b372609702ede87f2185",
274
- "tests/test_fused_kv_prefill_consistency.py": "50dfd01e6e00cc289bc483df1242ee58d504d2c48e7670a69331ef0ee14bf39e",
275
- "tests/test_ha_content_pp.py": "ede7c7543fd7fd78b108dee29b60abbe8308ef1fff55fd2b779aba272ddb1a87",
276
- "tests/test_ha_origami.py": "c9dcbfe0449f4c5332bcb89be8a549cfaf4e94d83cd92f2b5acb1cc75412110d",
277
- "tests/test_iha.py": "e53b7a14e27b57798929b0da06370dc08d7204dd2b422ccecb05004e7670f8c5",
278
- "tests/test_interleaved_forward.py": "533e43c99a2de4d3ff4fb2ae982e9d8fab8b8a99d539411f8391f0e30c2bcdc0",
279
- "tests/test_kv_fusion.py": "7aafff16c4e2c47468d64920fb1937e9570c234b02996ddf0453c96d3640c36a",
280
- "tests/test_kv_fusion_factory_integration_gate.py": "e03cbb0163bb75f18c9f3d619976f143665a6f9c69edce52521cf970379512c5",
281
- "tests/test_kv_source_mapping.py": "de282041d88a5dfcd026498e103603292186dae3b341f265f9b6c74a785f5e33",
282
- "tests/test_learnable_head_anchor.py": "9d71e07cc3a7c1066ecc1f2e2907435eeff24d82ddaa1cf07629d2ab84cd8760",
283
- "tests/test_libero_4090_dispatcher_policy.py": "9acfd91c87fc3298e0b67a71048f11fa5b943d42ee29c3756e10d33bd887aaac",
284
- "tests/test_libero_4090_dispatcher_sim.py": "353483387264277c129ed693fb508ea5a2c809a8c651154f95141c33c16eb514",
285
- "tests/test_libero_actor_topology.py": "1b2087b23fa811653b102c5e5c83a98b12e6c25dd5e9ef9f4559855cd25119e3",
286
- "tests/test_libero_batch_dispatch.py": "9252872495832d5a999f11968cd2e843dc54c9c2db96697c3620239aa29014b7",
287
- "tests/test_libero_batched_inference_gate.py": "1174a1d8237cf72b711a4915327d0a5444fb7440c5b30312eb8230a1bd58bb55",
288
- "tests/test_libero_context_cache.py": "e550ff058b42015e61b4a13e3cdad060155e0ed107d1441c0f0b8f4d0082995a",
289
- "tests/test_libero_eval_batch_topology.py": "3c81cd05c52f3f0784f54adb7cb9ac0876ffb8cf95b936444a5568f76a43ec58",
290
- "tests/test_libero_fixedenv_actor.py": "aa9c29060e45d47aa13fcd77b66264c4a0b78ff4862d697c6e5cca6102c0748c",
291
- "tests/test_libero_image_preprocessing.py": "f54af57369de94461d7f5223ae42f20d435b92ec5997eccdf9dd79d132ebc1e7",
292
- "tests/test_libero_jitter_handoff_queue.py": "357c463799c87b6b9b27de818479e281120f3d2268a541a0d9e48c80eab93f2b",
293
- "tests/test_libero_localpipeline_cluster.py": "1dacea0ccbbb5f6b008996433ce8d301ac7ba0721b5763a449f6642bd1b49714",
294
- "tests/test_libero_manager_shutdown.py": "7296534d9b2d4aa7a0077732361810de4a326de32bf5f0dbedf092eef5b3e107",
295
- "tests/test_libero_manager_topology.py": "532614cf5fdca51e43c54f89164bce1202048f84203349f6bd87e62e4addc4d1",
296
- "tests/test_libero_plus_gpu_defaults.py": "9584355e771ea339bb267775b9015e1870116885011a5e2a46980a3b4b061df3",
297
- "tests/test_libero_precision_pair.py": "d79d4e7db637c00d8ba5d76ac6c884996fb107da2ea5437fb8200d2e6658e267",
298
- "tests/test_libero_shared_model_batch.py": "9e98c9605ab229bf8389abe693524133c877fe20e4ca074f0e6ef6dfc84bf086",
299
- "tests/test_libero_standard_gpu_defaults.py": "1d86ca160c1a0130c046dbaa97a7ad32f6f22d9e53445cd6a706d9fa9239d79d",
300
- "tests/test_libero_standard_protocol.py": "6586b7e55cea78b1e8299c5d8a8e27a7f05c6585640b01b35bc191485a9fc66a",
301
- "tests/test_libero_task_work_queue.py": "be8b7430624bfdabb10980f2969dd24c73206679c2339ff5faad9f3539d9665c",
302
- "tests/test_libero_topology_config.py": "48b9ee1f0684037c1ad17b9ce82ed738534a0b977f1ac3da2c194689e0645aa3",
303
- "tests/test_libero_wide_s2_notextca_switch.py": "1bb77467c705d9327cbbf5309ee5749ea20e1704b3c054459e82c859846b0e7b",
304
- "tests/test_local_libero_reference_verifier.py": "015d4f8a30a56d2aecdcafd4fba8224d1867e787938cb7d2de8f9669b54101fe",
305
- "tests/test_lr_schedules.py": "e95695bc09ab2b73b91f4e0cdc744c1622e80f9b3ce6bf8bc2b72eae7e0bea77",
306
- "tests/test_nd01_resume_topology.py": "4626002a1106f9f391e84138d6c1b0fe912984e6efdcd8766442048c7404129c",
307
- "tests/test_new_fused_kv_freqs_guard.py": "1a05a96c6aa6a90c9c866bdc96e019402e8ab38cb8a02d58b00dd7f49fe10abe",
308
- "tests/test_new_fused_kv_projection_contracts.py": "a57ff0c233fb2a1674ff24e791b2f446c5fb36697b87738849cc33d3f854c40e",
309
- "tests/test_non_decoupled_inference.py": "083ec475d1c2e8fc051cbeaa347ad8ab53ff616095e78cc6eaf4f147dc4123c0",
310
- "tests/test_normalizer.py": "9a4385f581ef300d1a6c56ad354665bc78e4ddb00abd928fba27e8eed697299d",
311
- "tests/test_offset_head_anchor.py": "46da333b0d6958935a1e16fc251383f9b2c0730c0c5465e5547f36a3ecba0197",
312
- "tests/test_per_request_noise.py": "4983702be58d30196e1cccbc1dc168e6492617de2ff7f69a7bb57eb4f1c204ad",
313
- "tests/test_piha.py": "6af1a7db6bcba26b74423f39582bb8d299035bba02a4eb3711efea83f0645b59",
314
- "tests/test_piha_post_rope_kv.py": "211b76bd730f4030c3c4dbaa8b6e8074f9b7b2e1eaf061db474be60a1ca1b27b",
315
- "tests/test_plus_anchors.py": "665d9cf67b9c0c7f3b867c3a4b1a9ccd81ea18b262feca008f7151d7adc01d65",
316
- "tests/test_postrope_readout_observer.py": "1555c682e6c2e4c183c1dfcbd50d336d3ae04fd903f1a1c5b58f7644e2d7813a",
317
- "tests/test_preflight_libero.py": "252ec7ee0ecab2a39decf9a1118fb07787e9bc11fb1631c8aec71288f08b7eb4",
318
- "tests/test_proprio_offset_head_anchor.py": "9f0d076810f56874f4367f65806c34d94b7af5ac8230229fb99de5ca83899b2c",
319
- "tests/test_prox_pinhole.py": "b3c1a59252d3f5443e796283760f46157011ddb7461c0c52304161637e619ee5",
320
- "tests/test_relative_joint_transform.py": "f2df795ad3f9ec3eca37279e60d97602dc44508b1fe09d63907f0251c8ec46fd",
321
- "tests/test_resume_eval_manager.py": "7244d315c62c05aa111e99e0f81ef2757d49f1385798e2be70dbdce36cbeb8af",
322
- "tests/test_robotwin_3843_env0_extra2.py": "40e3f20410af3351872be41a3136653e7500148583e466e31012f911265e8c6f",
323
- "tests/test_robotwin_3843_env0_extra3.py": "d5ca67a8de097c1e837c1f6228ee9d4326295094370e87b0370fd987c8a3e61e",
324
- "tests/test_robotwin_3843_env0_switch.py": "b1b41709a15db2676683e04889a7efddb4fe1e8cbebb9a3238ee585e30c73e44",
325
- "tests/test_robotwin_3843_env42_switch.py": "10cad34ccab31e551a55ce8325d450ad63c39e3ff4c6c874f39de7aa36340355",
326
- "tests/test_robotwin_aligned_3dpft.py": "b04376e1b9417f47e30bcec7aeea88e32e722250c165125448749fda1b31c661",
327
- "tests/test_robotwin_aligned_3dpft_factory.py": "376cce62d42a209da5624ab46f63c362bae6f469605ead6fa87f0c38a99ce443",
328
- "tests/test_robotwin_aligned_3dpft_rope.py": "48b64a1d3b5e3f32e2dae228bd9e1d36e03a760c9a1433a5798a718f17914a41",
329
- "tests/test_robotwin_bf16first_overlay.py": "e30ccd71b92296444cbe76647f3ee0e1b17203ac2a3ab9bf54d85d965633dba6",
330
- "tests/test_robotwin_bf16first_repeats.py": "651c698df4210e43f8f49abbd838c7dd109695c79f1582bbda28ccc54ed3a54a",
331
- "tests/test_robotwin_cyclic3911_extra3.py": "d7f2af23780f066a2e0eb440d5a0181c6ce3e16ab9b6cd5bbc141bb9da930857",
332
- "tests/test_robotwin_cyclic_error_retry.py": "660bb6cf0199a49260cc1d18f0ccdfffe1cb1afddef5dee30b5a09b907a12e3e",
333
- "tests/test_robotwin_m2_wide_cyclic_notextca_recipe.py": "68c670f513b36683a62e5e48863f710fb73147281f6fe738d1e8f847cc84f4ac",
334
- "tests/test_robotwin_nd05_lr5e5_recipe.py": "adbac1f1c54b815541c31d32b9031c0a820923fceb0d60a7481dc74da3398110",
335
- "tests/test_robotwin_piha_post_rope_kv.py": "b2164aab5d28321e3bb8b303cf3d2012be706b1938b03408c02ee20a64044d36",
336
- "tests/test_robotwin_wide_cyclic_notextca_recipe.py": "95b93ece32702fc8adcbba2e82869b0228c25c8aa5a14ba5e47512e98220e787",
337
- "tests/test_robotwin_wide_s3_notextca_cosine_8ep_recipe.py": "e6d47bb5ec9ec6959bf8db9957ed0a007b7f7e1f33764aaa8124d0872209ef95",
338
- "tests/test_samplers.py": "998afae7f0e0d461b56ca195218b2ec428e6be5a0c29baee2e0103e8687510df",
339
- "tests/test_scheduler_continuous.py": "e1014ef8883075686c09faeb2c1e7a814dd20e25657ec409bb9b120b6db0a1bb",
340
- "tests/test_sepnull01_eval_summary.py": "56860f190ed804d549cfba18c9ea49b3d682971e1c32f7f2fd46eb9bf147257c",
341
- "tests/test_sepnull01_fixedenv.py": "c9ccc86f1315efce4d277c849277475e76cd1a2bd1e3de6e330386531a98f03f",
342
- "tests/test_simplepad_standard_cfgdrop01.py": "0de131a91a0118e131ff0f9aeb95dfabc3c536360f9cc7b3fce12959cca3ba26",
343
- "tests/test_state_dict_converters.py": "a5c2ee0e6fb098e9c07327afd55eb9aeecec8a24fc2b1e8b18e951ed8a9e5482",
344
- "tests/test_summarize_libero.py": "43ec6f729c399df11b192bff4b2f0a82310a118194efcc61f0fd3c51bf13bd60",
345
- "tests/test_transforms.py": "2893a02cab2eca2b94bf2f91a8c559094bae7c2d36bc25b79d41bce7ba795e81",
346
- "tests/test_v30_dataset.py": "355c6c29185e39279d8267e52b98eb3336284f2f4d64cb5c030d536b993cdb1e",
347
- "tests/test_verify_liberoplus_install.py": "79dfd53fbaed29dd687f198f7ffea70302ef5110fd50b1db6a57e172e80dfc8b",
348
- "tests/test_video_latent_cache.py": "791c0d93ef99735fd64c05235037042caf8cefda8682478d5eedf44cdbae6334",
349
- "tests/test_video_latent_cache_real_gate.py": "c087aeb585504c154bf2216791148a9858238f2d8295afe88cb4cab596c69435",
350
- "tests/test_video_metrics.py": "5d6ffdde7c0a642807f2c728b44d16909574118616ebfb70de66494e1435a7c7",
351
- "tests/test_viewlocal_ha_pp.py": "13159450cd3a6761edd4c35cbfbaab3addbfa3a2b3f6f516f3589bc18f46840a",
352
- "tests/test_weak_perspective.py": "edfda5a6793f76074548019c165b2285fc091916fa30ba93a6a81f8d2c29a76b",
353
- "tests/test_wsd_extension_preparation.py": "c1a6165849f88c78d9a14b3fa78dfd7c082086b78047fce22dfae3f9eeb380d8",
354
- "tests/verify_decoupled_config.py": "c20f11c029b168605a8b1c0b56d68cbeafea9649ceb54414c32f973282d61dd5",
355
- "tests/verify_dimensions_both_modes.py": "faeba9f69770520b4fcb849d2e277a9e4f14c968d69d7b493f17035fbb4a19ee",
356
- "tests/verify_kb_lean.py": "6527db5b95d7188b0d66f11ab3236e426b149261920c10f5503514fc6676c7b7",
357
- "tests/verify_kb_real_gpu.py": "6ce6180d2e16df40adffea84f56e85cadec81f90c2261728f5e0ba6f249cb0b7",
358
- "tests/verify_kb_sysdiag_gaps.py": "32d17be18535e8dd0a1326af5028f3a6ed66ff11f4ada626cb5ca87d6b8bda0f",
359
- "tests/verify_knowledge_base_deep_dive.py": "001a19226ca0c6b087609845e54677c609308ca549804df55365374905d1bafe",
360
- "tests/verify_knowledge_base_system_diagram.py": "6c5f554e5c4add0c51cf3c8d6d11b6b5bb0436bf4f38ab4028965d1a46f2665c",
361
- "tests/verify_mot_mask_topology.py": "32d50a2f48a93963d78916eace52414e8ef32bfa3462d6d6435f3e8b30ed0af6",
362
- "tests/verify_selective_init_integration.py": "518ad075e1c758c9cc655637ea4a4edfc99b9a053f36ad8ac570fb78976da00e"
363
- }
364
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/provenance/training.log DELETED
The diff for this file is too large to render. See raw diff
 
joint/fasterwam/train90_joint_job4247/provenance/training_summary.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "note": "Run cancelled by user; W&B summary not finalized."
3
- }
 
 
 
 
joint/fasterwam/train90_joint_job4247/requirements.txt DELETED
@@ -1,226 +0,0 @@
1
- anyio==4.14.2
2
- httpx==0.28.1
3
- nvidia-ml-py==13.595.45
4
- tqdm==4.70.0
5
- click==8.5.0
6
- h5py==3.16.0
7
- httpcore==1.0.9
8
- fsspec==2026.7.0
9
- numpy==2.2.6
10
- huggingface_hub==1.29.0
11
- nvitop==1.7.1
12
- filelock==3.32.4
13
- hf-xet==1.6.0
14
- typing_extensions==4.16.0
15
- exceptiongroup==1.3.1
16
- h11==0.16.0
17
- psutil==7.2.2
18
- Flask==3.1.3
19
- gymnasium==1.3.0
20
- packaging==25.0
21
- packaging==26.0
22
- Jinja2==3.1.6
23
- lazy-loader==0.5
24
- fsspec==2025.3.0
25
- pillow==12.0.0
26
- six==1.17.0
27
- frozenlist==1.8.0
28
- idna==3.16
29
- mplib==0.2.1
30
- aiosignal==1.4.0
31
- nvidia_curobo==0.0.0
32
- setuptools-scm==10.0.5
33
- sympy==1.14.0
34
- widgetsnbextension==4.0.15
35
- ConfigArgParse==1.7.5
36
- msgpack==1.1.2
37
- embreex==4.4.0
38
- parso==0.8.7
39
- zipp==4.1.0
40
- nvidia-cuda-nvrtc-cu12==12.8.61
41
- exceptiongroup==1.3.1
42
- datasets==3.6.0
43
- triton==3.3.1
44
- av==16.0.1
45
- tokenizers==0.21.4
46
- nvidia-nvjitlink-cu12==12.8.61
47
- fastwam==0.1.0
48
- Werkzeug==3.1.8
49
- retrying==1.4.2
50
- aiohttp==3.13.5
51
- dash==4.1.0
52
- propcache==0.5.2
53
- joblib==1.5.3
54
- tzdata==2026.2
55
- pandas==2.2.3
56
- nvidia-cusparse-cu12==12.5.7.53
57
- typing-inspection==0.4.2
58
- charset-normalizer==3.4.7
59
- nbformat==5.10.4
60
- boto3==1.35.99
61
- cycler==0.12.1
62
- ipython==8.39.0
63
- omegaconf==2.3.0
64
- setuptools==69.5.1
65
- setuptools==82.0.1
66
- jsonschema==4.26.0
67
- python-dateutil==2.9.0.post0
68
- pexpect==4.9.0
69
- svg.path==7.0
70
- numpy==1.26.4
71
- vcs-versioning==1.1.1
72
- blinker==1.9.0
73
- modelscope==1.34.0
74
- scikit-image==0.25.2
75
- fusepy==3.0.1
76
- plotly==6.7.0
77
- requests==2.34.2
78
- stack-data==0.6.3
79
- shapely==2.1.2
80
- jsonlines==4.0.0
81
- sapien==3.0.0b1
82
- typer==0.25.1
83
- hf-xet==1.5.0
84
- jedi==0.20.0
85
- imageio-ffmpeg==0.6.0
86
- ninja==1.13.0
87
- gitdb==4.0.12
88
- mapbox_earcut==2.0.0
89
- jsonschema-specifications==2025.9.1
90
- s3transfer==0.10.4
91
- Farama-Notifications==0.0.6
92
- tifffile==2025.5.10
93
- smmap==5.0.3
94
- nvidia-cudnn-cu12==9.7.1.26
95
- trimesh==4.12.2
96
- pydantic==2.13.4
97
- pycollada==0.9.3
98
- scipy==1.15.3
99
- nvidia-nccl-cu12==2.26.2
100
- rich==14.2.0
101
- mpmath==1.3.0
102
- nvidia-cuda-cupti-cu12==12.8.57
103
- aiohappyeyeballs==2.6.2
104
- torchvision==0.22.1+cu128
105
- torch==2.7.1+cu128
106
- Pygments==2.20.0
107
- annotated-types==0.7.0
108
- traitlets==5.15.0
109
- GitPython==3.1.45
110
- toppra==0.6.8
111
- opencv-python==4.11.0.86
112
- h5py==3.16.0
113
- vhacdx==0.0.10
114
- rpds-py==0.30.0
115
- yourdfpy==0.0.60
116
- torchcodec==0.5
117
- tqdm==4.66.5
118
- referencing==0.37.0
119
- transformers==4.49.0
120
- pytz==2026.2
121
- PyYAML==6.0.3
122
- itsdangerous==2.2.0
123
- warp-lang==1.6.1
124
- antlr4-python3-runtime==4.9.3
125
- regex==2025.11.3
126
- addict==2.4.0
127
- nvidia-cusolver-cu12==11.7.2.55
128
- pyarrow==23.0.0
129
- botocore==1.35.99
130
- httpcore==1.0.9
131
- imageio==2.37.0
132
- einops==0.8.1
133
- threadpoolctl==3.6.0
134
- multidict==6.7.1
135
- urllib3==2.7.0
136
- nvidia-curand-cu12==10.3.9.55
137
- anyio==4.13.0
138
- asttokens==3.0.1
139
- open3d==0.19.0
140
- fonttools==4.63.0
141
- certifi==2026.5.20
142
- narwhals==2.21.2
143
- safetensors==0.5.3
144
- jupyter_core==5.9.1
145
- h11==0.16.0
146
- hjson==3.1.0
147
- ipywidgets==8.1.8
148
- markdown-it-py==4.2.0
149
- deepspeed==0.18.5
150
- jupyterlab_widgets==3.0.16
151
- contourpy==1.3.2
152
- scikit-learn==1.7.2
153
- pyperclip==1.11.0
154
- pydantic_core==2.46.4
155
- annotated-doc==0.0.4
156
- dill==0.3.8
157
- pure_eval==0.2.3
158
- shellingham==1.5.4
159
- jmespath==1.1.0
160
- transforms3d==0.4.2
161
- psutil==7.2.2
162
- importlib_resources==7.1.0
163
- pyparsing==3.3.2
164
- importlib_metadata==9.0.0
165
- nvidia-nvtx-cu12==12.8.55
166
- MarkupSafe==3.0.3
167
- nvidia-cufile-cu12==1.13.0.11
168
- matplotlib==3.10.9
169
- click==8.4.1
170
- nest-asyncio==1.6.0
171
- nvidia-cusparselt-cu12==0.6.3
172
- kiwisolver==1.5.0
173
- numpy-quaternion==2024.0.13
174
- rtree==1.4.1
175
- sentry-sdk==2.60.0
176
- manifold3d==3.5.0
177
- matplotlib-inline==0.2.2
178
- multiprocess==0.70.16
179
- mdurl==0.1.2
180
- platformdirs==4.9.6
181
- nvidia-cufft-cu12==11.3.3.41
182
- nvidia-cuda-runtime-cu12==12.8.57
183
- accelerate==1.12.0
184
- xxhash==3.7.0
185
- wandb==0.23.1
186
- cloudpickle==3.1.2
187
- networkx==3.4.2
188
- ptyprocess==0.7.0
189
- termcolor==2.5.0
190
- tomli==2.4.1
191
- async-timeout==5.0.1
192
- typing_extensions==4.15.0
193
- wcwidth==0.7.0
194
- comm==0.2.3
195
- py-cpuinfo==9.0.0
196
- decorator==5.3.1
197
- prompt_toolkit==3.0.52
198
- nvidia-cublas-cu12==12.8.3.14
199
- hydra-core==1.3.2
200
- fastjsonschema==2.21.2
201
- filelock==3.29.0
202
- httpx==0.28.1
203
- attrs==26.1.0
204
- pybind11==3.0.4
205
- yarl==1.24.2
206
- executing==2.2.1
207
- protobuf==6.33.6
208
- colorlog==6.10.1
209
- lxml==6.1.1
210
- pyquaternion==0.9.9
211
- wheel==0.46.3
212
- pip==26.0.1
213
- huggingface-hub==0.29.2
214
- fastwam==0.1.0
215
- autocommand==2.2.2
216
- backports.tarfile==1.2.0
217
- importlib_metadata==8.7.1
218
- jaraco.text==4.0.0
219
- jaraco.context==6.1.0
220
- jaraco.functools==4.4.0
221
- more-itertools==10.8.0
222
- packaging==26.0
223
- platformdirs==4.4.0
224
- tomli==2.4.0
225
- wheel==0.46.3
226
- zipp==3.23.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/splits/dish_utensil_lerobot_v21.json DELETED
@@ -1,118 +0,0 @@
1
- {
2
- "dataset": "dish_utensil_lerobot_v21",
3
- "dataset_dir": "/shared_work/amin-a/realworld-wam/data/dish_utensil_lerobot_v21",
4
- "generated": "2026-09-10T20:23:03Z",
5
- "seed": 42,
6
- "val_proportion": 0.1,
7
- "recipe": "sorted(episode_ids) shuffled by np.random.default_rng(seed), split at int(total_episodes * (1 - val_proportion)); matches fastwam base_lerobot_dataset.py",
8
- "total_episodes": 100,
9
- "total_frames": 42863,
10
- "train_episodes": 90,
11
- "train_frames": 37936,
12
- "val_episodes": 10,
13
- "val_frames": 4927,
14
- "train": [
15
- 0,
16
- 1,
17
- 2,
18
- 3,
19
- 4,
20
- 5,
21
- 6,
22
- 7,
23
- 9,
24
- 10,
25
- 11,
26
- 15,
27
- 16,
28
- 17,
29
- 18,
30
- 19,
31
- 20,
32
- 21,
33
- 22,
34
- 23,
35
- 24,
36
- 25,
37
- 26,
38
- 27,
39
- 28,
40
- 29,
41
- 30,
42
- 31,
43
- 32,
44
- 33,
45
- 34,
46
- 35,
47
- 37,
48
- 38,
49
- 39,
50
- 40,
51
- 41,
52
- 42,
53
- 43,
54
- 44,
55
- 45,
56
- 46,
57
- 48,
58
- 49,
59
- 50,
60
- 51,
61
- 52,
62
- 54,
63
- 55,
64
- 56,
65
- 57,
66
- 58,
67
- 59,
68
- 60,
69
- 61,
70
- 62,
71
- 63,
72
- 64,
73
- 67,
74
- 68,
75
- 69,
76
- 70,
77
- 71,
78
- 72,
79
- 73,
80
- 74,
81
- 75,
82
- 76,
83
- 78,
84
- 79,
85
- 80,
86
- 81,
87
- 82,
88
- 83,
89
- 84,
90
- 85,
91
- 86,
92
- 87,
93
- 88,
94
- 89,
95
- 90,
96
- 91,
97
- 92,
98
- 93,
99
- 94,
100
- 95,
101
- 96,
102
- 97,
103
- 98,
104
- 99
105
- ],
106
- "val": [
107
- 8,
108
- 12,
109
- 13,
110
- 14,
111
- 36,
112
- 47,
113
- 53,
114
- 65,
115
- 66,
116
- 77
117
- ]
118
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/splits/fruit_pick_lerobot_v21.json DELETED
@@ -1,118 +0,0 @@
1
- {
2
- "dataset": "fruit_pick_lerobot_v21",
3
- "dataset_dir": "/shared_work/amin-a/realworld-wam/data/fruit_pick_lerobot_v21",
4
- "generated": "2026-09-10T20:23:10Z",
5
- "seed": 42,
6
- "val_proportion": 0.1,
7
- "recipe": "sorted(episode_ids) shuffled by np.random.default_rng(seed), split at int(total_episodes * (1 - val_proportion)); matches fastwam base_lerobot_dataset.py",
8
- "total_episodes": 100,
9
- "total_frames": 20149,
10
- "train_episodes": 90,
11
- "train_frames": 17863,
12
- "val_episodes": 10,
13
- "val_frames": 2286,
14
- "train": [
15
- 0,
16
- 1,
17
- 2,
18
- 3,
19
- 4,
20
- 5,
21
- 6,
22
- 7,
23
- 9,
24
- 10,
25
- 11,
26
- 15,
27
- 16,
28
- 17,
29
- 18,
30
- 19,
31
- 20,
32
- 21,
33
- 22,
34
- 23,
35
- 24,
36
- 25,
37
- 26,
38
- 27,
39
- 28,
40
- 29,
41
- 30,
42
- 31,
43
- 32,
44
- 33,
45
- 34,
46
- 35,
47
- 37,
48
- 38,
49
- 39,
50
- 40,
51
- 41,
52
- 42,
53
- 43,
54
- 44,
55
- 45,
56
- 46,
57
- 48,
58
- 49,
59
- 50,
60
- 51,
61
- 52,
62
- 54,
63
- 55,
64
- 56,
65
- 57,
66
- 58,
67
- 59,
68
- 60,
69
- 61,
70
- 62,
71
- 63,
72
- 64,
73
- 67,
74
- 68,
75
- 69,
76
- 70,
77
- 71,
78
- 72,
79
- 73,
80
- 74,
81
- 75,
82
- 76,
83
- 78,
84
- 79,
85
- 80,
86
- 81,
87
- 82,
88
- 83,
89
- 84,
90
- 85,
91
- 86,
92
- 87,
93
- 88,
94
- 89,
95
- 90,
96
- 91,
97
- 92,
98
- 93,
99
- 94,
100
- 95,
101
- 96,
102
- 97,
103
- 98,
104
- 99
105
- ],
106
- "val": [
107
- 8,
108
- 12,
109
- 13,
110
- 14,
111
- 36,
112
- 47,
113
- 53,
114
- 65,
115
- 66,
116
- 77
117
- ]
118
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/splits/place_cube_lerobot_v21.json DELETED
@@ -1,118 +0,0 @@
1
- {
2
- "dataset": "place_cube_lerobot_v21",
3
- "dataset_dir": "/shared_work/amin-a/realworld-wam/data/place_cube_lerobot_v21",
4
- "generated": "2026-09-10T20:23:10Z",
5
- "seed": 42,
6
- "val_proportion": 0.1,
7
- "recipe": "sorted(episode_ids) shuffled by np.random.default_rng(seed), split at int(total_episodes * (1 - val_proportion)); matches fastwam base_lerobot_dataset.py",
8
- "total_episodes": 100,
9
- "total_frames": 9267,
10
- "train_episodes": 90,
11
- "train_frames": 8362,
12
- "val_episodes": 10,
13
- "val_frames": 905,
14
- "train": [
15
- 0,
16
- 1,
17
- 2,
18
- 3,
19
- 4,
20
- 5,
21
- 6,
22
- 7,
23
- 9,
24
- 10,
25
- 11,
26
- 15,
27
- 16,
28
- 17,
29
- 18,
30
- 19,
31
- 20,
32
- 21,
33
- 22,
34
- 23,
35
- 24,
36
- 25,
37
- 26,
38
- 27,
39
- 28,
40
- 29,
41
- 30,
42
- 31,
43
- 32,
44
- 33,
45
- 34,
46
- 35,
47
- 37,
48
- 38,
49
- 39,
50
- 40,
51
- 41,
52
- 42,
53
- 43,
54
- 44,
55
- 45,
56
- 46,
57
- 48,
58
- 49,
59
- 50,
60
- 51,
61
- 52,
62
- 54,
63
- 55,
64
- 56,
65
- 57,
66
- 58,
67
- 59,
68
- 60,
69
- 61,
70
- 62,
71
- 63,
72
- 64,
73
- 67,
74
- 68,
75
- 69,
76
- 70,
77
- 71,
78
- 72,
79
- 73,
80
- 74,
81
- 75,
82
- 76,
83
- 78,
84
- 79,
85
- 80,
86
- 81,
87
- 82,
88
- 83,
89
- 84,
90
- 85,
91
- 86,
92
- 87,
93
- 88,
94
- 89,
95
- 90,
96
- 91,
97
- 92,
98
- 93,
99
- 94,
100
- 95,
101
- 96,
102
- 97,
103
- 98,
104
- 99
105
- ],
106
- "val": [
107
- 8,
108
- 12,
109
- 13,
110
- 14,
111
- 36,
112
- 47,
113
- 53,
114
- 65,
115
- 66,
116
- 77
117
- ]
118
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/splits/push_t_lerobot_v21.json DELETED
@@ -1,118 +0,0 @@
1
- {
2
- "dataset": "push_t_lerobot_v21",
3
- "dataset_dir": "/shared_work/amin-a/realworld-wam/data/push_t_lerobot_v21",
4
- "generated": "2026-09-10T20:23:10Z",
5
- "seed": 42,
6
- "val_proportion": 0.1,
7
- "recipe": "sorted(episode_ids) shuffled by np.random.default_rng(seed), split at int(total_episodes * (1 - val_proportion)); matches fastwam base_lerobot_dataset.py",
8
- "total_episodes": 100,
9
- "total_frames": 32131,
10
- "train_episodes": 90,
11
- "train_frames": 29610,
12
- "val_episodes": 10,
13
- "val_frames": 2521,
14
- "train": [
15
- 0,
16
- 1,
17
- 2,
18
- 3,
19
- 4,
20
- 5,
21
- 6,
22
- 7,
23
- 9,
24
- 10,
25
- 11,
26
- 15,
27
- 16,
28
- 17,
29
- 18,
30
- 19,
31
- 20,
32
- 21,
33
- 22,
34
- 23,
35
- 24,
36
- 25,
37
- 26,
38
- 27,
39
- 28,
40
- 29,
41
- 30,
42
- 31,
43
- 32,
44
- 33,
45
- 34,
46
- 35,
47
- 37,
48
- 38,
49
- 39,
50
- 40,
51
- 41,
52
- 42,
53
- 43,
54
- 44,
55
- 45,
56
- 46,
57
- 48,
58
- 49,
59
- 50,
60
- 51,
61
- 52,
62
- 54,
63
- 55,
64
- 56,
65
- 57,
66
- 58,
67
- 59,
68
- 60,
69
- 61,
70
- 62,
71
- 63,
72
- 64,
73
- 67,
74
- 68,
75
- 69,
76
- 70,
77
- 71,
78
- 72,
79
- 73,
80
- 74,
81
- 75,
82
- 76,
83
- 78,
84
- 79,
85
- 80,
86
- 81,
87
- 82,
88
- 83,
89
- 84,
90
- 85,
91
- 86,
92
- 87,
93
- 88,
94
- 89,
95
- 90,
96
- 91,
97
- 92,
98
- 93,
99
- 94,
100
- 95,
101
- 96,
102
- 97,
103
- 98,
104
- 99
105
- ],
106
- "val": [
107
- 8,
108
- 12,
109
- 13,
110
- 14,
111
- 36,
112
- 47,
113
- 53,
114
- 65,
115
- 66,
116
- 77
117
- ]
118
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/LICENSE DELETED
@@ -1,24 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 The FastWAM Authors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- Except for third-party code and files that retain their original license
16
- headers, this repository is released under the MIT License.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
- SOFTWARE.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/pyproject.toml DELETED
@@ -1,53 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools>=68", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "fastwam"
7
- version = "0.1.0"
8
- description = "FastWAM: standalone Wan2.2-TI2V-5B training/inference package"
9
- requires-python = ">=3.10"
10
- dependencies = [
11
- "accelerate==1.12.0",
12
- "av==16.0.1",
13
- "boto3==1.35.99",
14
- "datasets==3.6.0",
15
- "deepspeed==0.18.5",
16
- "einops==0.8.1",
17
- "gitpython==3.1.45",
18
- "huggingface-hub==0.29.2",
19
- "hydra-core==1.3.2",
20
- "imageio==2.37.0",
21
- "imageio-ffmpeg==0.6.0",
22
- "jsonlines==4.0.0",
23
- "modelscope==1.34.0",
24
- "numpy==1.26.4",
25
- "omegaconf==2.3.0",
26
- "packaging==25.0",
27
- "pandas==2.2.3",
28
- "pillow==12.0.0",
29
- "pyarrow==23.0.0",
30
- "regex==2025.11.3",
31
- "rich==14.2.0",
32
- "safetensors==0.5.3",
33
- "termcolor==2.5.0",
34
- "torch==2.7.1+cu128",
35
- "torchcodec==0.5",
36
- "torchvision==0.22.1+cu128",
37
- "tqdm==4.66.5",
38
- "transformers==4.49.0",
39
- "typing-extensions==4.15.0",
40
- "wandb==0.23.1",
41
- ]
42
-
43
- [tool.setuptools.package-dir]
44
- "" = "src"
45
-
46
- [tool.setuptools.packages.find]
47
- where = ["src"]
48
- include = ["fastwam*"]
49
-
50
- [tool.pytest.ini_options]
51
- markers = [
52
- "slow: marks tests as slow (deselect with -m 'not slow')",
53
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/scripts/train.py DELETED
@@ -1,24 +0,0 @@
1
- import sys
2
- from pathlib import Path
3
-
4
- import hydra
5
- from omegaconf import DictConfig
6
-
7
- PROJECT_ROOT = Path(__file__).resolve().parents[1]
8
- SRC_DIR = str(PROJECT_ROOT / "src")
9
- if SRC_DIR not in sys.path:
10
- sys.path.insert(0, SRC_DIR)
11
-
12
- from fastwam.runtime import run_training
13
- from fastwam.utils.config_resolvers import register_default_resolvers
14
-
15
- register_default_resolvers()
16
-
17
-
18
- @hydra.main(config_path="../configs", config_name="train", version_base="1.3")
19
- def main(cfg: DictConfig):
20
- run_training(cfg)
21
-
22
-
23
- if __name__ == "__main__":
24
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/__init__.py DELETED
@@ -1 +0,0 @@
1
- """FastWAM package."""
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/__init__.py DELETED
File without changes
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/aligned_3dpft.py DELETED
@@ -1,91 +0,0 @@
1
- """Opt-in LIBERO dataset context for ``aligned_3dpft``."""
2
-
3
- from __future__ import annotations
4
-
5
- import numpy as np
6
- import torch
7
-
8
- from fastwam.geometry.flow_context import build_flow_trajectory_context
9
- from fastwam.geometry.flow_trajectory import LIBERO_OSC_POSITION_SCALE
10
-
11
- from .lerobot.robot_video_dataset import DEFAULT_PROMPT, RobotVideoDataset
12
-
13
-
14
- class Aligned3DPFTRobotVideoDataset(RobotVideoDataset):
15
- """Attach observed cameras and normalization affine to each training sample."""
16
-
17
- def __init__(self, *args, **kwargs):
18
- if not kwargs.get("eef_anchor_calibration_path"):
19
- raise ValueError(
20
- "Aligned3DPFTRobotVideoDataset requires eef_anchor_calibration_path"
21
- )
22
- # Optional geometry-channel augmentation: gaussian noise on the observed
23
- # EEF pose BEFORE the flow context is built, as [position_std_m,
24
- # orientation_std_rad]. Perturbs only the anchor geometry (trajectory
25
- # origin + reconstructed wrist camera); the proprio the model consumes
26
- # is untouched. Teaches partial trust in the anchor under sensor noise.
27
- noise = kwargs.pop("aligned_3dpft_eef_noise_std", None)
28
- if noise is not None:
29
- noise = tuple(float(value) for value in noise)
30
- if len(noise) != 2 or any(value < 0 for value in noise):
31
- raise ValueError(
32
- "aligned_3dpft_eef_noise_std must be "
33
- f"[position_std_m>=0, orientation_std_rad>=0], got {noise}"
34
- )
35
- if noise == (0.0, 0.0):
36
- noise = None
37
- self.aligned_3dpft_eef_noise_std = noise
38
- super().__init__(*args, **kwargs)
39
-
40
- @staticmethod
41
- def _merged_normalization(processor, field: str) -> tuple[torch.Tensor, torch.Tensor]:
42
- meta_entries = processor.shape_meta[field]
43
- scales = []
44
- offsets = []
45
- for meta in meta_entries:
46
- normalizer = processor.normalizer.normalizers[field][meta["key"]]
47
- scales.append(torch.as_tensor(normalizer.scale, dtype=torch.float32))
48
- offsets.append(torch.as_tensor(normalizer.offset, dtype=torch.float32))
49
- return torch.cat(scales, dim=-1), torch.cat(offsets, dim=-1)
50
-
51
- def _get(self, idx):
52
- data = super()._get(idx)
53
- if self.precompute_video_only:
54
- return data
55
- if self.eef_anchor_index is None:
56
- raise RuntimeError("aligned_3dpft EEF projector was not initialized")
57
- processor = self.lerobot_dataset.processor
58
- if processor is None:
59
- raise RuntimeError("aligned_3dpft requires a configured processor")
60
-
61
- state_scale, state_offset = self._merged_normalization(processor, "state")
62
- normalized_state = torch.as_tensor(data["proprio"][0], dtype=torch.float32)
63
- raw_state = (normalized_state[: state_scale.numel()] - state_offset) / state_scale
64
- if raw_state.numel() < 6:
65
- raise ValueError("aligned_3dpft requires at least six EEF state dimensions")
66
-
67
- prefix = DEFAULT_PROMPT.split("{task}", 1)[0]
68
- prompt = str(data["prompt"])
69
- if not prompt.startswith(prefix):
70
- raise ValueError(f"unexpected training prompt format: {prompt!r}")
71
- task_language = prompt[len(prefix):]
72
- eef_state = raw_state[:6].numpy().copy()
73
- if self.aligned_3dpft_eef_noise_std is not None:
74
- position_std, orientation_std = self.aligned_3dpft_eef_noise_std
75
- eef_state[:3] += np.random.normal(0.0, position_std, size=3)
76
- eef_state[3:6] += np.random.normal(0.0, orientation_std, size=3)
77
- context = build_flow_trajectory_context(
78
- self.eef_anchor_index.projector,
79
- task_language,
80
- eef_state,
81
- )
82
- action_scale, action_offset = self._merged_normalization(processor, "action")
83
- context["action_scale"] = action_scale.numpy()
84
- context["action_offset"] = action_offset.numpy()
85
- # Denormalized LIBERO xyz actions are OSC commands, not meters.
86
- context["action_translation_scale"] = np.float32(LIBERO_OSC_POSITION_SCALE)
87
- data["aligned_3dpft_context"] = {
88
- key: torch.as_tensor(np.asarray(value, dtype=np.float32).copy())
89
- for key, value in context.items()
90
- }
91
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/dataset_utils.py DELETED
@@ -1,109 +0,0 @@
1
-
2
- from typing import Optional
3
-
4
- import torch
5
- import torchvision.transforms.functional as transforms_F
6
- from PIL import Image
7
-
8
-
9
- def obtain_image_size(data: torch.Tensor | Image.Image) -> tuple[int, int]:
10
- r"""Return spatial size from a PIL image or image/video tensor.
11
-
12
- Args:
13
- data (torch.Tensor | Image.Image): Input image or video tensor.
14
- Returns:
15
- width (int): Input width.
16
- height (int): Input height.
17
- """
18
-
19
- if isinstance(data, Image.Image):
20
- width, height = data.size
21
- elif isinstance(data, torch.Tensor):
22
- height, width = data.size()[-2:]
23
- else:
24
- raise ValueError("data to random crop should be PIL Image or tensor")
25
-
26
- return width, height
27
-
28
- class ResizeSmallestSideAspectPreserving:
29
- def __init__(self, args: Optional[dict] = None) -> None:
30
- self.args = args
31
-
32
- def __call__(self, video: torch.Tensor | Image.Image) -> torch.Tensor | Image.Image:
33
- r"""Resize while preserving aspect ratio.
34
-
35
- The output is scaled so both spatial dimensions are at least the
36
- requested target size.
37
-
38
- Args:
39
- video (torch.Tensor | Image.Image): Input image or video tensor.
40
- Returns:
41
- torch.Tensor | Image.Image: Resized image or video tensor.
42
- """
43
-
44
- assert self.args is not None, "Please specify args in augmentations"
45
-
46
- img_w, img_h = self.args["img_w"], self.args["img_h"]
47
-
48
- orig_w, orig_h = obtain_image_size(video)
49
- scaling_ratio = max((img_w / orig_w), (img_h / orig_h))
50
- target_size = (int(scaling_ratio * orig_h + 0.5), int(scaling_ratio * orig_w + 0.5))
51
-
52
- assert (
53
- target_size[0] >= img_h and target_size[1] >= img_w
54
- ), f"Resize error. orig {(orig_w, orig_h)} desire {(img_w, img_h)} compute {target_size}"
55
-
56
- return transforms_F.resize(
57
- video,
58
- size=target_size, # type: ignore
59
- interpolation=self.args.get("interpolation", transforms_F.InterpolationMode.BICUBIC),
60
- antialias=True,
61
- )
62
-
63
-
64
- class CenterCrop:
65
- def __init__(self, args: Optional[dict] = None) -> None:
66
- self.args = args
67
-
68
- def __call__(self, video: torch.Tensor | Image.Image) -> torch.Tensor | Image.Image:
69
- r"""Center crop to the requested spatial size.
70
-
71
- Args:
72
- video (torch.Tensor | Image.Image): Input image or video tensor.
73
- Returns:
74
- torch.Tensor | Image.Image: Center cropped image or video tensor.
75
- """
76
- assert (
77
- (self.args is not None) and ("img_w" in self.args) and ("img_h" in self.args)
78
- ), "Please specify size in args"
79
-
80
- img_w, img_h = self.args["img_w"], self.args["img_h"]
81
- return transforms_F.center_crop(video, [img_h, img_w])
82
-
83
-
84
- class Normalize:
85
- def __init__(self, args: Optional[dict] = None) -> None:
86
- self.args = args
87
-
88
- def __call__(self, video: torch.Tensor | Image.Image) -> torch.Tensor:
89
- r"""Convert to tensor if needed and normalize by mean/std.
90
-
91
- Args:
92
- video (torch.Tensor | Image.Image): Input image or video tensor.
93
- Returns:
94
- torch.Tensor: Normalized image or video tensor.
95
- """
96
- assert self.args is not None, "Please specify args"
97
-
98
- mean = self.args["mean"]
99
- std = self.args["std"]
100
-
101
- if isinstance(video, torch.Tensor):
102
- data = video.to(dtype=torch.float32)
103
- if video.dtype == torch.uint8:
104
- data = data / 255.0
105
- data = data.to(dtype=torch.get_default_dtype())
106
- else:
107
- data = transforms_F.to_tensor(video) # division by 255 is applied in to_tensor()
108
-
109
- return transforms_F.normalize(tensor=data, mean=mean, std=std)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/eef_anchors.py DELETED
@@ -1,310 +0,0 @@
1
- # Author: Rui Heng Yang
2
- """Load-time per-episode EEF anchor resolution.
3
-
4
- There is no sidecar. Anchors are projected and hold-resolved once per episode at
5
- dataset initialisation and held in RAM (plan Section 15). Projection costs about
6
- 35 microseconds per frame, so precomputing to disk bought nothing while adding
7
- stale-artifact and partial-write failure modes.
8
-
9
- Two traps this module exists to close:
10
-
11
- * **Episode identity is ``(dataset_index, episode_index)``, not
12
- ``episode_index``.** A ``MultiLeRobotDataset`` concatenates several LeRobot
13
- directories, each numbering its episodes from zero, so ``episode_index`` alone
14
- collides across directories and would silently pair one suite's frames with
15
- another suite's gripper positions.
16
- * **The anchor must key off the RETURNED sample, not the requested index.**
17
- Both ``RobotVideoDataset._get()`` and ``__getitem__()`` resample to a random
18
- global index on padding or on exception, so a lookup by requested ``idx``
19
- attaches geometry from whichever episode was originally asked for -- with no
20
- error and no log.
21
- """
22
-
23
- from __future__ import annotations
24
-
25
- import glob
26
- import json
27
- import logging
28
- import os
29
- from dataclasses import dataclass, field
30
- from typing import Any, Mapping, Sequence
31
-
32
- import numpy as np
33
-
34
- from fastwam.geometry import (
35
- CAMERA_ORDER,
36
- CalibrationTable,
37
- EEFProjector,
38
- load_calibration,
39
- resolve_anchor_series,
40
- )
41
-
42
- logger = logging.getLogger(__name__)
43
-
44
- EEF_POSE_DIMS = 6
45
- """``observation.state[0:3]`` is metric position, ``[3:6]`` axis-angle."""
46
-
47
-
48
- @dataclass
49
- class AnchorResolutionStats:
50
- """Substitution accounting, so no substitution is ever silent (Section 19.2)."""
51
-
52
- episodes: int = 0
53
- frames: int = 0
54
- observed_frames: int = 0
55
- held_offscreen_frames: int = 0
56
- centre_default_frames: int = 0
57
- max_consecutive_held: int = 0
58
- per_camera_observed: dict[str, int] = field(default_factory=dict)
59
-
60
- def as_dict(self) -> dict[str, Any]:
61
- return {
62
- "episodes": self.episodes,
63
- "frames": self.frames,
64
- "observed_frames": self.observed_frames,
65
- "held_offscreen_frames": self.held_offscreen_frames,
66
- "centre_default_frames": self.centre_default_frames,
67
- "max_consecutive_held": self.max_consecutive_held,
68
- "per_camera_observed": dict(self.per_camera_observed),
69
- }
70
-
71
-
72
- class EpisodeAnchorIndex:
73
- """Resolved per-episode anchors for every episode of every dataset dir.
74
-
75
- Keyed by ``(dataset_index, episode_index)``; ``lookup()`` then indexes the
76
- episode's series at the sample's own ``frame_index``.
77
- """
78
-
79
- def __init__(
80
- self,
81
- *,
82
- calibration: CalibrationTable,
83
- projector: EEFProjector,
84
- anchors: Mapping[tuple[int, int], np.ndarray],
85
- stats: AnchorResolutionStats,
86
- ) -> None:
87
- self.calibration = calibration
88
- self.projector = projector
89
- self._anchors = dict(anchors)
90
- self.stats = stats
91
-
92
- @property
93
- def calibration_digest(self) -> str:
94
- """Content digest of the calibration this index was resolved against."""
95
- return self.calibration.digest
96
-
97
- def __len__(self) -> int:
98
- return len(self._anchors)
99
-
100
- def lookup(
101
- self, dataset_index: int, episode_index: int, frame_index: int
102
- ) -> np.ndarray:
103
- """Return the ``[2, 2]`` float32 anchor for one frame, as ``(y, x)``.
104
-
105
- Args:
106
- dataset_index: Which LeRobot directory the returned sample came from.
107
- episode_index: Episode index **within that directory**.
108
- frame_index: Episode-local frame index of the sample's ``t0``.
109
-
110
- Raises:
111
- KeyError: If the episode was not resolved at initialisation.
112
- IndexError: If ``frame_index`` lies outside the episode.
113
- """
114
- key = (int(dataset_index), int(episode_index))
115
- try:
116
- series = self._anchors[key]
117
- except KeyError as err:
118
- raise KeyError(
119
- f"no resolved anchors for (dataset_index={dataset_index}, "
120
- f"episode_index={episode_index}); the anchor index and the "
121
- "dataset disagree about which episodes exist"
122
- ) from err
123
- frame = int(frame_index)
124
- if not 0 <= frame < series.shape[0]:
125
- raise IndexError(
126
- f"frame_index {frame} outside episode "
127
- f"(dataset_index={dataset_index}, episode_index={episode_index}) "
128
- f"of length {series.shape[0]}"
129
- )
130
- return series[frame]
131
-
132
- # ------------------------------------------------------------------
133
- # Construction
134
- # ------------------------------------------------------------------
135
-
136
- @classmethod
137
- def build(
138
- cls,
139
- dataset_dirs: Sequence[str],
140
- *,
141
- calibration_path: str,
142
- projector: EEFProjector | None = None,
143
- episode_filter: Mapping[int, Sequence[int]] | None = None,
144
- ) -> "EpisodeAnchorIndex":
145
- """Project and hold-resolve every episode of every directory.
146
-
147
- Args:
148
- dataset_dirs: LeRobot directories, in the order the dataset stacks
149
- them -- this ordering defines ``dataset_index``.
150
- calibration_path: Versioned calibration artifact (Section 15.3).
151
- projector: Defaults to the training projector (raw 512).
152
- episode_filter: Optional ``{dataset_index: [episode_index, ...]}``
153
- restricting work to the episodes actually selected by the split.
154
-
155
- Raises:
156
- InvalidProjectionError: If any frame projects to a nonfinite
157
- coordinate or at/behind the camera plane (Section 19.1
158
- Decision 1). This is a hard error by design: substituting a
159
- stale anchor would convert a detectable geometry fault into
160
- silently wrong training data.
161
- """
162
- calibration = load_calibration(calibration_path)
163
- if projector is None:
164
- projector = EEFProjector.for_training(calibration)
165
- if tuple(projector.camera_order) != CAMERA_ORDER:
166
- raise ValueError(
167
- f"camera order is LOCKED to {CAMERA_ORDER}, got "
168
- f"{tuple(projector.camera_order)}"
169
- )
170
-
171
- anchors: dict[tuple[int, int], np.ndarray] = {}
172
- stats = AnchorResolutionStats(
173
- per_camera_observed={name: 0 for name in CAMERA_ORDER}
174
- )
175
-
176
- for dataset_index, root in enumerate(dataset_dirs):
177
- wanted = None
178
- if episode_filter is not None:
179
- wanted = {int(e) for e in episode_filter.get(dataset_index, ())}
180
- cls._resolve_one_directory(
181
- root=root,
182
- dataset_index=dataset_index,
183
- projector=projector,
184
- anchors=anchors,
185
- stats=stats,
186
- wanted_episodes=wanted,
187
- )
188
-
189
- logger.info(
190
- "EEF anchors resolved at load time: %d episodes, %d frames, "
191
- "%d observed, %d held-offscreen, %d centre-default, "
192
- "longest hold %d frames (calibration %s)",
193
- stats.episodes,
194
- stats.frames,
195
- stats.observed_frames,
196
- stats.held_offscreen_frames,
197
- stats.centre_default_frames,
198
- stats.max_consecutive_held,
199
- calibration.digest[:12],
200
- )
201
- return cls(
202
- calibration=calibration,
203
- projector=projector,
204
- anchors=anchors,
205
- stats=stats,
206
- )
207
-
208
- @staticmethod
209
- def _episode_task_languages(root: str) -> dict[int, str]:
210
- """Map episode index to its task language string.
211
-
212
- Calibration is joined by **language string**, never ``task_index``:
213
- the dataset's task ordering differs from the benchmark's, so an
214
- index join silently selects another task's camera pose (Section 6.1).
215
- """
216
- path = os.path.join(root, "meta", "episodes.jsonl")
217
- languages: dict[int, str] = {}
218
- with open(path, "r", encoding="utf-8") as handle:
219
- for line in handle:
220
- line = line.strip()
221
- if not line:
222
- continue
223
- record = json.loads(line)
224
- tasks = record.get("tasks") or []
225
- if not tasks:
226
- raise ValueError(
227
- f"episode {record.get('episode_index')} in {root} has no "
228
- "task language; calibration cannot be joined"
229
- )
230
- languages[int(record["episode_index"])] = str(tasks[0])
231
- if not languages:
232
- raise ValueError(f"no episodes found in {path}")
233
- return languages
234
-
235
- @classmethod
236
- def _resolve_one_directory(
237
- cls,
238
- *,
239
- root: str,
240
- dataset_index: int,
241
- projector: EEFProjector,
242
- anchors: dict[tuple[int, int], np.ndarray],
243
- stats: AnchorResolutionStats,
244
- wanted_episodes: set[int] | None,
245
- ) -> None:
246
- import pandas as pd # local: keeps the module importable without pandas
247
-
248
- languages = cls._episode_task_languages(root)
249
- files = sorted(
250
- glob.glob(os.path.join(root, "data", "**", "*.parquet"), recursive=True)
251
- )
252
- if not files:
253
- raise FileNotFoundError(f"no parquet shards under {root}/data")
254
-
255
- columns = ["episode_index", "frame_index", "observation.state"]
256
- frames = pd.concat(
257
- (pd.read_parquet(path, columns=columns) for path in files),
258
- ignore_index=True,
259
- )
260
-
261
- for episode_index, group in frames.groupby("episode_index", sort=True):
262
- episode_index = int(episode_index)
263
- if wanted_episodes is not None and episode_index not in wanted_episodes:
264
- continue
265
- ordered = group.sort_values("frame_index")
266
- state = np.stack(
267
- [np.asarray(row, dtype=np.float64) for row in ordered["observation.state"]]
268
- )
269
- if state.ndim != 2 or state.shape[1] < EEF_POSE_DIMS:
270
- raise ValueError(
271
- f"observation.state for episode {episode_index} in {root} has "
272
- f"shape {state.shape}; expected [N, >= {EEF_POSE_DIMS}]"
273
- )
274
-
275
- language = languages.get(episode_index)
276
- if language is None:
277
- raise KeyError(
278
- f"episode {episode_index} present in {root}/data but absent "
279
- "from meta/episodes.jsonl; cannot resolve its task language"
280
- )
281
-
282
- projection = projector.project(language, state)
283
- resolved = np.empty(projection.anchor_token.shape, dtype=np.float32)
284
- episode_max_hold = 0
285
- for camera_index, camera_name in enumerate(CAMERA_ORDER):
286
- series, camera_stats = resolve_anchor_series(
287
- projection.anchor_token_precise[:, camera_index, :],
288
- projection.onscreen[:, camera_index],
289
- )
290
- resolved[:, camera_index, :] = series.astype(np.float32)
291
- stats.observed_frames += int(camera_stats.get("observed_frames", 0))
292
- stats.held_offscreen_frames += int(
293
- camera_stats.get("held_offscreen_frames", 0)
294
- )
295
- stats.centre_default_frames += int(
296
- camera_stats.get("centre_default_frames", 0)
297
- )
298
- stats.per_camera_observed[camera_name] += int(
299
- camera_stats.get("observed_frames", 0)
300
- )
301
- episode_max_hold = max(
302
- episode_max_hold, int(camera_stats.get("max_consecutive_held", 0))
303
- )
304
-
305
- anchors[(dataset_index, episode_index)] = resolved
306
- stats.episodes += 1
307
- stats.frames += int(state.shape[0])
308
- stats.max_consecutive_held = max(
309
- stats.max_consecutive_held, episode_max_hold
310
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/base_lerobot_dataset.py DELETED
@@ -1,517 +0,0 @@
1
- # Author: Rui Heng Yang
2
-
3
- import json
4
- import torch
5
- import numpy as np
6
- from pathlib import Path
7
- from typing import List, Literal, Dict, Optional, Any, DefaultDict, Sequence
8
- from tqdm import tqdm
9
- from .lerobot.lerobot_dataset import (
10
- LeRobotDatasetMetadata,
11
- MultiLeRobotDataset,
12
- dataset_dir_to_repo_id,
13
- )
14
-
15
- from concurrent.futures import ThreadPoolExecutor, as_completed
16
- import traceback
17
- from fastwam.utils.logging_config import get_logger
18
- from .processors.base_processor import BaseProcessor
19
-
20
- logger = get_logger(__name__)
21
-
22
- MAX_GETITEM_ATTEMPT = 5
23
-
24
- class BaseLerobotDataset(torch.utils.data.Dataset):
25
- def __init__(
26
- self,
27
- dataset_dirs: List[str],
28
-
29
- # shapes
30
- shape_meta: Dict[str, Any],
31
- action_size: int = 1,
32
- past_action_size: int = 0, # Excludes the current frame
33
- obs_size: int = 1, # should be
34
- past_obs_size: int = 0,
35
-
36
- # train vs val
37
- val_set_proportion: float = 0.05,
38
- is_training_set: bool = False,
39
- seed: int = 42,
40
- episode_indices: Optional[Sequence[int]] = None,
41
- episode_indices_path: Optional[str] = None,
42
- episode_indices_key: Optional[str] = None,
43
-
44
- # sampling
45
- global_sample_stride: int = 1,
46
- video_backend: Optional[str] = None,
47
- strict_getitem: bool = False,
48
- image_only: bool = False,
49
- image_sample_indices: Optional[Sequence[int]] = None,
50
- ):
51
- assert len(dataset_dirs) > 0, "At least one dataset directory is required"
52
- assert past_action_size == 0
53
- assert past_obs_size == 0
54
- assert action_size == obs_size - 1, "In this dataset, action_size should be obs_size - 1"
55
-
56
- self.dataset_dirs = dataset_dirs
57
- self.shape_meta = shape_meta
58
- self.action_size = action_size
59
- self.past_action_size = past_action_size
60
- self.obs_size = obs_size
61
- self.processor = None # Will be set externally
62
- self.return_images = True
63
- self.strict_getitem = bool(strict_getitem)
64
- self.image_only = bool(image_only)
65
- self.image_sample_indices = (
66
- None
67
- if image_sample_indices is None
68
- else [int(index) for index in image_sample_indices]
69
- )
70
- if self.image_only and not self.image_sample_indices:
71
- raise ValueError("image_only mode requires nonempty image_sample_indices.")
72
- metas = []
73
- for ds_dir in dataset_dirs:
74
- ds_root = Path(ds_dir)
75
- repo_id = dataset_dir_to_repo_id(ds_dir)
76
- meta = LeRobotDatasetMetadata(repo_id=repo_id, root=ds_root)
77
- metas.append(meta)
78
-
79
- fps_list = [m.fps for m in metas]
80
- assert len(set(fps_list)) == 1, f"All dataset_dirs must have the same fps, got {fps_list}"
81
- fps = fps_list[0]
82
-
83
- self.global_sample_stride = global_sample_stride
84
-
85
- self.val_set_proportion = val_set_proportion
86
- self.is_training_set = is_training_set
87
- self.episode_indices = self._load_episode_indices(
88
- episode_indices=episode_indices,
89
- episode_indices_path=episode_indices_path,
90
- episode_indices_key=episode_indices_key,
91
- )
92
-
93
- self.image_meta = shape_meta["images"]
94
- self.state_meta = shape_meta["state"]
95
- self.action_meta = shape_meta["action"]
96
-
97
- delta_timestamps = {}
98
- image_offsets = (
99
- range(-past_obs_size, -past_obs_size + obs_size)
100
- if self.image_sample_indices is None
101
- else self.image_sample_indices
102
- )
103
- for meta in self.image_meta:
104
- key = meta["key"]
105
- meta["lerobot_key"] = f"observation.images.{key}" if key != "default" else "observation.images"
106
- delta_timestamps[meta["lerobot_key"]] = [
107
- (t * global_sample_stride) / fps for t in image_offsets
108
- ]
109
-
110
- for meta in self.state_meta:
111
- key = meta["key"]
112
- meta["lerobot_key"] = f"observation.state.{key}" if key != "default" else "observation.state"
113
- if not self.image_only:
114
- delta_timestamps[meta["lerobot_key"]] = [
115
- (t * global_sample_stride) / fps for t in range(-past_obs_size, -past_obs_size + obs_size)
116
- ]
117
-
118
- for meta in self.action_meta:
119
- key = meta["key"]
120
- meta["lerobot_key"] = f"action.{key}" if key != "default" else "action"
121
- if not self.image_only:
122
- delta_timestamps[meta["lerobot_key"]] = [(t * global_sample_stride) / fps for t in range(-past_action_size, -past_action_size + action_size)]
123
-
124
- episodes = {}
125
- if self.episode_indices is not None:
126
- requested = sorted(set(int(i) for i in self.episode_indices))
127
- for meta in metas:
128
- available = set(meta.episodes.keys())
129
- valid = [i for i in requested if i in available]
130
- missing = len(requested) - len(valid)
131
- if missing:
132
- logger.warning(
133
- "Ignoring %d episode indices not present in %s",
134
- missing,
135
- meta.repo_id,
136
- )
137
- episodes.update({meta.repo_id: valid})
138
- elif val_set_proportion < 1e-6:
139
- for meta in metas:
140
- episodes.update({meta.repo_id: sorted(meta.episodes.keys())})
141
- else:
142
- for meta in metas:
143
- split_idx = int(meta.total_episodes * (1 - val_set_proportion))
144
- # random shuffle episode indices before splitting
145
- episode_indices = sorted(meta.episodes.keys())
146
- rng = np.random.default_rng(seed)
147
- rng.shuffle(episode_indices)
148
- if self.is_training_set:
149
- episodes.update({meta.repo_id: [episode_indices[i] for i in range(split_idx)]})
150
- else:
151
- episodes.update({meta.repo_id: [episode_indices[i] for i in range(split_idx, meta.total_episodes)]})
152
-
153
- self.multi_dataset = MultiLeRobotDataset(
154
- dataset_dirs=self.dataset_dirs,
155
- episodes=episodes,
156
- delta_timestamps=delta_timestamps,
157
- video_backend=video_backend,
158
- )
159
-
160
- # HACK: lerobot 3.0 will fix this
161
- episode_data_index = []
162
- end_index = 0
163
- for dataset in self.multi_dataset._datasets:
164
- multi_episode_data_index = {
165
- "from": dataset.episode_data_index["from"] + end_index,
166
- "to": dataset.episode_data_index["to"] + end_index,
167
- }
168
- episode_data_index.append(multi_episode_data_index)
169
- end_index = multi_episode_data_index["to"][-1]
170
-
171
- self.episode_data_index = {
172
- "from": torch.cat([dataset["from"] for dataset in episode_data_index]),
173
- "to": torch.cat([dataset["to"] for dataset in episode_data_index]),
174
- }
175
-
176
- @staticmethod
177
- def _load_episode_indices(
178
- *,
179
- episode_indices: Optional[Sequence[int]],
180
- episode_indices_path: Optional[str],
181
- episode_indices_key: Optional[str],
182
- ) -> Optional[list[int]]:
183
- if episode_indices is not None and episode_indices_path is not None:
184
- raise ValueError("Only one of `episode_indices` and `episode_indices_path` can be set.")
185
- if episode_indices is not None:
186
- return [int(i) for i in episode_indices]
187
- if episode_indices_path is None:
188
- return None
189
-
190
- path = Path(episode_indices_path)
191
- with path.open("r", encoding="utf-8") as f:
192
- payload = json.load(f)
193
- if episode_indices_key is not None:
194
- if episode_indices_key not in payload:
195
- raise KeyError(f"`{episode_indices_key}` not found in episode split file: {path}")
196
- payload = payload[episode_indices_key]
197
- if isinstance(payload, dict) and "episodes" in payload:
198
- payload = payload["episodes"]
199
- if not isinstance(payload, list):
200
- raise ValueError(
201
- f"Episode split in {path} must be a list, or a dict containing an `episodes` list."
202
- )
203
- return [int(i) for i in payload]
204
-
205
- def _get_action(self, meta, lerobot_sample) -> torch.Tensor:
206
- key, lerobot_key, raw_shape = meta["key"], meta["lerobot_key"], meta["raw_shape"]
207
- action: torch.Tensor = lerobot_sample[lerobot_key] # [T, action_dim]
208
- if action.ndim == 1: # for shape of 1, like gripper
209
- action = action.unsqueeze(-1)
210
- assert action.shape[-1] == raw_shape, f"Action '{key}' shape {action.shape[-1]} mismatch with meta {raw_shape}."
211
- return action
212
-
213
- def _get_state(self, meta, lerobot_sample) -> torch.Tensor:
214
- key, lerobot_key, raw_shape = meta["key"], meta["lerobot_key"], meta["raw_shape"]
215
- state: torch.Tensor = lerobot_sample[lerobot_key]
216
- if state.ndim == 1: # for shape of 1, like gripper
217
- state = state.unsqueeze(-1)
218
- # state = state[..., :-1, :] # use state_{t} as observation_t
219
- assert state.shape[-1] == raw_shape, f"State '{key}' shape {state.shape[-1]} mismatch with meta {raw_shape}."
220
- return state
221
-
222
- def _get_image(self, meta, lerobot_sample) -> torch.Tensor:
223
- key, lerobot_key, raw_shape = meta["key"], meta["lerobot_key"], meta["raw_shape"]
224
- image: torch.Tensor = lerobot_sample[lerobot_key]
225
- if image.ndim == 3: # time dim will lost when obs_size is 1
226
- image = image.unsqueeze(0)
227
- image = (image * 255).to(torch.uint8) # (1, 3, H, W)
228
- # For config simplication
229
- # assert image.shape[1:] == raw_shape, f"Image '{key}' shape {image.shape[1:]} mismatch with {raw_shape}."
230
- return image
231
-
232
- def _split_lerobot_sample(self, lerobot_sample) -> Dict[str, Any]:
233
- return lerobot_sample
234
-
235
- def _get_episode_data(self, episode_idx):
236
- lerobot_sample = self.multi_dataset.get_episode_data(episode_idx)
237
- lerobot_sample = self._split_lerobot_sample(lerobot_sample)
238
- state, action = {}, {}
239
- for meta in self.state_meta:
240
- s = self._get_state(meta, lerobot_sample)
241
- state[meta["key"]] = s.unsqueeze(1).float()
242
- for meta in self.action_meta:
243
- a = self._get_action(meta, lerobot_sample)
244
- a = sliding_window_with_replication(a, self.action_size)
245
- action[meta["key"]] = a.float()
246
- return {"action": action, "state": state}
247
-
248
- def _set_return_images(self, flag: bool):
249
- self.return_images = flag
250
- self.multi_dataset.set_during_training(flag)
251
-
252
- def __len__(self):
253
- return self.multi_dataset.num_frames
254
-
255
- def _get_additional_data(self, sample, lerobot_sample):
256
- return sample
257
-
258
- def __getitem__(self, idx):
259
- if idx >= len(self):
260
- raise IndexError(f"Index {idx} out of bounds {len(self)}.")
261
-
262
- # Cache construction and cache-backed training require index identity:
263
- # a requested global index must never silently resolve to another
264
- # sample. Other existing datasets retain the historical retry behavior.
265
- sample_idx = idx
266
- attempt = 0
267
- last_exception: Optional[Exception] = None
268
- strict_getitem = bool(getattr(self, "strict_getitem", False))
269
- max_attempts = 1 if strict_getitem else MAX_GETITEM_ATTEMPT
270
- while attempt < max_attempts:
271
- try:
272
- lerobot_sample = self.multi_dataset[sample_idx]
273
- lerobot_sample = self._split_lerobot_sample(lerobot_sample)
274
- break
275
- except Exception as err:
276
- attempt += 1
277
- last_exception = err
278
- if strict_getitem:
279
- raise RuntimeError(f"Strict sample load failed for index {idx}.") from err
280
- logger.warning(
281
- f"Error loading sample {sample_idx} (attempt {attempt}). "
282
- "Retrying with a random index. "
283
- f"Error: {err}"
284
- )
285
- sample_idx = np.random.randint(len(self))
286
- print(traceback.format_exc())
287
- else:
288
- raise RuntimeError(
289
- f"Failed to load a valid sample after {MAX_GETITEM_ATTEMPT} attempts "
290
- f"for index {idx}."
291
- ) from last_exception
292
-
293
- # Get data from lerobot, organized in nested dict
294
- sample = {
295
- "idx": sample_idx,
296
- "task": lerobot_sample["task"],
297
- "action": {},
298
- "state": {},
299
- "images": {},
300
- }
301
- image_only = bool(getattr(self, "image_only", False))
302
- if not image_only:
303
- for meta in self.state_meta:
304
- sample["state"][meta["key"]] = self._get_state(meta, lerobot_sample)
305
-
306
- for meta in self.action_meta:
307
- sample["action"][meta["key"]] = self._get_action(meta, lerobot_sample)
308
-
309
- if bool(getattr(self, "return_images", True)):
310
- for meta in self.image_meta:
311
- sample["images"][meta["key"]] = self._get_image(meta, lerobot_sample)
312
-
313
- sample["image_is_pad"] = lerobot_sample[f"{self.image_meta[0]['lerobot_key']}_is_pad"]
314
- if not image_only:
315
- sample["action_is_pad"] = lerobot_sample[f"{self.action_meta[0]['lerobot_key']}_is_pad"]
316
- sample["state_is_pad"] = lerobot_sample[f"{self.state_meta[0]['lerobot_key']}_is_pad"]
317
-
318
- sample = self._get_additional_data(sample, lerobot_sample)
319
-
320
- for key in lerobot_sample:
321
- if key not in sample and "observation" not in key and "action" not in key:
322
- sample[key] = lerobot_sample[key]
323
-
324
- # Preprocess the sample using the processor
325
- # for quick data loading
326
- if self.processor is not None:
327
- if image_only:
328
- pixel_values = self.processor.preprocess_images(
329
- sample["images"],
330
- expected_num_obs_steps=len(self.image_sample_indices),
331
- )
332
- sample = {
333
- "idx": sample["idx"],
334
- "pixel_values": pixel_values,
335
- "image_is_pad": sample["image_is_pad"],
336
- **{
337
- key: sample[key]
338
- for key in ("dataset_index", "episode_index", "frame_index")
339
- if key in sample
340
- },
341
- }
342
- else:
343
- sample = self.processor.preprocess(sample)
344
-
345
- return sample
346
-
347
- def set_processor(self, processor: BaseProcessor):
348
- """Set processor instance from external initialization."""
349
- self.processor = processor
350
- if self.is_training_set:
351
- self.processor.train()
352
- else:
353
- self.processor.eval()
354
- return self
355
-
356
- def get_dataset_stats(self, preprocessor: BaseProcessor):
357
- state_min = DefaultDict(list)
358
- state_max = DefaultDict(list)
359
- state_mean = DefaultDict(list)
360
- state_var = DefaultDict(list)
361
- state_q01 = DefaultDict(list)
362
- state_q99 = DefaultDict(list)
363
-
364
- action_min = DefaultDict(list)
365
- action_max = DefaultDict(list)
366
- action_mean = DefaultDict(list)
367
- action_var = DefaultDict(list)
368
- action_q01 = DefaultDict(list)
369
- action_q99 = DefaultDict(list)
370
-
371
- episodes_num = self.multi_dataset.num_episodes
372
-
373
- def process_episode(episode_idx):
374
- batch = self._get_episode_data(episode_idx)
375
- batch = preprocessor.action_state_transform(batch)
376
- return batch
377
-
378
- multi_thread = True
379
- if not multi_thread:
380
- for episode_idx in tqdm(range(episodes_num), desc="Iterating dataset to get normalization"):
381
- batch = process_episode(episode_idx)
382
- for meta in self.state_meta:
383
- key = meta["key"]
384
- cur_state: torch.Tensor = batch["state"][key] # (B, T, dim)
385
- state_min[key].append(cur_state.amin(0))
386
- state_max[key].append(cur_state.amax(0))
387
- state_mean[key].append(cur_state.mean(0))
388
- state_var[key].append(cur_state.var(0))
389
- state_q01[key].append(torch.quantile(cur_state, 0.01, dim=0, keepdim=False))
390
- state_q99[key].append(torch.quantile(cur_state, 0.99, dim=0, keepdim=False))
391
- for meta in self.action_meta:
392
- key = meta["key"]
393
- cur_action: torch.Tensor = batch["action"][key] # (B, T, dim)
394
- action_min[key].append(cur_action.amin(0))
395
- action_max[key].append(cur_action.amax(0))
396
- action_mean[key].append(cur_action.mean(0))
397
- action_var[key].append(cur_action.var(0))
398
- action_q01[key].append(torch.quantile(cur_action, 0.01, dim=0, keepdim=False))
399
- action_q99[key].append(torch.quantile(cur_action, 0.99, dim=0, keepdim=False))
400
-
401
- else:
402
- with ThreadPoolExecutor() as executor:
403
- futures = [executor.submit(process_episode, num) for num in range(episodes_num)]
404
-
405
- for future in tqdm(as_completed(futures), total=episodes_num, desc="Iterating dataset to get normalization"):
406
- try:
407
- batch = future.result()
408
- for meta in self.state_meta:
409
- key = meta["key"]
410
- cur_state: torch.Tensor = batch["state"][key] # (B, T, dim)
411
- state_min[key].append(cur_state.amin(0))
412
- state_max[key].append(cur_state.amax(0))
413
- state_mean[key].append(cur_state.mean(0))
414
- state_var[key].append(cur_state.var(0))
415
- state_q01[key].append(torch.quantile(cur_state, 0.01, dim=0, keepdim=False))
416
- state_q99[key].append(torch.quantile(cur_state, 0.99, dim=0, keepdim=False))
417
-
418
- for meta in self.action_meta:
419
- key = meta["key"]
420
- cur_action: torch.Tensor = batch["action"][key] # (B, T, dim)
421
- action_min[key].append(cur_action.amin(0))
422
- action_max[key].append(cur_action.amax(0))
423
- action_mean[key].append(cur_action.mean(0))
424
- action_var[key].append(cur_action.var(0))
425
- action_q01[key].append(torch.quantile(cur_action, 0.01, dim=0, keepdim=False))
426
- action_q99[key].append(torch.quantile(cur_action, 0.99, dim=0, keepdim=False))
427
-
428
- except Exception as e:
429
- logger.error(f"Error processing episode: {e}")
430
- print(traceback.format_exc())
431
- raise e
432
-
433
- # assume that each minibatch has equal number of samples
434
- def get_mean_std(means, vars):
435
- means = torch.stack(means)
436
- vars = torch.stack(vars)
437
- stepwise_mean = means.mean(0)
438
- stepwise_std = (vars + (means - stepwise_mean) ** 2).mean(0).sqrt()
439
- global_mean = means.mean((0, 1))
440
- global_std = (vars + (means - global_mean) ** 2).mean((0, 1)).sqrt()
441
- return stepwise_mean, stepwise_std, global_mean, global_std
442
-
443
- stats = {"state": DefaultDict(dict), "action": DefaultDict(dict), "num_episodes": episodes_num, "num_transition": self.multi_dataset.num_frames}
444
- for meta in self.state_meta:
445
- key = meta["key"]
446
- stats["state"][key]["stepwise_min"] = torch.stack(state_min[key]).amin(0)
447
- stats["state"][key]["stepwise_max"] = torch.stack(state_max[key]).amax(0)
448
- stats["state"][key]["global_min"] = stats["state"][key]["stepwise_min"].amin(0)
449
- stats["state"][key]["global_max"] = stats["state"][key]["stepwise_max"].amax(0)
450
- stats["state"][key]["stepwise_q01"] = torch.stack(state_q01[key]).amin(0)
451
- stats["state"][key]["stepwise_q99"] = torch.stack(state_q99[key]).amax(0)
452
- stats["state"][key]["global_q01"] = stats["state"][key]["stepwise_q01"].amin(0)
453
- stats["state"][key]["global_q99"] = stats["state"][key]["stepwise_q99"].amax(0)
454
- (
455
- stats["state"][key]["stepwise_mean"],
456
- stats["state"][key]["stepwise_std"],
457
- stats["state"][key]["global_mean"],
458
- stats["state"][key]["global_std"],
459
- ) = get_mean_std(state_mean[key], state_var[key])
460
-
461
- for meta in self.action_meta:
462
- key = meta["key"]
463
- stats["action"][key]["stepwise_min"] = torch.stack(action_min[key]).amin(0)
464
- stats["action"][key]["stepwise_max"] = torch.stack(action_max[key]).amax(0)
465
- stats["action"][key]["global_min"] = stats["action"][key]["stepwise_min"].amin(0)
466
- stats["action"][key]["global_max"] = stats["action"][key]["stepwise_max"].amax(0)
467
- stats["action"][key]["stepwise_q01"] = torch.stack(action_q01[key]).amin(0)
468
- stats["action"][key]["stepwise_q99"] = torch.stack(action_q99[key]).amax(0)
469
- stats["action"][key]["global_q01"] = stats["action"][key]["stepwise_q01"].amin(0)
470
- stats["action"][key]["global_q99"] = stats["action"][key]["stepwise_q99"].amax(0)
471
- (
472
- stats["action"][key]["stepwise_mean"],
473
- stats["action"][key]["stepwise_std"],
474
- stats["action"][key]["global_mean"],
475
- stats["action"][key]["global_std"],
476
- ) = get_mean_std(action_mean[key], action_var[key])
477
-
478
- return stats
479
-
480
-
481
- def sliding_window_with_replication(x: torch.Tensor, window_size: int) -> torch.Tensor:
482
- """
483
- Construct a sliding-window tensor from the input tensor x (shape: [N, D]).
484
- The output shape is [N, window_size, D].
485
-
486
- For each starting index i:
487
- out[i, j, :] =
488
- x[i + j, :] if i + j < N
489
- x[-1, :] otherwise (replicate the last row when out of bounds)
490
-
491
- Args:
492
- x (torch.Tensor): Input tensor of shape [N, D]
493
- window_size (int): Size of the sliding window
494
-
495
- Returns:
496
- torch.Tensor: Tensor of shape [N, window_size, D]
497
- """
498
- assert x.dim() == 2
499
- assert window_size > 0
500
-
501
- N, D = x.shape
502
-
503
- # shape [N, window_size]
504
- # indices[i, j] = i + j
505
- i_indices = torch.arange(N).unsqueeze(1) # [N, 1]
506
- j_indices = torch.arange(window_size).unsqueeze(0) # [1, window_size]
507
- indices = i_indices + j_indices # [N, window_size]
508
-
509
- # N-1
510
- # torch.clamp [0, N-1]
511
- clamped_indices = torch.clamp(indices, min=0, max=N - 1)
512
-
513
- # clamped_indices [N, window_size],x [N, D]
514
- # out[i, j, :] = x[clamped_indices[i, j], :]
515
- out = x[clamped_indices] # [N, window_size, D]
516
-
517
- return out
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/constants.py DELETED
@@ -1,54 +0,0 @@
1
- # Copyright 2024 The HuggingFace Inc. team. All rights reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- # keys
15
- import os
16
- from pathlib import Path
17
-
18
- from huggingface_hub.constants import HF_HOME
19
-
20
- OBS_ENV_STATE = "observation.environment_state"
21
- OBS_STATE = "observation.state"
22
- OBS_IMAGE = "observation.image"
23
- OBS_IMAGES = "observation.images"
24
- ACTION = "action"
25
- REWARD = "next.reward"
26
-
27
- ROBOTS = "robots"
28
- ROBOT_TYPE = "robot_type"
29
- TELEOPERATORS = "teleoperators"
30
-
31
- # files & directories
32
- CHECKPOINTS_DIR = "checkpoints"
33
- LAST_CHECKPOINT_LINK = "last"
34
- PRETRAINED_MODEL_DIR = "pretrained_model"
35
- TRAINING_STATE_DIR = "training_state"
36
- RNG_STATE = "rng_state.safetensors"
37
- TRAINING_STEP = "training_step.json"
38
- OPTIMIZER_STATE = "optimizer_state.safetensors"
39
- OPTIMIZER_PARAM_GROUPS = "optimizer_param_groups.json"
40
- SCHEDULER_STATE = "scheduler_state.json"
41
-
42
- if "LEROBOT_HOME" in os.environ:
43
- raise ValueError(
44
- f"You have a 'LEROBOT_HOME' environment variable set to '{os.getenv('LEROBOT_HOME')}'.\n"
45
- "'LEROBOT_HOME' is deprecated, please use 'HF_LEROBOT_HOME' instead."
46
- )
47
-
48
- # cache dir
49
- default_cache_path = Path(HF_HOME) / "lerobot"
50
- HF_LEROBOT_HOME = Path(os.getenv("HF_LEROBOT_HOME", default_cache_path)).expanduser()
51
-
52
- # calibration dir
53
- default_calibration_path = HF_LEROBOT_HOME / "calibration"
54
- HF_LEROBOT_CALIBRATION = Path(os.getenv("HF_LEROBOT_CALIBRATION", default_calibration_path)).expanduser()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/compute_stats.py DELETED
@@ -1,179 +0,0 @@
1
- #!/usr/bin/env python
2
-
3
- # Copyright 2024 The HuggingFace Inc. team. All rights reserved.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
- import numpy as np
17
-
18
- from .utils import load_image_as_numpy
19
-
20
-
21
- def estimate_num_samples(
22
- dataset_len: int, min_num_samples: int = 100, max_num_samples: int = 10_000, power: float = 0.75
23
- ) -> int:
24
- """Heuristic to estimate the number of samples based on dataset size.
25
- The power controls the sample growth relative to dataset size.
26
- Lower the power for less number of samples.
27
-
28
- For default arguments, we have:
29
- - from 1 to ~500, num_samples=100
30
- - at 1000, num_samples=177
31
- - at 2000, num_samples=299
32
- - at 5000, num_samples=594
33
- - at 10000, num_samples=1000
34
- - at 20000, num_samples=1681
35
- """
36
- if dataset_len < min_num_samples:
37
- min_num_samples = dataset_len
38
- return max(min_num_samples, min(int(dataset_len**power), max_num_samples))
39
-
40
-
41
- def sample_indices(data_len: int) -> list[int]:
42
- num_samples = estimate_num_samples(data_len)
43
- return np.round(np.linspace(0, data_len - 1, num_samples)).astype(int).tolist()
44
-
45
-
46
- def auto_downsample_height_width(img: np.ndarray, target_size: int = 150, max_size_threshold: int = 300):
47
- _, height, width = img.shape
48
-
49
- if max(width, height) < max_size_threshold:
50
- # no downsampling needed
51
- return img
52
-
53
- downsample_factor = int(width / target_size) if width > height else int(height / target_size)
54
- return img[:, ::downsample_factor, ::downsample_factor]
55
-
56
-
57
- def sample_images(image_paths: list[str]) -> np.ndarray:
58
- sampled_indices = sample_indices(len(image_paths))
59
-
60
- images = None
61
- for i, idx in enumerate(sampled_indices):
62
- path = image_paths[idx]
63
- # we load as uint8 to reduce memory usage
64
- img = load_image_as_numpy(path, dtype=np.uint8, channel_first=True)
65
- img = auto_downsample_height_width(img)
66
-
67
- if images is None:
68
- images = np.empty((len(sampled_indices), *img.shape), dtype=np.uint8)
69
-
70
- images[i] = img
71
-
72
- return images
73
-
74
-
75
- def get_feature_stats(array: np.ndarray, axis: tuple, keepdims: bool) -> dict[str, np.ndarray]:
76
- return {
77
- "min": np.min(array, axis=axis, keepdims=keepdims),
78
- "max": np.max(array, axis=axis, keepdims=keepdims),
79
- "mean": np.mean(array, axis=axis, keepdims=keepdims),
80
- "std": np.std(array, axis=axis, keepdims=keepdims),
81
- "count": np.array([len(array)]),
82
- }
83
-
84
-
85
- def compute_episode_stats(episode_data: dict[str, list[str] | np.ndarray], features: dict, is_compute_episode_stats_image : bool) -> dict:
86
- ep_stats = {}
87
- for key, data in episode_data.items():
88
- if features[key]["dtype"] == "string":
89
- continue # HACK: we should receive np.arrays of strings
90
- elif features[key]["dtype"] in ["image", "video"]:
91
- if is_compute_episode_stats_image:
92
- ep_ft_array = sample_images(data) # data is a list of image paths
93
- axes_to_reduce = (0, 2, 3) # keep channel dim
94
- keepdims = True
95
- else:
96
- continue
97
- else:
98
- ep_ft_array = data # data is already a np.ndarray
99
- axes_to_reduce = 0 # compute stats over the first axis
100
- keepdims = data.ndim == 1 # keep as np.array
101
-
102
- ep_stats[key] = get_feature_stats(ep_ft_array, axis=axes_to_reduce, keepdims=keepdims)
103
-
104
- # finally, we normalize and remove batch dim for images
105
- if features[key]["dtype"] in ["image", "video"]:
106
- ep_stats[key] = {
107
- k: v if k == "count" else np.squeeze(v / 255.0, axis=0) for k, v in ep_stats[key].items()
108
- }
109
-
110
- return ep_stats
111
-
112
-
113
- def _assert_type_and_shape(stats_list: list[dict[str, dict]]):
114
- for i in range(len(stats_list)):
115
- for fkey in stats_list[i]:
116
- for k, v in stats_list[i][fkey].items():
117
- if not isinstance(v, np.ndarray):
118
- raise ValueError(
119
- f"Stats must be composed of numpy array, but key '{k}' of feature '{fkey}' is of type '{type(v)}' instead."
120
- )
121
- if v.ndim == 0:
122
- raise ValueError("Number of dimensions must be at least 1, and is 0 instead.")
123
- if k == "count" and v.shape != (1,):
124
- raise ValueError(f"Shape of 'count' must be (1), but is {v.shape} instead.")
125
- if "image" in fkey and k != "count" and v.shape != (3, 1, 1):
126
- raise ValueError(f"Shape of '{k}' must be (3,1,1), but is {v.shape} instead.")
127
-
128
-
129
- def aggregate_feature_stats(stats_ft_list: list[dict[str, dict]]) -> dict[str, dict[str, np.ndarray]]:
130
- """Aggregates stats for a single feature."""
131
- means = np.stack([s["mean"] for s in stats_ft_list])
132
- variances = np.stack([s["std"] ** 2 for s in stats_ft_list])
133
- counts = np.stack([s["count"] for s in stats_ft_list])
134
- total_count = counts.sum(axis=0)
135
-
136
- # Prepare weighted mean by matching number of dimensions
137
- while counts.ndim < means.ndim:
138
- counts = np.expand_dims(counts, axis=-1)
139
-
140
- # Compute the weighted mean
141
- weighted_means = means * counts
142
- total_mean = weighted_means.sum(axis=0) / total_count
143
-
144
- # Compute the variance using the parallel algorithm
145
- delta_means = means - total_mean
146
- weighted_variances = (variances + delta_means**2) * counts
147
- total_variance = weighted_variances.sum(axis=0) / total_count
148
-
149
- return {
150
- "min": np.min(np.stack([s["min"] for s in stats_ft_list]), axis=0),
151
- "max": np.max(np.stack([s["max"] for s in stats_ft_list]), axis=0),
152
- "mean": total_mean,
153
- "std": np.sqrt(total_variance),
154
- "count": total_count,
155
- }
156
-
157
-
158
- def aggregate_stats(stats_list: list[dict[str, dict]]) -> dict[str, dict[str, np.ndarray]]:
159
- """Aggregate stats from multiple compute_stats outputs into a single set of stats.
160
-
161
- The final stats will have the union of all data keys from each of the stats dicts.
162
-
163
- For instance:
164
- - new_min = min(min_dataset_0, min_dataset_1, ...)
165
- - new_max = max(max_dataset_0, max_dataset_1, ...)
166
- - new_mean = (mean of all data, weighted by counts)
167
- - new_std = (std of all data)
168
- """
169
-
170
- _assert_type_and_shape(stats_list)
171
-
172
- data_keys = {key for stats in stats_list for key in stats}
173
- aggregated_stats = {key: {} for key in data_keys}
174
-
175
- for key in data_keys:
176
- stats_with_key = [stats[key] for stats in stats_list if key in stats]
177
- aggregated_stats[key] = aggregate_feature_stats(stats_with_key)
178
-
179
- return aggregated_stats
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/utils.py DELETED
@@ -1,965 +0,0 @@
1
- #!/usr/bin/env python
2
-
3
- # Copyright 2024 The HuggingFace Inc. team. All rights reserved.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
- import contextlib
17
- import importlib.resources
18
- import io
19
- import json
20
- import logging
21
- from collections.abc import Iterator
22
- from itertools import accumulate
23
- from pathlib import Path
24
- from pprint import pformat
25
- from types import SimpleNamespace
26
- from typing import Any
27
- import array
28
-
29
- import datasets
30
- import jsonlines
31
- import numpy as np
32
- import packaging.version
33
- import torch
34
- from datasets.table import embed_table_storage
35
- from huggingface_hub import DatasetCard, DatasetCardData, HfApi
36
- from huggingface_hub.errors import RevisionNotFoundError
37
- from PIL import Image as PILImage
38
- from torchvision import transforms
39
-
40
- from functools import partial
41
-
42
- # from lerobot.configs.types import DictLike, FeatureType, PolicyFeature
43
- # from lerobot.datasets.backward_compatibility import (
44
- # V21_MESSAGE,
45
- # BackwardCompatibilityError,
46
- # ForwardCompatibilityError,
47
- # )
48
- # from lerobot.robots import Robot
49
- # from lerobot.utils.utils import is_valid_numpy_dtype_string
50
-
51
- DEFAULT_CHUNK_SIZE = 1000 # Max number of episodes per chunk
52
-
53
- INFO_PATH = "meta/info.json"
54
- EPISODES_PATH = "meta/episodes.jsonl"
55
- STATS_PATH = "meta/stats.json"
56
- EPISODES_STATS_PATH = "meta/episodes_stats.jsonl"
57
- TASKS_PATH = "meta/tasks.jsonl"
58
-
59
- ANNOTATION_PATHS = {
60
- "subtask": "annotations/subtask_annotations.jsonl",
61
- "scene": "annotations/scene_annotations.jsonl",
62
- "gripper_mode": "annotations/gripper_mode_annotation.jsonl",
63
- "gripper_activity": "annotations/gripper_activity_annotation.jsonl",
64
- "eef_velocity": "annotations/eef_velocity_annotation.jsonl",
65
- "eef_acc_mag": "annotations/eef_acc_mag_annotation.jsonl",
66
- "eef_direction": "annotations/eef_direction_annotation.jsonl",
67
- }
68
-
69
- DEFAULT_VIDEO_PATH = "videos/chunk-{episode_chunk:03d}/{video_key}/episode_{episode_index:06d}.mp4"
70
- DEFAULT_PARQUET_PATH = "data/chunk-{episode_chunk:03d}/episode_{episode_index:06d}.parquet"
71
- DEFAULT_IMAGE_PATH = "images/{image_key}/episode_{episode_index:06d}/frame_{frame_index:06d}.jpeg"
72
-
73
- # v3.0 paths — multi-episode files, paths derived from per-episode metadata
74
- DEFAULT_DATA_PATH_V30 = "data/chunk-{chunk_index:03d}/file-{file_index:03d}.parquet"
75
- DEFAULT_VIDEO_PATH_V30 = "videos/{video_key}/chunk-{chunk_index:03d}/file-{file_index:03d}.mp4"
76
- EPISODES_DIR_V30 = "meta/episodes"
77
- TASKS_PATH_V30 = "meta/tasks.parquet" # flat single-file in v3.0
78
- _TASKS_V30_CACHE = {}
79
-
80
- DATASET_CARD_TEMPLATE = """
81
- ---
82
- # Metadata will go there
83
- ---
84
- This dataset was created using [LeRobot](https://github.com/huggingface/lerobot).
85
-
86
- ## {}
87
-
88
- """
89
-
90
- DEFAULT_FEATURES = {
91
- "timestamp": {"dtype": "float32", "shape": (1,), "names": None},
92
- "frame_index": {"dtype": "int64", "shape": (1,), "names": None},
93
- "episode_index": {"dtype": "int64", "shape": (1,), "names": None},
94
- "index": {"dtype": "int64", "shape": (1,), "names": None},
95
- "coarse_task_index": {"dtype": "int64", "shape": (1,), "names": None},
96
- "task_index": {"dtype": "int64", "shape": (1,), "names": None},
97
- "coarse_quality_index": {"dtype": "int64", "shape": (1,), "names": None},
98
- "quality_index": {"dtype": "int64", "shape": (1,), "names": None},
99
- }
100
-
101
-
102
- def flatten_dict(d: dict, parent_key: str = "", sep: str = "/") -> dict:
103
- """Flatten a nested dictionary structure by collapsing nested keys into one key with a separator.
104
-
105
- For example:
106
- ```
107
- >>> dct = {"a": {"b": 1, "c": {"d": 2}}, "e": 3}`
108
- >>> print(flatten_dict(dct))
109
- {"a/b": 1, "a/c/d": 2, "e": 3}
110
- """
111
- items = []
112
- for k, v in d.items():
113
- new_key = f"{parent_key}{sep}{k}" if parent_key else k
114
- if isinstance(v, dict):
115
- items.extend(flatten_dict(v, new_key, sep=sep).items())
116
- else:
117
- items.append((new_key, v))
118
- return dict(items)
119
-
120
-
121
- def unflatten_dict(d: dict, sep: str = "/") -> dict:
122
- outdict = {}
123
- for key, value in d.items():
124
- parts = key.split(sep)
125
- d = outdict
126
- for part in parts[:-1]:
127
- if part not in d:
128
- d[part] = {}
129
- d = d[part]
130
- d[parts[-1]] = value
131
- return outdict
132
-
133
-
134
- # def get_nested_item(obj: DictLike, flattened_key: str, sep: str = "/") -> Any:
135
- # split_keys = flattened_key.split(sep)
136
- # getter = obj[split_keys[0]]
137
- # if len(split_keys) == 1:
138
- # return getter
139
-
140
- # for key in split_keys[1:]:
141
- # getter = getter[key]
142
-
143
- # return getter
144
-
145
-
146
- def serialize_dict(stats: dict[str, torch.Tensor | np.ndarray | dict]) -> dict:
147
- serialized_dict = {}
148
- for key, value in flatten_dict(stats).items():
149
- if isinstance(value, (torch.Tensor, np.ndarray)):
150
- serialized_dict[key] = value.tolist()
151
- elif isinstance(value, np.generic):
152
- serialized_dict[key] = value.item()
153
- elif isinstance(value, (int, float)):
154
- serialized_dict[key] = value
155
- else:
156
- raise NotImplementedError(f"The value '{value}' of type '{type(value)}' is not supported.")
157
- return unflatten_dict(serialized_dict)
158
-
159
-
160
- def embed_images(dataset: datasets.Dataset) -> datasets.Dataset:
161
- # Embed image bytes into the table before saving to parquet
162
- format = dataset.format
163
- dataset = dataset.with_format("arrow")
164
- dataset = dataset.map(embed_table_storage, batched=False)
165
- dataset = dataset.with_format(**format)
166
- return dataset
167
-
168
-
169
- def load_json(fpath: Path) -> Any:
170
- with open(fpath) as f:
171
- return json.load(f)
172
-
173
-
174
- def write_json(data: dict, fpath: Path) -> None:
175
- fpath.parent.mkdir(exist_ok=True, parents=True)
176
- with open(fpath, "w") as f:
177
- json.dump(data, f, indent=4, ensure_ascii=False)
178
-
179
-
180
- def load_jsonlines(fpath: Path) -> list[Any]:
181
- # allow \t and \n
182
- loose_loader = partial(json.loads, strict=False)
183
- with jsonlines.open(fpath, "r", loads=loose_loader) as reader:
184
- return list(reader)
185
-
186
-
187
- def write_jsonlines(data: dict, fpath: Path) -> None:
188
- fpath.parent.mkdir(exist_ok=True, parents=True)
189
- with jsonlines.open(fpath, "w") as writer:
190
- writer.write_all(data)
191
-
192
-
193
- def append_jsonlines(data: dict, fpath: Path) -> None:
194
- fpath.parent.mkdir(exist_ok=True, parents=True)
195
- with jsonlines.open(fpath, "a") as writer:
196
- writer.write(data)
197
-
198
-
199
- def write_info(info: dict, local_dir: Path):
200
- write_json(info, local_dir / INFO_PATH)
201
-
202
-
203
- def load_info(local_dir: Path) -> dict:
204
- info = load_json(local_dir / INFO_PATH)
205
- for ft in info["features"].values():
206
- ft["shape"] = tuple(ft["shape"])
207
- return info
208
-
209
-
210
- def write_stats(stats: dict, local_dir: Path):
211
- serialized_stats = serialize_dict(stats)
212
- write_json(serialized_stats, local_dir / STATS_PATH)
213
-
214
-
215
- def cast_stats_to_numpy(stats) -> dict[str, dict[str, np.ndarray]]:
216
- stats = {key: np.array(value) for key, value in flatten_dict(stats).items()}
217
- return unflatten_dict(stats)
218
-
219
-
220
- def load_stats(local_dir: Path) -> dict[str, dict[str, np.ndarray]]:
221
- if not (local_dir / STATS_PATH).exists():
222
- return None
223
- stats = load_json(local_dir / STATS_PATH)
224
- return cast_stats_to_numpy(stats)
225
-
226
-
227
- def write_task(task_index: int, task: dict, local_dir: Path):
228
- task_dict = {
229
- "task_index": task_index,
230
- "task": task,
231
- }
232
- append_jsonlines(task_dict, local_dir / TASKS_PATH)
233
-
234
-
235
- def load_tasks(local_dir: Path) -> tuple[dict, dict]:
236
- tasks = load_jsonlines(local_dir / TASKS_PATH)
237
- tasks = {item["task_index"]: item["task"] for item in sorted(tasks, key=lambda x: x["task_index"])}
238
- task_to_task_index = {task: task_index for task_index, task in tasks.items()}
239
- return tasks, task_to_task_index
240
-
241
-
242
- def load_tasks_v30(local_dir: Path) -> tuple[dict, dict]:
243
- """Load tasks from v3.0 parquet format (meta/tasks.parquet).
244
-
245
- The parquet has index=task_string, column="task_index" (integer).
246
- Returns (tasks, task_to_task_index) matching the v2.1 interface.
247
- """
248
- import pandas as pd
249
- path = local_dir / TASKS_PATH_V30
250
- if not path.exists():
251
- raise FileNotFoundError(f"Tasks parquet not found: {path}")
252
- stat = path.stat()
253
- cache_key = (path.name, stat.st_size, stat.st_mtime_ns)
254
- if cache_key in _TASKS_V30_CACHE:
255
- return _TASKS_V30_CACHE[cache_key]
256
- df = pd.read_parquet(path)
257
- # index = task string, column "task_index" = integer task index
258
- tasks = {int(row["task_index"]): task_str for task_str, row in df.iterrows()}
259
- task_to_task_index = {task_str: int(row["task_index"]) for task_str, row in df.iterrows()}
260
- result = (tasks, task_to_task_index)
261
- _TASKS_V30_CACHE[cache_key] = result
262
- return result
263
-
264
-
265
- def load_episodes_v30(local_dir: Path) -> dict:
266
- """Load episodes from v3.0 parquet format (meta/episodes/chunk-000/file-000.parquet).
267
-
268
- Returns a dict mapping episode_index → row dict, compatible with get_episode_data_index.
269
- Each row contains at minimum: episode_index, tasks, length, dataset_from_index,
270
- dataset_to_index, data/chunk_index, data/file_index, videos/{key}/chunk_index,
271
- videos/{key}/file_index, videos/{key}/from_timestamp, videos/{key}/to_timestamp.
272
- """
273
- import pandas as pd
274
- paths = sorted((local_dir / EPISODES_DIR_V30).glob("*/*.parquet"))
275
- if not paths:
276
- raise FileNotFoundError(f"No episodes parquet found in {local_dir / EPISODES_DIR_V30}")
277
- # Drop stats/* and tasks columns — arrays not needed for data loading
278
- import numpy as np
279
- dfs = []
280
- for p in paths:
281
- df_part = pd.read_parquet(p)
282
- drop_cols = [c for c in df_part.columns if c.startswith("stats/") or c == "tasks"]
283
- dfs.append(df_part.drop(columns=drop_cols))
284
- df = pd.concat(dfs).reset_index(drop=True)
285
- episodes = {}
286
- for _, row in df.iterrows():
287
- ep_idx = int(row["episode_index"])
288
- episodes[ep_idx] = {
289
- k: (int(v) if isinstance(v, (int, np.integer)) else
290
- float(v) if isinstance(v, (float, np.floating)) else v)
291
- for k, v in row.items()
292
- }
293
- episodes[ep_idx]["length"] = int(row["length"])
294
- return episodes
295
-
296
- def load_annotations(local_dir: Path) -> dict[str, dict[int, str]]:
297
- annotations = {}
298
- for key, path in ANNOTATION_PATHS.items():
299
- anno = load_jsonlines(local_dir / path)
300
- anno = {item[f"{key}_index"]: item[key] for item in sorted(anno, key=lambda x: x[f"{key}_index"])}
301
- annotations[key] = anno
302
- return annotations
303
-
304
- def write_episode(episode: dict, local_dir: Path):
305
- append_jsonlines(episode, local_dir / EPISODES_PATH)
306
-
307
-
308
- def load_episodes(local_dir: Path) -> dict:
309
- episodes = load_jsonlines(local_dir / EPISODES_PATH)
310
- return {item["episode_index"]: item for item in sorted(episodes, key=lambda x: x["episode_index"])}
311
-
312
-
313
- def write_episode_stats(episode_index: int, episode_stats: dict, local_dir: Path):
314
- # We wrap episode_stats in a dictionary since `episode_stats["episode_index"]`
315
- # is a dictionary of stats and not an integer.
316
- episode_stats = {"episode_index": episode_index, "stats": serialize_dict(episode_stats)}
317
- append_jsonlines(episode_stats, local_dir / EPISODES_STATS_PATH)
318
-
319
-
320
- def load_episodes_stats(local_dir: Path) -> dict:
321
- episodes_stats = load_jsonlines(local_dir / EPISODES_STATS_PATH)
322
- return {
323
- item["episode_index"]: cast_stats_to_numpy(item["stats"])
324
- for item in sorted(episodes_stats, key=lambda x: x["episode_index"])
325
- }
326
-
327
-
328
- def backward_compatible_episodes_stats(
329
- stats: dict[str, dict[str, np.ndarray]], episodes: list[int]
330
- ) -> dict[str, dict[str, np.ndarray]]:
331
- return dict.fromkeys(episodes, stats)
332
-
333
-
334
- def load_image_as_numpy(
335
- fpath: str | Path, dtype: np.dtype = np.float32, channel_first: bool = True
336
- ) -> np.ndarray:
337
- img = PILImage.open(fpath).convert("RGB")
338
- img_array = np.array(img, dtype=dtype)
339
- if channel_first: # (H, W, C) -> (C, H, W)
340
- img_array = np.transpose(img_array, (2, 0, 1))
341
- if np.issubdtype(dtype, np.floating):
342
- img_array /= 255.0
343
- return img_array
344
-
345
-
346
- def hf_transform_to_torch(items_dict: dict[torch.Tensor | None]):
347
- """Get a transform function that convert items from Hugging Face dataset (pyarrow)
348
- to torch tensors. Importantly, images are converted from PIL, which corresponds to
349
- a channel last representation (h w c) of uint8 type, to a torch image representation
350
- with channel first (c h w) of float32 type in range [0,1].
351
- """
352
- for key in items_dict:
353
- first_item = items_dict[key][0]
354
- if type(first_item) == dict and 'bytes' in first_item and first_item['bytes'] is not None:
355
- to_pil = lambda img: PILImage.open(io.BytesIO(img['bytes']))
356
- items_dict[key] = [to_pil(img) for img in items_dict[key]]
357
- first_item = items_dict[key][0]
358
- if isinstance(first_item, PILImage.Image):
359
- to_tensor = transforms.ToTensor()
360
- items_dict[key] = [to_tensor(img) for img in items_dict[key]]
361
- elif first_item is None:
362
- pass
363
- else:
364
- items_dict[key] = [x if isinstance(x, str) else torch.tensor(x) for x in items_dict[key]]
365
- return items_dict
366
-
367
-
368
- def is_valid_version(version: str) -> bool:
369
- try:
370
- packaging.version.parse(version)
371
- return True
372
- except packaging.version.InvalidVersion:
373
- return False
374
-
375
-
376
- # def check_version_compatibility(
377
- # repo_id: str,
378
- # version_to_check: str | packaging.version.Version,
379
- # current_version: str | packaging.version.Version,
380
- # enforce_breaking_major: bool = True,
381
- # ) -> None:
382
- # v_check = (
383
- # packaging.version.parse(version_to_check)
384
- # if not isinstance(version_to_check, packaging.version.Version)
385
- # else version_to_check
386
- # )
387
- # v_current = (
388
- # packaging.version.parse(current_version)
389
- # if not isinstance(current_version, packaging.version.Version)
390
- # else current_version
391
- # )
392
- # if v_check.major < v_current.major and enforce_breaking_major:
393
- # raise BackwardCompatibilityError(repo_id, v_check)
394
- # elif v_check.minor < v_current.minor:
395
- # logging.warning(V21_MESSAGE.format(repo_id=repo_id, version=v_check))
396
-
397
-
398
- def get_repo_versions(repo_id: str) -> list[packaging.version.Version]:
399
- """Returns available valid versions (branches and tags) on given repo."""
400
- api = HfApi()
401
- repo_refs = api.list_repo_refs(repo_id, repo_type="dataset")
402
- repo_refs = [b.name for b in repo_refs.branches + repo_refs.tags]
403
- repo_versions = []
404
- for ref in repo_refs:
405
- with contextlib.suppress(packaging.version.InvalidVersion):
406
- repo_versions.append(packaging.version.parse(ref))
407
-
408
- return repo_versions
409
-
410
-
411
- # def get_safe_version(repo_id: str, version: str | packaging.version.Version) -> str:
412
- # """
413
- # Returns the version if available on repo or the latest compatible one.
414
- # Otherwise, will throw a `CompatibilityError`.
415
- # """
416
- # target_version = (
417
- # packaging.version.parse(version) if not isinstance(version, packaging.version.Version) else version
418
- # )
419
- # hub_versions = get_repo_versions(repo_id)
420
-
421
- # if not hub_versions:
422
- # raise RevisionNotFoundError(
423
- # f"""Your dataset must be tagged with a codebase version.
424
- # Assuming _version_ is the codebase_version value in the info.json, you can run this:
425
- # ```python
426
- # from huggingface_hub import HfApi
427
-
428
- # hub_api = HfApi()
429
- # hub_api.create_tag("{repo_id}", tag="_version_", repo_type="dataset")
430
- # ```
431
- # """
432
- # )
433
-
434
- # if target_version in hub_versions:
435
- # return f"v{target_version}"
436
-
437
- # compatibles = [
438
- # v for v in hub_versions if v.major == target_version.major and v.minor <= target_version.minor
439
- # ]
440
- # if compatibles:
441
- # return_version = max(compatibles)
442
- # if return_version < target_version:
443
- # logging.warning(f"Revision {version} for {repo_id} not found, using version v{return_version}")
444
- # return f"v{return_version}"
445
-
446
- # lower_major = [v for v in hub_versions if v.major < target_version.major]
447
- # if lower_major:
448
- # raise BackwardCompatibilityError(repo_id, max(lower_major))
449
-
450
- # upper_versions = [v for v in hub_versions if v > target_version]
451
- # assert len(upper_versions) > 0
452
- # raise ForwardCompatibilityError(repo_id, min(upper_versions))
453
-
454
-
455
- def get_hf_features_from_features(features: dict) -> datasets.Features:
456
- hf_features = {}
457
- for key, ft in features.items():
458
- if ft["dtype"] == "video":
459
- continue
460
- elif ft["dtype"] == "image":
461
- hf_features[key] = datasets.Image()
462
- elif ft["shape"] == (1,):
463
- hf_features[key] = datasets.Value(dtype=ft["dtype"])
464
- elif len(ft["shape"]) == 1:
465
- hf_features[key] = datasets.Sequence(
466
- length=ft["shape"][0], feature=datasets.Value(dtype=ft["dtype"])
467
- )
468
- elif len(ft["shape"]) == 2:
469
- hf_features[key] = datasets.Array2D(shape=ft["shape"], dtype=ft["dtype"])
470
- elif len(ft["shape"]) == 3:
471
- hf_features[key] = datasets.Array3D(shape=ft["shape"], dtype=ft["dtype"])
472
- elif len(ft["shape"]) == 4:
473
- hf_features[key] = datasets.Array4D(shape=ft["shape"], dtype=ft["dtype"])
474
- elif len(ft["shape"]) == 5:
475
- hf_features[key] = datasets.Array5D(shape=ft["shape"], dtype=ft["dtype"])
476
- else:
477
- raise ValueError(f"Corresponding feature is not valid: {ft}")
478
-
479
- return datasets.Features(hf_features)
480
-
481
-
482
- def _validate_feature_names(features: dict[str, dict]) -> None:
483
- invalid_features = {name: ft for name, ft in features.items() if "/" in name}
484
- if invalid_features:
485
- raise ValueError(f"Feature names should not contain '/'. Found '/' in '{invalid_features}'.")
486
-
487
-
488
- def hw_to_dataset_features(
489
- hw_features: dict[str, type | tuple], prefix: str, use_video: bool = True
490
- ) -> dict[str, dict]:
491
- features = {}
492
- joint_fts = {key: ftype for key, ftype in hw_features.items() if ftype is float}
493
- cam_fts = {key: shape for key, shape in hw_features.items() if isinstance(shape, tuple)}
494
-
495
- if joint_fts and prefix == "action":
496
- features[prefix] = {
497
- "dtype": "float32",
498
- "shape": (len(joint_fts),),
499
- "names": list(joint_fts),
500
- }
501
-
502
- if joint_fts and prefix == "observation":
503
- features[f"{prefix}.state"] = {
504
- "dtype": "float32",
505
- "shape": (len(joint_fts),),
506
- "names": list(joint_fts),
507
- }
508
-
509
- for key, shape in cam_fts.items():
510
- features[f"{prefix}.images.{key}"] = {
511
- "dtype": "video" if use_video else "image",
512
- "shape": shape,
513
- "names": ["height", "width", "channels"],
514
- }
515
-
516
- _validate_feature_names(features)
517
- return features
518
-
519
-
520
- def build_dataset_frame(
521
- ds_features: dict[str, dict], values: dict[str, Any], prefix: str
522
- ) -> dict[str, np.ndarray]:
523
- frame = {}
524
- for key, ft in ds_features.items():
525
- if key in DEFAULT_FEATURES or not key.startswith(prefix):
526
- continue
527
- elif ft["dtype"] == "float32" and len(ft["shape"]) == 1:
528
- frame[key] = np.array([values[name] for name in ft["names"]], dtype=np.float32)
529
- elif ft["dtype"] in ["image", "video"]:
530
- frame[key] = values[key.removeprefix(f"{prefix}.images.")]
531
-
532
- return frame
533
-
534
-
535
- # def get_features_from_robot(robot: Robot, use_videos: bool = True) -> dict:
536
- # camera_ft = {}
537
- # if robot.cameras:
538
- # camera_ft = {
539
- # key: {"dtype": "video" if use_videos else "image", **ft}
540
- # for key, ft in robot.camera_features.items()
541
- # }
542
- # return {**robot.motor_features, **camera_ft, **DEFAULT_FEATURES}
543
-
544
-
545
- # def dataset_to_policy_features(features: dict[str, dict]) -> dict[str, PolicyFeature]:
546
- # # TODO(aliberts): Implement "type" in dataset features and simplify this
547
- # policy_features = {}
548
- # for key, ft in features.items():
549
- # shape = ft["shape"]
550
- # if ft["dtype"] in ["image", "video"]:
551
- # type = FeatureType.VISUAL
552
- # if len(shape) != 3:
553
- # raise ValueError(f"Number of dimensions of {key} != 3 (shape={shape})")
554
-
555
- # names = ft["names"]
556
- # # Backward compatibility for "channel" which is an error introduced in LeRobotDataset v2.0 for ported datasets.
557
- # if names[2] in ["channel", "channels"]: # (h, w, c) -> (c, h, w)
558
- # shape = (shape[2], shape[0], shape[1])
559
- # elif key == "observation.environment_state":
560
- # type = FeatureType.ENV
561
- # elif key.startswith("observation"):
562
- # type = FeatureType.STATE
563
- # elif key.startswith("action"):
564
- # type = FeatureType.ACTION
565
- # else:
566
- # continue
567
-
568
- # policy_features[key] = PolicyFeature(
569
- # type=type,
570
- # shape=shape,
571
- # )
572
-
573
- # return policy_features
574
-
575
-
576
- def create_empty_dataset_info(
577
- codebase_version: str,
578
- fps: int,
579
- features: dict,
580
- use_videos: bool,
581
- robot_type: str | None = None,
582
- ) -> dict:
583
- return {
584
- "codebase_version": codebase_version,
585
- "robot_type": robot_type,
586
- "total_episodes": 0,
587
- "total_frames": 0,
588
- "total_tasks": 0,
589
- "total_videos": 0,
590
- "total_chunks": 0,
591
- "chunks_size": DEFAULT_CHUNK_SIZE,
592
- "fps": fps,
593
- "splits": {},
594
- "data_path": DEFAULT_PARQUET_PATH,
595
- "video_path": DEFAULT_VIDEO_PATH if use_videos else None,
596
- "features": features,
597
- }
598
-
599
-
600
- def get_episode_data_index(
601
- episode_dicts: dict[dict], episodes: list[int] | None = None
602
- ) -> dict[str, torch.Tensor]:
603
- episode_lengths = {ep_idx: ep_dict["length"] for ep_idx, ep_dict in episode_dicts.items()}
604
- if episodes is not None:
605
- episode_lengths = {ep_idx: episode_lengths[ep_idx] for ep_idx in episodes}
606
-
607
- cumulative_lengths = list(accumulate(episode_lengths.values()))
608
- return {
609
- "from": torch.LongTensor([0] + cumulative_lengths[:-1]),
610
- "to": torch.LongTensor(cumulative_lengths),
611
- }
612
-
613
-
614
- def check_timestamps_sync(
615
- timestamps: np.ndarray,
616
- episode_indices: np.ndarray,
617
- episode_data_index: dict[str, np.ndarray],
618
- fps: int,
619
- tolerance_s: float,
620
- raise_value_error: bool = True,
621
- ) -> bool:
622
- """
623
- This check is to make sure that each timestamp is separated from the next by (1/fps) +/- tolerance
624
- to account for possible numerical error.
625
-
626
- Args:
627
- timestamps (np.ndarray): Array of timestamps in seconds.
628
- episode_indices (np.ndarray): Array indicating the episode index for each timestamp.
629
- episode_data_index (dict[str, np.ndarray]): A dictionary that includes 'to',
630
- which identifies indices for the end of each episode.
631
- fps (int): Frames per second. Used to check the expected difference between consecutive timestamps.
632
- tolerance_s (float): Allowed deviation from the expected (1/fps) difference.
633
- raise_value_error (bool): Whether to raise a ValueError if the check fails.
634
-
635
- Returns:
636
- bool: True if all checked timestamp differences lie within tolerance, False otherwise.
637
-
638
- Raises:
639
- ValueError: If the check fails and `raise_value_error` is True.
640
- """
641
- if timestamps.shape != episode_indices.shape:
642
- raise ValueError(
643
- "timestamps and episode_indices should have the same shape. "
644
- f"Found {timestamps.shape=} and {episode_indices.shape=}."
645
- )
646
-
647
- # Consecutive differences
648
- diffs = np.diff(timestamps)
649
- within_tolerance = np.abs(diffs - (1.0 / fps)) <= tolerance_s
650
-
651
- # Mask to ignore differences at the boundaries between episodes
652
- mask = np.ones(len(diffs), dtype=bool)
653
- ignored_diffs = episode_data_index["to"][:-1] - 1 # indices at the end of each episode
654
- mask[ignored_diffs] = False
655
- filtered_within_tolerance = within_tolerance[mask]
656
-
657
- # Check if all remaining diffs are within tolerance
658
- if not np.all(filtered_within_tolerance):
659
- # Track original indices before masking
660
- original_indices = np.arange(len(diffs))
661
- filtered_indices = original_indices[mask]
662
- outside_tolerance_filtered_indices = np.nonzero(~filtered_within_tolerance)[0]
663
- outside_tolerance_indices = filtered_indices[outside_tolerance_filtered_indices]
664
-
665
- outside_tolerances = []
666
- for idx in outside_tolerance_indices:
667
- entry = {
668
- "timestamps": [timestamps[idx], timestamps[idx + 1]],
669
- "diff": diffs[idx],
670
- "episode_index": episode_indices[idx].item()
671
- if hasattr(episode_indices[idx], "item")
672
- else episode_indices[idx],
673
- }
674
- outside_tolerances.append(entry)
675
-
676
- if raise_value_error:
677
- raise ValueError(
678
- f"""One or several timestamps unexpectedly violate the tolerance inside episode range.
679
- This might be due to synchronization issues during data collection.
680
- \n{pformat(outside_tolerances)}"""
681
- )
682
- return False
683
-
684
- return True
685
-
686
-
687
- def check_delta_timestamps(
688
- delta_timestamps: dict[str, list[float]], fps: int, tolerance_s: float, raise_value_error: bool = True
689
- ) -> bool:
690
- """This will check if all the values in delta_timestamps are multiples of 1/fps +/- tolerance.
691
- This is to ensure that these delta_timestamps added to any timestamp from a dataset will themselves be
692
- actual timestamps from the dataset.
693
- """
694
- outside_tolerance = {}
695
- for key, delta_ts in delta_timestamps.items():
696
- within_tolerance = [abs(ts * fps - round(ts * fps)) / fps <= tolerance_s for ts in delta_ts]
697
- if not all(within_tolerance):
698
- outside_tolerance[key] = [
699
- ts for ts, is_within in zip(delta_ts, within_tolerance, strict=True) if not is_within
700
- ]
701
-
702
- if len(outside_tolerance) > 0:
703
- if raise_value_error:
704
- raise ValueError(
705
- f"""
706
- The following delta_timestamps are found outside of tolerance range.
707
- Please make sure they are multiples of 1/{fps} +/- tolerance and adjust
708
- their values accordingly.
709
- \n{pformat(outside_tolerance)}
710
- """
711
- )
712
- return False
713
-
714
- return True
715
-
716
-
717
- def get_delta_indices(delta_timestamps: dict[str, list[float]], fps: int) -> dict[str, list[int]]:
718
- delta_indices = {}
719
- for key, delta_ts in delta_timestamps.items():
720
- delta_indices[key] = [round(d * fps) for d in delta_ts]
721
-
722
- return delta_indices
723
-
724
-
725
- def cycle(iterable):
726
- """The equivalent of itertools.cycle, but safe for Pytorch dataloaders.
727
-
728
- See https://github.com/pytorch/pytorch/issues/23900 for information on why itertools.cycle is not safe.
729
- """
730
- iterator = iter(iterable)
731
- while True:
732
- try:
733
- yield next(iterator)
734
- except StopIteration:
735
- iterator = iter(iterable)
736
-
737
-
738
- def create_branch(repo_id, *, branch: str, repo_type: str | None = None) -> None:
739
- """Create a branch on a existing Hugging Face repo. Delete the branch if it already
740
- exists before creating it.
741
- """
742
- api = HfApi()
743
-
744
- branches = api.list_repo_refs(repo_id, repo_type=repo_type).branches
745
- refs = [branch.ref for branch in branches]
746
- ref = f"refs/heads/{branch}"
747
- if ref in refs:
748
- api.delete_branch(repo_id, repo_type=repo_type, branch=branch)
749
-
750
- api.create_branch(repo_id, repo_type=repo_type, branch=branch)
751
-
752
-
753
- def create_lerobot_dataset_card(
754
- tags: list | None = None,
755
- dataset_info: dict | None = None,
756
- **kwargs,
757
- ) -> DatasetCard:
758
- """
759
- Keyword arguments will be used to replace values in src/lerobot/datasets/card_template.md.
760
- Note: If specified, license must be one of https://huggingface.co/docs/hub/repositories-licenses.
761
- """
762
- card_tags = ["LeRobot"]
763
-
764
- if tags:
765
- card_tags += tags
766
- if dataset_info:
767
- dataset_structure = "[meta/info.json](meta/info.json):\n"
768
- dataset_structure += f"```json\n{json.dumps(dataset_info, indent=4)}\n```\n"
769
- kwargs = {**kwargs, "dataset_structure": dataset_structure}
770
- card_data = DatasetCardData(
771
- license=kwargs.get("license"),
772
- tags=card_tags,
773
- task_categories=["robotics"],
774
- configs=[
775
- {
776
- "config_name": "default",
777
- "data_files": "data/*/*.parquet",
778
- }
779
- ],
780
- )
781
-
782
- card_template = (importlib.resources.files("lerobot.datasets") / "card_template.md").read_text()
783
-
784
- return DatasetCard.from_template(
785
- card_data=card_data,
786
- template_str=card_template,
787
- **kwargs,
788
- )
789
-
790
-
791
- class IterableNamespace(SimpleNamespace):
792
- """
793
- A namespace object that supports both dictionary-like iteration and dot notation access.
794
- Automatically converts nested dictionaries into IterableNamespaces.
795
-
796
- This class extends SimpleNamespace to provide:
797
- - Dictionary-style iteration over keys
798
- - Access to items via both dot notation (obj.key) and brackets (obj["key"])
799
- - Dictionary-like methods: items(), keys(), values()
800
- - Recursive conversion of nested dictionaries
801
-
802
- Args:
803
- dictionary: Optional dictionary to initialize the namespace
804
- **kwargs: Additional keyword arguments passed to SimpleNamespace
805
-
806
- Examples:
807
- >>> data = {"name": "Alice", "details": {"age": 25}}
808
- >>> ns = IterableNamespace(data)
809
- >>> ns.name
810
- 'Alice'
811
- >>> ns.details.age
812
- 25
813
- >>> list(ns.keys())
814
- ['name', 'details']
815
- >>> for key, value in ns.items():
816
- ... print(f"{key}: {value}")
817
- name: Alice
818
- details: IterableNamespace(age=25)
819
- """
820
-
821
- def __init__(self, dictionary: dict[str, Any] = None, **kwargs):
822
- super().__init__(**kwargs)
823
- if dictionary is not None:
824
- for key, value in dictionary.items():
825
- if isinstance(value, dict):
826
- setattr(self, key, IterableNamespace(value))
827
- else:
828
- setattr(self, key, value)
829
-
830
- def __iter__(self) -> Iterator[str]:
831
- return iter(vars(self))
832
-
833
- def __getitem__(self, key: str) -> Any:
834
- return vars(self)[key]
835
-
836
- def items(self):
837
- return vars(self).items()
838
-
839
- def values(self):
840
- return vars(self).values()
841
-
842
- def keys(self):
843
- return vars(self).keys()
844
-
845
-
846
- def validate_frame(frame: dict, features: dict):
847
- expected_features = set(features) - set(DEFAULT_FEATURES)
848
- actual_features = set(frame)
849
-
850
- error_message = validate_features_presence(actual_features, expected_features)
851
-
852
- common_features = actual_features & expected_features
853
- for name in common_features - {"task"}:
854
- error_message += validate_feature_dtype_and_shape(name, features[name], frame[name])
855
-
856
- if error_message:
857
- raise ValueError(error_message)
858
-
859
-
860
- def validate_features_presence(actual_features: set[str], expected_features: set[str]):
861
- error_message = ""
862
- missing_features = expected_features - actual_features
863
- extra_features = actual_features - expected_features
864
-
865
- if missing_features or extra_features:
866
- error_message += "Feature mismatch in `frame` dictionary:\n"
867
- if missing_features:
868
- error_message += f"Missing features: {missing_features}\n"
869
- if extra_features:
870
- error_message += f"Extra features: {extra_features}\n"
871
-
872
- return error_message
873
-
874
- def is_valid_numpy_dtype_string(dtype_str: str) -> bool:
875
- """
876
- Return True if a given string can be converted to a numpy dtype.
877
- """
878
- try:
879
- # Attempt to convert the string to a numpy dtype
880
- np.dtype(dtype_str)
881
- return True
882
- except TypeError:
883
- # If a TypeError is raised, the string is not a valid dtype
884
- return False
885
- def validate_feature_dtype_and_shape(name: str, feature: dict, value: np.ndarray | PILImage.Image | str | bytes):
886
- if isinstance(value, bytes) or isinstance(value, array.array): # ROS 1 and 2
887
- #TODO fix bytes
888
- return ""
889
- expected_dtype = feature["dtype"]
890
- expected_shape = feature["shape"]
891
- if is_valid_numpy_dtype_string(expected_dtype):
892
- return validate_feature_numpy_array(name, expected_dtype, expected_shape, value)
893
- elif expected_dtype in ["image", "video"]:
894
- return validate_feature_image_or_video(name, expected_shape, value)
895
- elif expected_dtype == "string":
896
- return validate_feature_string(name, value)
897
- else:
898
- raise NotImplementedError(f"The feature dtype '{expected_dtype}' is not implemented yet.")
899
-
900
-
901
- def validate_feature_numpy_array(
902
- name: str, expected_dtype: str, expected_shape: list[int], value: np.ndarray
903
- ):
904
- error_message = ""
905
- if isinstance(value, np.ndarray):
906
- actual_dtype = value.dtype
907
- actual_shape = value.shape
908
-
909
- if actual_dtype != np.dtype(expected_dtype):
910
- error_message += f"The feature '{name}' of dtype '{actual_dtype}' is not of the expected dtype '{expected_dtype}'.\n"
911
-
912
- if actual_shape != expected_shape:
913
- error_message += f"The feature '{name}' of shape '{actual_shape}' does not have the expected shape '{expected_shape}'.\n"
914
- else:
915
- error_message += f"The feature '{name}' is not a 'np.ndarray'. Expected type is '{expected_dtype}', but type '{type(value)}' provided instead.\n"
916
-
917
- return error_message
918
-
919
-
920
- def validate_feature_image_or_video(name: str, expected_shape: list[str], value: np.ndarray | PILImage.Image):
921
- # Note: The check of pixels range ([0,1] for float and [0,255] for uint8) is done by the image writer threads.
922
- error_message = ""
923
- if isinstance(value, np.ndarray):
924
- actual_shape = value.shape
925
- c, h, w = expected_shape
926
- if len(actual_shape) != 3 or (actual_shape != (c, h, w) and actual_shape != (h, w, c)):
927
- error_message += f"The feature '{name}' of shape '{actual_shape}' does not have the expected shape '{(c, h, w)}' or '{(h, w, c)}'.\n"
928
- elif isinstance(value, PILImage.Image):
929
- pass
930
- else:
931
- error_message += f"The feature '{name}' is expected to be of type 'PIL.Image' or 'np.ndarray' channel first or channel last, but type '{type(value)}' provided instead.\n"
932
-
933
- return error_message
934
-
935
-
936
- def validate_feature_string(name: str, value: str):
937
- if not isinstance(value, str):
938
- return f"The feature '{name}' is expected to be of type 'str', but type '{type(value)}' provided instead.\n"
939
- return ""
940
-
941
-
942
- def validate_episode_buffer(episode_buffer: dict, total_episodes: int, features: dict):
943
- if "size" not in episode_buffer:
944
- raise ValueError("size key not found in episode_buffer")
945
-
946
- if "task" not in episode_buffer:
947
- raise ValueError("task key not found in episode_buffer")
948
-
949
- if episode_buffer["episode_index"] != total_episodes:
950
- # TODO(aliberts): Add option to use existing episode_index
951
- raise NotImplementedError(
952
- "You might have manually provided the episode_buffer with an episode_index that doesn't "
953
- "match the total number of episodes already in the dataset. This is not supported for now."
954
- )
955
-
956
- if episode_buffer["size"] == 0:
957
- raise ValueError("You must add one or several frames with `add_frame` before calling `add_episode`.")
958
-
959
- buffer_keys = set(episode_buffer.keys()) - {"task", "size"}
960
- if not buffer_keys == set(features):
961
- raise ValueError(
962
- f"Features from `episode_buffer` don't match the ones in `features`."
963
- f"In episode_buffer not in features: {buffer_keys - set(features)}"
964
- f"In features not in episode_buffer: {set(features) - buffer_keys}"
965
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/datasets/video_utils.py DELETED
@@ -1,527 +0,0 @@
1
- #!/usr/bin/env python
2
-
3
- # Copyright 2024 The HuggingFace Inc. team. All rights reserved.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
- import glob
17
- import importlib
18
- import logging
19
- import warnings
20
- from dataclasses import dataclass, field
21
- from pathlib import Path
22
- from typing import Any, ClassVar
23
-
24
- import av
25
- import pyarrow as pa
26
- import torch
27
- import torchvision
28
- from datasets.features.features import register_feature
29
- from PIL import Image
30
-
31
-
32
- def get_safe_default_codec():
33
- if importlib.util.find_spec("torchcodec"):
34
- return "torchcodec"
35
- else:
36
- logging.warning(
37
- "'torchcodec' is not available in your platform, falling back to 'pyav' as a default decoder"
38
- )
39
- return "pyav"
40
-
41
-
42
- def decode_video_frames(
43
- video_path: Path | str,
44
- timestamps: list[float],
45
- tolerance_s: float,
46
- backend: str | None = None,
47
- ) -> torch.Tensor:
48
- """
49
- Decodes video frames using the specified backend.
50
-
51
- Args:
52
- video_path (Path): Path to the video file.
53
- timestamps (list[float]): List of timestamps to extract frames.
54
- tolerance_s (float): Allowed deviation in seconds for frame retrieval.
55
- backend (str, optional): Backend to use for decoding. Defaults to "torchcodec" when available in the platform; otherwise, defaults to "pyav"..
56
-
57
- Returns:
58
- torch.Tensor: Decoded frames.
59
-
60
- Currently supports torchcodec on cpu and pyav.
61
- """
62
- if backend is None:
63
- backend = get_safe_default_codec()
64
- if backend == "torchcodec":
65
- try:
66
- return decode_video_frames_torchcodec(video_path, timestamps, tolerance_s)
67
- except Exception as err:
68
- warnings.warn(
69
- f"torchcodec video decode failed ({type(err).__name__}: {err}); falling back to torchvision/pyav."
70
- )
71
- return decode_video_frames_torchvision(video_path, timestamps, tolerance_s, backend="pyav")
72
- elif backend in ["pyav", "video_reader"]:
73
- return decode_video_frames_torchvision(video_path, timestamps, tolerance_s, backend)
74
- else:
75
- raise ValueError(f"Unsupported video backend: {backend}")
76
-
77
-
78
- def decode_video_frames_torchvision(
79
- video_path: Path | str,
80
- timestamps: list[float],
81
- tolerance_s: float,
82
- backend: str = "pyav",
83
- log_loaded_timestamps: bool = False,
84
- ) -> torch.Tensor:
85
- """Loads frames associated to the requested timestamps of a video
86
-
87
- The backend can be either "pyav" (default) or "video_reader".
88
- "video_reader" requires installing torchvision from source, see:
89
- https://github.com/pytorch/vision/blob/main/torchvision/csrc/io/decoder/gpu/README.rst
90
- (note that you need to compile against ffmpeg<4.3)
91
-
92
- While both use cpu, "video_reader" is supposedly faster than "pyav" but requires additional setup.
93
- For more info on video decoding, see `benchmark/video/README.md`
94
-
95
- See torchvision doc for more info on these two backends:
96
- https://pytorch.org/vision/0.18/index.html?highlight=backend#torchvision.set_video_backend
97
-
98
- Note: Video benefits from inter-frame compression. Instead of storing every frame individually,
99
- the encoder stores a reference frame (or a key frame) and subsequent frames as differences relative to
100
- that key frame. As a consequence, to access a requested frame, we need to load the preceding key frame,
101
- and all subsequent frames until reaching the requested frame. The number of key frames in a video
102
- can be adjusted during encoding to take into account decoding time and video size in bytes.
103
- """
104
- video_path = str(video_path)
105
-
106
- # set backend
107
- keyframes_only = False
108
- torchvision.set_video_backend(backend)
109
- if backend == "pyav":
110
- keyframes_only = True # pyav doesn't support accurate seek
111
-
112
- # set a video stream reader
113
- # TODO(rcadene): also load audio stream at the same time
114
- reader = torchvision.io.VideoReader(video_path, "video")
115
-
116
- # set the first and last requested timestamps
117
- # Note: previous timestamps are usually loaded, since we need to access the previous key frame
118
- first_ts = min(timestamps)
119
- last_ts = max(timestamps)
120
-
121
- # access closest key frame of the first requested frame
122
- # Note: closest key frame timestamp is usually smaller than `first_ts` (e.g. key frame can be the first frame of the video)
123
- # for details on what `seek` is doing see: https://pyav.basswood-io.com/docs/stable/api/container.html?highlight=inputcontainer#av.container.InputContainer.seek
124
- reader.seek(first_ts, keyframes_only=keyframes_only)
125
-
126
- # load all frames until last requested frame
127
- loaded_frames = []
128
- loaded_ts = []
129
- for frame in reader:
130
- current_ts = frame["pts"]
131
- if log_loaded_timestamps:
132
- logging.info(f"frame loaded at timestamp={current_ts:.4f}")
133
- loaded_frames.append(frame["data"])
134
- loaded_ts.append(current_ts)
135
- if current_ts >= last_ts:
136
- break
137
-
138
- if backend == "pyav":
139
- reader.container.close()
140
-
141
- reader = None
142
-
143
- # Use float32 for timestamp distance computation (torch.cdist doesn't support bfloat16).
144
- query_ts = torch.tensor(timestamps, dtype=torch.float32)
145
- loaded_ts = torch.tensor(loaded_ts, dtype=torch.float32)
146
-
147
- # compute distances between each query timestamp and timestamps of all loaded frames
148
- dist = torch.cdist(query_ts[:, None], loaded_ts[:, None], p=1)
149
- min_, argmin_ = dist.min(1)
150
-
151
- is_within_tol = min_ < tolerance_s
152
- assert is_within_tol.all(), (
153
- f"One or several query timestamps unexpectedly violate the tolerance ({min_[~is_within_tol]} > {tolerance_s=})."
154
- "It means that the closest frame that can be loaded from the video is too far away in time."
155
- "This might be due to synchronization issues with timestamps during data collection."
156
- "To be safe, we advise to ignore this item during training."
157
- f"\nqueried timestamps: {query_ts}"
158
- f"\nloaded timestamps: {loaded_ts}"
159
- f"\nvideo: {video_path}"
160
- f"\nbackend: {backend}"
161
- )
162
-
163
- # get closest frames to the query timestamps
164
- closest_frames = torch.stack([loaded_frames[idx] for idx in argmin_])
165
- closest_ts = loaded_ts[argmin_]
166
-
167
- if log_loaded_timestamps:
168
- logging.info(f"{closest_ts=}")
169
-
170
- # convert to the pytorch format which is float32 in [0,1] range (and channel first)
171
- closest_frames = closest_frames.type(torch.float32) / 255
172
-
173
- assert len(timestamps) == len(closest_frames)
174
- return closest_frames
175
-
176
-
177
- def decode_video_frames_torchcodec(
178
- video_path: Path | str,
179
- timestamps: list[float],
180
- tolerance_s: float,
181
- device: str = "cpu",
182
- log_loaded_timestamps: bool = False,
183
- ) -> torch.Tensor:
184
- """Loads frames associated with the requested timestamps of a video using torchcodec.
185
-
186
- Note: Setting device="cuda" outside the main process, e.g. in data loader workers, will lead to CUDA initialization errors.
187
-
188
- Note: Video benefits from inter-frame compression. Instead of storing every frame individually,
189
- the encoder stores a reference frame (or a key frame) and subsequent frames as differences relative to
190
- that key frame. As a consequence, to access a requested frame, we need to load the preceding key frame,
191
- and all subsequent frames until reaching the requested frame. The number of key frames in a video
192
- can be adjusted during encoding to take into account decoding time and video size in bytes.
193
- """
194
-
195
- if importlib.util.find_spec("torchcodec"):
196
- from torchcodec.decoders import VideoDecoder
197
- else:
198
- raise ImportError("torchcodec is required but not available.")
199
-
200
- # initialize video decoder
201
- decoder = VideoDecoder(video_path, device=device, seek_mode="approximate")
202
- loaded_frames = []
203
- loaded_ts = []
204
- # get metadata for frame information
205
- metadata = decoder.metadata
206
- average_fps = metadata.average_fps
207
-
208
- # convert timestamps to frame indices
209
- frame_indices = [round(ts * average_fps) for ts in timestamps]
210
-
211
- # retrieve frames based on indices
212
- frames_batch = decoder.get_frames_at(indices=frame_indices)
213
-
214
- for frame, pts in zip(frames_batch.data, frames_batch.pts_seconds, strict=False):
215
- loaded_frames.append(frame)
216
- loaded_ts.append(pts.item())
217
- if log_loaded_timestamps:
218
- logging.info(f"Frame loaded at timestamp={pts:.4f}")
219
-
220
- # Use float32 for timestamp distance computation (torch.cdist doesn't support bfloat16).
221
- query_ts = torch.tensor(timestamps, dtype=torch.float32)
222
- loaded_ts = torch.tensor(loaded_ts, dtype=torch.float32)
223
-
224
- # compute distances between each query timestamp and loaded timestamps
225
- dist = torch.cdist(query_ts[:, None], loaded_ts[:, None], p=1)
226
- min_, argmin_ = dist.min(1)
227
-
228
- is_within_tol = min_ < tolerance_s
229
- assert is_within_tol.all(), (
230
- f"One or several query timestamps unexpectedly violate the tolerance ({min_[~is_within_tol]} > {tolerance_s=})."
231
- "It means that the closest frame that can be loaded from the video is too far away in time."
232
- "This might be due to synchronization issues with timestamps during data collection."
233
- "To be safe, we advise to ignore this item during training."
234
- f"\nqueried timestamps: {query_ts}"
235
- f"\nloaded timestamps: {loaded_ts}"
236
- f"\nvideo: {video_path}"
237
- )
238
-
239
- # get closest frames to the query timestamps
240
- closest_frames = torch.stack([loaded_frames[idx] for idx in argmin_])
241
- closest_ts = loaded_ts[argmin_]
242
-
243
- if log_loaded_timestamps:
244
- logging.info(f"{closest_ts=}")
245
-
246
- # convert to float32 in [0,1] range (channel first)
247
- closest_frames = closest_frames.type(torch.float32) / 255
248
-
249
- assert len(timestamps) == len(closest_frames)
250
- return closest_frames
251
-
252
- def encode_video_frames(
253
- imgs_dir: Path | str,
254
- video_path: Path | str,
255
- fps: int,
256
- vcodec: str = "libsvtav1",
257
- pix_fmt: str = "yuv420p",
258
- g: int | None = 2,
259
- crf: int | None = 30,
260
- fast_decode: int = 0,
261
- log_level: int | None = av.logging.ERROR,
262
- overwrite: bool = False,
263
- ) -> None:
264
- """More info on ffmpeg arguments tuning on `benchmark/video/README.md`"""
265
- # Check encoder availability
266
- if vcodec == "h264_nvenc" :
267
- return encode_video_frames_ffmpeg(
268
- imgs_dir, video_path, fps, pix_fmt = pix_fmt, overwrite=overwrite
269
- )
270
-
271
- if vcodec not in ["h264", "hevc", "libsvtav1"]:
272
- raise ValueError(f"Unsupported video codec: {vcodec}. Supported codecs are: h264, hevc, libsvtav1.")
273
-
274
- video_path = Path(video_path)
275
- imgs_dir = Path(imgs_dir)
276
-
277
- video_path.parent.mkdir(parents=True, exist_ok=overwrite)
278
-
279
- # Encoders/pixel formats incompatibility check
280
- if (vcodec == "libsvtav1" or vcodec == "hevc") and pix_fmt == "yuv444p":
281
- logging.warning(
282
- f"Incompatible pixel format 'yuv444p' for codec {vcodec}, auto-selecting format 'yuv420p'"
283
- )
284
- pix_fmt = "yuv420p"
285
-
286
- # Get input frames
287
- template = "frame_" + ("[0-9]" * 6) + ".jpeg"
288
- input_list = sorted(
289
- glob.glob(str(imgs_dir / template)), key=lambda x: int(x.split("_")[-1].split(".")[0])
290
- )
291
-
292
- # Define video output frame size (assuming all input frames are the same size)
293
- if len(input_list) == 0:
294
- raise FileNotFoundError(f"No images found in {imgs_dir}.")
295
- dummy_image = Image.open(input_list[0])
296
- width, height = dummy_image.size
297
-
298
- # Define video codec options
299
- video_options = {}
300
-
301
- if g is not None:
302
- video_options["g"] = str(g)
303
-
304
- if crf is not None:
305
- video_options["crf"] = str(crf)
306
-
307
- if fast_decode:
308
- key = "svtav1-params" if vcodec == "libsvtav1" else "tune"
309
- value = f"fast-decode={fast_decode}" if vcodec == "libsvtav1" else "fastdecode"
310
- video_options[key] = value
311
-
312
- # Set logging level
313
- if log_level is not None:
314
- # "While less efficient, it is generally preferable to modify logging with Python’s logging"
315
- logging.getLogger("libav").setLevel(log_level)
316
-
317
- # Create and open output file (overwrite by default)
318
- with av.open(str(video_path), "w") as output:
319
- output_stream = output.add_stream(vcodec, fps, options=video_options)
320
- output_stream.pix_fmt = pix_fmt
321
- output_stream.width = width
322
- output_stream.height = height
323
-
324
- # Loop through input frames and encode them
325
- for input_data in input_list:
326
- input_image = Image.open(input_data).convert("RGB")
327
- input_frame = av.VideoFrame.from_image(input_image)
328
- packet = output_stream.encode(input_frame)
329
- if packet:
330
- output.mux(packet)
331
-
332
- # Flush the encoder
333
- packet = output_stream.encode()
334
- if packet:
335
- output.mux(packet)
336
-
337
- # Reset logging level
338
- if log_level is not None:
339
- av.logging.restore_default_callback()
340
-
341
- if not video_path.exists():
342
- raise OSError(f"Video encoding did not work. File not found: {video_path}.")
343
-
344
- # GPU speed up video encoding
345
- def encode_video_frames_ffmpeg(
346
- imgs_dir: Path | str,
347
- video_path: Path | str,
348
- fps: int,
349
- vcodec: str = "h264_nvenc",
350
- pix_fmt: str = "yuv420p",
351
- g: int | None = 4,
352
- crf: int | None = 30,
353
- overwrite: bool = False,
354
- ) -> None:
355
- import subprocess
356
- import shutil
357
-
358
- video_path = Path(video_path)
359
- imgs_dir = Path(imgs_dir)
360
-
361
- video_path.parent.mkdir(parents=True, exist_ok=True)
362
-
363
- # Check if ffmpeg is available
364
- ffmpeg_path = shutil.which("ffmpeg")
365
- if ffmpeg_path is None:
366
- raise RuntimeError("ffmpeg is not installed or not in system PATH")
367
-
368
- # Build ffmpeg command
369
- cmd = [
370
- ffmpeg_path,
371
- "-y" if overwrite else "-n",
372
- "-framerate", str(fps),
373
- "-pattern_type", "sequence",
374
- "-i", str(imgs_dir / "frame_%06d.jpeg"),
375
- "-vcodec", vcodec,
376
- "-pix_fmt", pix_fmt,
377
- ]
378
-
379
- if g is not None:
380
- cmd.extend(["-g", str(g)])
381
-
382
- if crf is not None:
383
- cmd.extend(["-crf", str(crf)])
384
-
385
- cmd.append(str(video_path))
386
-
387
- try:
388
- result = subprocess.run(
389
- cmd,
390
- capture_output=True,
391
- text=True,
392
- check=True
393
- )
394
- except subprocess.CalledProcessError as e:
395
- error_msg = f"FFmpeg failed:\nCommand: {' '.join(cmd)}\nStdout: {e.stdout}\nStderr: {e.stderr}"
396
- logging.error(error_msg)
397
- logging.error(video_path)
398
- logging.error(vcodec)
399
- print(f"ffmpeg {' '.join(cmd)}")
400
- raise RuntimeError(error_msg)
401
- except Exception as e:
402
- error_msg = f"Failed to run FFmpeg command: {' '.join(cmd)}\nError: {str(e)}"
403
- logging.error(error_msg)
404
- raise RuntimeError(error_msg)
405
-
406
-
407
- @dataclass
408
- class VideoFrame:
409
- # TODO(rcadene, lhoestq): move to Hugging Face `datasets` repo
410
- """
411
- Provides a type for a dataset containing video frames.
412
-
413
- Example:
414
-
415
- ```python
416
- data_dict = [{"image": {"path": "videos/episode_0.mp4", "timestamp": 0.3}}]
417
- features = {"image": VideoFrame()}
418
- Dataset.from_dict(data_dict, features=Features(features))
419
- ```
420
- """
421
-
422
- pa_type: ClassVar[Any] = pa.struct({"path": pa.string(), "timestamp": pa.float32()})
423
- _type: str = field(default="VideoFrame", init=False, repr=False)
424
-
425
- def __call__(self):
426
- return self.pa_type
427
-
428
-
429
- with warnings.catch_warnings():
430
- warnings.filterwarnings(
431
- "ignore",
432
- "'register_feature' is experimental and might be subject to breaking changes in the future.",
433
- category=UserWarning,
434
- )
435
- # to make VideoFrame available in HuggingFace `datasets`
436
- register_feature(VideoFrame, "VideoFrame")
437
-
438
-
439
- def get_audio_info(video_path: Path | str) -> dict:
440
- # Set logging level
441
- logging.getLogger("libav").setLevel(av.logging.ERROR)
442
-
443
- # Getting audio stream information
444
- audio_info = {}
445
- with av.open(str(video_path), "r") as audio_file:
446
- try:
447
- audio_stream = audio_file.streams.audio[0]
448
- except IndexError:
449
- # Reset logging level
450
- av.logging.restore_default_callback()
451
- return {"has_audio": False}
452
-
453
- audio_info["audio.channels"] = audio_stream.channels
454
- audio_info["audio.codec"] = audio_stream.codec.canonical_name
455
- # In an ideal loseless case : bit depth x sample rate x channels = bit rate.
456
- # In an actual compressed case, the bit rate is set according to the compression level : the lower the bit rate, the more compression is applied.
457
- audio_info["audio.bit_rate"] = audio_stream.bit_rate
458
- audio_info["audio.sample_rate"] = audio_stream.sample_rate # Number of samples per second
459
- # In an ideal loseless case : fixed number of bits per sample.
460
- # In an actual compressed case : variable number of bits per sample (often reduced to match a given depth rate).
461
- audio_info["audio.bit_depth"] = audio_stream.format.bits
462
- audio_info["audio.channel_layout"] = audio_stream.layout.name
463
- audio_info["has_audio"] = True
464
-
465
- # Reset logging level
466
- av.logging.restore_default_callback()
467
-
468
- return audio_info
469
-
470
-
471
- def get_video_info(video_path: Path | str) -> dict:
472
- # Set logging level
473
- logging.getLogger("libav").setLevel(av.logging.ERROR)
474
-
475
- # Getting video stream information
476
- video_info = {}
477
- with av.open(str(video_path), "r") as video_file:
478
- try:
479
- video_stream = video_file.streams.video[0]
480
- except IndexError:
481
- # Reset logging level
482
- av.logging.restore_default_callback()
483
- return {}
484
-
485
- video_info["video.height"] = video_stream.height
486
- video_info["video.width"] = video_stream.width
487
- video_info["video.codec"] = video_stream.codec.canonical_name
488
- video_info["video.pix_fmt"] = video_stream.pix_fmt
489
- video_info["video.is_depth_map"] = False
490
-
491
- # Calculate fps from r_frame_rate
492
- video_info["video.fps"] = int(video_stream.base_rate)
493
-
494
- pixel_channels = get_video_pixel_channels(video_stream.pix_fmt)
495
- video_info["video.channels"] = pixel_channels
496
-
497
- # Reset logging level
498
- av.logging.restore_default_callback()
499
-
500
- # Adding audio stream information
501
- video_info.update(**get_audio_info(video_path))
502
-
503
- return video_info
504
-
505
-
506
- def get_video_pixel_channels(pix_fmt: str) -> int:
507
- if "gray" in pix_fmt or "depth" in pix_fmt or "monochrome" in pix_fmt:
508
- return 1
509
- elif "rgba" in pix_fmt or "yuva" in pix_fmt:
510
- return 4
511
- elif "rgb" in pix_fmt or "yuv" in pix_fmt:
512
- return 3
513
- else:
514
- raise ValueError("Unknown format")
515
-
516
-
517
- def get_image_pixel_channels(image: Image):
518
- if image.mode == "L":
519
- return 1 # Grayscale
520
- elif image.mode == "LA":
521
- return 2 # Grayscale + Alpha
522
- elif image.mode == "RGB":
523
- return 3 # RGB
524
- elif image.mode == "RGBA":
525
- return 4 # RGBA
526
- else:
527
- raise ValueError("Unknown format")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/lerobot/lerobot_dataset.py DELETED
@@ -1,1404 +0,0 @@
1
- #!/usr/bin/env python
2
-
3
- # Copyright 2024 The HuggingFace Inc. team. All rights reserved.
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
- import contextlib
17
- import logging
18
- import shutil
19
- from pathlib import Path
20
- from typing import Callable, List, Literal
21
- import warnings
22
-
23
- import datasets
24
- import numpy as np
25
- import packaging.version
26
- import PIL.Image
27
- import torch
28
- import torch.utils
29
- import pyarrow.parquet as pq
30
- from datasets import concatenate_datasets, load_dataset
31
- from huggingface_hub import HfApi, snapshot_download
32
- from huggingface_hub.constants import REPOCARD_NAME
33
- from huggingface_hub.errors import RevisionNotFoundError
34
-
35
- # adapte to own path
36
- from ..constants import HF_LEROBOT_HOME
37
- from .datasets.compute_stats import aggregate_stats, compute_episode_stats
38
- from .datasets.utils import (
39
- DEFAULT_FEATURES,
40
- DEFAULT_IMAGE_PATH,
41
- DEFAULT_DATA_PATH_V30,
42
- DEFAULT_VIDEO_PATH_V30,
43
- INFO_PATH,
44
- TASKS_PATH,
45
- _validate_feature_names,
46
- append_jsonlines,
47
- backward_compatible_episodes_stats,
48
- check_delta_timestamps,
49
- check_timestamps_sync,
50
- # check_version_compatibility,
51
- create_empty_dataset_info,
52
- create_lerobot_dataset_card,
53
- embed_images,
54
- get_delta_indices,
55
- get_episode_data_index,
56
- get_hf_features_from_features,
57
- # get_safe_version,
58
- hf_transform_to_torch,
59
- is_valid_version,
60
- load_episodes,
61
- load_episodes_stats,
62
- load_episodes_v30,
63
- load_info,
64
- load_stats,
65
- load_tasks,
66
- load_tasks_v30,
67
- load_annotations,
68
- validate_episode_buffer,
69
- validate_frame,
70
- write_episode,
71
- write_episode_stats,
72
- write_info,
73
- write_json,
74
- )
75
- from .datasets.video_utils import (
76
- VideoFrame,
77
- decode_video_frames,
78
- encode_video_frames,
79
- get_safe_default_codec,
80
- get_video_info,
81
- )
82
- import traceback
83
-
84
- CODEBASE_VERSION = "v2.1"
85
-
86
-
87
- def dataset_dir_to_repo_id(dataset_dir: str | Path) -> str:
88
- """Return a valid repo id while preserving local paths as dataset roots.
89
-
90
- LeRobot accepts a local ``root`` and a Hub-style ``repo_id``. Several local
91
- training configs pass paths such as ``./data/libero_...`` as
92
- ``dataset_dirs``; using that path string as ``repo_id`` trips HuggingFace
93
- validation even though the dataset is local. For local paths, use the leaf
94
- directory name as a stable, valid dataset id while keeping the original path
95
- for ``root``.
96
- """
97
- ds_path = Path(dataset_dir)
98
- ds_str = str(dataset_dir)
99
- if ds_path.is_absolute() or ds_str.startswith(".") or ds_path.exists():
100
- return ds_path.name
101
- return ds_str
102
-
103
-
104
- class LeRobotDatasetMetadata:
105
- def __init__(
106
- self,
107
- repo_id: str,
108
- root: str | Path | None = None,
109
- revision: str | None = None,
110
- force_cache_sync: bool = False,
111
- ):
112
- self.repo_id = repo_id
113
- self.revision = revision if revision else CODEBASE_VERSION
114
- self.root = Path(root) if root is not None else HF_LEROBOT_HOME / repo_id
115
-
116
- try:
117
- if force_cache_sync:
118
- raise FileNotFoundError
119
- self.load_metadata()
120
- except (FileNotFoundError, NotADirectoryError):
121
- # if is_valid_version(self.revision):
122
- # self.revision = get_safe_version(self.repo_id, self.revision)
123
-
124
- (self.root / "meta").mkdir(exist_ok=True, parents=True)
125
- self.pull_from_repo(allow_patterns="meta/")
126
- self.load_metadata()
127
-
128
- def load_metadata(self):
129
- self.info = load_info(self.root)
130
- # TODO add new check
131
- # check_version_compatibility(self.repo_id, self._version, CODEBASE_VERSION)
132
- if self._version >= packaging.version.parse("v3.0"):
133
- self.tasks, self.task_to_task_index = load_tasks_v30(self.root)
134
- self.episodes = load_episodes_v30(self.root)
135
- self.episodes_stats = {}
136
- self.stats = load_stats(self.root) or {}
137
- elif self._version < packaging.version.parse("v2.1"):
138
- self.tasks, self.task_to_task_index = load_tasks(self.root)
139
- if (self.root / "annotations").exists():
140
- self.annotations = load_annotations(self.root)
141
- self.episodes = load_episodes(self.root)
142
- self.stats = load_stats(self.root)
143
- self.episodes_stats = backward_compatible_episodes_stats(self.stats, self.episodes)
144
- else:
145
- self.tasks, self.task_to_task_index = load_tasks(self.root)
146
- if (self.root / "annotations").exists():
147
- self.annotations = load_annotations(self.root)
148
- self.episodes = load_episodes(self.root)
149
- self.episodes_stats = load_episodes_stats(self.root)
150
- self.stats = aggregate_stats(list(self.episodes_stats.values()))
151
-
152
- def pull_from_repo(
153
- self,
154
- allow_patterns: list[str] | str | None = None,
155
- ignore_patterns: list[str] | str | None = None,
156
- ) -> None:
157
- snapshot_download(
158
- self.repo_id,
159
- repo_type="dataset",
160
- revision=self.revision,
161
- local_dir=self.root,
162
- allow_patterns=allow_patterns,
163
- ignore_patterns=ignore_patterns,
164
- )
165
-
166
- @property
167
- def _version(self) -> packaging.version.Version:
168
- """Codebase version used to create this dataset."""
169
- return packaging.version.parse(self.info["codebase_version"])
170
-
171
- def get_data_file_path(self, ep_index: int) -> Path:
172
- if self._version >= packaging.version.parse("v3.0"):
173
- ep = self.episodes[ep_index]
174
- fpath = DEFAULT_DATA_PATH_V30.format(
175
- chunk_index=int(ep["data/chunk_index"]),
176
- file_index=int(ep["data/file_index"]),
177
- )
178
- else:
179
- ep_chunk = self.get_episode_chunk(ep_index)
180
- fpath = self.data_path.format(episode_chunk=ep_chunk, episode_index=ep_index)
181
- return Path(fpath)
182
-
183
- def get_video_file_path(self, ep_index: int, vid_key: str) -> Path:
184
- if self._version >= packaging.version.parse("v3.0"):
185
- ep = self.episodes[ep_index]
186
- fpath = DEFAULT_VIDEO_PATH_V30.format(
187
- video_key=vid_key,
188
- chunk_index=int(ep[f"videos/{vid_key}/chunk_index"]),
189
- file_index=int(ep[f"videos/{vid_key}/file_index"]),
190
- )
191
- else:
192
- ep_chunk = self.get_episode_chunk(ep_index)
193
- fpath = self.video_path.format(episode_chunk=ep_chunk, video_key=vid_key, episode_index=ep_index)
194
- return Path(fpath)
195
-
196
- def get_episode_chunk(self, ep_index: int) -> int:
197
- return ep_index // self.chunks_size
198
-
199
- @property
200
- def data_path(self) -> str:
201
- """Formattable string for the parquet files."""
202
- return self.info["data_path"]
203
-
204
- @property
205
- def video_path(self) -> str | None:
206
- """Formattable string for the video files."""
207
- return self.info["video_path"]
208
-
209
- @property
210
- def robot_type(self) -> str | None:
211
- """Robot type used in recording this dataset."""
212
- return self.info["robot_type"]
213
-
214
- @property
215
- def fps(self) -> int:
216
- """Frames per second used during data collection."""
217
- return self.info["fps"]
218
-
219
- @property
220
- def features(self) -> dict[str, dict]:
221
- """All features contained in the dataset."""
222
- return self.info["features"]
223
-
224
- @property
225
- def image_keys(self) -> list[str]:
226
- """Keys to access visual modalities stored as images."""
227
- return [key for key, ft in self.features.items() if ft["dtype"] == "image"]
228
-
229
- @property
230
- def video_keys(self) -> list[str]:
231
- """Keys to access visual modalities stored as videos."""
232
- return [key for key, ft in self.features.items() if ft["dtype"] == "video"]
233
-
234
- @property
235
- def camera_keys(self) -> list[str]:
236
- """Keys to access visual modalities (regardless of their storage method)."""
237
- return [key for key, ft in self.features.items() if ft["dtype"] in ["video", "image"]]
238
-
239
- @property
240
- def names(self) -> dict[str, list | dict]:
241
- """Names of the various dimensions of vector modalities."""
242
- return {key: ft["names"] for key, ft in self.features.items()}
243
-
244
- @property
245
- def shapes(self) -> dict:
246
- """Shapes for the different features."""
247
- return {key: tuple(ft["shape"]) for key, ft in self.features.items()}
248
-
249
- @property
250
- def total_episodes(self) -> int:
251
- """Total number of episodes available."""
252
- return self.info["total_episodes"]
253
-
254
- @property
255
- def total_frames(self) -> int:
256
- """Total number of frames saved in this dataset."""
257
- return self.info["total_frames"]
258
-
259
- @property
260
- def total_tasks(self) -> int:
261
- """Total number of different tasks performed in this dataset."""
262
- return self.info["total_tasks"]
263
-
264
- @property
265
- def total_chunks(self) -> int:
266
- """Total number of chunks (groups of episodes)."""
267
- return self.info["total_chunks"]
268
-
269
- @property
270
- def chunks_size(self) -> int:
271
- """Max number of episodes per chunk."""
272
- return self.info["chunks_size"]
273
-
274
- def get_task_index(self, task: str) -> int | None:
275
- """
276
- Given a task in natural language, returns its task_index if the task already exists in the dataset,
277
- otherwise return None.
278
- """
279
- return self.task_to_task_index.get(task, None)
280
-
281
- def add_task(self, task: str):
282
- """
283
- Given a task in natural language, add it to the dictionary of tasks.
284
- """
285
- if task in self.task_to_task_index:
286
- raise ValueError(f"The task '{task}' already exists and can't be added twice.")
287
-
288
- task_index = self.info["total_tasks"]
289
- self.task_to_task_index[task] = task_index
290
- self.tasks[task_index] = task
291
- self.info["total_tasks"] += 1
292
-
293
- task_dict = {
294
- "task_index": task_index,
295
- "task": task,
296
- }
297
- append_jsonlines(task_dict, self.root / TASKS_PATH)
298
-
299
- def save_episode(
300
- self,
301
- episode_index: int,
302
- episode_length: int,
303
- episode_tasks: list[str],
304
- episode_stats: dict[str, dict],
305
- raw_file_name: str | None = None,
306
- ) -> None:
307
- self.info["total_episodes"] += 1
308
- self.info["total_frames"] += episode_length
309
-
310
- chunk = self.get_episode_chunk(episode_index)
311
- if chunk >= self.total_chunks:
312
- self.info["total_chunks"] += 1
313
-
314
- self.info["splits"] = {"train": f"0:{self.info['total_episodes']}"}
315
- self.info["total_videos"] += len(self.video_keys)
316
- if len(self.video_keys) > 0:
317
- self.update_video_info()
318
-
319
- write_info(self.info, self.root)
320
-
321
- episode_dict = {
322
- "episode_index": episode_index,
323
- "tasks": episode_tasks,
324
- "length": episode_length,
325
- }
326
- if raw_file_name is not None:
327
- episode_dict["raw_file_name"] = raw_file_name
328
-
329
- self.episodes[episode_index] = episode_dict
330
- write_episode(episode_dict, self.root)
331
-
332
- self.episodes_stats[episode_index] = episode_stats
333
- self.stats = aggregate_stats([self.stats, episode_stats]) if self.stats else episode_stats
334
- write_episode_stats(episode_index, episode_stats, self.root)
335
-
336
- def update_video_info(self) -> None:
337
- """
338
- Warning: this function writes info from first episode videos, implicitly assuming that all videos have
339
- been encoded the same way. Also, this means it assumes the first episode exists.
340
- """
341
- for key in self.video_keys:
342
- if not self.features[key].get("info", None):
343
- video_path = self.root / self.get_video_file_path(ep_index=0, vid_key=key)
344
- self.info["features"][key]["info"] = get_video_info(video_path)
345
-
346
- def __repr__(self):
347
- feature_keys = list(self.features)
348
- return (
349
- f"{self.__class__.__name__}({{\n"
350
- f" Repository ID: '{self.repo_id}',\n"
351
- f" Total episodes: '{self.total_episodes}',\n"
352
- f" Total frames: '{self.total_frames}',\n"
353
- f" Features: '{feature_keys}',\n"
354
- "})',\n"
355
- )
356
-
357
- @classmethod
358
- def create(
359
- cls,
360
- repo_id: str,
361
- fps: int,
362
- features: dict,
363
- robot_type: str | None = None,
364
- root: str | Path | None = None,
365
- use_videos: bool = True,
366
- ) -> "LeRobotDatasetMetadata":
367
- """Creates metadata for a LeRobotDataset."""
368
- obj = cls.__new__(cls)
369
- obj.repo_id = repo_id
370
- obj.root = Path(root) if root is not None else HF_LEROBOT_HOME / repo_id
371
-
372
- obj.root.mkdir(parents=True, exist_ok=False)
373
-
374
- # TODO(aliberts, rcadene): implement sanity check for features
375
- features = {**features, **DEFAULT_FEATURES}
376
- _validate_feature_names(features)
377
-
378
- obj.tasks, obj.task_to_task_index = {}, {}
379
- obj.episodes_stats, obj.stats, obj.episodes = {}, {}, {}
380
- obj.info = create_empty_dataset_info(CODEBASE_VERSION, fps, features, use_videos, robot_type)
381
- if len(obj.video_keys) > 0 and not use_videos:
382
- raise ValueError()
383
- write_json(obj.info, obj.root / INFO_PATH)
384
- obj.revision = None
385
- return obj
386
-
387
-
388
- class LeRobotDataset(torch.utils.data.Dataset):
389
- def __init__(
390
- self,
391
- repo_id: str,
392
- root: str | Path | None = None,
393
- episodes: list[int] | None = None,
394
- image_transforms: Callable | None = None,
395
- delta_timestamps: dict[list[float]] | None = None,
396
- tolerance_s: float = 1e-4,
397
- revision: str | None = None,
398
- force_cache_sync: bool = False,
399
- download_videos: bool = True,
400
- video_backend: str | None = None,
401
- video_codec: Literal["h264", "hevc", "libsvtav1", "h264_nvenc"] = "libsvtav1",
402
- is_compute_episode_stats_image: bool = True,
403
- ):
404
- """
405
- 2 modes are available for instantiating this class, depending on 2 different use cases:
406
-
407
- 1. Your dataset already exists:
408
- - On your local disk in the 'root' folder. This is typically the case when you recorded your
409
- dataset locally and you may or may not have pushed it to the hub yet. Instantiating this class
410
- with 'root' will load your dataset directly from disk. This can happen while you're offline (no
411
- internet connection).
412
-
413
- - On the Hugging Face Hub at the address https://huggingface.co/datasets/{repo_id} and not on
414
- your local disk in the 'root' folder. Instantiating this class with this 'repo_id' will download
415
- the dataset from that address and load it, pending your dataset is compliant with
416
- codebase_version v2.0. If your dataset has been created before this new format, you will be
417
- prompted to convert it using our conversion script from v1.6 to v2.0, which you can find at
418
- lerobot/datasets/v2/convert_dataset_v1_to_v2.py.
419
-
420
-
421
- 2. Your dataset doesn't already exists (either on local disk or on the Hub): you can create an empty
422
- LeRobotDataset with the 'create' classmethod. This can be used for recording a dataset or port an
423
- existing dataset to the LeRobotDataset format.
424
-
425
-
426
- In terms of files, LeRobotDataset encapsulates 3 main things:
427
- - metadata:
428
- - info contains various information about the dataset like shapes, keys, fps etc.
429
- - stats stores the dataset statistics of the different modalities for normalization
430
- - tasks contains the prompts for each task of the dataset, which can be used for
431
- task-conditioned training.
432
- - hf_dataset (from datasets.Dataset), which will read any values from parquet files.
433
- - videos (optional) from which frames are loaded to be synchronous with data from parquet files.
434
-
435
- A typical LeRobotDataset looks like this from its root path:
436
- .
437
- ├── data
438
- │ ├── chunk-000
439
- │ │ ├── episode_000000.parquet
440
- │ │ ├── episode_000001.parquet
441
- │ │ ├── episode_000002.parquet
442
- │ │ └── ...
443
- │ ├── chunk-001
444
- │ │ ├── episode_001000.parquet
445
- │ │ ├── episode_001001.parquet
446
- │ │ ├── episode_001002.parquet
447
- │ │ └── ...
448
- │ └── ...
449
- ├── meta
450
- │ ├── episodes.jsonl
451
- │ ├── info.json
452
- │ ├── stats.json
453
- │ └── tasks.jsonl
454
- └── videos
455
- ├── chunk-000
456
- │ ├── observation.images.laptop
457
- │ │ ├── episode_000000.mp4
458
- │ │ ├── episode_000001.mp4
459
- │ │ ├── episode_000002.mp4
460
- │ │ └── ...
461
- │ ├── observation.images.phone
462
- │ │ ├── episode_000000.mp4
463
- │ │ ├── episode_000001.mp4
464
- │ │ ├── episode_000002.mp4
465
- │ │ └── ...
466
- ├── chunk-001
467
- └── ...
468
-
469
- Note that this file-based structure is designed to be as versatile as possible. The files are split by
470
- episodes which allows a more granular control over which episodes one wants to use and download. The
471
- structure of the dataset is entirely described in the info.json file, which can be easily downloaded
472
- or viewed directly on the hub before downloading any actual data. The type of files used are very
473
- simple and do not need complex tools to be read, it only uses .parquet, .json and .mp4 files (and .md
474
- for the README).
475
-
476
- Args:
477
- repo_id (str): This is the repo id that will be used to fetch the dataset. Locally, the dataset
478
- will be stored under root/repo_id.
479
- root (Path | None, optional): Local directory to use for downloading/writing files. You can also
480
- set the LEROBOT_HOME environment variable to point to a different location. Defaults to
481
- '~/.cache/huggingface/lerobot'.
482
- episodes (list[int] | None, optional): If specified, this will only load episodes specified by
483
- their episode_index in this list. Defaults to None.
484
- image_transforms (Callable | None, optional): You can pass standard v2 image transforms from
485
- torchvision.transforms.v2 here which will be applied to visual modalities (whether they come
486
- from videos or images). Defaults to None.
487
- delta_timestamps (dict[list[float]] | None, optional): _description_. Defaults to None.
488
- tolerance_s (float, optional): Tolerance in seconds used to ensure data timestamps are actually in
489
- sync with the fps value. It is used at the init of the dataset to make sure that each
490
- timestamps is separated to the next by 1/fps +/- tolerance_s. This also applies to frames
491
- decoded from video files. It is also used to check that `delta_timestamps` (when provided) are
492
- multiples of 1/fps. Defaults to 1e-4.
493
- revision (str, optional): An optional Git revision id which can be a branch name, a tag, or a
494
- commit hash. Defaults to current codebase version tag.
495
- sync_cache_first (bool, optional): Flag to sync and refresh local files first. If True and files
496
- are already present in the local cache, this will be faster. However, files loaded might not
497
- be in sync with the version on the hub, especially if you specified 'revision'. Defaults to
498
- False.
499
- download_videos (bool, optional): Flag to download the videos. Note that when set to True but the
500
- video files are already present on local disk, they won't be downloaded again. Defaults to
501
- True.
502
- video_backend (str | None, optional): Video backend to use for decoding videos. Defaults to torchcodec when available int the platform; otherwise, defaults to 'pyav'.
503
- You can also use the 'pyav' decoder used by Torchvision, which used to be the default option, or 'video_reader' which is another decoder of Torchvision.
504
- """
505
- super().__init__()
506
- self.repo_id = repo_id
507
- self.root = Path(root) if root else HF_LEROBOT_HOME / repo_id
508
- self.image_transforms = image_transforms
509
- self.delta_timestamps = delta_timestamps
510
- self.episodes = episodes
511
- self.tolerance_s = tolerance_s
512
- self.revision = revision if revision else CODEBASE_VERSION
513
- self.video_backend = video_backend if video_backend else get_safe_default_codec()
514
- self.video_codec = video_codec
515
- self.is_compute_episode_stats_image = is_compute_episode_stats_image
516
- self.delta_indices = None
517
- self.during_training = True
518
-
519
- # Unused attributes
520
- self.image_writer = None
521
- self.episode_buffer = None
522
-
523
- self.root.mkdir(exist_ok=True, parents=True)
524
-
525
- # Load metadata
526
- self.meta = LeRobotDatasetMetadata(
527
- self.repo_id, self.root, self.revision, force_cache_sync=force_cache_sync
528
- )
529
- # v3.0 RoboTwin task shards keep global episode ids. When episodes is None,
530
- # range(total_episodes) is wrong for every shard after the first and also
531
- # disables parquet filtering. Default to the ids declared by this shard.
532
- if self.episodes is None and self.meta._version >= packaging.version.parse("v3.0"):
533
- self.episodes = sorted(self.meta.episodes.keys())
534
- if (self.episodes is not None
535
- and self.meta._version >= packaging.version.parse("v2.1")
536
- and self.meta._version < packaging.version.parse("v3.0")):
537
- episodes_stats = [self.meta.episodes_stats[ep_idx] for ep_idx in self.episodes]
538
- self.stats = aggregate_stats(episodes_stats)
539
-
540
- # v3.0: Dataset.from_parquet with isin() returns rows in parquet storage order
541
- # (episode_index order), regardless of the order in self.episodes. Sort episodes
542
- # so episode_data_index aligns with the HF dataset row ordering.
543
- if self.episodes is not None and self.meta._version >= packaging.version.parse("v3.0"):
544
- self.episodes = sorted(self.episodes)
545
-
546
- # Load actual data
547
- try:
548
- if force_cache_sync:
549
- raise FileNotFoundError
550
- assert all((self.root / fpath).is_file() for fpath in self.get_episodes_file_paths())
551
- self.hf_dataset = self.load_hf_dataset()
552
- except (AssertionError, FileNotFoundError, NotADirectoryError):
553
- # self.revision = get_safe_version(self.repo_id, self.revision)
554
- self.revision = CODEBASE_VERSION
555
- self.download_episodes(download_videos)
556
- self.hf_dataset = self.load_hf_dataset()
557
-
558
- self.episode_data_index = get_episode_data_index(self.meta.episodes, self.episodes)
559
-
560
- # check_timestamps_sync skipped (requires decoded column access not compatible with set_transform)
561
-
562
- # Setup delta_indices
563
- if self.delta_timestamps is not None:
564
- # check_delta_timestamps(self.delta_timestamps, self.fps, self.tolerance_s)
565
- self.delta_indices = get_delta_indices(self.delta_timestamps, self.fps)
566
-
567
- def push_to_hub(
568
- self,
569
- branch: str | None = None,
570
- tags: list | None = None,
571
- license: str | None = "apache-2.0",
572
- tag_version: bool = True,
573
- push_videos: bool = True,
574
- private: bool = False,
575
- allow_patterns: list[str] | str | None = None,
576
- upload_large_folder: bool = False,
577
- **card_kwargs,
578
- ) -> None:
579
- ignore_patterns = ["images/"]
580
- if not push_videos:
581
- ignore_patterns.append("videos/")
582
-
583
- hub_api = HfApi()
584
- hub_api.create_repo(
585
- repo_id=self.repo_id,
586
- private=private,
587
- repo_type="dataset",
588
- exist_ok=True,
589
- )
590
- if branch:
591
- hub_api.create_branch(
592
- repo_id=self.repo_id,
593
- branch=branch,
594
- revision=self.revision,
595
- repo_type="dataset",
596
- exist_ok=True,
597
- )
598
-
599
- upload_kwargs = {
600
- "repo_id": self.repo_id,
601
- "folder_path": self.root,
602
- "repo_type": "dataset",
603
- "revision": branch,
604
- "allow_patterns": allow_patterns,
605
- "ignore_patterns": ignore_patterns,
606
- }
607
- if upload_large_folder:
608
- hub_api.upload_large_folder(**upload_kwargs)
609
- else:
610
- hub_api.upload_folder(**upload_kwargs)
611
-
612
- if not hub_api.file_exists(self.repo_id, REPOCARD_NAME, repo_type="dataset", revision=branch):
613
- card = create_lerobot_dataset_card(
614
- tags=tags, dataset_info=self.meta.info, license=license, **card_kwargs
615
- )
616
- card.push_to_hub(repo_id=self.repo_id, repo_type="dataset", revision=branch)
617
-
618
- if tag_version:
619
- with contextlib.suppress(RevisionNotFoundError):
620
- hub_api.delete_tag(self.repo_id, tag=CODEBASE_VERSION, repo_type="dataset")
621
- hub_api.create_tag(self.repo_id, tag=CODEBASE_VERSION, revision=branch, repo_type="dataset")
622
-
623
- def pull_from_repo(
624
- self,
625
- allow_patterns: list[str] | str | None = None,
626
- ignore_patterns: list[str] | str | None = None,
627
- ) -> None:
628
- snapshot_download(
629
- self.repo_id,
630
- repo_type="dataset",
631
- revision=self.revision,
632
- local_dir=self.root,
633
- allow_patterns=allow_patterns,
634
- ignore_patterns=ignore_patterns,
635
- )
636
-
637
- def download_episodes(self, download_videos: bool = True) -> None:
638
- """Downloads the dataset from the given 'repo_id' at the provided version. If 'episodes' is given, this
639
- will only download those episodes (selected by their episode_index). If 'episodes' is None, the whole
640
- dataset will be downloaded. Thanks to the behavior of snapshot_download, if the files are already present
641
- in 'local_dir', they won't be downloaded again.
642
- """
643
- # TODO(rcadene, aliberts): implement faster transfer
644
- # https://huggingface.co/docs/huggingface_hub/en/guides/download#faster-downloads
645
- files = None
646
- ignore_patterns = None if download_videos else "videos/"
647
- if self.episodes is not None:
648
- files = self.get_episodes_file_paths()
649
-
650
- self.pull_from_repo(allow_patterns=files, ignore_patterns=ignore_patterns)
651
-
652
- def get_episodes_file_paths(self) -> list[Path]:
653
- episodes = (
654
- self.episodes
655
- if self.episodes is not None
656
- else sorted(self.meta.episodes.keys())
657
- )
658
- fpaths = [str(self.meta.get_data_file_path(ep_idx)) for ep_idx in episodes]
659
- if len(self.meta.video_keys) > 0:
660
- video_files = [
661
- str(self.meta.get_video_file_path(ep_idx, vid_key))
662
- for vid_key in self.meta.video_keys
663
- for ep_idx in episodes
664
- ]
665
- fpaths += video_files
666
- # v3.0 multi-episode files: many episodes share one file — deduplicate
667
- return list(dict.fromkeys(fpaths))
668
-
669
- def load_hf_dataset(self) -> datasets.Dataset:
670
- """hf_dataset contains all the observations, states, actions, rewards, etc."""
671
- if self.meta._version >= packaging.version.parse("v3.0"):
672
- return self._load_hf_dataset_v30()
673
- if self.episodes is None:
674
- path = str(self.root / "data")
675
- hf_dataset = load_dataset("parquet", data_dir=path, split="train")
676
- else:
677
- files = [str(self.root / self.meta.get_data_file_path(ep_idx)) for ep_idx in self.episodes]
678
- hf_dataset = load_dataset("parquet", data_files=files, split="train")
679
-
680
- # TODO(aliberts): hf_dataset.set_format("torch")
681
- hf_dataset.set_transform(hf_transform_to_torch)
682
- return hf_dataset
683
-
684
- def _load_hf_dataset_v30(self) -> datasets.Dataset:
685
- """v3.0 loader: multi-episode parquet files with predicate pushdown filtering."""
686
- import pyarrow.dataset as pa_ds
687
- features = get_hf_features_from_features(self.meta.features)
688
- paths = sorted((self.root / "data").glob("*/*.parquet"))
689
- if not paths:
690
- raise FileNotFoundError(f"No parquet files found in {self.root / 'data'}")
691
- filters = pa_ds.field("episode_index").isin(self.episodes) if self.episodes is not None else None
692
- hf_dataset = datasets.Dataset.from_parquet(
693
- [str(p) for p in paths], filters=filters, features=features
694
- )
695
- hf_dataset.set_transform(hf_transform_to_torch)
696
- return hf_dataset
697
-
698
- def create_hf_dataset(self) -> datasets.Dataset:
699
- features = get_hf_features_from_features(self.features)
700
- ft_dict = {col: [] for col in features}
701
- hf_dataset = datasets.Dataset.from_dict(ft_dict, features=features, split="train")
702
-
703
- # TODO(aliberts): hf_dataset.set_format("torch")
704
- hf_dataset.set_transform(hf_transform_to_torch)
705
- return hf_dataset
706
-
707
- @property
708
- def fps(self) -> int:
709
- """Frames per second used during data collection."""
710
- return self.meta.fps
711
-
712
- @property
713
- def num_frames(self) -> int:
714
- """Number of frames in selected episodes."""
715
- return len(self.hf_dataset) if self.hf_dataset is not None else self.meta.total_frames
716
-
717
- @property
718
- def num_episodes(self) -> int:
719
- """Number of episodes selected."""
720
- return len(self.episodes) if self.episodes is not None else self.meta.total_episodes
721
-
722
- @property
723
- def features(self) -> dict[str, dict]:
724
- return self.meta.features
725
-
726
- @property
727
- def hf_features(self) -> datasets.Features:
728
- """Features of the hf_dataset."""
729
- if self.hf_dataset is not None:
730
- return self.hf_dataset.features
731
- else:
732
- return get_hf_features_from_features(self.features)
733
-
734
- def _get_query_indices(self, idx: int, ep_idx: int) -> tuple[dict[str, list[int | bool]]]:
735
- ep_start = self.episode_data_index["from"][ep_idx]
736
- ep_end = self.episode_data_index["to"][ep_idx]
737
- query_indices = {
738
- key: [max(ep_start.item(), min(ep_end.item() - 1, idx + delta)) for delta in delta_idx]
739
- for key, delta_idx in self.delta_indices.items()
740
- }
741
- padding = { # Pad values outside of current episode range
742
- f"{key}_is_pad": torch.BoolTensor(
743
- [(idx + delta < ep_start.item()) | (idx + delta >= ep_end.item()) for delta in delta_idx]
744
- )
745
- for key, delta_idx in self.delta_indices.items()
746
- }
747
- return query_indices, padding
748
-
749
- def _get_query_timestamps(
750
- self,
751
- current_ts: float,
752
- query_indices: dict[str, list[int]] | None = None,
753
- ) -> dict[str, list[float]]:
754
- query_timestamps = {}
755
- for key in self.meta.video_keys:
756
- if query_indices is not None and key in query_indices:
757
- timestamps = self.hf_dataset.select(query_indices[key])["timestamp"]
758
- query_timestamps[key] = torch.stack(timestamps).tolist()
759
- else:
760
- query_timestamps[key] = [current_ts]
761
-
762
- return query_timestamps
763
-
764
- def _query_hf_dataset(self, query_indices: dict[str, list[int]]) -> dict:
765
- return {
766
- key: torch.stack(self.hf_dataset.select(q_idx)[key])
767
- for key, q_idx in query_indices.items()
768
- if key not in self.meta.video_keys
769
- }
770
-
771
- def _query_hf_dataset_fast(self, query_indices: dict[str, list[int]]) -> dict:
772
- result = {}
773
- processed_indices = set()
774
- index_to_selected = {}
775
- for key, q_idx in query_indices.items():
776
- if key not in self.meta.video_keys :
777
- if 'images' in key and not self.during_training:
778
- continue
779
- q_idx_tuple = tuple(q_idx)
780
- if q_idx_tuple not in processed_indices:
781
- # List-index access triggers set_transform and returns a batch dict.
782
- # .select() returns a Dataset whose column access bypasses the transform.
783
- selected_data = self.hf_dataset[list(q_idx)]
784
- index_to_selected[q_idx_tuple] = selected_data
785
- processed_indices.add(q_idx_tuple)
786
- else:
787
- selected_data = index_to_selected[q_idx_tuple]
788
- col = selected_data[key]
789
- result[key] = col if isinstance(col, torch.Tensor) else torch.stack(col)
790
- return result
791
-
792
- # no videos
793
- def get_episode_data(self, episode_id: int) -> dict:
794
- ep_start = self.episode_data_index["from"][episode_id].item()
795
- ep_end = self.episode_data_index["to"][episode_id].item()
796
- q_idx = list(range(ep_start, ep_end))
797
- # selected_data = self.hf_dataset.select(q_idx)
798
- selected_data = self.hf_dataset[q_idx]
799
- res_keys = self.meta.features.keys() - set(self.meta.video_keys)
800
- res = {key : torch.stack(selected_data[key]) for key in res_keys}
801
- return res
802
-
803
- def _query_videos(self, query_timestamps: dict[str, list[float]], ep_idx: int) -> dict[str, torch.Tensor]:
804
- """Note: When using data workers (e.g. DataLoader with num_workers>0), do not call this function
805
- in the main process (e.g. by using a second Dataloader with num_workers=0). It will result in a
806
- Segmentation Fault. This probably happens because a memory reference to the video loader is created in
807
- the main process and a subprocess fails to access it.
808
- """
809
- item = {}
810
- for vid_key, query_ts in query_timestamps.items():
811
- video_path = self.root / self.meta.get_video_file_path(ep_idx, vid_key)
812
- if self.meta._version >= packaging.version.parse("v3.0"):
813
- # In v3.0, episodes are concatenated into shared video files.
814
- # Timestamps in the parquet are episode-relative; shift by from_timestamp
815
- # to get the seek position within the concatenated file.
816
- from_ts = float(self.meta.episodes[ep_idx][f"videos/{vid_key}/from_timestamp"])
817
- query_ts = [from_ts + ts for ts in query_ts]
818
- frames = decode_video_frames(video_path, query_ts, self.tolerance_s, self.video_backend)
819
- item[vid_key] = frames.squeeze(0)
820
-
821
- return item
822
-
823
- def _add_padding_keys(self, item: dict, padding: dict[str, list[bool]]) -> dict:
824
- for key, val in padding.items():
825
- item[key] = torch.BoolTensor(val)
826
- return item
827
-
828
- def __len__(self):
829
- return self.num_frames
830
-
831
- def __getitem__(self, idx) -> dict:
832
- item = self.hf_dataset[idx]
833
- ep_idx = item["episode_index"].item()
834
-
835
- query_indices = None
836
- if self.delta_indices is not None:
837
- current_ep_idx = self.episodes.index(ep_idx) if self.episodes is not None else ep_idx
838
- query_indices, padding = self._get_query_indices(idx, current_ep_idx)
839
- query_result = self._query_hf_dataset_fast(query_indices)
840
- item = {**item, **padding}
841
- for key, val in query_result.items():
842
- item[key] = val
843
-
844
- if len(self.meta.video_keys) > 0 and self.during_training:
845
- current_ts = item["timestamp"].item()
846
- query_timestamps = self._get_query_timestamps(current_ts, query_indices)
847
- video_frames = self._query_videos(query_timestamps, ep_idx)
848
- item = {**video_frames, **item}
849
-
850
- if self.image_transforms is not None:
851
- image_keys = self.meta.camera_keys
852
- for cam in image_keys:
853
- item[cam] = self.image_transforms(item[cam])
854
-
855
- # Add task as a string
856
- task_idx = item["task_index"].item()
857
- item["task"] = self.meta.tasks[task_idx]
858
- if "coarse_task_index" in item:
859
- coarse_task_index = item["coarse_task_index"].item()
860
- item["coarse_task"] = self.meta.tasks[coarse_task_index]
861
-
862
- if "operating_hand_index" in item:
863
- operating_hand_index = item["operating_hand_index"].item()
864
- item["operating_hand"] = self.meta.tasks[operating_hand_index]
865
-
866
- if "subtask_annotation" in item and hasattr(self.meta, "annotations"):
867
- index = item["subtask_annotation"][0].item()
868
- item["subtask"] = self.meta.annotations["subtask"][index]
869
-
870
- if "atomic_task_index" in item and item["atomic_task_index"] is not None:
871
- atomic_task_index = item["atomic_task_index"].item()
872
- item["subtask"] = self.meta.tasks[int(atomic_task_index)]
873
-
874
- return item
875
-
876
- def __repr__(self):
877
- feature_keys = list(self.features)
878
- return (
879
- f"{self.__class__.__name__}({{\n"
880
- f" Repository ID: '{self.repo_id}',\n"
881
- f" Number of selected episodes: '{self.num_episodes}',\n"
882
- f" Number of selected samples: '{self.num_frames}',\n"
883
- f" Features: '{feature_keys}',\n"
884
- "})',\n"
885
- )
886
-
887
- def create_episode_buffer(self, episode_index: int | None = None) -> dict:
888
- current_ep_idx = self.meta.total_episodes if episode_index is None else episode_index
889
- ep_buffer = {}
890
- # size and task are special cases that are not in self.features
891
- ep_buffer["size"] = 0
892
- ep_buffer["task"] = []
893
- for key in self.features:
894
- ep_buffer[key] = current_ep_idx if key == "episode_index" else []
895
- return ep_buffer
896
-
897
- def _get_image_file_path(self, episode_index: int, image_key: str, frame_index: int) -> Path:
898
- fpath = DEFAULT_IMAGE_PATH.format(
899
- image_key=image_key, episode_index=episode_index, frame_index=frame_index
900
- )
901
- return self.root / fpath
902
-
903
- # def _save_image(self, image: torch.Tensor | np.ndarray | PIL.Image.Image | bytes, fpath: Path) -> None:
904
- # if self.image_writer is None:
905
- # if isinstance(image, torch.Tensor):
906
- # image = image.cpu().numpy()
907
- # write_image(image, fpath)
908
- # else:
909
- # self.image_writer.save_image(image=image, fpath=fpath)
910
-
911
- def add_frame(self, frame: dict, task: List[str], timestamp: float | None = None) -> None:
912
- """
913
- This function only adds the frame to the episode_buffer. Apart from images — which are written in a
914
- temporary directory — nothing is written to disk. To save those frames, the 'save_episode()' method
915
- then needs to be called.
916
- """
917
- # Convert torch to numpy if needed
918
- assert len(task) == 4, "Task frame must be of two elements"
919
- for name in frame:
920
- if isinstance(frame[name], torch.Tensor):
921
- frame[name] = frame[name].numpy()
922
-
923
- validate_frame(frame, self.features)
924
-
925
- if self.episode_buffer is None:
926
- self.episode_buffer = self.create_episode_buffer()
927
-
928
- # Automatically add frame_index and timestamp to episode buffer
929
- frame_index = self.episode_buffer["size"]
930
- if timestamp is None:
931
- timestamp = frame_index / self.fps
932
- self.episode_buffer["frame_index"].append(frame_index)
933
- self.episode_buffer["timestamp"].append(timestamp)
934
- self.episode_buffer["task"].append(task)
935
-
936
- # Add frame features to episode_buffer
937
- for key in frame:
938
- if key not in self.features:
939
- raise ValueError(
940
- f"An element of the frame is not in the features. '{key}' not in '{self.features.keys()}'."
941
- )
942
-
943
- if self.features[key]["dtype"] in ["image", "video"]:
944
- img_path = self._get_image_file_path(
945
- episode_index=self.episode_buffer["episode_index"], image_key=key, frame_index=frame_index
946
- )
947
- if frame_index == 0:
948
- img_path.parent.mkdir(parents=True, exist_ok=True)
949
- # self._save_image(frame[key], img_path)
950
- self.episode_buffer[key].append(str(img_path))
951
- else:
952
- self.episode_buffer[key].append(frame[key])
953
-
954
- self.episode_buffer["size"] += 1
955
-
956
- def save_episode(self, episode_data: dict | None = None, raw_file_name: str | None = None) -> None:
957
- """
958
- This will save to disk the current episode in self.episode_buffer.
959
-
960
- Args:
961
- episode_data (dict | None, optional): Dict containing the episode data to save. If None, this will
962
- save the current episode in self.episode_buffer, which is filled with 'add_frame'. Defaults to
963
- None.
964
- """
965
- if not episode_data:
966
- episode_buffer = self.episode_buffer
967
-
968
- validate_episode_buffer(episode_buffer, self.meta.total_episodes, self.features)
969
-
970
- # size and task are special cases that won't be added to hf_dataset
971
- episode_length = episode_buffer.pop("size")
972
- tasks = episode_buffer.pop("task")
973
- episode_tasks = list(set([item for sublist in tasks for item in sublist]))
974
- episode_index = episode_buffer["episode_index"]
975
-
976
- episode_buffer["index"] = np.arange(self.meta.total_frames, self.meta.total_frames + episode_length)
977
- episode_buffer["episode_index"] = np.full((episode_length,), episode_index)
978
-
979
- # Add new tasks to the tasks dictionary
980
- for task in episode_tasks:
981
- task_index = self.meta.get_task_index(task)
982
- if task_index is None:
983
- self.meta.add_task(task)
984
-
985
- # Given tasks in natural language, find their corresponding task indices
986
- episode_buffer["coarse_task_index"] = np.array([self.meta.get_task_index(task[0]) for task in tasks])
987
- episode_buffer["task_index"] = np.array([self.meta.get_task_index(task[1]) for task in tasks])
988
- episode_buffer["coarse_quality_index"] = np.array([self.meta.get_task_index(task[2]) for task in tasks])
989
- episode_buffer["quality_index"] = np.array([self.meta.get_task_index(task[3]) for task in tasks])
990
-
991
- for key, ft in self.features.items():
992
- # index, episode_index, task_index are already processed above, and image and video
993
- # are processed separately by storing image path and frame info as meta data
994
- if key in ["index", "episode_index", "coarse_task_index", "task_index", "coarse_quality_index", "quality_index"] or ft["dtype"] in ["image", "video"]:
995
- continue
996
- episode_buffer[key] = np.stack(episode_buffer[key])
997
-
998
- self._wait_image_writer()
999
- self._save_episode_table(episode_buffer, episode_index)
1000
- ep_stats = compute_episode_stats(episode_buffer, self.features, self.is_compute_episode_stats_image)
1001
-
1002
- if len(self.meta.video_keys) > 0:
1003
- video_paths = self.encode_episode_videos(episode_index)
1004
- for key in self.meta.video_keys:
1005
- episode_buffer[key] = video_paths[key]
1006
-
1007
- # `meta.save_episode` be executed after encoding the videos
1008
- self.meta.save_episode(episode_index, episode_length, episode_tasks, ep_stats, raw_file_name)
1009
-
1010
- ep_data_index = get_episode_data_index(self.meta.episodes, [episode_index])
1011
- ep_data_index_np = {k: t.numpy() for k, t in ep_data_index.items()}
1012
- check_timestamps_sync(
1013
- episode_buffer["timestamp"],
1014
- episode_buffer["episode_index"],
1015
- ep_data_index_np,
1016
- self.fps,
1017
- self.tolerance_s,
1018
- )
1019
-
1020
- video_files = list(self.root.rglob("*.mp4"))
1021
- assert len(video_files) == self.num_episodes * len(self.meta.video_keys)
1022
-
1023
- parquet_files = list(self.root.rglob("*.parquet"))
1024
- assert len(parquet_files) == self.num_episodes
1025
-
1026
- # delete images
1027
- img_dir = self.root / "images"
1028
- if img_dir.is_dir():
1029
- shutil.rmtree(self.root / "images")
1030
-
1031
- if not episode_data: # Reset the buffer
1032
- self.episode_buffer = self.create_episode_buffer()
1033
-
1034
- def _save_episode_table(self, episode_buffer: dict, episode_index: int) -> None:
1035
- episode_dict = {key: episode_buffer[key] for key in self.hf_features}
1036
- ep_dataset = datasets.Dataset.from_dict(episode_dict, features=self.hf_features, split="train")
1037
- ep_dataset = embed_images(ep_dataset)
1038
- self.hf_dataset = concatenate_datasets([self.hf_dataset, ep_dataset])
1039
- self.hf_dataset.set_transform(hf_transform_to_torch)
1040
- ep_data_path = self.root / self.meta.get_data_file_path(ep_index=episode_index)
1041
- ep_data_path.parent.mkdir(parents=True, exist_ok=True)
1042
- ep_dataset.to_parquet(ep_data_path)
1043
-
1044
- def clear_episode_buffer(self) -> None:
1045
- episode_index = self.episode_buffer["episode_index"]
1046
- if self.image_writer is not None:
1047
- for cam_key in self.meta.camera_keys:
1048
- img_dir = self._get_image_file_path(
1049
- episode_index=episode_index, image_key=cam_key, frame_index=0
1050
- ).parent
1051
- if img_dir.is_dir():
1052
- shutil.rmtree(img_dir)
1053
-
1054
- # Reset the buffer
1055
- self.episode_buffer = self.create_episode_buffer()
1056
-
1057
- # def start_image_writer(self, num_processes: int = 0, num_threads: int = 4) -> None:
1058
- # if isinstance(self.image_writer, AsyncImageWriter):
1059
- # logging.warning(
1060
- # "You are starting a new AsyncImageWriter that is replacing an already existing one in the dataset."
1061
- # )
1062
-
1063
- # self.image_writer = AsyncImageWriter(
1064
- # num_processes=num_processes,
1065
- # num_threads=num_threads,
1066
- # )
1067
-
1068
- def stop_image_writer(self) -> None:
1069
- """
1070
- Whenever wrapping this dataset inside a parallelized DataLoader, this needs to be called first to
1071
- remove the image_writer in order for the LeRobotDataset object to be picklable and parallelized.
1072
- """
1073
- if self.image_writer is not None:
1074
- self.image_writer.stop()
1075
- self.image_writer = None
1076
-
1077
- def _wait_image_writer(self) -> None:
1078
- """Wait for asynchronous image writer to finish."""
1079
- if self.image_writer is not None:
1080
- self.image_writer.wait_until_done()
1081
-
1082
- def encode_videos(self) -> None:
1083
- """
1084
- Use ffmpeg to convert frames stored as png into mp4 videos.
1085
- Note: `encode_video_frames` is a blocking call. Making it asynchronous shouldn't speedup encoding,
1086
- since video encoding with ffmpeg is already using multithreading.
1087
- """
1088
- for ep_idx in range(self.meta.total_episodes):
1089
- self.encode_episode_videos(ep_idx)
1090
-
1091
- def encode_episode_videos(self, episode_index: int) -> dict:
1092
- """
1093
- Use ffmpeg to convert frames stored as png into mp4 videos.
1094
- Note: `encode_video_frames` is a blocking call. Making it asynchronous shouldn't speedup encoding,
1095
- since video encoding with ffmpeg is already using multithreading.
1096
- """
1097
- video_paths = {}
1098
- for key in self.meta.video_keys:
1099
- video_path = self.root / self.meta.get_video_file_path(episode_index, key)
1100
- video_paths[key] = str(video_path)
1101
- if video_path.is_file():
1102
- # Skip if video is already encoded. Could be the case when resuming data recording.
1103
- continue
1104
- img_dir = self._get_image_file_path(
1105
- episode_index=episode_index, image_key=key, frame_index=0
1106
- ).parent
1107
- encode_video_frames(img_dir, video_path, self.fps, overwrite=True, vcodec=self.video_codec)
1108
-
1109
- return video_paths
1110
-
1111
- @classmethod
1112
- def create(
1113
- cls,
1114
- repo_id: str,
1115
- fps: int,
1116
- features: dict,
1117
- root: str | Path | None = None,
1118
- robot_type: str | None = None,
1119
- use_videos: bool = True,
1120
- tolerance_s: float = 1e-4,
1121
- image_writer_processes: int = 0,
1122
- image_writer_threads: int = 0,
1123
- video_backend: str | None = None,
1124
- video_codec: Literal["h264", "hevc", "libsvtav1", "h264_nvenc"] = "libsvtav1",
1125
- is_compute_episode_stats_image = True,
1126
- ) -> "LeRobotDataset":
1127
- """Create a LeRobot Dataset from scratch in order to record data."""
1128
- obj = cls.__new__(cls)
1129
- obj.meta = LeRobotDatasetMetadata.create(
1130
- repo_id=repo_id,
1131
- fps=fps,
1132
- robot_type=robot_type,
1133
- features=features,
1134
- root=root,
1135
- use_videos=use_videos,
1136
- )
1137
- obj.repo_id = obj.meta.repo_id
1138
- obj.root = obj.meta.root
1139
- obj.revision = None
1140
- obj.tolerance_s = tolerance_s
1141
- obj.image_writer = None
1142
-
1143
- # if image_writer_processes or image_writer_threads:
1144
- # obj.start_image_writer(image_writer_processes, image_writer_threads)
1145
-
1146
- # TODO(aliberts, rcadene, alexander-soare): Merge this with OnlineBuffer/DataBuffer
1147
- obj.episode_buffer = obj.create_episode_buffer()
1148
-
1149
- obj.episodes = None
1150
- obj.hf_dataset = obj.create_hf_dataset()
1151
- obj.image_transforms = None
1152
- obj.delta_timestamps = None
1153
- obj.delta_indices = None
1154
- obj.episode_data_index = None
1155
- obj.video_backend = video_backend if video_backend is not None else get_safe_default_codec()
1156
- obj.video_codec = video_codec
1157
- obj.is_compute_episode_stats_image = is_compute_episode_stats_image
1158
- return obj
1159
-
1160
-
1161
- class MultiLeRobotDataset(torch.utils.data.Dataset):
1162
- """A dataset consisting of multiple underlying `LeRobotDataset`s.
1163
-
1164
- The underlying `LeRobotDataset`s are effectively concatenated, and this class adopts much of the API
1165
- structure of `LeRobotDataset`.
1166
- """
1167
-
1168
- def __init__(
1169
- self,
1170
- dataset_dirs: list[str],
1171
- episodes: dict | None = None,
1172
- image_transforms: Callable | None = None,
1173
- delta_timestamps: dict[list[float]] | None = None,
1174
- tolerances_s: dict | None = None,
1175
- download_videos: bool = True,
1176
- video_backend: str | None = None,
1177
- ):
1178
- super().__init__()
1179
- self.dataset_dirs = dataset_dirs
1180
- ds_roots = [Path(ds_dir) for ds_dir in dataset_dirs]
1181
- ds_names = [dataset_dir_to_repo_id(ds_dir) for ds_dir in dataset_dirs]
1182
- self.ds_names = ds_names
1183
- self.ds_roots = ds_roots
1184
- self.tolerances_s = tolerances_s if tolerances_s else dict.fromkeys(ds_names, 0.0001)
1185
- # Construct the underlying datasets passing everything but `transform` and `delta_timestamps` which
1186
- # are handled by this class.
1187
- self._datasets = []
1188
- for ds_root, ds_name in zip(ds_roots, ds_names, strict=True):
1189
- try:
1190
- _dataset = LeRobotDataset(
1191
- ds_name,
1192
- root=ds_root,
1193
- episodes=episodes[ds_name] if episodes else None,
1194
- image_transforms=image_transforms,
1195
- delta_timestamps=delta_timestamps,
1196
- tolerance_s=self.tolerances_s[ds_name],
1197
- download_videos=download_videos,
1198
- video_backend=video_backend,
1199
- )
1200
- self._datasets.append(_dataset)
1201
- except Exception as e:
1202
- # logging.error(e)
1203
- logging.error(f"Exception while process ds_root: {ds_root}, ds_name: {ds_name}")
1204
- traceback.print_exc()
1205
- raise e
1206
-
1207
- # Disable any data keys that are not common across all of the datasets. Note: we may relax this
1208
- # restriction in future iterations of this class. For now, this is necessary at least for being able
1209
- # to use PyTorch's default DataLoader collate function.
1210
- self.disabled_features = set()
1211
- intersection_features = set(self._datasets[0].features)
1212
- for ds in self._datasets:
1213
- intersection_features.intersection_update(ds.features)
1214
- if len(intersection_features) == 0:
1215
- raise RuntimeError(
1216
- "Multiple datasets were provided but they had no keys common to all of them. "
1217
- "The multi-dataset functionality currently only keeps common keys."
1218
- )
1219
- for ds_name, ds in zip(self.ds_names, self._datasets, strict=True):
1220
- # Disable warning of empty extra keys do
1221
- extra_keys = set(ds.features).difference(intersection_features)
1222
- if extra_keys:
1223
- logging.warning(
1224
- f"keys {extra_keys} of {ds_name} were disabled as they are not contained in all the "
1225
- "other datasets."
1226
- )
1227
- self.disabled_features.update(extra_keys)
1228
-
1229
- self.image_transforms = image_transforms
1230
- self.delta_timestamps = delta_timestamps
1231
- # TODO(rcadene, aliberts): We should not perform this aggregation for datasets
1232
- # with multiple robots of different ranges. Instead we should have one normalization
1233
- # per robot.
1234
- try:
1235
- self.stats = aggregate_stats([dataset.meta.stats for dataset in self._datasets])
1236
- except ValueError as err:
1237
- logging.warning(
1238
- "Failed to aggregate metadata stats across datasets; using the first "
1239
- "dataset stats instead. Error: %s",
1240
- err,
1241
- )
1242
- self.stats = self._datasets[0].meta.stats
1243
-
1244
- def set_during_training(self, during_training: bool):
1245
- for dataset in self._datasets:
1246
- dataset.during_training = during_training
1247
-
1248
- @property
1249
- def repo_id_to_index(self):
1250
- """Return a mapping from dataset repo_id to a dataset index automatically created by this class.
1251
-
1252
- This index is incorporated as a data key in the dictionary returned by `__getitem__`.
1253
- """
1254
- return {repo_id: i for i, repo_id in enumerate(self.ds_names)}
1255
-
1256
- @property
1257
- def repo_index_to_id(self):
1258
- """Return the inverse mapping if repo_id_to_index."""
1259
- return {v: k for k, v in self.repo_id_to_index}
1260
-
1261
- @property
1262
- def fps(self) -> int:
1263
- """Frames per second used during data collection.
1264
-
1265
- NOTE: Fow now, this relies on a check in __init__ to make sure all sub-datasets have the same info.
1266
- """
1267
- return self._datasets[0].meta.info["fps"]
1268
-
1269
- @property
1270
- def video(self) -> bool:
1271
- """Returns True if this dataset loads video frames from mp4 files.
1272
-
1273
- Returns False if it only loads images from png files.
1274
-
1275
- NOTE: Fow now, this relies on a check in __init__ to make sure all sub-datasets have the same info.
1276
- """
1277
- return self._datasets[0].meta.info.get("video", False)
1278
-
1279
- @property
1280
- def features(self) -> datasets.Features:
1281
- features = {}
1282
- for dataset in self._datasets:
1283
- features.update({k: v for k, v in dataset.hf_features.items() if k not in self.disabled_features})
1284
- return features
1285
-
1286
- @property
1287
- def camera_keys(self) -> list[str]:
1288
- """Keys to access image and video stream from cameras."""
1289
- keys = []
1290
- for key, feats in self.features.items():
1291
- if isinstance(feats, (datasets.Image, VideoFrame)):
1292
- keys.append(key)
1293
- return keys
1294
-
1295
- @property
1296
- def video_frame_keys(self) -> list[str]:
1297
- """Keys to access video frames that requires to be decoded into images.
1298
-
1299
- Note: It is empty if the dataset contains images only,
1300
- or equal to `self.cameras` if the dataset contains videos only,
1301
- or can even be a subset of `self.cameras` in a case of a mixed image/video dataset.
1302
- """
1303
- video_frame_keys = []
1304
- for key, feats in self.features.items():
1305
- if isinstance(feats, VideoFrame):
1306
- video_frame_keys.append(key)
1307
- return video_frame_keys
1308
-
1309
- @property
1310
- def num_frames(self) -> int:
1311
- """Number of samples/frames."""
1312
- return sum(d.num_frames for d in self._datasets)
1313
-
1314
- @property
1315
- def num_episodes(self) -> int:
1316
- """Number of episodes."""
1317
- return sum(d.num_episodes for d in self._datasets)
1318
-
1319
- @property
1320
- def tolerance_s(self) -> float:
1321
- """Tolerance in seconds used to discard loaded frames when their timestamps
1322
- are not close enough from the requested frames. It is only used when `delta_timestamps`
1323
- is provided or when loading video frames from mp4 files.
1324
- """
1325
- # 1e-4 to account for possible numerical error
1326
- return 1 / self.fps - 1e-4
1327
-
1328
- def get_episode_data(self, episode_idx: int) -> dict:
1329
- for dataset in self._datasets:
1330
- if episode_idx < dataset.num_episodes:
1331
- actual_ep_idx = (
1332
- dataset.episodes[episode_idx]
1333
- if dataset.episodes is not None
1334
- else sorted(dataset.meta.episodes.keys())[episode_idx]
1335
- )
1336
- file = str(dataset.root / dataset.meta.get_data_file_path(actual_ep_idx))
1337
- if dataset.meta._version >= packaging.version.parse("v3.0"):
1338
- # v3.0: shared files contain multiple episodes; filter to this one
1339
- table = pq.read_table(file, filters=[("episode_index", "=", actual_ep_idx)])
1340
- else:
1341
- table = pq.read_table(file)
1342
-
1343
- result_dict = {}
1344
- for col_name in table.column_names:
1345
- col = table[col_name]
1346
- try:
1347
- np_arr = col.to_numpy(zero_copy_only=True)
1348
- except Exception:
1349
- raw = col.to_numpy()
1350
- np_arr = np.stack(raw) if raw.dtype == object else raw
1351
- with warnings.catch_warnings():
1352
- warnings.filterwarnings(
1353
- "ignore",
1354
- message="The given NumPy array is not writable",
1355
- category=UserWarning,
1356
- )
1357
- # deal with string in parquet file
1358
- if np_arr.dtype == 'O':
1359
- result_dict[col_name] = np_arr
1360
- else:
1361
- result_dict[col_name] = torch.from_numpy(np_arr)
1362
- return result_dict
1363
- else:
1364
- episode_idx -= dataset.num_episodes
1365
- raise IndexError(f"Episode index {episode_idx} out of bounds.")
1366
-
1367
- def __len__(self):
1368
- return self.num_frames
1369
-
1370
- def __getitem__(self, idx: int) -> dict[str, torch.Tensor]:
1371
- if idx >= len(self):
1372
- raise IndexError(f"Index {idx} out of bounds.")
1373
- # Determine which dataset to get an item from based on the index.
1374
- start_idx = 0
1375
- dataset_idx = 0
1376
- for dataset in self._datasets:
1377
- if idx >= start_idx + dataset.num_frames:
1378
- start_idx += dataset.num_frames
1379
- dataset_idx += 1
1380
- continue
1381
- break
1382
- else:
1383
- raise AssertionError("We expect the loop to break out as long as the index is within bounds.")
1384
- item = self._datasets[dataset_idx][idx - start_idx]
1385
- item["dataset_index"] = torch.tensor(dataset_idx)
1386
- for data_key in self.disabled_features:
1387
- if data_key in item:
1388
- del item[data_key]
1389
-
1390
- return item
1391
-
1392
- def __repr__(self):
1393
- return (
1394
- f"{self.__class__.__name__}(\n"
1395
- f" Dataset Names: '{self.ds_names}',\n"
1396
- f" Number of Samples: {self.num_frames},\n"
1397
- f" Number of Episodes: {self.num_episodes},\n"
1398
- f" Type: {'video (.mp4)' if self.video else 'image (.png)'},\n"
1399
- f" Recorded Frames per Second: {self.fps},\n"
1400
- f" Camera Keys: {self.camera_keys},\n"
1401
- f" Video Frame Keys: {self.video_frame_keys if self.video else 'N/A'},\n"
1402
- f" Transformations: {self.image_transforms},\n"
1403
- f")"
1404
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/processors/base_processor.py DELETED
@@ -1,263 +0,0 @@
1
- from abc import ABC, abstractmethod
2
- from typing import Dict, Any, Optional, List, Literal
3
-
4
- import torch
5
- import numpy as np
6
- from copy import deepcopy
7
- from ..utils.normalizer import LinearNormalizer, NormMode
8
- from fastwam.utils.pytorch_utils import dict_apply
9
-
10
-
11
- class BaseProcessor(ABC):
12
- def __init__(
13
- self,
14
- # keys
15
- shape_meta: Dict[str, Any],
16
- num_obs_steps: int,
17
- num_output_cameras: int,
18
- action_output_dim: int,
19
- proprio_output_dim: int,
20
-
21
- action_state_transforms: Optional[List[Any]],
22
-
23
- # action & state normalization
24
- use_stepwise_action_norm: bool,
25
- norm_default_mode: NormMode,
26
- norm_exception_mode: Dict[str, Dict[str, NormMode]],
27
-
28
- action_state_merger,
29
-
30
- # image transform
31
- train_transforms: Dict[str, List[Any]] | None,
32
- val_transforms: Dict[str, List[Any]] | None,
33
-
34
- # instruction transform
35
- drop_high_level_prob: float,
36
- use_zh_instruction: bool,
37
-
38
- tokenizer: Any
39
- ):
40
- self.shape_meta = shape_meta
41
- self.num_obs_steps = num_obs_steps
42
- self.num_output_cameras = num_output_cameras
43
- self.action_output_dim = action_output_dim
44
- self.proprio_output_dim = proprio_output_dim
45
-
46
- self.drop_high_level_prob = drop_high_level_prob
47
- self.use_zh_instruction = use_zh_instruction
48
-
49
- # image
50
- self.train_transforms = train_transforms
51
- self.val_transforms = val_transforms
52
-
53
- self._is_train = None
54
-
55
- self.action_state_transforms = action_state_transforms
56
- self.action_state_merger = action_state_merger
57
- self.action_state_merger.set_shape_meta(self.shape_meta)
58
-
59
- self.use_stepwise_action_norm = use_stepwise_action_norm
60
- self.norm_default_mode = norm_default_mode
61
- self.norm_exception_mode = norm_exception_mode
62
- self._normalizer = None
63
-
64
- self.tokenizer = tokenizer
65
-
66
- @property
67
- def is_train(self):
68
- if self._is_train is None:
69
- raise ValueError("is_train has not been set. Please call train() and eval() first.")
70
- return self._is_train
71
-
72
- @property
73
- def normalizer(self) -> LinearNormalizer:
74
- if self._normalizer is None:
75
- raise ValueError("normalizer has not been set. Please call set_normalizer_from_stats() first.")
76
- return self._normalizer
77
-
78
- def train(self):
79
- self._is_train = True
80
- return self
81
-
82
- def eval(self):
83
- self._is_train = False
84
- return self
85
-
86
- def set_normalizer_from_stats(self, dataset_stats: Dict[str, Any] = None):
87
- self._normalizer = LinearNormalizer(
88
- use_stepwise_action_norm=self.use_stepwise_action_norm,
89
- shape_meta=self.shape_meta,
90
- default_mode=self.norm_default_mode,
91
- exception_mode=self.norm_exception_mode,
92
- stats=dataset_stats,
93
- )
94
-
95
- def augment_instruction(self, data: Dict[str, str] | List[str]) -> List[str]:
96
- """
97
- Args:
98
- data: Dict[str, str] | List[str], lerobot sample in raw mcap
99
-
100
- Returns:
101
- List[str], processed instructions
102
- """
103
- # if single instruction, convert to list
104
- if "coarse_task" in data:
105
- high_level_instruction = data["coarse_task"]
106
- else:
107
- high_level_instruction = ""
108
- if "task" not in data:
109
- return f"[high] {high_level_instruction}"
110
-
111
- low_level_instruction = data["task"]
112
- # Galaxea lerobot use @ to split Chinese and English instruction
113
- if "@" in low_level_instruction:
114
- zh, eng = low_level_instruction.split("@")
115
- low_level_instruction = zh if self.use_zh_instruction else eng
116
-
117
- if np.random.rand() < self.drop_high_level_prob:
118
- instruction = f"[Low]: {low_level_instruction}"
119
- else:
120
- instruction = f"[High]: {high_level_instruction}, [Low]: {low_level_instruction}"
121
-
122
- return instruction
123
-
124
- def action_state_transform(self, batch):
125
- if "action" in batch:
126
- for meta in self.shape_meta["action"]:
127
- k, meta_shape = meta["key"], meta["raw_shape"]
128
- actual_shape = batch["action"][k].shape[-1]
129
- assert actual_shape == meta_shape, \
130
- f"Action key {k} actual raw shape {actual_shape} mismatch with meta raw shape {meta_shape}."
131
-
132
- for meta in self.shape_meta["state"]:
133
- k, meta_shape = meta["key"], meta["raw_shape"]
134
- actual_shape = batch["state"][k].shape[-1]
135
- assert actual_shape == meta_shape, \
136
- f"State key {k} actual raw shape {actual_shape} mismatch with meta raw shape{meta_shape}."
137
-
138
- if self.action_state_transforms is not None:
139
- for trans in self.action_state_transforms:
140
- batch = trans.forward(batch)
141
-
142
- if "action" in batch:
143
- for meta in self.shape_meta["action"]:
144
- k, meta_shape = meta["key"], meta["shape"]
145
- actual_shape = batch["action"][k].shape[-1]
146
- assert actual_shape == meta_shape, \
147
- f"Action key {k} actual transformed shape {actual_shape} mismatch with meta shape {meta_shape}."
148
-
149
- for meta in self.shape_meta["state"]:
150
- k, meta_shape = meta["key"], meta["shape"]
151
- actual_shape = batch["state"][k].shape[-1]
152
- assert actual_shape == meta_shape, \
153
- f"State key {k} actual transformed shape {actual_shape} mismatch with meta raw shape {meta_shape}."
154
-
155
- return batch
156
-
157
- def preprocess(self, data: Dict[str, Any]) -> Dict[str, Any]:
158
- """
159
- Preprocess the data for the policy model.
160
-
161
- Args:
162
- Data: Dict[str, Any], lerobot sample in raw mcap obtained from dataset __getitem__:
163
- - "action": Optional, Dict[str, torch.Tensor] -> [action_horizon, action_dim]
164
- - "state": Dict[str, torch.Tensor] -> [num_obs_steps, state_dim]
165
- - "images": Dict[str, torch.Tensor] -> [num_obs_steps, C, H, W]
166
- - "action_is_pad": Optional, torch.Tensor -> [action_horizon,]
167
- - "state_is_pad": torch.Tensor -> [num_obs_steps,]
168
- - "image_is_pad": torch.Tensor -> [num_obs_steps,]
169
- - "idx": int, sample index
170
-
171
- Returns:
172
- Sample: Dict[str, Any], which can collated:
173
- - "input_ids": torch.Tensor -> [max_image_text_tokens,]
174
- - "attention_mask": torch.Tensor -> [max_image_text_tokens,]
175
- - "pixel_values": torch.Tensor -> [num_input_cameras, C, H, W]
176
- - "image_is_pad": torch.Tensor -> [num_obs_steps,]
177
- - "proprio": torch.Tensor -> [num_obs_steps, proprio_dim]
178
- - "state_is_pad": torch.Tensor -> [num_obs_steps,]
179
- - "action": Optional, torch.Tensor -> [action_horizon, action_dim]
180
- - "action_is_pad": Optional, torch.Tensor -> [action_horizon,]
181
- - "gt_action: Optional, deepcopy of input action for open loop eval, which is left untouched
182
- - "idx": int, sample index
183
- """
184
- sample = {}
185
- # 1. instruction
186
- sample["instruction"] = self.augment_instruction(data)
187
- sample["image_is_pad"] = data["image_is_pad"]
188
-
189
- # 2. image
190
- processed_images = []
191
- for meta in self.shape_meta["images"]:
192
- key, shape = meta["key"], meta["shape"]
193
- image = data["images"][key] # [num_obs_steps, C, H, W]
194
- assert image.ndim == 4, f"Expected 4 dimensions (num_obs_steps, C, H, W), got shape {image.shape}"
195
-
196
- # Apply transforms efficiently on the merged batch
197
- transforms = self.train_transforms if self.is_train else self.val_transforms
198
- for trans in transforms[key]:
199
- image = trans(image)
200
-
201
- meta_shape = [self.num_obs_steps] + shape
202
- assert image.shape == meta_shape, \
203
- f"Expected shape {meta_shape}, got {image.shape} after transforms for key {key}"
204
-
205
- processed_images.append(image)
206
-
207
- pixel_values = torch.cat(processed_images, dim=0) # [num_input_cameras, C, H, W]
208
- if self.num_output_cameras > pixel_values.shape[0]:
209
- out = torch.zeros((self.num_output_cameras,) + pixel_values.shape[1:], device=pixel_values.device, dtype=pixel_values.dtype)
210
- out[0: pixel_values.shape[0]] = pixel_values
211
- sample["pixel_values"] = out
212
- else:
213
- sample["pixel_values"] = pixel_values
214
-
215
- # Copy action before transform for open-loop evaluation,
216
- # disabled for training dataset as it may cause collating key problem.
217
- if not self.is_train and "action" in data:
218
- sample["gt_action"] = deepcopy(data["action"])
219
-
220
- # 3. action & state
221
- data = self.action_state_transform(data)
222
- data = self.normalizer.forward(data)
223
- data = self.action_state_merger.forward(data)
224
-
225
- if "action" in data:
226
- sample["action"] = data["action"] # [action_horizon, action_dim]
227
- sample["action_is_pad"] = data["action_is_pad"] # [action_horizon,]
228
- sample["action_dim_is_pad"] = data["action_dim_is_pad"] # [action_dim,]
229
- assert sample["action"].shape[-1] == self.action_output_dim
230
-
231
- # TODO: rename all "state" into "proprio"
232
- sample["proprio"] = data["state"] # [num_obs_steps, proprio_dim]
233
- sample["proprio_is_pad"] = data["state_is_pad"] # [num_obs_steps,]
234
- sample["proprio_dim_is_pad"] = data["state_dim_is_pad"] # [proprio_dim,]
235
- assert sample["proprio"].shape[-1] == self.proprio_output_dim
236
-
237
- sample["idx"] = data["idx"]
238
-
239
- sample = self.tokenizer(sample)
240
-
241
- return sample
242
-
243
- def postprocess(self, data: Dict[str, Any]) -> Dict[str, Any]:
244
- """
245
- Postprocess the data for the policy model.
246
-
247
- Args:
248
- data: Dict[str, Any], lerobot sample in raw mcap
249
-
250
- Returns:
251
- data: Dict[str, Any], processed data including unnormalized action
252
- """
253
- assert "action" in data, "Action is required in postprocess"
254
- data["state"] = data.pop("proprio")
255
- data = self.action_state_merger.backward(data)
256
- data = self.normalizer.backward(data)
257
- if self.action_state_transforms is not None:
258
- for trans in reversed(self.action_state_transforms):
259
- data = trans.backward(data)
260
-
261
- start_obs_step = self.num_obs_steps - 1
262
- data["action"] = dict_apply(data["action"], lambda x: x[:, start_obs_step:, :])
263
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/processors/fastwam_processor.py DELETED
@@ -1,362 +0,0 @@
1
- # Author: Rui Heng Yang
2
-
3
- from abc import ABC, abstractmethod
4
- from typing import Dict, Any, Optional, List, Literal
5
-
6
- import torch
7
- import numpy as np
8
- from copy import deepcopy
9
- from ..utils.normalizer import LinearNormalizer, NormMode
10
- from fastwam.utils.pytorch_utils import dict_apply
11
- from fastwam.utils.logging_config import get_logger
12
- from .base_processor import BaseProcessor
13
-
14
- logger = get_logger(__name__)
15
-
16
- # Sample-identity keys that `preprocess()` forwards untouched. Consumers that
17
- # need to know *which* episode/frame a sample came from (e.g. the EEF-anchor
18
- # lookup in `RobotVideoDataset`) must read them off the returned sample, since
19
- # both resample paths mean the requested index is not the delivered one.
20
- IDENTITY_KEYS = ("dataset_index", "episode_index", "frame_index")
21
-
22
-
23
- class FastWAMProcessor(BaseProcessor):
24
- def __init__(
25
- self,
26
- # keys
27
- shape_meta: Dict[str, Any],
28
- num_obs_steps: int,
29
- num_output_cameras: int,
30
- action_output_dim: int,
31
- proprio_output_dim: int,
32
-
33
- action_state_transforms: Optional[List[Any]],
34
-
35
- # action & state normalization
36
- use_stepwise_action_norm: bool,
37
- norm_default_mode: NormMode,
38
- norm_exception_mode: Dict[str, Dict[str, NormMode]],
39
-
40
- action_state_merger,
41
-
42
- # image transform
43
- train_transforms: Dict[str, List[Any]] | None,
44
- val_transforms: Dict[str, List[Any]] | None,
45
-
46
- # instruction transform
47
- drop_high_level_prob: float = 1.0,
48
- use_zh_instruction: bool = False,
49
-
50
- tokenizer: Optional[Any] = None,
51
- delta_action_dim_mask: Optional[Dict[str, List[bool]]] = None,
52
- ):
53
- self.shape_meta = shape_meta
54
- self.num_obs_steps = num_obs_steps
55
- self.num_output_cameras = num_output_cameras
56
- self.action_output_dim = action_output_dim
57
- self.proprio_output_dim = proprio_output_dim
58
-
59
- self.drop_high_level_prob = drop_high_level_prob
60
- self.use_zh_instruction = use_zh_instruction
61
-
62
- # image
63
- self.train_transforms = train_transforms
64
- self.val_transforms = val_transforms
65
-
66
- self._is_train = None
67
-
68
- self.action_state_transforms = action_state_transforms
69
- self.action_state_merger = action_state_merger
70
- self.action_state_merger.set_shape_meta(self.shape_meta)
71
-
72
- self.use_stepwise_action_norm = use_stepwise_action_norm
73
- self.norm_default_mode = norm_default_mode
74
- self.norm_exception_mode = norm_exception_mode
75
- self._normalizer = None
76
-
77
- self.tokenizer = tokenizer
78
- if delta_action_dim_mask is None:
79
- self.delta_action_dim_mask = None
80
- else:
81
- action_meta = self.shape_meta["action"]
82
- expected_keys = [m["key"] for m in action_meta]
83
- provided_keys = list(delta_action_dim_mask.keys())
84
- if set(provided_keys) != set(expected_keys):
85
- raise ValueError(
86
- f"`delta_action_dim_mask` keys mismatch. Expected {expected_keys}, got {provided_keys}."
87
- )
88
-
89
- self.delta_action_dim_mask = {}
90
- for meta in action_meta:
91
- key = meta["key"]
92
- expected_dim = meta["shape"]
93
- mask = delta_action_dim_mask[key]
94
- if len(mask) != expected_dim:
95
- raise ValueError(
96
- f"`delta_action_dim_mask[{key}]` length must be {expected_dim}, got {len(mask)}."
97
- )
98
- self.delta_action_dim_mask[key] = torch.as_tensor(mask, dtype=torch.bool)
99
-
100
- @property
101
- def is_train(self):
102
- if self._is_train is None:
103
- raise ValueError("is_train has not been set. Please call train() and eval() first.")
104
- return self._is_train
105
-
106
- @property
107
- def normalizer(self) -> LinearNormalizer:
108
- if self._normalizer is None:
109
- raise ValueError("normalizer has not been set. Please call set_normalizer_from_stats() first.")
110
- return self._normalizer
111
-
112
- def train(self):
113
- self._is_train = True
114
- return self
115
-
116
- def eval(self):
117
- self._is_train = False
118
- return self
119
-
120
- def set_normalizer_from_stats(self, dataset_stats: Dict[str, Any] = None):
121
- self._normalizer = LinearNormalizer(
122
- use_stepwise_action_norm=self.use_stepwise_action_norm,
123
- shape_meta=self.shape_meta,
124
- default_mode=self.norm_default_mode,
125
- exception_mode=self.norm_exception_mode,
126
- stats=dataset_stats,
127
- )
128
-
129
- def augment_instruction(self, data: Dict[str, str] | List[str]) -> List[str]:
130
- """
131
- Args:
132
- data: Dict[str, str] | List[str], lerobot sample in raw mcap
133
-
134
- Returns:
135
- List[str], processed instructions
136
- """
137
- # if single instruction, convert to list
138
- if "coarse_task" in data:
139
- high_level_instruction = data["coarse_task"]
140
- else:
141
- high_level_instruction = ""
142
- if "task" not in data:
143
- return f"[high] {high_level_instruction}"
144
-
145
- low_level_instruction = data["task"]
146
- # Galaxea lerobot use @ to split Chinese and English instruction
147
- if "@" in low_level_instruction:
148
- zh, eng = low_level_instruction.split("@")
149
- low_level_instruction = zh if self.use_zh_instruction else eng
150
-
151
- if np.random.rand() < self.drop_high_level_prob:
152
- instruction = f"{low_level_instruction}"
153
- else:
154
- instruction = f"[High]: {high_level_instruction}, [Low]: {low_level_instruction}"
155
-
156
- return instruction
157
-
158
- def action_state_transform(self, batch):
159
- if "action" in batch:
160
- for meta in self.shape_meta["action"]:
161
- k, meta_shape = meta["key"], meta["raw_shape"]
162
- actual_shape = batch["action"][k].shape[-1]
163
- assert actual_shape == meta_shape, \
164
- f"Action key {k} actual raw shape {actual_shape} mismatch with meta raw shape {meta_shape}."
165
-
166
- for meta in self.shape_meta["state"]:
167
- k, meta_shape = meta["key"], meta["raw_shape"]
168
- actual_shape = batch["state"][k].shape[-1]
169
- assert actual_shape == meta_shape, \
170
- f"State key {k} actual raw shape {actual_shape} mismatch with meta raw shape {meta_shape}."
171
-
172
- if self.action_state_transforms is not None:
173
- for trans in self.action_state_transforms:
174
- batch = trans.forward(batch)
175
-
176
- if "action" in batch:
177
- for meta in self.shape_meta["action"]:
178
- k, meta_shape = meta["key"], meta["shape"]
179
- actual_shape = batch["action"][k].shape[-1]
180
- assert actual_shape == meta_shape, \
181
- f"Action key {k} actual transformed shape {actual_shape} mismatch with meta shape {meta_shape}."
182
-
183
- for meta in self.shape_meta["state"]:
184
- k, meta_shape = meta["key"], meta["shape"]
185
- actual_shape = batch["state"][k].shape[-1]
186
- assert actual_shape == meta_shape, \
187
- f"State key {k} actual transformed shape {actual_shape} mismatch with meta raw shape {meta_shape}."
188
-
189
- return batch
190
-
191
- def preprocess_images(
192
- self,
193
- images: Dict[str, torch.Tensor],
194
- *,
195
- expected_num_obs_steps: Optional[int] = None,
196
- ) -> torch.Tensor:
197
- """Transform and stack camera images without processing action or state.
198
-
199
- Parameters
200
- ----------
201
- images:
202
- Mapping from configured camera key to ``[T,C,H,W]`` uint8 tensors.
203
- expected_num_obs_steps:
204
- Expected temporal length. Defaults to the training dataset's full
205
- observation horizon; precompute uses the nine VAE-sampled frames.
206
-
207
- Returns
208
- -------
209
- torch.Tensor
210
- Camera-major tensor ``[num_output_cameras,T,C,H,W]``.
211
- """
212
-
213
- num_obs_steps = (
214
- self.num_obs_steps
215
- if expected_num_obs_steps is None
216
- else int(expected_num_obs_steps)
217
- )
218
- processed_images = []
219
- for meta in self.shape_meta["images"]:
220
- key, shape = meta["key"], meta["shape"]
221
- image = images[key]
222
- if image.ndim != 4:
223
- raise ValueError(
224
- "Expected image dimensions [T,C,H,W], "
225
- f"got {tuple(image.shape)} for key {key}"
226
- )
227
-
228
- transforms = self.train_transforms if self.is_train else self.val_transforms
229
- current_transforms = transforms[key] if isinstance(transforms, dict) else transforms
230
- for transform in current_transforms:
231
- image = transform(image)
232
-
233
- expected_shape = [num_obs_steps] + shape
234
- if list(image.shape) != expected_shape:
235
- raise ValueError(
236
- f"Expected shape {expected_shape}, got {tuple(image.shape)} "
237
- f"after transforms for key {key}"
238
- )
239
- processed_images.append(image)
240
-
241
- pixel_values = torch.stack(processed_images, dim=0)
242
- if self.num_output_cameras > pixel_values.shape[0]:
243
- output = torch.zeros(
244
- (self.num_output_cameras,) + pixel_values.shape[1:],
245
- device=pixel_values.device,
246
- dtype=pixel_values.dtype,
247
- )
248
- output[: pixel_values.shape[0]] = pixel_values
249
- return output
250
- if self.num_output_cameras < pixel_values.shape[0]:
251
- logger.warning(
252
- "num_output_cameras %d is less than input cameras %d; truncating.",
253
- self.num_output_cameras,
254
- pixel_values.shape[0],
255
- )
256
- return pixel_values[: self.num_output_cameras]
257
- return pixel_values
258
-
259
- def preprocess(self, data: Dict[str, Any]) -> Dict[str, Any]:
260
- """
261
- Preprocess the data for the policy model.
262
-
263
- Args:
264
- Data: Dict[str, Any], lerobot sample in raw mcap obtained from dataset __getitem__:
265
- - "action": Optional, Dict[str, torch.Tensor] -> [action_horizon, action_dim]
266
- - "state": Dict[str, torch.Tensor] -> [num_obs_steps, state_dim]
267
- - "images": Dict[str, torch.Tensor] -> [num_obs_steps, C, H, W]
268
- - "action_is_pad": Optional, torch.Tensor -> [action_horizon,]
269
- - "state_is_pad": torch.Tensor -> [num_obs_steps,]
270
- - "image_is_pad": torch.Tensor -> [num_obs_steps,]
271
- - "idx": int, sample index
272
-
273
- Returns:
274
- Sample: Dict[str, Any], which can collated:
275
- - "input_ids": torch.Tensor -> [max_image_text_tokens,]
276
- - "attention_mask": torch.Tensor -> [max_image_text_tokens,]
277
- - "pixel_values": torch.Tensor -> [num_input_cameras, C, H, W]
278
- - "image_is_pad": torch.Tensor -> [num_obs_steps,]
279
- - "proprio": torch.Tensor -> [num_obs_steps, proprio_dim]
280
- - "state_is_pad": torch.Tensor -> [num_obs_steps,]
281
- - "action": Optional, torch.Tensor -> [action_horizon, action_dim]
282
- - "action_is_pad": Optional, torch.Tensor -> [action_horizon,]
283
- - "gt_action: Optional, deepcopy of input action for open loop eval, which is left untouched
284
- - "idx": int, sample index
285
- """
286
- sample = {}
287
- # 1. instruction
288
- sample["instruction"] = self.augment_instruction(data)
289
- sample["image_is_pad"] = data["image_is_pad"]
290
-
291
- # 2. image
292
- if data["images"]:
293
- sample["pixel_values"] = self.preprocess_images(data["images"])
294
-
295
- # Copy action before transform for open-loop evaluation,
296
- # disabled for training dataset as it may cause collating key problem.
297
- if not self.is_train and "action" in data:
298
- sample["gt_action"] = deepcopy(data["action"])
299
-
300
- # 3. action & state
301
- if "action" in data and self.delta_action_dim_mask is not None:
302
- action_is_pad = torch.as_tensor(data["action_is_pad"], dtype=torch.bool)
303
- if bool(action_is_pad.any().item()):
304
- for key, dim_mask in self.delta_action_dim_mask.items():
305
- cur_action = data["action"][key]
306
- cur_action_is_pad = action_is_pad.to(device=cur_action.device)
307
- cur_dim_mask = dim_mask.to(device=cur_action.device)
308
- pad_delta_mask = cur_action_is_pad.unsqueeze(1) & cur_dim_mask.unsqueeze(0)
309
- cur_action[pad_delta_mask] = 0.0
310
- data = self.action_state_transform(data)
311
- data = self.normalizer.forward(data)
312
- data = self.action_state_merger.forward(data)
313
-
314
- if "action" in data:
315
- sample["action"] = data["action"] # [action_horizon, action_dim]
316
- sample["action_is_pad"] = data["action_is_pad"] # [action_horizon,]
317
- sample["action_dim_is_pad"] = data["action_dim_is_pad"] # [action_dim,]
318
- assert sample["action"].shape[-1] == self.action_output_dim
319
- # sample["action"][sample["action_is_pad"], :-1] = 0.0 # NOTE: we assume use delta_eef_pose + gripper, so pad action is 0
320
-
321
-
322
- # TODO: rename all "state" into "proprio"
323
- sample["proprio"] = data["state"] # [num_obs_steps, proprio_dim]
324
- sample["proprio_is_pad"] = data["state_is_pad"] # [num_obs_steps,]
325
- sample["proprio_dim_is_pad"] = data["state_dim_is_pad"] # [proprio_dim,]
326
- assert sample["proprio"].shape[-1] == self.proprio_output_dim
327
-
328
- sample["idx"] = data["idx"]
329
-
330
- # Episode/frame identity, forwarded verbatim. `preprocess` builds a fresh
331
- # dict, so anything not copied here is dropped before the caller sees it.
332
- # `RobotVideoDataset` keys its EEF-anchor lookup off these and consumes
333
- # them, so they never reach the collater.
334
- for key in IDENTITY_KEYS:
335
- if key in data:
336
- sample[key] = data[key]
337
-
338
- # sample = self.tokenizer(sample)
339
-
340
- return sample
341
-
342
- def postprocess(self, data: Dict[str, Any]) -> Dict[str, Any]:
343
- """
344
- Postprocess the data for the policy model.
345
-
346
- Args:
347
- data: Dict[str, Any], lerobot sample in raw mcap
348
-
349
- Returns:
350
- data: Dict[str, Any], processed data including unnormalized action
351
- """
352
- assert "action" in data, "Action is required in postprocess"
353
- data["state"] = data.pop("proprio")
354
- data = self.action_state_merger.backward(data)
355
- data = self.normalizer.backward(data)
356
- if self.action_state_transforms is not None:
357
- for trans in reversed(self.action_state_transforms):
358
- data = trans.backward(data)
359
-
360
- start_obs_step = self.num_obs_steps - 1
361
- data["action"] = dict_apply(data["action"], lambda x: x[:, start_obs_step:, :])
362
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/robot_video_dataset.py DELETED
@@ -1,446 +0,0 @@
1
- # Author: Rui Heng Yang
2
-
3
- import hashlib
4
- import os
5
- from pathlib import Path
6
- from typing import Any, Optional
7
- import numpy as np
8
- import traceback
9
- import torch
10
- import torchvision.transforms.functional as transforms_F
11
-
12
- from omegaconf import DictConfig, OmegaConf
13
-
14
- from hydra.utils import instantiate
15
- from .base_lerobot_dataset import BaseLerobotDataset
16
- from ..video_latent_cache import VideoLatentCache, build_video_preprocess_spec
17
- from .utils.normalizer import save_dataset_stats_to_json, load_dataset_stats_from_json
18
- from ..dataset_utils import ResizeSmallestSideAspectPreserving, CenterCrop, Normalize
19
- from fastwam.utils.logging_config import get_logger
20
- from fastwam.utils import misc, pytorch_utils
21
- logger = get_logger(__name__)
22
-
23
-
24
- DEFAULT_PROMPT = "A video recorded from a robot's point of view executing the following instruction: {task}"
25
-
26
-
27
- def _is_main_process_without_init() -> bool:
28
- return pytorch_utils._resolve_global_rank() == 0
29
-
30
- class RobotVideoDataset(torch.utils.data.Dataset):
31
- def __init__(
32
- self,
33
- dataset_dirs,
34
- shape_meta,
35
- num_frames=33,
36
- video_size=[384, 640],
37
- camera_key=None,
38
- processor=None,
39
- text_embedding_cache_dir=None,
40
- context_len=128,
41
- pretrained_norm_stats=None,
42
- val_set_proportion=0.05,
43
- is_training_set=False,
44
- episode_indices=None,
45
- episode_indices_path=None,
46
- episode_indices_key=None,
47
- global_sample_stride=1,
48
- action_video_freq_ratio: int = 1,
49
- skip_padding_as_possible: bool = False,
50
- max_padding_retry: int = 3,
51
- concat_multi_camera: str = "horizontal", # "horizontal", "vertical", "robotwin", or None
52
- video_backend: Optional[str] = None,
53
- video_latent_cache_dir: Optional[str] = None,
54
- video_latent_cache_vae_identity: Optional[dict[str, str]] = None,
55
- strict_getitem: bool = False,
56
- precompute_video_only: bool = False,
57
- override_instruction: Optional[str] = None, # whether to hardcode a specific instruction for all samples, for debugging
58
- eef_anchor_calibration_path: Optional[str] = None,
59
- eef_anchor_raw_resolution: int = 512,
60
- ):
61
- if video_latent_cache_dir is not None and not strict_getitem:
62
- raise ValueError(
63
- "`strict_getitem=true` is required with `video_latent_cache_dir` "
64
- "to prevent latent/action sample misalignment."
65
- )
66
- if video_latent_cache_dir is not None and video_backend is None:
67
- raise ValueError(
68
- "`video_backend` must be explicitly pinned when using a video latent cache."
69
- )
70
- if video_latent_cache_dir is not None and video_latent_cache_vae_identity is None:
71
- raise ValueError(
72
- "`video_latent_cache_vae_identity` is required with a video latent cache."
73
- )
74
- if strict_getitem and skip_padding_as_possible:
75
- raise ValueError(
76
- "`skip_padding_as_possible` must be false in strict/cache mode because "
77
- "padding retries substitute a random global sample index."
78
- )
79
- if precompute_video_only and (not strict_getitem or video_latent_cache_dir is not None):
80
- raise ValueError(
81
- "`precompute_video_only` requires strict_getitem=true and no latent cache."
82
- )
83
-
84
- if (num_frames - 1) % action_video_freq_ratio != 0:
85
- raise ValueError(
86
- "num_frames-1 must be divisible by action_video_freq_ratio, got "
87
- f"{num_frames - 1} and {action_video_freq_ratio}"
88
- )
89
- if ((num_frames - 1) // action_video_freq_ratio) % 4 != 0:
90
- raise ValueError(
91
- "video transitions must be divisible by 4 for tokenization, got "
92
- f"{(num_frames - 1) // action_video_freq_ratio}"
93
- )
94
- video_sample_indices = list(range(0, num_frames, action_video_freq_ratio))
95
-
96
- shape_meta_container = OmegaConf.to_container(shape_meta, resolve=True)
97
- processor_config: dict[str, Any] | None = None
98
- if isinstance(processor, DictConfig):
99
- processor_config = OmegaConf.to_container(processor, resolve=True)
100
-
101
- self.lerobot_dataset = BaseLerobotDataset(
102
- dataset_dirs=dataset_dirs,
103
- shape_meta=shape_meta_container,
104
- obs_size=num_frames,
105
- action_size=num_frames - 1,
106
- val_set_proportion=val_set_proportion,
107
- is_training_set=is_training_set,
108
- episode_indices=episode_indices,
109
- episode_indices_path=episode_indices_path,
110
- episode_indices_key=episode_indices_key,
111
- global_sample_stride=global_sample_stride,
112
- video_backend=video_backend,
113
- strict_getitem=strict_getitem,
114
- image_only=precompute_video_only,
115
- image_sample_indices=(video_sample_indices if precompute_video_only else None),
116
- )
117
-
118
- self.num_frames = num_frames
119
- self.action_video_freq_ratio = action_video_freq_ratio
120
-
121
- self.video_sample_indices = video_sample_indices
122
-
123
- self.camera_key = camera_key
124
- self.video_latent_cache_dir = video_latent_cache_dir
125
- self.strict_getitem = bool(strict_getitem)
126
- self.precompute_video_only = bool(precompute_video_only)
127
- self.lerobot_dataset._set_return_images(video_latent_cache_dir is None)
128
-
129
- self.video_size = video_size
130
- self.text_embedding_cache_dir = text_embedding_cache_dir
131
- self.context_len = context_len
132
- self.skip_padding_as_possible = skip_padding_as_possible
133
- self.max_padding_retry = max_padding_retry
134
- self.concat_multi_camera = concat_multi_camera
135
- self.override_instruction = override_instruction
136
-
137
- resolved_episode_indices_path = None
138
- episode_indices_path_sha256 = None
139
- if episode_indices_path is not None:
140
- resolved_path = Path(episode_indices_path).expanduser().resolve()
141
- if not resolved_path.is_file():
142
- raise FileNotFoundError(f"Episode split file not found: {resolved_path}")
143
- resolved_episode_indices_path = str(resolved_path)
144
- episode_indices_path_sha256 = hashlib.sha256(resolved_path.read_bytes()).hexdigest()
145
-
146
- preprocess_spec = build_video_preprocess_spec(
147
- dataset_dirs=dataset_dirs,
148
- shape_meta=shape_meta_container,
149
- processor_config=processor_config,
150
- num_frames=num_frames,
151
- action_video_freq_ratio=action_video_freq_ratio,
152
- video_size=video_size,
153
- concat_multi_camera=concat_multi_camera,
154
- video_backend=str(video_backend),
155
- global_sample_stride=global_sample_stride,
156
- sample_selection={
157
- "val_set_proportion": float(val_set_proportion),
158
- "is_training_set": bool(is_training_set),
159
- "episode_indices": (
160
- None if episode_indices is None else [int(value) for value in episode_indices]
161
- ),
162
- "episode_indices_path": (
163
- resolved_episode_indices_path
164
- ),
165
- "episode_indices_path_sha256": episode_indices_path_sha256,
166
- "episode_indices_key": episode_indices_key,
167
- },
168
- )
169
- self.video_preprocess_spec = preprocess_spec
170
- self.video_latent_cache = None
171
- if video_latent_cache_dir is not None:
172
- expected_vae_identity = OmegaConf.to_container(
173
- video_latent_cache_vae_identity, resolve=True
174
- ) if isinstance(video_latent_cache_vae_identity, DictConfig) else dict(
175
- video_latent_cache_vae_identity
176
- )
177
- self.video_latent_cache = VideoLatentCache(
178
- video_latent_cache_dir,
179
- expected_sample_count=len(self.lerobot_dataset),
180
- expected_preprocess_spec=preprocess_spec,
181
- expected_vae_identity=expected_vae_identity,
182
- )
183
-
184
- self.resize_transform = ResizeSmallestSideAspectPreserving(
185
- args={"img_w": self.video_size[1], "img_h": self.video_size[0]},
186
- )
187
- self.crop_transform = CenterCrop(
188
- args={"img_w": self.video_size[1], "img_h": self.video_size[0]},
189
- )
190
- self.normalize_transform = Normalize(
191
- args={"mean": 0.5, "std": 0.5},
192
- )
193
- if processor is not None:
194
- if isinstance(processor, DictConfig):
195
- processor = instantiate(processor)
196
- if not pretrained_norm_stats:
197
- if not is_training_set:
198
- raise ValueError("pretrained_norm_stats must be provided for validation/test sets since we don't want to calculate stats on them.")
199
- dist_initialized = (
200
- torch.distributed.is_available() and torch.distributed.is_initialized()
201
- )
202
- if _is_main_process_without_init() or not dist_initialized:
203
- logger.info("Calculating dataset stats for normalization...")
204
- dataset_stats = self.lerobot_dataset.get_dataset_stats(processor)
205
- work_dir = misc.get_work_dir()
206
- if _is_main_process_without_init():
207
- save_dataset_stats_to_json(dataset_stats, os.path.join(work_dir, "dataset_stats.json"))
208
- else:
209
- dataset_stats = None
210
- if dist_initialized:
211
- obj_list = [dataset_stats]
212
- torch.distributed.broadcast_object_list(obj_list, src=0)
213
- dataset_stats = obj_list[0]
214
- else:
215
- dataset_stats = load_dataset_stats_from_json(pretrained_norm_stats)
216
- logger.info(f"Using dataset stats: {pretrained_norm_stats}")
217
- if _is_main_process_without_init():
218
- work_dir = misc.get_work_dir()
219
- save_dataset_stats_to_json(dataset_stats, os.path.join(work_dir, "dataset_stats.json"))
220
-
221
- processor.set_normalizer_from_stats(dataset_stats)
222
- self.lerobot_dataset.set_processor(processor)
223
-
224
- # EEF-relative camera RoPE: resolve every episode's anchors once, here,
225
- # and hold them in RAM. No sidecar exists (plan Section 15). Disabled
226
- # unless a calibration path is configured, so every other mode keeps its
227
- # existing sample keys and behavior byte for byte.
228
- self.eef_anchor_index = None
229
- if eef_anchor_calibration_path:
230
- from fastwam.datasets.eef_anchors import EpisodeAnchorIndex
231
- from fastwam.geometry import EEFProjector, load_calibration
232
-
233
- calibration = load_calibration(eef_anchor_calibration_path)
234
- self.eef_anchor_index = EpisodeAnchorIndex.build(
235
- [str(d) for d in dataset_dirs],
236
- calibration_path=eef_anchor_calibration_path,
237
- projector=EEFProjector(calibration, int(eef_anchor_raw_resolution)),
238
- )
239
-
240
- def __len__(self):
241
- return len(self.lerobot_dataset)
242
-
243
- def _get(self, idx):
244
- sample_idx = idx
245
- sample = None
246
- for attempt in range(self.max_padding_retry + 1):
247
- sample = self.lerobot_dataset[sample_idx]
248
-
249
- if not self.skip_padding_as_possible:
250
- break
251
-
252
- action_is_pad = sample["action_is_pad"]
253
- image_is_pad = sample["image_is_pad"]
254
- proprio_is_pad = sample["proprio_is_pad"]
255
- has_pad = False
256
- if bool(action_is_pad.any().item()):
257
- has_pad = True
258
- if bool(image_is_pad.any().item()):
259
- has_pad = True
260
- if bool(proprio_is_pad.any().item()):
261
- has_pad = True
262
-
263
- if not has_pad or attempt >= self.max_padding_retry:
264
- break
265
-
266
- sample_idx = np.random.randint(len(self.lerobot_dataset))
267
-
268
- image_is_pad = sample["image_is_pad"]
269
-
270
- if not self.precompute_video_only:
271
- image_is_pad = image_is_pad[self.video_sample_indices]
272
- video = None
273
- video_latents = None
274
- if self.video_latent_cache is None:
275
- video = sample["pixel_values"] # [T, C, H, W] or [num_cameras, T, C, H, W]
276
- num_cameras = 1
277
- if video.ndim == 5:
278
- if not self.precompute_video_only:
279
- video = video[:, self.video_sample_indices, :, :, :]
280
- num_cameras, T_video, C, H, W = video.shape
281
- else:
282
- assert video.ndim == 4, f"Expected video to have shape [T, C, H, W], but got {video.shape}"
283
- if not self.precompute_video_only:
284
- video = video[self.video_sample_indices, :, :, :]
285
- T_video, C, H, W = video.shape
286
-
287
- video = video.view(num_cameras, T_video, C, H, W) # [num_cameras, T_video, C, H, W]
288
- if self.concat_multi_camera == "robotwin":
289
- if num_cameras != 3:
290
- raise ValueError(
291
- f"`concat_multi_camera='robotwin'` requires exactly 3 cameras, got {num_cameras}"
292
- )
293
- cam_top = transforms_F.resize(
294
- video[0],
295
- size=[256, 320],
296
- interpolation=transforms_F.InterpolationMode.BILINEAR,
297
- antialias=True,
298
- ) # [T_video, C, 256, 320]
299
- cam_left = transforms_F.resize(
300
- video[1],
301
- size=[128, 160],
302
- interpolation=transforms_F.InterpolationMode.BILINEAR,
303
- antialias=True,
304
- ) # [T_video, C, 128, 160]
305
- cam_right = transforms_F.resize(
306
- video[2],
307
- size=[128, 160],
308
- interpolation=transforms_F.InterpolationMode.BILINEAR,
309
- antialias=True,
310
- ) # [T_video, C, 128, 160]
311
- bottom = torch.cat([cam_left, cam_right], dim=-1) # [T_video, C, 128, 320]
312
- video = torch.cat([cam_top, bottom], dim=-2) # [T_video, C, 384, 320]
313
- elif num_cameras > 1:
314
- if self.concat_multi_camera == "horizontal":
315
- video = torch.cat([video[i] for i in range(num_cameras)], dim=-1) # [T_video, C, H, num_cameras*W]
316
- elif self.concat_multi_camera == "vertical":
317
- video = torch.cat([video[i] for i in range(num_cameras)], dim=-2) # [T_video, C, num_cameras*H, W]
318
- else:
319
- raise ValueError(
320
- f"Invalid concat_multi_camera: {self.concat_multi_camera}. "
321
- "Expected one of: horizontal, vertical, robotwin."
322
- )
323
- else:
324
- video = video.squeeze(0) # [T_video, C, H, W]
325
-
326
- # final resize and normalization
327
- video = self.resize_transform(video)
328
- video = self.crop_transform(video)
329
- video = self.normalize_transform(video) # [T_video, C, H, W]
330
-
331
- video = video.permute(1, 0, 2, 3) # [C, T_video, H, W], range [-1, 1]
332
- else:
333
- returned_index = int(sample["idx"])
334
- video_latents = self.video_latent_cache[returned_index]
335
-
336
- if self.precompute_video_only:
337
- if video is None:
338
- raise RuntimeError("precompute_video_only unexpectedly resolved cached latents.")
339
- return {"video": video}
340
-
341
- # Proxy (from lerobot):
342
- # action: [num_frames-1, action_dim] # start from t0, except the last frame
343
- # proprio: [num_frames, proprio_dim] # start from t0 to the last frame, aligned with video frames
344
- action = sample["action"] # [T-1, action_dim]
345
- proprio = sample["proprio"][:-1, :] # [T-1, state_dim], to align with action
346
- sampled_video_frames = len(self.video_sample_indices)
347
- if sampled_video_frames <= 1:
348
- raise ValueError(f"At least two sampled video frames are required, got {sampled_video_frames}")
349
- if action.shape[0] % (sampled_video_frames - 1) != 0:
350
- raise ValueError(
351
- f"`action` horizon must be divisible by `video` transitions, got {action.shape[0]} and {sampled_video_frames - 1}"
352
- )
353
-
354
- task = sample["instruction"]
355
-
356
- # FIXME
357
- if self.override_instruction is not None:
358
- task = self.override_instruction
359
- instruction = DEFAULT_PROMPT.format(task=task)
360
-
361
- context, context_mask = self._get_cached_text_context(instruction)
362
- # NOTE: to keep consistent with wan2.2's behavior
363
- context[~context_mask] = 0.0
364
- context_mask = torch.ones_like(context_mask)
365
-
366
- data = {
367
- "action": action,
368
- "proprio": proprio,
369
- "prompt": instruction,
370
- "context": context,
371
- "context_mask": context_mask,
372
- "image_is_pad": image_is_pad,
373
- "action_is_pad": sample["action_is_pad"],
374
- "proprio_is_pad": sample["proprio_is_pad"],
375
- }
376
- if video is not None:
377
- data["video"] = video
378
- else:
379
- data["video_latents"] = video_latents
380
- data["source_video_shape"] = torch.tensor(
381
- [3, sampled_video_frames, int(self.video_size[0]), int(self.video_size[1])],
382
- dtype=torch.int64,
383
- )
384
- if self.eef_anchor_index is not None:
385
- # Key off the RETURNED sample, never the requested idx. Both the
386
- # padding retry above and __getitem__'s exception handler resample
387
- # to a random global index, so an idx-keyed lookup would pair these
388
- # frames with another episode's gripper position -- silently.
389
- # Indexed, not `.get(..., 0)`: a missing `dataset_index` would
390
- # silently key every directory of a concatenated MultiLeRobotDataset
391
- # to directory 0, serving one suite's geometry for another's frames.
392
- data["eef_anchor_token"] = torch.from_numpy(
393
- self.eef_anchor_index.lookup(
394
- dataset_index=int(sample["dataset_index"]),
395
- episode_index=int(sample["episode_index"]),
396
- frame_index=int(sample["frame_index"]),
397
- ).copy()
398
- )
399
- return data
400
-
401
- def _get_cached_text_context(self, prompt: str):
402
- if self.text_embedding_cache_dir is None:
403
- raise ValueError("text_embedding_cache_dir is not set.")
404
- cache_dir = self.text_embedding_cache_dir
405
- os.makedirs(cache_dir, exist_ok=True)
406
- hashed = hashlib.sha256(prompt.encode("utf-8")).hexdigest()
407
- cache_path = os.path.join(cache_dir, f"{hashed}.t5_len{self.context_len}.wan22ti2v5b.pt")
408
- if not os.path.exists(cache_path):
409
- raise FileNotFoundError(
410
- f"Missing text embedding cache: {cache_path}. "
411
- "Run scripts/precompute_text_embeds.py first."
412
- )
413
- payload = torch.load(cache_path, map_location="cpu")
414
- context = payload["context"]
415
- context_mask = payload["mask"].bool()
416
- if context.ndim != 2:
417
- raise ValueError(
418
- f"Cached `context` must be 2D [L, D], got shape {tuple(context.shape)} in {cache_path}"
419
- )
420
- if context_mask.ndim != 1:
421
- raise ValueError(
422
- f"Cached `mask` must be 1D [L], got shape {tuple(context_mask.shape)} in {cache_path}"
423
- )
424
- if context.shape[0] != self.context_len:
425
- raise ValueError(
426
- f"Cached context_len mismatch: expected {self.context_len}, got {context.shape[0]} in {cache_path}"
427
- )
428
- if context_mask.shape[0] != self.context_len:
429
- raise ValueError(
430
- f"Cached mask_len mismatch: expected {self.context_len}, got {context_mask.shape[0]} in {cache_path}"
431
- )
432
-
433
- return context, context_mask
434
-
435
- def __getitem__(self, idx):
436
- if self.strict_getitem:
437
- return self._get(idx)
438
- try:
439
- data = self._get(idx)
440
- except Exception as e:
441
- print(f"Error processing sample idx {idx}: {e}. Returning a random sample instead.")
442
- # trace back
443
- print(traceback.format_exc())
444
- random_idx = np.random.randint(len(self))
445
- data = self._get(random_idx)
446
- return data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
joint/fasterwam/train90_joint_job4247/training_code/fastwam/src/fastwam/datasets/lerobot/transforms/action_state_merger.py DELETED
@@ -1,78 +0,0 @@
1
- from typing import Dict
2
-
3
- import torch
4
- from torch.nn.functional import pad
5
-
6
-
7
- class ConcatLeftAlign:
8
- def __init__(
9
- self,
10
- action_target_dim: int | None = None,
11
- state_target_dim: int | None = None
12
- ):
13
- self.action_target_dim = action_target_dim
14
- self.state_target_dim = state_target_dim
15
-
16
- def set_shape_meta(self, shape_meta):
17
- self.action_meta = shape_meta["action"]
18
- self.state_meta = shape_meta["state"]
19
-
20
- def forward(self, batch):
21
- if "action" in batch:
22
- batch["action"] = self._concat(batch["action"], self.action_meta)
23
- batch["action"], batch["action_dim_is_pad"] = self._pad(batch["action"], self.action_target_dim)
24
-
25
- batch["state"] = self._concat(batch["state"], self.state_meta)
26
- batch["state"], batch["state_dim_is_pad"] = self._pad(batch["state"], self.state_target_dim)
27
-
28
- return batch
29
-
30
- def backward(self, batch):
31
- if self.state_target_dim is not None:
32
- assert batch["state"].shape[-1] == self.state_target_dim
33
- batch["state"] = self._crop(batch["state"], self.state_meta)
34
- batch["state"] = self._split(batch["state"], self.state_meta)
35
-
36
- if self.action_target_dim is not None:
37
- assert batch["action"].shape[-1] == self.action_target_dim
38
- batch["action"] = self._crop(batch["action"], self.action_meta)
39
- batch["action"] = self._split(batch["action"], self.action_meta)
40
-
41
- return batch
42
-
43
- @staticmethod
44
- def _pad(x: torch.Tensor, dim: int):
45
- if dim is None:
46
- dim = x.shape[-1]
47
-
48
- assert x.ndim == 2 and x.shape[-1] <= dim
49
- pad_dim = dim - x.shape[-1]
50
- x_padded = pad(x, (0, pad_dim))
51
- mask = torch.zeros_like(x[0]).bool()
52
- mask = pad(mask, (0, pad_dim), value=True)
53
- return x_padded, mask
54
-
55
- @staticmethod
56
- def _crop(x: torch.Tensor, meta: int):
57
- assert x.ndim == 3
58
- dim = sum([m["shape"] for m in meta])
59
- x = x[:, :, :dim]
60
- return x
61
-
62
- @staticmethod
63
- def _concat(x: Dict[str, torch.Tensor], meta: Dict[str, Dict]):
64
- x = torch.cat([x[m["key"]] for m in meta], dim=-1)
65
- assert x.ndim == 2
66
- return x
67
-
68
- @staticmethod
69
- def _split(x: torch.Tensor, meta: Dict[str, Dict]):
70
- assert x.ndim == 3
71
- y = {}
72
- idx = 0
73
- for m in meta:
74
- key, dim = m["key"], m["shape"]
75
- y[key] = x[:, :, idx: idx + dim]
76
- idx += dim
77
-
78
- return y