wuruiqi0722 commited on
Commit
01c7703
·
verified ·
1 Parent(s): ff3a6e0

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/example_case/0001.jpg filter=lfs diff=lfs merge=lfs -text
37
+ assets/example_case/0002.jpg filter=lfs diff=lfs merge=lfs -text
38
+ assets/framework.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ outputs
2
+ checkpoints
assets/example_case/0001.jpg ADDED

Git LFS Details

  • SHA256: af6a9aff8f0bd82b871ec1d6f8bc61cfe4c8082b1b65ed72ded9883549e87a25
  • Pointer size: 132 Bytes
  • Size of remote file: 2.81 MB
assets/example_case/0001.json ADDED
@@ -0,0 +1,5586 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "move": "go forward",
4
+ "view": "no-op"
5
+ },
6
+ {
7
+ "move": "go forward",
8
+ "view": "no-op"
9
+ },
10
+ {
11
+ "move": "go forward",
12
+ "view": "no-op"
13
+ },
14
+ {
15
+ "move": "go forward",
16
+ "view": "no-op"
17
+ },
18
+ {
19
+ "move": "go forward",
20
+ "view": "no-op"
21
+ },
22
+ {
23
+ "move": "go forward",
24
+ "view": "no-op"
25
+ },
26
+ {
27
+ "move": "go forward",
28
+ "view": "no-op"
29
+ },
30
+ {
31
+ "move": "go forward",
32
+ "view": "no-op"
33
+ },
34
+ {
35
+ "move": "go forward",
36
+ "view": "no-op"
37
+ },
38
+ {
39
+ "move": "go forward",
40
+ "view": "no-op"
41
+ },
42
+ {
43
+ "move": "go forward",
44
+ "view": "no-op"
45
+ },
46
+ {
47
+ "move": "go forward",
48
+ "view": "no-op"
49
+ },
50
+ {
51
+ "move": "go forward",
52
+ "view": "no-op"
53
+ },
54
+ {
55
+ "move": "go forward",
56
+ "view": "no-op"
57
+ },
58
+ {
59
+ "move": "go forward",
60
+ "view": "no-op"
61
+ },
62
+ {
63
+ "move": "go forward",
64
+ "view": "no-op"
65
+ },
66
+ {
67
+ "move": "go forward",
68
+ "view": "no-op"
69
+ },
70
+ {
71
+ "move": "go forward",
72
+ "view": "no-op"
73
+ },
74
+ {
75
+ "move": "go forward",
76
+ "view": "no-op"
77
+ },
78
+ {
79
+ "move": "go forward",
80
+ "view": "no-op"
81
+ },
82
+ {
83
+ "move": "go forward",
84
+ "view": "no-op"
85
+ },
86
+ {
87
+ "move": "go forward",
88
+ "view": "no-op"
89
+ },
90
+ {
91
+ "move": "go forward",
92
+ "view": "no-op"
93
+ },
94
+ {
95
+ "move": "go forward",
96
+ "view": "no-op"
97
+ },
98
+ {
99
+ "move": "go forward",
100
+ "view": "no-op"
101
+ },
102
+ {
103
+ "move": "go forward",
104
+ "view": "no-op"
105
+ },
106
+ {
107
+ "move": "go forward",
108
+ "view": "no-op"
109
+ },
110
+ {
111
+ "move": "go forward",
112
+ "view": "no-op"
113
+ },
114
+ {
115
+ "move": "go forward",
116
+ "view": "no-op"
117
+ },
118
+ {
119
+ "move": "go forward",
120
+ "view": "no-op"
121
+ },
122
+ {
123
+ "move": "go forward",
124
+ "view": "no-op"
125
+ },
126
+ {
127
+ "move": "go forward",
128
+ "view": "no-op"
129
+ },
130
+ {
131
+ "move": "go forward",
132
+ "view": "no-op"
133
+ },
134
+ {
135
+ "move": "go forward",
136
+ "view": "no-op"
137
+ },
138
+ {
139
+ "move": "go forward",
140
+ "view": "no-op"
141
+ },
142
+ {
143
+ "move": "go forward",
144
+ "view": "no-op"
145
+ },
146
+ {
147
+ "move": "go forward",
148
+ "view": "no-op"
149
+ },
150
+ {
151
+ "move": "go forward",
152
+ "view": "no-op"
153
+ },
154
+ {
155
+ "move": "go forward",
156
+ "view": "no-op"
157
+ },
158
+ {
159
+ "move": "go forward",
160
+ "view": "no-op"
161
+ },
162
+ {
163
+ "move": "go forward",
164
+ "view": "no-op"
165
+ },
166
+ {
167
+ "move": "go forward",
168
+ "view": "no-op"
169
+ },
170
+ {
171
+ "move": "go forward",
172
+ "view": "no-op"
173
+ },
174
+ {
175
+ "move": "go forward",
176
+ "view": "no-op"
177
+ },
178
+ {
179
+ "move": "go forward",
180
+ "view": "no-op"
181
+ },
182
+ {
183
+ "move": "go forward",
184
+ "view": "no-op"
185
+ },
186
+ {
187
+ "move": "go forward",
188
+ "view": "no-op"
189
+ },
190
+ {
191
+ "move": "go forward",
192
+ "view": "no-op"
193
+ },
194
+ {
195
+ "move": "go forward",
196
+ "view": "no-op"
197
+ },
198
+ {
199
+ "move": "go forward",
200
+ "view": "no-op"
201
+ },
202
+ {
203
+ "move": "go forward",
204
+ "view": "no-op"
205
+ },
206
+ {
207
+ "move": "go forward",
208
+ "view": "no-op"
209
+ },
210
+ {
211
+ "move": "go forward",
212
+ "view": "no-op"
213
+ },
214
+ {
215
+ "move": "go forward",
216
+ "view": "no-op"
217
+ },
218
+ {
219
+ "move": "go forward",
220
+ "view": "no-op"
221
+ },
222
+ {
223
+ "move": "go forward",
224
+ "view": "no-op"
225
+ },
226
+ {
227
+ "move": "go forward",
228
+ "view": "no-op"
229
+ },
230
+ {
231
+ "move": "go forward",
232
+ "view": "no-op"
233
+ },
234
+ {
235
+ "move": "go forward",
236
+ "view": "no-op"
237
+ },
238
+ {
239
+ "move": "go forward",
240
+ "view": "no-op"
241
+ },
242
+ {
243
+ "move": "go forward",
244
+ "view": "no-op"
245
+ },
246
+ {
247
+ "move": "go forward",
248
+ "view": "no-op"
249
+ },
250
+ {
251
+ "move": "go forward",
252
+ "view": "no-op"
253
+ },
254
+ {
255
+ "move": "go forward",
256
+ "view": "no-op"
257
+ },
258
+ {
259
+ "move": "go forward",
260
+ "view": "no-op"
261
+ },
262
+ {
263
+ "move": "go forward",
264
+ "view": "no-op"
265
+ },
266
+ {
267
+ "move": "go forward",
268
+ "view": "no-op"
269
+ },
270
+ {
271
+ "move": "go forward",
272
+ "view": "no-op"
273
+ },
274
+ {
275
+ "move": "go forward",
276
+ "view": "no-op"
277
+ },
278
+ {
279
+ "move": "go forward",
280
+ "view": "no-op"
281
+ },
282
+ {
283
+ "move": "go forward",
284
+ "view": "no-op"
285
+ },
286
+ {
287
+ "move": "go forward",
288
+ "view": "no-op"
289
+ },
290
+ {
291
+ "move": "go forward",
292
+ "view": "no-op"
293
+ },
294
+ {
295
+ "move": "go forward",
296
+ "view": "no-op"
297
+ },
298
+ {
299
+ "move": "go forward",
300
+ "view": "no-op"
301
+ },
302
+ {
303
+ "move": "go forward",
304
+ "view": "no-op"
305
+ },
306
+ {
307
+ "move": "go forward",
308
+ "view": "no-op"
309
+ },
310
+ {
311
+ "move": "go forward",
312
+ "view": "no-op"
313
+ },
314
+ {
315
+ "move": "go forward",
316
+ "view": "no-op"
317
+ },
318
+ {
319
+ "move": "go forward",
320
+ "view": "no-op"
321
+ },
322
+ {
323
+ "move": "go forward",
324
+ "view": "no-op"
325
+ },
326
+ {
327
+ "move": "no-op",
328
+ "view": "turn right"
329
+ },
330
+ {
331
+ "move": "no-op",
332
+ "view": "turn right"
333
+ },
334
+ {
335
+ "move": "no-op",
336
+ "view": "turn right"
337
+ },
338
+ {
339
+ "move": "no-op",
340
+ "view": "turn right"
341
+ },
342
+ {
343
+ "move": "no-op",
344
+ "view": "turn right"
345
+ },
346
+ {
347
+ "move": "no-op",
348
+ "view": "turn right"
349
+ },
350
+ {
351
+ "move": "no-op",
352
+ "view": "turn right"
353
+ },
354
+ {
355
+ "move": "no-op",
356
+ "view": "turn right"
357
+ },
358
+ {
359
+ "move": "no-op",
360
+ "view": "turn right"
361
+ },
362
+ {
363
+ "move": "no-op",
364
+ "view": "turn right"
365
+ },
366
+ {
367
+ "move": "no-op",
368
+ "view": "turn right"
369
+ },
370
+ {
371
+ "move": "no-op",
372
+ "view": "turn right"
373
+ },
374
+ {
375
+ "move": "no-op",
376
+ "view": "turn right"
377
+ },
378
+ {
379
+ "move": "no-op",
380
+ "view": "turn right"
381
+ },
382
+ {
383
+ "move": "no-op",
384
+ "view": "turn right"
385
+ },
386
+ {
387
+ "move": "no-op",
388
+ "view": "turn right"
389
+ },
390
+ {
391
+ "move": "no-op",
392
+ "view": "turn right"
393
+ },
394
+ {
395
+ "move": "no-op",
396
+ "view": "turn right"
397
+ },
398
+ {
399
+ "move": "no-op",
400
+ "view": "turn right"
401
+ },
402
+ {
403
+ "move": "no-op",
404
+ "view": "turn right"
405
+ },
406
+ {
407
+ "move": "no-op",
408
+ "view": "turn right"
409
+ },
410
+ {
411
+ "move": "no-op",
412
+ "view": "turn right"
413
+ },
414
+ {
415
+ "move": "no-op",
416
+ "view": "turn right"
417
+ },
418
+ {
419
+ "move": "no-op",
420
+ "view": "turn right"
421
+ },
422
+ {
423
+ "move": "no-op",
424
+ "view": "turn right"
425
+ },
426
+ {
427
+ "move": "no-op",
428
+ "view": "turn right"
429
+ },
430
+ {
431
+ "move": "no-op",
432
+ "view": "turn right"
433
+ },
434
+ {
435
+ "move": "no-op",
436
+ "view": "turn right"
437
+ },
438
+ {
439
+ "move": "no-op",
440
+ "view": "turn right"
441
+ },
442
+ {
443
+ "move": "no-op",
444
+ "view": "turn right"
445
+ },
446
+ {
447
+ "move": "no-op",
448
+ "view": "turn right"
449
+ },
450
+ {
451
+ "move": "no-op",
452
+ "view": "turn right"
453
+ },
454
+ {
455
+ "move": "no-op",
456
+ "view": "turn right"
457
+ },
458
+ {
459
+ "move": "no-op",
460
+ "view": "turn right"
461
+ },
462
+ {
463
+ "move": "no-op",
464
+ "view": "turn right"
465
+ },
466
+ {
467
+ "move": "no-op",
468
+ "view": "turn right"
469
+ },
470
+ {
471
+ "move": "no-op",
472
+ "view": "turn right"
473
+ },
474
+ {
475
+ "move": "no-op",
476
+ "view": "turn right"
477
+ },
478
+ {
479
+ "move": "no-op",
480
+ "view": "turn right"
481
+ },
482
+ {
483
+ "move": "no-op",
484
+ "view": "turn right"
485
+ },
486
+ {
487
+ "move": "no-op",
488
+ "view": "turn right"
489
+ },
490
+ {
491
+ "move": "no-op",
492
+ "view": "turn right"
493
+ },
494
+ {
495
+ "move": "no-op",
496
+ "view": "turn right"
497
+ },
498
+ {
499
+ "move": "no-op",
500
+ "view": "turn right"
501
+ },
502
+ {
503
+ "move": "no-op",
504
+ "view": "turn right"
505
+ },
506
+ {
507
+ "move": "no-op",
508
+ "view": "turn right"
509
+ },
510
+ {
511
+ "move": "no-op",
512
+ "view": "turn right"
513
+ },
514
+ {
515
+ "move": "no-op",
516
+ "view": "turn right"
517
+ },
518
+ {
519
+ "move": "no-op",
520
+ "view": "turn right"
521
+ },
522
+ {
523
+ "move": "no-op",
524
+ "view": "turn right"
525
+ },
526
+ {
527
+ "move": "no-op",
528
+ "view": "turn right"
529
+ },
530
+ {
531
+ "move": "no-op",
532
+ "view": "turn right"
533
+ },
534
+ {
535
+ "move": "no-op",
536
+ "view": "turn right"
537
+ },
538
+ {
539
+ "move": "no-op",
540
+ "view": "turn right"
541
+ },
542
+ {
543
+ "move": "no-op",
544
+ "view": "turn right"
545
+ },
546
+ {
547
+ "move": "no-op",
548
+ "view": "turn right"
549
+ },
550
+ {
551
+ "move": "no-op",
552
+ "view": "turn right"
553
+ },
554
+ {
555
+ "move": "no-op",
556
+ "view": "turn right"
557
+ },
558
+ {
559
+ "move": "no-op",
560
+ "view": "turn right"
561
+ },
562
+ {
563
+ "move": "no-op",
564
+ "view": "turn right"
565
+ },
566
+ {
567
+ "move": "no-op",
568
+ "view": "turn right"
569
+ },
570
+ {
571
+ "move": "no-op",
572
+ "view": "turn right"
573
+ },
574
+ {
575
+ "move": "no-op",
576
+ "view": "turn right"
577
+ },
578
+ {
579
+ "move": "no-op",
580
+ "view": "turn right"
581
+ },
582
+ {
583
+ "move": "no-op",
584
+ "view": "turn right"
585
+ },
586
+ {
587
+ "move": "no-op",
588
+ "view": "turn right"
589
+ },
590
+ {
591
+ "move": "no-op",
592
+ "view": "turn right"
593
+ },
594
+ {
595
+ "move": "no-op",
596
+ "view": "turn right"
597
+ },
598
+ {
599
+ "move": "no-op",
600
+ "view": "turn right"
601
+ },
602
+ {
603
+ "move": "no-op",
604
+ "view": "turn right"
605
+ },
606
+ {
607
+ "move": "no-op",
608
+ "view": "turn right"
609
+ },
610
+ {
611
+ "move": "no-op",
612
+ "view": "turn right"
613
+ },
614
+ {
615
+ "move": "no-op",
616
+ "view": "turn right"
617
+ },
618
+ {
619
+ "move": "no-op",
620
+ "view": "turn right"
621
+ },
622
+ {
623
+ "move": "no-op",
624
+ "view": "turn right"
625
+ },
626
+ {
627
+ "move": "no-op",
628
+ "view": "turn right"
629
+ },
630
+ {
631
+ "move": "no-op",
632
+ "view": "turn right"
633
+ },
634
+ {
635
+ "move": "no-op",
636
+ "view": "turn right"
637
+ },
638
+ {
639
+ "move": "no-op",
640
+ "view": "turn right"
641
+ },
642
+ {
643
+ "move": "no-op",
644
+ "view": "turn right"
645
+ },
646
+ {
647
+ "move": "no-op",
648
+ "view": "turn right"
649
+ },
650
+ {
651
+ "move": "go forward",
652
+ "view": "no-op"
653
+ },
654
+ {
655
+ "move": "go forward",
656
+ "view": "no-op"
657
+ },
658
+ {
659
+ "move": "go forward",
660
+ "view": "no-op"
661
+ },
662
+ {
663
+ "move": "go forward",
664
+ "view": "no-op"
665
+ },
666
+ {
667
+ "move": "go forward",
668
+ "view": "no-op"
669
+ },
670
+ {
671
+ "move": "go forward",
672
+ "view": "no-op"
673
+ },
674
+ {
675
+ "move": "go forward",
676
+ "view": "no-op"
677
+ },
678
+ {
679
+ "move": "go forward",
680
+ "view": "no-op"
681
+ },
682
+ {
683
+ "move": "go forward",
684
+ "view": "no-op"
685
+ },
686
+ {
687
+ "move": "go forward",
688
+ "view": "no-op"
689
+ },
690
+ {
691
+ "move": "go forward",
692
+ "view": "no-op"
693
+ },
694
+ {
695
+ "move": "go forward",
696
+ "view": "no-op"
697
+ },
698
+ {
699
+ "move": "go forward",
700
+ "view": "no-op"
701
+ },
702
+ {
703
+ "move": "go forward",
704
+ "view": "no-op"
705
+ },
706
+ {
707
+ "move": "go forward",
708
+ "view": "no-op"
709
+ },
710
+ {
711
+ "move": "go forward",
712
+ "view": "no-op"
713
+ },
714
+ {
715
+ "move": "go forward",
716
+ "view": "no-op"
717
+ },
718
+ {
719
+ "move": "go forward",
720
+ "view": "no-op"
721
+ },
722
+ {
723
+ "move": "go forward",
724
+ "view": "no-op"
725
+ },
726
+ {
727
+ "move": "go forward",
728
+ "view": "no-op"
729
+ },
730
+ {
731
+ "move": "go forward",
732
+ "view": "no-op"
733
+ },
734
+ {
735
+ "move": "go forward",
736
+ "view": "no-op"
737
+ },
738
+ {
739
+ "move": "go forward",
740
+ "view": "no-op"
741
+ },
742
+ {
743
+ "move": "go forward",
744
+ "view": "no-op"
745
+ },
746
+ {
747
+ "move": "go forward",
748
+ "view": "no-op"
749
+ },
750
+ {
751
+ "move": "go forward",
752
+ "view": "no-op"
753
+ },
754
+ {
755
+ "move": "go forward",
756
+ "view": "no-op"
757
+ },
758
+ {
759
+ "move": "go forward",
760
+ "view": "no-op"
761
+ },
762
+ {
763
+ "move": "go forward",
764
+ "view": "no-op"
765
+ },
766
+ {
767
+ "move": "go forward",
768
+ "view": "no-op"
769
+ },
770
+ {
771
+ "move": "go forward",
772
+ "view": "no-op"
773
+ },
774
+ {
775
+ "move": "go forward",
776
+ "view": "no-op"
777
+ },
778
+ {
779
+ "move": "go forward",
780
+ "view": "no-op"
781
+ },
782
+ {
783
+ "move": "go forward",
784
+ "view": "no-op"
785
+ },
786
+ {
787
+ "move": "go forward",
788
+ "view": "no-op"
789
+ },
790
+ {
791
+ "move": "go forward",
792
+ "view": "no-op"
793
+ },
794
+ {
795
+ "move": "go forward",
796
+ "view": "no-op"
797
+ },
798
+ {
799
+ "move": "go forward",
800
+ "view": "no-op"
801
+ },
802
+ {
803
+ "move": "go forward",
804
+ "view": "no-op"
805
+ },
806
+ {
807
+ "move": "go forward",
808
+ "view": "no-op"
809
+ },
810
+ {
811
+ "move": "go forward",
812
+ "view": "no-op"
813
+ },
814
+ {
815
+ "move": "go forward",
816
+ "view": "no-op"
817
+ },
818
+ {
819
+ "move": "go forward",
820
+ "view": "no-op"
821
+ },
822
+ {
823
+ "move": "go forward",
824
+ "view": "no-op"
825
+ },
826
+ {
827
+ "move": "go forward",
828
+ "view": "no-op"
829
+ },
830
+ {
831
+ "move": "go forward",
832
+ "view": "no-op"
833
+ },
834
+ {
835
+ "move": "go forward",
836
+ "view": "no-op"
837
+ },
838
+ {
839
+ "move": "go forward",
840
+ "view": "no-op"
841
+ },
842
+ {
843
+ "move": "go forward",
844
+ "view": "no-op"
845
+ },
846
+ {
847
+ "move": "go forward",
848
+ "view": "no-op"
849
+ },
850
+ {
851
+ "move": "go forward",
852
+ "view": "no-op"
853
+ },
854
+ {
855
+ "move": "go forward",
856
+ "view": "no-op"
857
+ },
858
+ {
859
+ "move": "go forward",
860
+ "view": "no-op"
861
+ },
862
+ {
863
+ "move": "go forward",
864
+ "view": "no-op"
865
+ },
866
+ {
867
+ "move": "go forward",
868
+ "view": "no-op"
869
+ },
870
+ {
871
+ "move": "go forward",
872
+ "view": "no-op"
873
+ },
874
+ {
875
+ "move": "go forward",
876
+ "view": "no-op"
877
+ },
878
+ {
879
+ "move": "go forward",
880
+ "view": "no-op"
881
+ },
882
+ {
883
+ "move": "go forward",
884
+ "view": "no-op"
885
+ },
886
+ {
887
+ "move": "go forward",
888
+ "view": "no-op"
889
+ },
890
+ {
891
+ "move": "go forward",
892
+ "view": "no-op"
893
+ },
894
+ {
895
+ "move": "go forward",
896
+ "view": "no-op"
897
+ },
898
+ {
899
+ "move": "go forward",
900
+ "view": "no-op"
901
+ },
902
+ {
903
+ "move": "go forward",
904
+ "view": "no-op"
905
+ },
906
+ {
907
+ "move": "go forward",
908
+ "view": "no-op"
909
+ },
910
+ {
911
+ "move": "go forward",
912
+ "view": "no-op"
913
+ },
914
+ {
915
+ "move": "go forward",
916
+ "view": "no-op"
917
+ },
918
+ {
919
+ "move": "go forward",
920
+ "view": "no-op"
921
+ },
922
+ {
923
+ "move": "go forward",
924
+ "view": "no-op"
925
+ },
926
+ {
927
+ "move": "go forward",
928
+ "view": "no-op"
929
+ },
930
+ {
931
+ "move": "go forward",
932
+ "view": "no-op"
933
+ },
934
+ {
935
+ "move": "go forward",
936
+ "view": "no-op"
937
+ },
938
+ {
939
+ "move": "go forward",
940
+ "view": "no-op"
941
+ },
942
+ {
943
+ "move": "go forward",
944
+ "view": "no-op"
945
+ },
946
+ {
947
+ "move": "go forward",
948
+ "view": "no-op"
949
+ },
950
+ {
951
+ "move": "go forward",
952
+ "view": "no-op"
953
+ },
954
+ {
955
+ "move": "go forward",
956
+ "view": "no-op"
957
+ },
958
+ {
959
+ "move": "go forward",
960
+ "view": "no-op"
961
+ },
962
+ {
963
+ "move": "go forward",
964
+ "view": "no-op"
965
+ },
966
+ {
967
+ "move": "go forward",
968
+ "view": "no-op"
969
+ },
970
+ {
971
+ "move": "go forward",
972
+ "view": "no-op"
973
+ },
974
+ {
975
+ "move": "no-op",
976
+ "view": "turn right"
977
+ },
978
+ {
979
+ "move": "no-op",
980
+ "view": "turn right"
981
+ },
982
+ {
983
+ "move": "no-op",
984
+ "view": "turn right"
985
+ },
986
+ {
987
+ "move": "no-op",
988
+ "view": "turn right"
989
+ },
990
+ {
991
+ "move": "no-op",
992
+ "view": "turn right"
993
+ },
994
+ {
995
+ "move": "no-op",
996
+ "view": "turn right"
997
+ },
998
+ {
999
+ "move": "no-op",
1000
+ "view": "turn right"
1001
+ },
1002
+ {
1003
+ "move": "no-op",
1004
+ "view": "turn right"
1005
+ },
1006
+ {
1007
+ "move": "no-op",
1008
+ "view": "turn right"
1009
+ },
1010
+ {
1011
+ "move": "no-op",
1012
+ "view": "turn right"
1013
+ },
1014
+ {
1015
+ "move": "no-op",
1016
+ "view": "turn right"
1017
+ },
1018
+ {
1019
+ "move": "no-op",
1020
+ "view": "turn right"
1021
+ },
1022
+ {
1023
+ "move": "no-op",
1024
+ "view": "turn right"
1025
+ },
1026
+ {
1027
+ "move": "no-op",
1028
+ "view": "turn right"
1029
+ },
1030
+ {
1031
+ "move": "no-op",
1032
+ "view": "turn right"
1033
+ },
1034
+ {
1035
+ "move": "no-op",
1036
+ "view": "turn right"
1037
+ },
1038
+ {
1039
+ "move": "no-op",
1040
+ "view": "turn right"
1041
+ },
1042
+ {
1043
+ "move": "no-op",
1044
+ "view": "turn right"
1045
+ },
1046
+ {
1047
+ "move": "no-op",
1048
+ "view": "turn right"
1049
+ },
1050
+ {
1051
+ "move": "no-op",
1052
+ "view": "turn right"
1053
+ },
1054
+ {
1055
+ "move": "no-op",
1056
+ "view": "turn right"
1057
+ },
1058
+ {
1059
+ "move": "no-op",
1060
+ "view": "turn right"
1061
+ },
1062
+ {
1063
+ "move": "no-op",
1064
+ "view": "turn right"
1065
+ },
1066
+ {
1067
+ "move": "no-op",
1068
+ "view": "turn right"
1069
+ },
1070
+ {
1071
+ "move": "no-op",
1072
+ "view": "turn right"
1073
+ },
1074
+ {
1075
+ "move": "no-op",
1076
+ "view": "turn right"
1077
+ },
1078
+ {
1079
+ "move": "no-op",
1080
+ "view": "turn right"
1081
+ },
1082
+ {
1083
+ "move": "no-op",
1084
+ "view": "turn right"
1085
+ },
1086
+ {
1087
+ "move": "no-op",
1088
+ "view": "turn right"
1089
+ },
1090
+ {
1091
+ "move": "no-op",
1092
+ "view": "turn right"
1093
+ },
1094
+ {
1095
+ "move": "no-op",
1096
+ "view": "turn right"
1097
+ },
1098
+ {
1099
+ "move": "no-op",
1100
+ "view": "turn right"
1101
+ },
1102
+ {
1103
+ "move": "no-op",
1104
+ "view": "turn right"
1105
+ },
1106
+ {
1107
+ "move": "no-op",
1108
+ "view": "turn right"
1109
+ },
1110
+ {
1111
+ "move": "no-op",
1112
+ "view": "turn right"
1113
+ },
1114
+ {
1115
+ "move": "no-op",
1116
+ "view": "turn right"
1117
+ },
1118
+ {
1119
+ "move": "no-op",
1120
+ "view": "turn right"
1121
+ },
1122
+ {
1123
+ "move": "no-op",
1124
+ "view": "turn right"
1125
+ },
1126
+ {
1127
+ "move": "no-op",
1128
+ "view": "turn right"
1129
+ },
1130
+ {
1131
+ "move": "no-op",
1132
+ "view": "turn right"
1133
+ },
1134
+ {
1135
+ "move": "no-op",
1136
+ "view": "turn right"
1137
+ },
1138
+ {
1139
+ "move": "no-op",
1140
+ "view": "turn right"
1141
+ },
1142
+ {
1143
+ "move": "no-op",
1144
+ "view": "turn right"
1145
+ },
1146
+ {
1147
+ "move": "no-op",
1148
+ "view": "turn right"
1149
+ },
1150
+ {
1151
+ "move": "no-op",
1152
+ "view": "turn right"
1153
+ },
1154
+ {
1155
+ "move": "no-op",
1156
+ "view": "turn right"
1157
+ },
1158
+ {
1159
+ "move": "no-op",
1160
+ "view": "turn right"
1161
+ },
1162
+ {
1163
+ "move": "no-op",
1164
+ "view": "turn right"
1165
+ },
1166
+ {
1167
+ "move": "no-op",
1168
+ "view": "turn right"
1169
+ },
1170
+ {
1171
+ "move": "no-op",
1172
+ "view": "turn right"
1173
+ },
1174
+ {
1175
+ "move": "no-op",
1176
+ "view": "turn right"
1177
+ },
1178
+ {
1179
+ "move": "no-op",
1180
+ "view": "turn right"
1181
+ },
1182
+ {
1183
+ "move": "no-op",
1184
+ "view": "turn right"
1185
+ },
1186
+ {
1187
+ "move": "no-op",
1188
+ "view": "turn right"
1189
+ },
1190
+ {
1191
+ "move": "no-op",
1192
+ "view": "turn right"
1193
+ },
1194
+ {
1195
+ "move": "no-op",
1196
+ "view": "turn right"
1197
+ },
1198
+ {
1199
+ "move": "no-op",
1200
+ "view": "turn right"
1201
+ },
1202
+ {
1203
+ "move": "no-op",
1204
+ "view": "turn right"
1205
+ },
1206
+ {
1207
+ "move": "no-op",
1208
+ "view": "turn right"
1209
+ },
1210
+ {
1211
+ "move": "no-op",
1212
+ "view": "turn right"
1213
+ },
1214
+ {
1215
+ "move": "no-op",
1216
+ "view": "turn right"
1217
+ },
1218
+ {
1219
+ "move": "no-op",
1220
+ "view": "turn right"
1221
+ },
1222
+ {
1223
+ "move": "no-op",
1224
+ "view": "turn right"
1225
+ },
1226
+ {
1227
+ "move": "no-op",
1228
+ "view": "turn right"
1229
+ },
1230
+ {
1231
+ "move": "no-op",
1232
+ "view": "turn right"
1233
+ },
1234
+ {
1235
+ "move": "no-op",
1236
+ "view": "turn right"
1237
+ },
1238
+ {
1239
+ "move": "no-op",
1240
+ "view": "turn right"
1241
+ },
1242
+ {
1243
+ "move": "no-op",
1244
+ "view": "turn right"
1245
+ },
1246
+ {
1247
+ "move": "no-op",
1248
+ "view": "turn right"
1249
+ },
1250
+ {
1251
+ "move": "no-op",
1252
+ "view": "turn right"
1253
+ },
1254
+ {
1255
+ "move": "no-op",
1256
+ "view": "turn right"
1257
+ },
1258
+ {
1259
+ "move": "no-op",
1260
+ "view": "turn right"
1261
+ },
1262
+ {
1263
+ "move": "no-op",
1264
+ "view": "turn right"
1265
+ },
1266
+ {
1267
+ "move": "no-op",
1268
+ "view": "turn right"
1269
+ },
1270
+ {
1271
+ "move": "no-op",
1272
+ "view": "turn right"
1273
+ },
1274
+ {
1275
+ "move": "no-op",
1276
+ "view": "turn right"
1277
+ },
1278
+ {
1279
+ "move": "no-op",
1280
+ "view": "turn right"
1281
+ },
1282
+ {
1283
+ "move": "no-op",
1284
+ "view": "turn right"
1285
+ },
1286
+ {
1287
+ "move": "no-op",
1288
+ "view": "turn right"
1289
+ },
1290
+ {
1291
+ "move": "no-op",
1292
+ "view": "turn right"
1293
+ },
1294
+ {
1295
+ "move": "no-op",
1296
+ "view": "turn right"
1297
+ },
1298
+ {
1299
+ "move": "no-op",
1300
+ "view": "turn left"
1301
+ },
1302
+ {
1303
+ "move": "no-op",
1304
+ "view": "turn left"
1305
+ },
1306
+ {
1307
+ "move": "no-op",
1308
+ "view": "turn left"
1309
+ },
1310
+ {
1311
+ "move": "no-op",
1312
+ "view": "turn left"
1313
+ },
1314
+ {
1315
+ "move": "no-op",
1316
+ "view": "turn left"
1317
+ },
1318
+ {
1319
+ "move": "no-op",
1320
+ "view": "turn left"
1321
+ },
1322
+ {
1323
+ "move": "no-op",
1324
+ "view": "turn left"
1325
+ },
1326
+ {
1327
+ "move": "no-op",
1328
+ "view": "turn left"
1329
+ },
1330
+ {
1331
+ "move": "no-op",
1332
+ "view": "turn left"
1333
+ },
1334
+ {
1335
+ "move": "no-op",
1336
+ "view": "turn left"
1337
+ },
1338
+ {
1339
+ "move": "no-op",
1340
+ "view": "turn left"
1341
+ },
1342
+ {
1343
+ "move": "no-op",
1344
+ "view": "turn left"
1345
+ },
1346
+ {
1347
+ "move": "no-op",
1348
+ "view": "turn left"
1349
+ },
1350
+ {
1351
+ "move": "no-op",
1352
+ "view": "turn left"
1353
+ },
1354
+ {
1355
+ "move": "no-op",
1356
+ "view": "turn left"
1357
+ },
1358
+ {
1359
+ "move": "no-op",
1360
+ "view": "turn left"
1361
+ },
1362
+ {
1363
+ "move": "no-op",
1364
+ "view": "turn left"
1365
+ },
1366
+ {
1367
+ "move": "no-op",
1368
+ "view": "turn left"
1369
+ },
1370
+ {
1371
+ "move": "no-op",
1372
+ "view": "turn left"
1373
+ },
1374
+ {
1375
+ "move": "no-op",
1376
+ "view": "turn left"
1377
+ },
1378
+ {
1379
+ "move": "no-op",
1380
+ "view": "turn left"
1381
+ },
1382
+ {
1383
+ "move": "no-op",
1384
+ "view": "turn left"
1385
+ },
1386
+ {
1387
+ "move": "no-op",
1388
+ "view": "turn left"
1389
+ },
1390
+ {
1391
+ "move": "no-op",
1392
+ "view": "turn left"
1393
+ },
1394
+ {
1395
+ "move": "no-op",
1396
+ "view": "turn left"
1397
+ },
1398
+ {
1399
+ "move": "no-op",
1400
+ "view": "turn left"
1401
+ },
1402
+ {
1403
+ "move": "no-op",
1404
+ "view": "turn left"
1405
+ },
1406
+ {
1407
+ "move": "no-op",
1408
+ "view": "turn left"
1409
+ },
1410
+ {
1411
+ "move": "no-op",
1412
+ "view": "turn left"
1413
+ },
1414
+ {
1415
+ "move": "no-op",
1416
+ "view": "turn left"
1417
+ },
1418
+ {
1419
+ "move": "no-op",
1420
+ "view": "turn left"
1421
+ },
1422
+ {
1423
+ "move": "no-op",
1424
+ "view": "turn left"
1425
+ },
1426
+ {
1427
+ "move": "no-op",
1428
+ "view": "turn left"
1429
+ },
1430
+ {
1431
+ "move": "no-op",
1432
+ "view": "turn left"
1433
+ },
1434
+ {
1435
+ "move": "no-op",
1436
+ "view": "turn left"
1437
+ },
1438
+ {
1439
+ "move": "no-op",
1440
+ "view": "turn left"
1441
+ },
1442
+ {
1443
+ "move": "no-op",
1444
+ "view": "turn left"
1445
+ },
1446
+ {
1447
+ "move": "no-op",
1448
+ "view": "turn left"
1449
+ },
1450
+ {
1451
+ "move": "no-op",
1452
+ "view": "turn left"
1453
+ },
1454
+ {
1455
+ "move": "no-op",
1456
+ "view": "turn left"
1457
+ },
1458
+ {
1459
+ "move": "no-op",
1460
+ "view": "turn left"
1461
+ },
1462
+ {
1463
+ "move": "no-op",
1464
+ "view": "turn left"
1465
+ },
1466
+ {
1467
+ "move": "no-op",
1468
+ "view": "turn left"
1469
+ },
1470
+ {
1471
+ "move": "no-op",
1472
+ "view": "turn left"
1473
+ },
1474
+ {
1475
+ "move": "no-op",
1476
+ "view": "turn left"
1477
+ },
1478
+ {
1479
+ "move": "no-op",
1480
+ "view": "turn left"
1481
+ },
1482
+ {
1483
+ "move": "no-op",
1484
+ "view": "turn left"
1485
+ },
1486
+ {
1487
+ "move": "no-op",
1488
+ "view": "turn left"
1489
+ },
1490
+ {
1491
+ "move": "no-op",
1492
+ "view": "turn left"
1493
+ },
1494
+ {
1495
+ "move": "no-op",
1496
+ "view": "turn left"
1497
+ },
1498
+ {
1499
+ "move": "no-op",
1500
+ "view": "turn left"
1501
+ },
1502
+ {
1503
+ "move": "no-op",
1504
+ "view": "turn left"
1505
+ },
1506
+ {
1507
+ "move": "no-op",
1508
+ "view": "turn left"
1509
+ },
1510
+ {
1511
+ "move": "no-op",
1512
+ "view": "turn left"
1513
+ },
1514
+ {
1515
+ "move": "no-op",
1516
+ "view": "turn left"
1517
+ },
1518
+ {
1519
+ "move": "no-op",
1520
+ "view": "turn left"
1521
+ },
1522
+ {
1523
+ "move": "no-op",
1524
+ "view": "turn left"
1525
+ },
1526
+ {
1527
+ "move": "no-op",
1528
+ "view": "turn left"
1529
+ },
1530
+ {
1531
+ "move": "no-op",
1532
+ "view": "turn left"
1533
+ },
1534
+ {
1535
+ "move": "no-op",
1536
+ "view": "turn left"
1537
+ },
1538
+ {
1539
+ "move": "no-op",
1540
+ "view": "turn left"
1541
+ },
1542
+ {
1543
+ "move": "no-op",
1544
+ "view": "turn left"
1545
+ },
1546
+ {
1547
+ "move": "no-op",
1548
+ "view": "turn left"
1549
+ },
1550
+ {
1551
+ "move": "no-op",
1552
+ "view": "turn left"
1553
+ },
1554
+ {
1555
+ "move": "no-op",
1556
+ "view": "turn left"
1557
+ },
1558
+ {
1559
+ "move": "no-op",
1560
+ "view": "turn left"
1561
+ },
1562
+ {
1563
+ "move": "no-op",
1564
+ "view": "turn left"
1565
+ },
1566
+ {
1567
+ "move": "no-op",
1568
+ "view": "turn left"
1569
+ },
1570
+ {
1571
+ "move": "no-op",
1572
+ "view": "turn left"
1573
+ },
1574
+ {
1575
+ "move": "no-op",
1576
+ "view": "turn left"
1577
+ },
1578
+ {
1579
+ "move": "no-op",
1580
+ "view": "turn left"
1581
+ },
1582
+ {
1583
+ "move": "no-op",
1584
+ "view": "turn left"
1585
+ },
1586
+ {
1587
+ "move": "no-op",
1588
+ "view": "turn left"
1589
+ },
1590
+ {
1591
+ "move": "no-op",
1592
+ "view": "turn left"
1593
+ },
1594
+ {
1595
+ "move": "no-op",
1596
+ "view": "turn left"
1597
+ },
1598
+ {
1599
+ "move": "no-op",
1600
+ "view": "turn left"
1601
+ },
1602
+ {
1603
+ "move": "no-op",
1604
+ "view": "turn left"
1605
+ },
1606
+ {
1607
+ "move": "no-op",
1608
+ "view": "turn left"
1609
+ },
1610
+ {
1611
+ "move": "no-op",
1612
+ "view": "turn left"
1613
+ },
1614
+ {
1615
+ "move": "no-op",
1616
+ "view": "turn left"
1617
+ },
1618
+ {
1619
+ "move": "no-op",
1620
+ "view": "turn left"
1621
+ },
1622
+ {
1623
+ "move": "no-op",
1624
+ "view": "turn left"
1625
+ },
1626
+ {
1627
+ "move": "no-op",
1628
+ "view": "turn left"
1629
+ },
1630
+ {
1631
+ "move": "no-op",
1632
+ "view": "turn left"
1633
+ },
1634
+ {
1635
+ "move": "no-op",
1636
+ "view": "turn left"
1637
+ },
1638
+ {
1639
+ "move": "no-op",
1640
+ "view": "turn left"
1641
+ },
1642
+ {
1643
+ "move": "no-op",
1644
+ "view": "turn left"
1645
+ },
1646
+ {
1647
+ "move": "no-op",
1648
+ "view": "turn left"
1649
+ },
1650
+ {
1651
+ "move": "no-op",
1652
+ "view": "turn left"
1653
+ },
1654
+ {
1655
+ "move": "no-op",
1656
+ "view": "turn left"
1657
+ },
1658
+ {
1659
+ "move": "no-op",
1660
+ "view": "turn left"
1661
+ },
1662
+ {
1663
+ "move": "no-op",
1664
+ "view": "turn left"
1665
+ },
1666
+ {
1667
+ "move": "no-op",
1668
+ "view": "turn left"
1669
+ },
1670
+ {
1671
+ "move": "no-op",
1672
+ "view": "turn left"
1673
+ },
1674
+ {
1675
+ "move": "no-op",
1676
+ "view": "turn left"
1677
+ },
1678
+ {
1679
+ "move": "no-op",
1680
+ "view": "turn left"
1681
+ },
1682
+ {
1683
+ "move": "no-op",
1684
+ "view": "turn left"
1685
+ },
1686
+ {
1687
+ "move": "no-op",
1688
+ "view": "turn left"
1689
+ },
1690
+ {
1691
+ "move": "no-op",
1692
+ "view": "turn left"
1693
+ },
1694
+ {
1695
+ "move": "no-op",
1696
+ "view": "turn left"
1697
+ },
1698
+ {
1699
+ "move": "no-op",
1700
+ "view": "turn left"
1701
+ },
1702
+ {
1703
+ "move": "no-op",
1704
+ "view": "turn left"
1705
+ },
1706
+ {
1707
+ "move": "no-op",
1708
+ "view": "turn left"
1709
+ },
1710
+ {
1711
+ "move": "no-op",
1712
+ "view": "turn left"
1713
+ },
1714
+ {
1715
+ "move": "no-op",
1716
+ "view": "turn left"
1717
+ },
1718
+ {
1719
+ "move": "no-op",
1720
+ "view": "turn left"
1721
+ },
1722
+ {
1723
+ "move": "no-op",
1724
+ "view": "turn left"
1725
+ },
1726
+ {
1727
+ "move": "no-op",
1728
+ "view": "turn left"
1729
+ },
1730
+ {
1731
+ "move": "no-op",
1732
+ "view": "turn left"
1733
+ },
1734
+ {
1735
+ "move": "no-op",
1736
+ "view": "turn left"
1737
+ },
1738
+ {
1739
+ "move": "no-op",
1740
+ "view": "turn left"
1741
+ },
1742
+ {
1743
+ "move": "no-op",
1744
+ "view": "turn left"
1745
+ },
1746
+ {
1747
+ "move": "no-op",
1748
+ "view": "turn left"
1749
+ },
1750
+ {
1751
+ "move": "no-op",
1752
+ "view": "turn left"
1753
+ },
1754
+ {
1755
+ "move": "no-op",
1756
+ "view": "turn left"
1757
+ },
1758
+ {
1759
+ "move": "no-op",
1760
+ "view": "turn left"
1761
+ },
1762
+ {
1763
+ "move": "no-op",
1764
+ "view": "turn left"
1765
+ },
1766
+ {
1767
+ "move": "no-op",
1768
+ "view": "turn left"
1769
+ },
1770
+ {
1771
+ "move": "no-op",
1772
+ "view": "turn left"
1773
+ },
1774
+ {
1775
+ "move": "no-op",
1776
+ "view": "turn left"
1777
+ },
1778
+ {
1779
+ "move": "no-op",
1780
+ "view": "turn left"
1781
+ },
1782
+ {
1783
+ "move": "no-op",
1784
+ "view": "turn left"
1785
+ },
1786
+ {
1787
+ "move": "no-op",
1788
+ "view": "turn left"
1789
+ },
1790
+ {
1791
+ "move": "no-op",
1792
+ "view": "turn left"
1793
+ },
1794
+ {
1795
+ "move": "no-op",
1796
+ "view": "turn left"
1797
+ },
1798
+ {
1799
+ "move": "no-op",
1800
+ "view": "turn left"
1801
+ },
1802
+ {
1803
+ "move": "no-op",
1804
+ "view": "turn left"
1805
+ },
1806
+ {
1807
+ "move": "no-op",
1808
+ "view": "turn left"
1809
+ },
1810
+ {
1811
+ "move": "no-op",
1812
+ "view": "turn left"
1813
+ },
1814
+ {
1815
+ "move": "no-op",
1816
+ "view": "turn left"
1817
+ },
1818
+ {
1819
+ "move": "no-op",
1820
+ "view": "turn left"
1821
+ },
1822
+ {
1823
+ "move": "no-op",
1824
+ "view": "turn left"
1825
+ },
1826
+ {
1827
+ "move": "no-op",
1828
+ "view": "turn left"
1829
+ },
1830
+ {
1831
+ "move": "no-op",
1832
+ "view": "turn left"
1833
+ },
1834
+ {
1835
+ "move": "no-op",
1836
+ "view": "turn left"
1837
+ },
1838
+ {
1839
+ "move": "no-op",
1840
+ "view": "turn left"
1841
+ },
1842
+ {
1843
+ "move": "no-op",
1844
+ "view": "turn left"
1845
+ },
1846
+ {
1847
+ "move": "no-op",
1848
+ "view": "turn left"
1849
+ },
1850
+ {
1851
+ "move": "no-op",
1852
+ "view": "turn left"
1853
+ },
1854
+ {
1855
+ "move": "no-op",
1856
+ "view": "turn left"
1857
+ },
1858
+ {
1859
+ "move": "no-op",
1860
+ "view": "turn left"
1861
+ },
1862
+ {
1863
+ "move": "no-op",
1864
+ "view": "turn left"
1865
+ },
1866
+ {
1867
+ "move": "no-op",
1868
+ "view": "turn left"
1869
+ },
1870
+ {
1871
+ "move": "no-op",
1872
+ "view": "turn left"
1873
+ },
1874
+ {
1875
+ "move": "no-op",
1876
+ "view": "turn left"
1877
+ },
1878
+ {
1879
+ "move": "no-op",
1880
+ "view": "turn left"
1881
+ },
1882
+ {
1883
+ "move": "no-op",
1884
+ "view": "turn left"
1885
+ },
1886
+ {
1887
+ "move": "no-op",
1888
+ "view": "turn left"
1889
+ },
1890
+ {
1891
+ "move": "no-op",
1892
+ "view": "turn left"
1893
+ },
1894
+ {
1895
+ "move": "no-op",
1896
+ "view": "turn left"
1897
+ },
1898
+ {
1899
+ "move": "no-op",
1900
+ "view": "turn left"
1901
+ },
1902
+ {
1903
+ "move": "no-op",
1904
+ "view": "turn left"
1905
+ },
1906
+ {
1907
+ "move": "no-op",
1908
+ "view": "turn left"
1909
+ },
1910
+ {
1911
+ "move": "no-op",
1912
+ "view": "turn left"
1913
+ },
1914
+ {
1915
+ "move": "no-op",
1916
+ "view": "turn left"
1917
+ },
1918
+ {
1919
+ "move": "no-op",
1920
+ "view": "turn left"
1921
+ },
1922
+ {
1923
+ "move": "no-op",
1924
+ "view": "turn left"
1925
+ },
1926
+ {
1927
+ "move": "no-op",
1928
+ "view": "turn left"
1929
+ },
1930
+ {
1931
+ "move": "no-op",
1932
+ "view": "turn left"
1933
+ },
1934
+ {
1935
+ "move": "no-op",
1936
+ "view": "turn left"
1937
+ },
1938
+ {
1939
+ "move": "no-op",
1940
+ "view": "turn left"
1941
+ },
1942
+ {
1943
+ "move": "no-op",
1944
+ "view": "turn left"
1945
+ },
1946
+ {
1947
+ "move": "no-op",
1948
+ "view": "turn left"
1949
+ },
1950
+ {
1951
+ "move": "no-op",
1952
+ "view": "turn left"
1953
+ },
1954
+ {
1955
+ "move": "no-op",
1956
+ "view": "turn left"
1957
+ },
1958
+ {
1959
+ "move": "no-op",
1960
+ "view": "turn left"
1961
+ },
1962
+ {
1963
+ "move": "no-op",
1964
+ "view": "turn left"
1965
+ },
1966
+ {
1967
+ "move": "no-op",
1968
+ "view": "turn left"
1969
+ },
1970
+ {
1971
+ "move": "no-op",
1972
+ "view": "turn left"
1973
+ },
1974
+ {
1975
+ "move": "no-op",
1976
+ "view": "turn left"
1977
+ },
1978
+ {
1979
+ "move": "no-op",
1980
+ "view": "turn left"
1981
+ },
1982
+ {
1983
+ "move": "no-op",
1984
+ "view": "turn left"
1985
+ },
1986
+ {
1987
+ "move": "no-op",
1988
+ "view": "turn left"
1989
+ },
1990
+ {
1991
+ "move": "no-op",
1992
+ "view": "turn left"
1993
+ },
1994
+ {
1995
+ "move": "no-op",
1996
+ "view": "turn left"
1997
+ },
1998
+ {
1999
+ "move": "no-op",
2000
+ "view": "turn left"
2001
+ },
2002
+ {
2003
+ "move": "no-op",
2004
+ "view": "turn left"
2005
+ },
2006
+ {
2007
+ "move": "no-op",
2008
+ "view": "turn left"
2009
+ },
2010
+ {
2011
+ "move": "no-op",
2012
+ "view": "turn left"
2013
+ },
2014
+ {
2015
+ "move": "no-op",
2016
+ "view": "turn left"
2017
+ },
2018
+ {
2019
+ "move": "no-op",
2020
+ "view": "turn left"
2021
+ },
2022
+ {
2023
+ "move": "no-op",
2024
+ "view": "turn left"
2025
+ },
2026
+ {
2027
+ "move": "no-op",
2028
+ "view": "turn left"
2029
+ },
2030
+ {
2031
+ "move": "no-op",
2032
+ "view": "turn left"
2033
+ },
2034
+ {
2035
+ "move": "no-op",
2036
+ "view": "turn left"
2037
+ },
2038
+ {
2039
+ "move": "no-op",
2040
+ "view": "turn left"
2041
+ },
2042
+ {
2043
+ "move": "no-op",
2044
+ "view": "turn left"
2045
+ },
2046
+ {
2047
+ "move": "no-op",
2048
+ "view": "turn left"
2049
+ },
2050
+ {
2051
+ "move": "no-op",
2052
+ "view": "turn left"
2053
+ },
2054
+ {
2055
+ "move": "no-op",
2056
+ "view": "turn left"
2057
+ },
2058
+ {
2059
+ "move": "no-op",
2060
+ "view": "turn left"
2061
+ },
2062
+ {
2063
+ "move": "no-op",
2064
+ "view": "turn left"
2065
+ },
2066
+ {
2067
+ "move": "no-op",
2068
+ "view": "turn left"
2069
+ },
2070
+ {
2071
+ "move": "no-op",
2072
+ "view": "turn left"
2073
+ },
2074
+ {
2075
+ "move": "no-op",
2076
+ "view": "turn left"
2077
+ },
2078
+ {
2079
+ "move": "no-op",
2080
+ "view": "turn left"
2081
+ },
2082
+ {
2083
+ "move": "no-op",
2084
+ "view": "turn left"
2085
+ },
2086
+ {
2087
+ "move": "no-op",
2088
+ "view": "turn left"
2089
+ },
2090
+ {
2091
+ "move": "no-op",
2092
+ "view": "turn left"
2093
+ },
2094
+ {
2095
+ "move": "no-op",
2096
+ "view": "turn left"
2097
+ },
2098
+ {
2099
+ "move": "no-op",
2100
+ "view": "turn left"
2101
+ },
2102
+ {
2103
+ "move": "no-op",
2104
+ "view": "turn left"
2105
+ },
2106
+ {
2107
+ "move": "no-op",
2108
+ "view": "turn left"
2109
+ },
2110
+ {
2111
+ "move": "no-op",
2112
+ "view": "turn left"
2113
+ },
2114
+ {
2115
+ "move": "no-op",
2116
+ "view": "turn left"
2117
+ },
2118
+ {
2119
+ "move": "no-op",
2120
+ "view": "turn left"
2121
+ },
2122
+ {
2123
+ "move": "no-op",
2124
+ "view": "turn left"
2125
+ },
2126
+ {
2127
+ "move": "no-op",
2128
+ "view": "turn left"
2129
+ },
2130
+ {
2131
+ "move": "no-op",
2132
+ "view": "turn left"
2133
+ },
2134
+ {
2135
+ "move": "no-op",
2136
+ "view": "turn left"
2137
+ },
2138
+ {
2139
+ "move": "no-op",
2140
+ "view": "turn left"
2141
+ },
2142
+ {
2143
+ "move": "no-op",
2144
+ "view": "turn left"
2145
+ },
2146
+ {
2147
+ "move": "no-op",
2148
+ "view": "turn left"
2149
+ },
2150
+ {
2151
+ "move": "no-op",
2152
+ "view": "turn left"
2153
+ },
2154
+ {
2155
+ "move": "no-op",
2156
+ "view": "turn left"
2157
+ },
2158
+ {
2159
+ "move": "no-op",
2160
+ "view": "turn left"
2161
+ },
2162
+ {
2163
+ "move": "no-op",
2164
+ "view": "turn left"
2165
+ },
2166
+ {
2167
+ "move": "no-op",
2168
+ "view": "turn left"
2169
+ },
2170
+ {
2171
+ "move": "no-op",
2172
+ "view": "turn left"
2173
+ },
2174
+ {
2175
+ "move": "no-op",
2176
+ "view": "turn left"
2177
+ },
2178
+ {
2179
+ "move": "no-op",
2180
+ "view": "turn left"
2181
+ },
2182
+ {
2183
+ "move": "no-op",
2184
+ "view": "turn left"
2185
+ },
2186
+ {
2187
+ "move": "no-op",
2188
+ "view": "turn left"
2189
+ },
2190
+ {
2191
+ "move": "no-op",
2192
+ "view": "turn left"
2193
+ },
2194
+ {
2195
+ "move": "no-op",
2196
+ "view": "turn left"
2197
+ },
2198
+ {
2199
+ "move": "no-op",
2200
+ "view": "turn left"
2201
+ },
2202
+ {
2203
+ "move": "no-op",
2204
+ "view": "turn left"
2205
+ },
2206
+ {
2207
+ "move": "no-op",
2208
+ "view": "turn left"
2209
+ },
2210
+ {
2211
+ "move": "no-op",
2212
+ "view": "turn left"
2213
+ },
2214
+ {
2215
+ "move": "no-op",
2216
+ "view": "turn left"
2217
+ },
2218
+ {
2219
+ "move": "no-op",
2220
+ "view": "turn left"
2221
+ },
2222
+ {
2223
+ "move": "no-op",
2224
+ "view": "turn left"
2225
+ },
2226
+ {
2227
+ "move": "no-op",
2228
+ "view": "turn left"
2229
+ },
2230
+ {
2231
+ "move": "no-op",
2232
+ "view": "turn left"
2233
+ },
2234
+ {
2235
+ "move": "no-op",
2236
+ "view": "turn left"
2237
+ },
2238
+ {
2239
+ "move": "no-op",
2240
+ "view": "turn left"
2241
+ },
2242
+ {
2243
+ "move": "no-op",
2244
+ "view": "turn left"
2245
+ },
2246
+ {
2247
+ "move": "no-op",
2248
+ "view": "turn left"
2249
+ },
2250
+ {
2251
+ "move": "no-op",
2252
+ "view": "turn left"
2253
+ },
2254
+ {
2255
+ "move": "no-op",
2256
+ "view": "turn left"
2257
+ },
2258
+ {
2259
+ "move": "no-op",
2260
+ "view": "turn left"
2261
+ },
2262
+ {
2263
+ "move": "no-op",
2264
+ "view": "turn left"
2265
+ },
2266
+ {
2267
+ "move": "no-op",
2268
+ "view": "turn left"
2269
+ },
2270
+ {
2271
+ "move": "go forward",
2272
+ "view": "no-op"
2273
+ },
2274
+ {
2275
+ "move": "go forward",
2276
+ "view": "no-op"
2277
+ },
2278
+ {
2279
+ "move": "go forward",
2280
+ "view": "no-op"
2281
+ },
2282
+ {
2283
+ "move": "go forward",
2284
+ "view": "no-op"
2285
+ },
2286
+ {
2287
+ "move": "go forward",
2288
+ "view": "no-op"
2289
+ },
2290
+ {
2291
+ "move": "go forward",
2292
+ "view": "no-op"
2293
+ },
2294
+ {
2295
+ "move": "go forward",
2296
+ "view": "no-op"
2297
+ },
2298
+ {
2299
+ "move": "go forward",
2300
+ "view": "no-op"
2301
+ },
2302
+ {
2303
+ "move": "go forward",
2304
+ "view": "no-op"
2305
+ },
2306
+ {
2307
+ "move": "go forward",
2308
+ "view": "no-op"
2309
+ },
2310
+ {
2311
+ "move": "go forward",
2312
+ "view": "no-op"
2313
+ },
2314
+ {
2315
+ "move": "go forward",
2316
+ "view": "no-op"
2317
+ },
2318
+ {
2319
+ "move": "go forward",
2320
+ "view": "no-op"
2321
+ },
2322
+ {
2323
+ "move": "go forward",
2324
+ "view": "no-op"
2325
+ },
2326
+ {
2327
+ "move": "go forward",
2328
+ "view": "no-op"
2329
+ },
2330
+ {
2331
+ "move": "go forward",
2332
+ "view": "no-op"
2333
+ },
2334
+ {
2335
+ "move": "go forward",
2336
+ "view": "no-op"
2337
+ },
2338
+ {
2339
+ "move": "go forward",
2340
+ "view": "no-op"
2341
+ },
2342
+ {
2343
+ "move": "go forward",
2344
+ "view": "no-op"
2345
+ },
2346
+ {
2347
+ "move": "go forward",
2348
+ "view": "no-op"
2349
+ },
2350
+ {
2351
+ "move": "go forward",
2352
+ "view": "no-op"
2353
+ },
2354
+ {
2355
+ "move": "go forward",
2356
+ "view": "no-op"
2357
+ },
2358
+ {
2359
+ "move": "go forward",
2360
+ "view": "no-op"
2361
+ },
2362
+ {
2363
+ "move": "go forward",
2364
+ "view": "no-op"
2365
+ },
2366
+ {
2367
+ "move": "go forward",
2368
+ "view": "no-op"
2369
+ },
2370
+ {
2371
+ "move": "go forward",
2372
+ "view": "no-op"
2373
+ },
2374
+ {
2375
+ "move": "go forward",
2376
+ "view": "no-op"
2377
+ },
2378
+ {
2379
+ "move": "go forward",
2380
+ "view": "no-op"
2381
+ },
2382
+ {
2383
+ "move": "go forward",
2384
+ "view": "no-op"
2385
+ },
2386
+ {
2387
+ "move": "go forward",
2388
+ "view": "no-op"
2389
+ },
2390
+ {
2391
+ "move": "go forward",
2392
+ "view": "no-op"
2393
+ },
2394
+ {
2395
+ "move": "go forward",
2396
+ "view": "no-op"
2397
+ },
2398
+ {
2399
+ "move": "go forward",
2400
+ "view": "no-op"
2401
+ },
2402
+ {
2403
+ "move": "go forward",
2404
+ "view": "no-op"
2405
+ },
2406
+ {
2407
+ "move": "go forward",
2408
+ "view": "no-op"
2409
+ },
2410
+ {
2411
+ "move": "go forward",
2412
+ "view": "no-op"
2413
+ },
2414
+ {
2415
+ "move": "go forward",
2416
+ "view": "no-op"
2417
+ },
2418
+ {
2419
+ "move": "go forward",
2420
+ "view": "no-op"
2421
+ },
2422
+ {
2423
+ "move": "go forward",
2424
+ "view": "no-op"
2425
+ },
2426
+ {
2427
+ "move": "go forward",
2428
+ "view": "no-op"
2429
+ },
2430
+ {
2431
+ "move": "go forward",
2432
+ "view": "no-op"
2433
+ },
2434
+ {
2435
+ "move": "go forward",
2436
+ "view": "no-op"
2437
+ },
2438
+ {
2439
+ "move": "go forward",
2440
+ "view": "no-op"
2441
+ },
2442
+ {
2443
+ "move": "go forward",
2444
+ "view": "no-op"
2445
+ },
2446
+ {
2447
+ "move": "go forward",
2448
+ "view": "no-op"
2449
+ },
2450
+ {
2451
+ "move": "go forward",
2452
+ "view": "no-op"
2453
+ },
2454
+ {
2455
+ "move": "go forward",
2456
+ "view": "no-op"
2457
+ },
2458
+ {
2459
+ "move": "go forward",
2460
+ "view": "no-op"
2461
+ },
2462
+ {
2463
+ "move": "go forward",
2464
+ "view": "no-op"
2465
+ },
2466
+ {
2467
+ "move": "go forward",
2468
+ "view": "no-op"
2469
+ },
2470
+ {
2471
+ "move": "go forward",
2472
+ "view": "no-op"
2473
+ },
2474
+ {
2475
+ "move": "go forward",
2476
+ "view": "no-op"
2477
+ },
2478
+ {
2479
+ "move": "go forward",
2480
+ "view": "no-op"
2481
+ },
2482
+ {
2483
+ "move": "go forward",
2484
+ "view": "no-op"
2485
+ },
2486
+ {
2487
+ "move": "go forward",
2488
+ "view": "no-op"
2489
+ },
2490
+ {
2491
+ "move": "go forward",
2492
+ "view": "no-op"
2493
+ },
2494
+ {
2495
+ "move": "go forward",
2496
+ "view": "no-op"
2497
+ },
2498
+ {
2499
+ "move": "go forward",
2500
+ "view": "no-op"
2501
+ },
2502
+ {
2503
+ "move": "go forward",
2504
+ "view": "no-op"
2505
+ },
2506
+ {
2507
+ "move": "go forward",
2508
+ "view": "no-op"
2509
+ },
2510
+ {
2511
+ "move": "go forward",
2512
+ "view": "no-op"
2513
+ },
2514
+ {
2515
+ "move": "go forward",
2516
+ "view": "no-op"
2517
+ },
2518
+ {
2519
+ "move": "go forward",
2520
+ "view": "no-op"
2521
+ },
2522
+ {
2523
+ "move": "go forward",
2524
+ "view": "no-op"
2525
+ },
2526
+ {
2527
+ "move": "go forward",
2528
+ "view": "no-op"
2529
+ },
2530
+ {
2531
+ "move": "go forward",
2532
+ "view": "no-op"
2533
+ },
2534
+ {
2535
+ "move": "go forward",
2536
+ "view": "no-op"
2537
+ },
2538
+ {
2539
+ "move": "go forward",
2540
+ "view": "no-op"
2541
+ },
2542
+ {
2543
+ "move": "go forward",
2544
+ "view": "no-op"
2545
+ },
2546
+ {
2547
+ "move": "go forward",
2548
+ "view": "no-op"
2549
+ },
2550
+ {
2551
+ "move": "go forward",
2552
+ "view": "no-op"
2553
+ },
2554
+ {
2555
+ "move": "go forward",
2556
+ "view": "no-op"
2557
+ },
2558
+ {
2559
+ "move": "go forward",
2560
+ "view": "no-op"
2561
+ },
2562
+ {
2563
+ "move": "go forward",
2564
+ "view": "no-op"
2565
+ },
2566
+ {
2567
+ "move": "go forward",
2568
+ "view": "no-op"
2569
+ },
2570
+ {
2571
+ "move": "go forward",
2572
+ "view": "no-op"
2573
+ },
2574
+ {
2575
+ "move": "go forward",
2576
+ "view": "no-op"
2577
+ },
2578
+ {
2579
+ "move": "go forward",
2580
+ "view": "no-op"
2581
+ },
2582
+ {
2583
+ "move": "go forward",
2584
+ "view": "no-op"
2585
+ },
2586
+ {
2587
+ "move": "go forward",
2588
+ "view": "no-op"
2589
+ },
2590
+ {
2591
+ "move": "go forward",
2592
+ "view": "no-op"
2593
+ },
2594
+ {
2595
+ "move": "go forward",
2596
+ "view": "no-op"
2597
+ },
2598
+ {
2599
+ "move": "go forward",
2600
+ "view": "no-op"
2601
+ },
2602
+ {
2603
+ "move": "go forward",
2604
+ "view": "no-op"
2605
+ },
2606
+ {
2607
+ "move": "go forward",
2608
+ "view": "no-op"
2609
+ },
2610
+ {
2611
+ "move": "go forward",
2612
+ "view": "no-op"
2613
+ },
2614
+ {
2615
+ "move": "go forward",
2616
+ "view": "no-op"
2617
+ },
2618
+ {
2619
+ "move": "go forward",
2620
+ "view": "no-op"
2621
+ },
2622
+ {
2623
+ "move": "go forward",
2624
+ "view": "no-op"
2625
+ },
2626
+ {
2627
+ "move": "go forward",
2628
+ "view": "no-op"
2629
+ },
2630
+ {
2631
+ "move": "go forward",
2632
+ "view": "no-op"
2633
+ },
2634
+ {
2635
+ "move": "go forward",
2636
+ "view": "no-op"
2637
+ },
2638
+ {
2639
+ "move": "go forward",
2640
+ "view": "no-op"
2641
+ },
2642
+ {
2643
+ "move": "go forward",
2644
+ "view": "no-op"
2645
+ },
2646
+ {
2647
+ "move": "go forward",
2648
+ "view": "no-op"
2649
+ },
2650
+ {
2651
+ "move": "go forward",
2652
+ "view": "no-op"
2653
+ },
2654
+ {
2655
+ "move": "go forward",
2656
+ "view": "no-op"
2657
+ },
2658
+ {
2659
+ "move": "go forward",
2660
+ "view": "no-op"
2661
+ },
2662
+ {
2663
+ "move": "go forward",
2664
+ "view": "no-op"
2665
+ },
2666
+ {
2667
+ "move": "go forward",
2668
+ "view": "no-op"
2669
+ },
2670
+ {
2671
+ "move": "go forward",
2672
+ "view": "no-op"
2673
+ },
2674
+ {
2675
+ "move": "go forward",
2676
+ "view": "no-op"
2677
+ },
2678
+ {
2679
+ "move": "go forward",
2680
+ "view": "no-op"
2681
+ },
2682
+ {
2683
+ "move": "go forward",
2684
+ "view": "no-op"
2685
+ },
2686
+ {
2687
+ "move": "go forward",
2688
+ "view": "no-op"
2689
+ },
2690
+ {
2691
+ "move": "go forward",
2692
+ "view": "no-op"
2693
+ },
2694
+ {
2695
+ "move": "go forward",
2696
+ "view": "no-op"
2697
+ },
2698
+ {
2699
+ "move": "go forward",
2700
+ "view": "no-op"
2701
+ },
2702
+ {
2703
+ "move": "go forward",
2704
+ "view": "no-op"
2705
+ },
2706
+ {
2707
+ "move": "go forward",
2708
+ "view": "no-op"
2709
+ },
2710
+ {
2711
+ "move": "go forward",
2712
+ "view": "no-op"
2713
+ },
2714
+ {
2715
+ "move": "go forward",
2716
+ "view": "no-op"
2717
+ },
2718
+ {
2719
+ "move": "go forward",
2720
+ "view": "no-op"
2721
+ },
2722
+ {
2723
+ "move": "go forward",
2724
+ "view": "no-op"
2725
+ },
2726
+ {
2727
+ "move": "go forward",
2728
+ "view": "no-op"
2729
+ },
2730
+ {
2731
+ "move": "go forward",
2732
+ "view": "no-op"
2733
+ },
2734
+ {
2735
+ "move": "go forward",
2736
+ "view": "no-op"
2737
+ },
2738
+ {
2739
+ "move": "go forward",
2740
+ "view": "no-op"
2741
+ },
2742
+ {
2743
+ "move": "go forward",
2744
+ "view": "no-op"
2745
+ },
2746
+ {
2747
+ "move": "go forward",
2748
+ "view": "no-op"
2749
+ },
2750
+ {
2751
+ "move": "go forward",
2752
+ "view": "no-op"
2753
+ },
2754
+ {
2755
+ "move": "go forward",
2756
+ "view": "no-op"
2757
+ },
2758
+ {
2759
+ "move": "go forward",
2760
+ "view": "no-op"
2761
+ },
2762
+ {
2763
+ "move": "go forward",
2764
+ "view": "no-op"
2765
+ },
2766
+ {
2767
+ "move": "go forward",
2768
+ "view": "no-op"
2769
+ },
2770
+ {
2771
+ "move": "go forward",
2772
+ "view": "no-op"
2773
+ },
2774
+ {
2775
+ "move": "go forward",
2776
+ "view": "no-op"
2777
+ },
2778
+ {
2779
+ "move": "go forward",
2780
+ "view": "no-op"
2781
+ },
2782
+ {
2783
+ "move": "go forward",
2784
+ "view": "no-op"
2785
+ },
2786
+ {
2787
+ "move": "go forward",
2788
+ "view": "no-op"
2789
+ },
2790
+ {
2791
+ "move": "go forward",
2792
+ "view": "no-op"
2793
+ },
2794
+ {
2795
+ "move": "go forward",
2796
+ "view": "no-op"
2797
+ },
2798
+ {
2799
+ "move": "go forward",
2800
+ "view": "no-op"
2801
+ },
2802
+ {
2803
+ "move": "go forward",
2804
+ "view": "no-op"
2805
+ },
2806
+ {
2807
+ "move": "go forward",
2808
+ "view": "no-op"
2809
+ },
2810
+ {
2811
+ "move": "go forward",
2812
+ "view": "no-op"
2813
+ },
2814
+ {
2815
+ "move": "go forward",
2816
+ "view": "no-op"
2817
+ },
2818
+ {
2819
+ "move": "go forward",
2820
+ "view": "no-op"
2821
+ },
2822
+ {
2823
+ "move": "go forward",
2824
+ "view": "no-op"
2825
+ },
2826
+ {
2827
+ "move": "go forward",
2828
+ "view": "no-op"
2829
+ },
2830
+ {
2831
+ "move": "go forward",
2832
+ "view": "no-op"
2833
+ },
2834
+ {
2835
+ "move": "go forward",
2836
+ "view": "no-op"
2837
+ },
2838
+ {
2839
+ "move": "go forward",
2840
+ "view": "no-op"
2841
+ },
2842
+ {
2843
+ "move": "go forward",
2844
+ "view": "no-op"
2845
+ },
2846
+ {
2847
+ "move": "go forward",
2848
+ "view": "no-op"
2849
+ },
2850
+ {
2851
+ "move": "go forward",
2852
+ "view": "no-op"
2853
+ },
2854
+ {
2855
+ "move": "go forward",
2856
+ "view": "no-op"
2857
+ },
2858
+ {
2859
+ "move": "go forward",
2860
+ "view": "no-op"
2861
+ },
2862
+ {
2863
+ "move": "go forward",
2864
+ "view": "no-op"
2865
+ },
2866
+ {
2867
+ "move": "go forward",
2868
+ "view": "no-op"
2869
+ },
2870
+ {
2871
+ "move": "go forward",
2872
+ "view": "no-op"
2873
+ },
2874
+ {
2875
+ "move": "go forward",
2876
+ "view": "no-op"
2877
+ },
2878
+ {
2879
+ "move": "go forward",
2880
+ "view": "no-op"
2881
+ },
2882
+ {
2883
+ "move": "go forward",
2884
+ "view": "no-op"
2885
+ },
2886
+ {
2887
+ "move": "go forward",
2888
+ "view": "no-op"
2889
+ },
2890
+ {
2891
+ "move": "go forward",
2892
+ "view": "no-op"
2893
+ },
2894
+ {
2895
+ "move": "go forward",
2896
+ "view": "no-op"
2897
+ },
2898
+ {
2899
+ "move": "go forward",
2900
+ "view": "no-op"
2901
+ },
2902
+ {
2903
+ "move": "go forward",
2904
+ "view": "no-op"
2905
+ },
2906
+ {
2907
+ "move": "go forward",
2908
+ "view": "no-op"
2909
+ },
2910
+ {
2911
+ "move": "go forward",
2912
+ "view": "no-op"
2913
+ },
2914
+ {
2915
+ "move": "go forward",
2916
+ "view": "no-op"
2917
+ },
2918
+ {
2919
+ "move": "no-op",
2920
+ "view": "turn right"
2921
+ },
2922
+ {
2923
+ "move": "no-op",
2924
+ "view": "turn right"
2925
+ },
2926
+ {
2927
+ "move": "no-op",
2928
+ "view": "turn right"
2929
+ },
2930
+ {
2931
+ "move": "no-op",
2932
+ "view": "turn right"
2933
+ },
2934
+ {
2935
+ "move": "no-op",
2936
+ "view": "turn right"
2937
+ },
2938
+ {
2939
+ "move": "no-op",
2940
+ "view": "turn right"
2941
+ },
2942
+ {
2943
+ "move": "no-op",
2944
+ "view": "turn right"
2945
+ },
2946
+ {
2947
+ "move": "no-op",
2948
+ "view": "turn right"
2949
+ },
2950
+ {
2951
+ "move": "no-op",
2952
+ "view": "turn right"
2953
+ },
2954
+ {
2955
+ "move": "no-op",
2956
+ "view": "turn right"
2957
+ },
2958
+ {
2959
+ "move": "no-op",
2960
+ "view": "turn right"
2961
+ },
2962
+ {
2963
+ "move": "no-op",
2964
+ "view": "turn right"
2965
+ },
2966
+ {
2967
+ "move": "no-op",
2968
+ "view": "turn right"
2969
+ },
2970
+ {
2971
+ "move": "no-op",
2972
+ "view": "turn right"
2973
+ },
2974
+ {
2975
+ "move": "no-op",
2976
+ "view": "turn right"
2977
+ },
2978
+ {
2979
+ "move": "no-op",
2980
+ "view": "turn right"
2981
+ },
2982
+ {
2983
+ "move": "no-op",
2984
+ "view": "turn right"
2985
+ },
2986
+ {
2987
+ "move": "no-op",
2988
+ "view": "turn right"
2989
+ },
2990
+ {
2991
+ "move": "no-op",
2992
+ "view": "turn right"
2993
+ },
2994
+ {
2995
+ "move": "no-op",
2996
+ "view": "turn right"
2997
+ },
2998
+ {
2999
+ "move": "no-op",
3000
+ "view": "turn right"
3001
+ },
3002
+ {
3003
+ "move": "no-op",
3004
+ "view": "turn right"
3005
+ },
3006
+ {
3007
+ "move": "no-op",
3008
+ "view": "turn right"
3009
+ },
3010
+ {
3011
+ "move": "no-op",
3012
+ "view": "turn right"
3013
+ },
3014
+ {
3015
+ "move": "no-op",
3016
+ "view": "turn right"
3017
+ },
3018
+ {
3019
+ "move": "no-op",
3020
+ "view": "turn right"
3021
+ },
3022
+ {
3023
+ "move": "no-op",
3024
+ "view": "turn right"
3025
+ },
3026
+ {
3027
+ "move": "no-op",
3028
+ "view": "turn right"
3029
+ },
3030
+ {
3031
+ "move": "no-op",
3032
+ "view": "turn right"
3033
+ },
3034
+ {
3035
+ "move": "no-op",
3036
+ "view": "turn right"
3037
+ },
3038
+ {
3039
+ "move": "no-op",
3040
+ "view": "turn right"
3041
+ },
3042
+ {
3043
+ "move": "no-op",
3044
+ "view": "turn right"
3045
+ },
3046
+ {
3047
+ "move": "no-op",
3048
+ "view": "turn right"
3049
+ },
3050
+ {
3051
+ "move": "no-op",
3052
+ "view": "turn right"
3053
+ },
3054
+ {
3055
+ "move": "no-op",
3056
+ "view": "turn right"
3057
+ },
3058
+ {
3059
+ "move": "no-op",
3060
+ "view": "turn right"
3061
+ },
3062
+ {
3063
+ "move": "no-op",
3064
+ "view": "turn right"
3065
+ },
3066
+ {
3067
+ "move": "no-op",
3068
+ "view": "turn right"
3069
+ },
3070
+ {
3071
+ "move": "no-op",
3072
+ "view": "turn right"
3073
+ },
3074
+ {
3075
+ "move": "no-op",
3076
+ "view": "turn right"
3077
+ },
3078
+ {
3079
+ "move": "no-op",
3080
+ "view": "turn right"
3081
+ },
3082
+ {
3083
+ "move": "no-op",
3084
+ "view": "turn right"
3085
+ },
3086
+ {
3087
+ "move": "no-op",
3088
+ "view": "turn right"
3089
+ },
3090
+ {
3091
+ "move": "no-op",
3092
+ "view": "turn right"
3093
+ },
3094
+ {
3095
+ "move": "no-op",
3096
+ "view": "turn right"
3097
+ },
3098
+ {
3099
+ "move": "no-op",
3100
+ "view": "turn right"
3101
+ },
3102
+ {
3103
+ "move": "no-op",
3104
+ "view": "turn right"
3105
+ },
3106
+ {
3107
+ "move": "no-op",
3108
+ "view": "turn right"
3109
+ },
3110
+ {
3111
+ "move": "no-op",
3112
+ "view": "turn right"
3113
+ },
3114
+ {
3115
+ "move": "no-op",
3116
+ "view": "turn right"
3117
+ },
3118
+ {
3119
+ "move": "no-op",
3120
+ "view": "turn right"
3121
+ },
3122
+ {
3123
+ "move": "no-op",
3124
+ "view": "turn right"
3125
+ },
3126
+ {
3127
+ "move": "no-op",
3128
+ "view": "turn right"
3129
+ },
3130
+ {
3131
+ "move": "no-op",
3132
+ "view": "turn right"
3133
+ },
3134
+ {
3135
+ "move": "no-op",
3136
+ "view": "turn right"
3137
+ },
3138
+ {
3139
+ "move": "no-op",
3140
+ "view": "turn right"
3141
+ },
3142
+ {
3143
+ "move": "no-op",
3144
+ "view": "turn right"
3145
+ },
3146
+ {
3147
+ "move": "no-op",
3148
+ "view": "turn right"
3149
+ },
3150
+ {
3151
+ "move": "no-op",
3152
+ "view": "turn right"
3153
+ },
3154
+ {
3155
+ "move": "no-op",
3156
+ "view": "turn right"
3157
+ },
3158
+ {
3159
+ "move": "no-op",
3160
+ "view": "turn right"
3161
+ },
3162
+ {
3163
+ "move": "no-op",
3164
+ "view": "turn right"
3165
+ },
3166
+ {
3167
+ "move": "no-op",
3168
+ "view": "turn right"
3169
+ },
3170
+ {
3171
+ "move": "no-op",
3172
+ "view": "turn right"
3173
+ },
3174
+ {
3175
+ "move": "no-op",
3176
+ "view": "turn right"
3177
+ },
3178
+ {
3179
+ "move": "no-op",
3180
+ "view": "turn right"
3181
+ },
3182
+ {
3183
+ "move": "no-op",
3184
+ "view": "turn right"
3185
+ },
3186
+ {
3187
+ "move": "no-op",
3188
+ "view": "turn right"
3189
+ },
3190
+ {
3191
+ "move": "no-op",
3192
+ "view": "turn right"
3193
+ },
3194
+ {
3195
+ "move": "no-op",
3196
+ "view": "turn right"
3197
+ },
3198
+ {
3199
+ "move": "no-op",
3200
+ "view": "turn right"
3201
+ },
3202
+ {
3203
+ "move": "no-op",
3204
+ "view": "turn right"
3205
+ },
3206
+ {
3207
+ "move": "no-op",
3208
+ "view": "turn right"
3209
+ },
3210
+ {
3211
+ "move": "no-op",
3212
+ "view": "turn right"
3213
+ },
3214
+ {
3215
+ "move": "no-op",
3216
+ "view": "turn right"
3217
+ },
3218
+ {
3219
+ "move": "no-op",
3220
+ "view": "turn right"
3221
+ },
3222
+ {
3223
+ "move": "no-op",
3224
+ "view": "turn right"
3225
+ },
3226
+ {
3227
+ "move": "no-op",
3228
+ "view": "turn right"
3229
+ },
3230
+ {
3231
+ "move": "no-op",
3232
+ "view": "turn right"
3233
+ },
3234
+ {
3235
+ "move": "no-op",
3236
+ "view": "turn right"
3237
+ },
3238
+ {
3239
+ "move": "no-op",
3240
+ "view": "turn right"
3241
+ },
3242
+ {
3243
+ "move": "no-op",
3244
+ "view": "turn right"
3245
+ },
3246
+ {
3247
+ "move": "no-op",
3248
+ "view": "turn right"
3249
+ },
3250
+ {
3251
+ "move": "no-op",
3252
+ "view": "turn right"
3253
+ },
3254
+ {
3255
+ "move": "no-op",
3256
+ "view": "turn right"
3257
+ },
3258
+ {
3259
+ "move": "no-op",
3260
+ "view": "turn right"
3261
+ },
3262
+ {
3263
+ "move": "no-op",
3264
+ "view": "turn right"
3265
+ },
3266
+ {
3267
+ "move": "no-op",
3268
+ "view": "turn right"
3269
+ },
3270
+ {
3271
+ "move": "no-op",
3272
+ "view": "turn right"
3273
+ },
3274
+ {
3275
+ "move": "no-op",
3276
+ "view": "turn right"
3277
+ },
3278
+ {
3279
+ "move": "no-op",
3280
+ "view": "turn right"
3281
+ },
3282
+ {
3283
+ "move": "no-op",
3284
+ "view": "turn right"
3285
+ },
3286
+ {
3287
+ "move": "no-op",
3288
+ "view": "turn right"
3289
+ },
3290
+ {
3291
+ "move": "no-op",
3292
+ "view": "turn right"
3293
+ },
3294
+ {
3295
+ "move": "no-op",
3296
+ "view": "turn right"
3297
+ },
3298
+ {
3299
+ "move": "no-op",
3300
+ "view": "turn right"
3301
+ },
3302
+ {
3303
+ "move": "no-op",
3304
+ "view": "turn right"
3305
+ },
3306
+ {
3307
+ "move": "no-op",
3308
+ "view": "turn right"
3309
+ },
3310
+ {
3311
+ "move": "no-op",
3312
+ "view": "turn right"
3313
+ },
3314
+ {
3315
+ "move": "no-op",
3316
+ "view": "turn right"
3317
+ },
3318
+ {
3319
+ "move": "no-op",
3320
+ "view": "turn right"
3321
+ },
3322
+ {
3323
+ "move": "no-op",
3324
+ "view": "turn right"
3325
+ },
3326
+ {
3327
+ "move": "no-op",
3328
+ "view": "turn right"
3329
+ },
3330
+ {
3331
+ "move": "no-op",
3332
+ "view": "turn right"
3333
+ },
3334
+ {
3335
+ "move": "no-op",
3336
+ "view": "turn right"
3337
+ },
3338
+ {
3339
+ "move": "no-op",
3340
+ "view": "turn right"
3341
+ },
3342
+ {
3343
+ "move": "no-op",
3344
+ "view": "turn right"
3345
+ },
3346
+ {
3347
+ "move": "no-op",
3348
+ "view": "turn right"
3349
+ },
3350
+ {
3351
+ "move": "no-op",
3352
+ "view": "turn right"
3353
+ },
3354
+ {
3355
+ "move": "no-op",
3356
+ "view": "turn right"
3357
+ },
3358
+ {
3359
+ "move": "no-op",
3360
+ "view": "turn right"
3361
+ },
3362
+ {
3363
+ "move": "no-op",
3364
+ "view": "turn right"
3365
+ },
3366
+ {
3367
+ "move": "no-op",
3368
+ "view": "turn right"
3369
+ },
3370
+ {
3371
+ "move": "no-op",
3372
+ "view": "turn right"
3373
+ },
3374
+ {
3375
+ "move": "no-op",
3376
+ "view": "turn right"
3377
+ },
3378
+ {
3379
+ "move": "no-op",
3380
+ "view": "turn right"
3381
+ },
3382
+ {
3383
+ "move": "no-op",
3384
+ "view": "turn right"
3385
+ },
3386
+ {
3387
+ "move": "no-op",
3388
+ "view": "turn right"
3389
+ },
3390
+ {
3391
+ "move": "no-op",
3392
+ "view": "turn right"
3393
+ },
3394
+ {
3395
+ "move": "no-op",
3396
+ "view": "turn right"
3397
+ },
3398
+ {
3399
+ "move": "no-op",
3400
+ "view": "turn right"
3401
+ },
3402
+ {
3403
+ "move": "no-op",
3404
+ "view": "turn right"
3405
+ },
3406
+ {
3407
+ "move": "no-op",
3408
+ "view": "turn right"
3409
+ },
3410
+ {
3411
+ "move": "no-op",
3412
+ "view": "turn right"
3413
+ },
3414
+ {
3415
+ "move": "no-op",
3416
+ "view": "turn right"
3417
+ },
3418
+ {
3419
+ "move": "no-op",
3420
+ "view": "turn right"
3421
+ },
3422
+ {
3423
+ "move": "no-op",
3424
+ "view": "turn right"
3425
+ },
3426
+ {
3427
+ "move": "no-op",
3428
+ "view": "turn right"
3429
+ },
3430
+ {
3431
+ "move": "no-op",
3432
+ "view": "turn right"
3433
+ },
3434
+ {
3435
+ "move": "no-op",
3436
+ "view": "turn right"
3437
+ },
3438
+ {
3439
+ "move": "no-op",
3440
+ "view": "turn right"
3441
+ },
3442
+ {
3443
+ "move": "no-op",
3444
+ "view": "turn right"
3445
+ },
3446
+ {
3447
+ "move": "no-op",
3448
+ "view": "turn right"
3449
+ },
3450
+ {
3451
+ "move": "no-op",
3452
+ "view": "turn right"
3453
+ },
3454
+ {
3455
+ "move": "no-op",
3456
+ "view": "turn right"
3457
+ },
3458
+ {
3459
+ "move": "no-op",
3460
+ "view": "turn right"
3461
+ },
3462
+ {
3463
+ "move": "no-op",
3464
+ "view": "turn right"
3465
+ },
3466
+ {
3467
+ "move": "no-op",
3468
+ "view": "turn right"
3469
+ },
3470
+ {
3471
+ "move": "no-op",
3472
+ "view": "turn right"
3473
+ },
3474
+ {
3475
+ "move": "no-op",
3476
+ "view": "turn right"
3477
+ },
3478
+ {
3479
+ "move": "no-op",
3480
+ "view": "turn right"
3481
+ },
3482
+ {
3483
+ "move": "no-op",
3484
+ "view": "turn right"
3485
+ },
3486
+ {
3487
+ "move": "no-op",
3488
+ "view": "turn right"
3489
+ },
3490
+ {
3491
+ "move": "no-op",
3492
+ "view": "turn right"
3493
+ },
3494
+ {
3495
+ "move": "no-op",
3496
+ "view": "turn right"
3497
+ },
3498
+ {
3499
+ "move": "no-op",
3500
+ "view": "turn right"
3501
+ },
3502
+ {
3503
+ "move": "no-op",
3504
+ "view": "turn right"
3505
+ },
3506
+ {
3507
+ "move": "no-op",
3508
+ "view": "turn right"
3509
+ },
3510
+ {
3511
+ "move": "no-op",
3512
+ "view": "turn right"
3513
+ },
3514
+ {
3515
+ "move": "no-op",
3516
+ "view": "turn right"
3517
+ },
3518
+ {
3519
+ "move": "no-op",
3520
+ "view": "turn right"
3521
+ },
3522
+ {
3523
+ "move": "no-op",
3524
+ "view": "turn right"
3525
+ },
3526
+ {
3527
+ "move": "no-op",
3528
+ "view": "turn right"
3529
+ },
3530
+ {
3531
+ "move": "no-op",
3532
+ "view": "turn right"
3533
+ },
3534
+ {
3535
+ "move": "no-op",
3536
+ "view": "turn right"
3537
+ },
3538
+ {
3539
+ "move": "no-op",
3540
+ "view": "turn right"
3541
+ },
3542
+ {
3543
+ "move": "no-op",
3544
+ "view": "turn right"
3545
+ },
3546
+ {
3547
+ "move": "no-op",
3548
+ "view": "turn right"
3549
+ },
3550
+ {
3551
+ "move": "no-op",
3552
+ "view": "turn right"
3553
+ },
3554
+ {
3555
+ "move": "no-op",
3556
+ "view": "turn right"
3557
+ },
3558
+ {
3559
+ "move": "no-op",
3560
+ "view": "turn right"
3561
+ },
3562
+ {
3563
+ "move": "no-op",
3564
+ "view": "turn right"
3565
+ },
3566
+ {
3567
+ "move": "go forward",
3568
+ "view": "no-op"
3569
+ },
3570
+ {
3571
+ "move": "go forward",
3572
+ "view": "no-op"
3573
+ },
3574
+ {
3575
+ "move": "go forward",
3576
+ "view": "no-op"
3577
+ },
3578
+ {
3579
+ "move": "go forward",
3580
+ "view": "no-op"
3581
+ },
3582
+ {
3583
+ "move": "go forward",
3584
+ "view": "no-op"
3585
+ },
3586
+ {
3587
+ "move": "go forward",
3588
+ "view": "no-op"
3589
+ },
3590
+ {
3591
+ "move": "go forward",
3592
+ "view": "no-op"
3593
+ },
3594
+ {
3595
+ "move": "go forward",
3596
+ "view": "no-op"
3597
+ },
3598
+ {
3599
+ "move": "go forward",
3600
+ "view": "no-op"
3601
+ },
3602
+ {
3603
+ "move": "go forward",
3604
+ "view": "no-op"
3605
+ },
3606
+ {
3607
+ "move": "go forward",
3608
+ "view": "no-op"
3609
+ },
3610
+ {
3611
+ "move": "go forward",
3612
+ "view": "no-op"
3613
+ },
3614
+ {
3615
+ "move": "go forward",
3616
+ "view": "no-op"
3617
+ },
3618
+ {
3619
+ "move": "go forward",
3620
+ "view": "no-op"
3621
+ },
3622
+ {
3623
+ "move": "go forward",
3624
+ "view": "no-op"
3625
+ },
3626
+ {
3627
+ "move": "go forward",
3628
+ "view": "no-op"
3629
+ },
3630
+ {
3631
+ "move": "go forward",
3632
+ "view": "no-op"
3633
+ },
3634
+ {
3635
+ "move": "go forward",
3636
+ "view": "no-op"
3637
+ },
3638
+ {
3639
+ "move": "go forward",
3640
+ "view": "no-op"
3641
+ },
3642
+ {
3643
+ "move": "go forward",
3644
+ "view": "no-op"
3645
+ },
3646
+ {
3647
+ "move": "go forward",
3648
+ "view": "no-op"
3649
+ },
3650
+ {
3651
+ "move": "go forward",
3652
+ "view": "no-op"
3653
+ },
3654
+ {
3655
+ "move": "go forward",
3656
+ "view": "no-op"
3657
+ },
3658
+ {
3659
+ "move": "go forward",
3660
+ "view": "no-op"
3661
+ },
3662
+ {
3663
+ "move": "go forward",
3664
+ "view": "no-op"
3665
+ },
3666
+ {
3667
+ "move": "go forward",
3668
+ "view": "no-op"
3669
+ },
3670
+ {
3671
+ "move": "go forward",
3672
+ "view": "no-op"
3673
+ },
3674
+ {
3675
+ "move": "go forward",
3676
+ "view": "no-op"
3677
+ },
3678
+ {
3679
+ "move": "go forward",
3680
+ "view": "no-op"
3681
+ },
3682
+ {
3683
+ "move": "go forward",
3684
+ "view": "no-op"
3685
+ },
3686
+ {
3687
+ "move": "go forward",
3688
+ "view": "no-op"
3689
+ },
3690
+ {
3691
+ "move": "go forward",
3692
+ "view": "no-op"
3693
+ },
3694
+ {
3695
+ "move": "go forward",
3696
+ "view": "no-op"
3697
+ },
3698
+ {
3699
+ "move": "go forward",
3700
+ "view": "no-op"
3701
+ },
3702
+ {
3703
+ "move": "go forward",
3704
+ "view": "no-op"
3705
+ },
3706
+ {
3707
+ "move": "go forward",
3708
+ "view": "no-op"
3709
+ },
3710
+ {
3711
+ "move": "go forward",
3712
+ "view": "no-op"
3713
+ },
3714
+ {
3715
+ "move": "go forward",
3716
+ "view": "no-op"
3717
+ },
3718
+ {
3719
+ "move": "go forward",
3720
+ "view": "no-op"
3721
+ },
3722
+ {
3723
+ "move": "go forward",
3724
+ "view": "no-op"
3725
+ },
3726
+ {
3727
+ "move": "go forward",
3728
+ "view": "no-op"
3729
+ },
3730
+ {
3731
+ "move": "go forward",
3732
+ "view": "no-op"
3733
+ },
3734
+ {
3735
+ "move": "go forward",
3736
+ "view": "no-op"
3737
+ },
3738
+ {
3739
+ "move": "go forward",
3740
+ "view": "no-op"
3741
+ },
3742
+ {
3743
+ "move": "go forward",
3744
+ "view": "no-op"
3745
+ },
3746
+ {
3747
+ "move": "go forward",
3748
+ "view": "no-op"
3749
+ },
3750
+ {
3751
+ "move": "go forward",
3752
+ "view": "no-op"
3753
+ },
3754
+ {
3755
+ "move": "go forward",
3756
+ "view": "no-op"
3757
+ },
3758
+ {
3759
+ "move": "go forward",
3760
+ "view": "no-op"
3761
+ },
3762
+ {
3763
+ "move": "go forward",
3764
+ "view": "no-op"
3765
+ },
3766
+ {
3767
+ "move": "go forward",
3768
+ "view": "no-op"
3769
+ },
3770
+ {
3771
+ "move": "go forward",
3772
+ "view": "no-op"
3773
+ },
3774
+ {
3775
+ "move": "go forward",
3776
+ "view": "no-op"
3777
+ },
3778
+ {
3779
+ "move": "go forward",
3780
+ "view": "no-op"
3781
+ },
3782
+ {
3783
+ "move": "go forward",
3784
+ "view": "no-op"
3785
+ },
3786
+ {
3787
+ "move": "go forward",
3788
+ "view": "no-op"
3789
+ },
3790
+ {
3791
+ "move": "go forward",
3792
+ "view": "no-op"
3793
+ },
3794
+ {
3795
+ "move": "go forward",
3796
+ "view": "no-op"
3797
+ },
3798
+ {
3799
+ "move": "go forward",
3800
+ "view": "no-op"
3801
+ },
3802
+ {
3803
+ "move": "go forward",
3804
+ "view": "no-op"
3805
+ },
3806
+ {
3807
+ "move": "go forward",
3808
+ "view": "no-op"
3809
+ },
3810
+ {
3811
+ "move": "go forward",
3812
+ "view": "no-op"
3813
+ },
3814
+ {
3815
+ "move": "go forward",
3816
+ "view": "no-op"
3817
+ },
3818
+ {
3819
+ "move": "go forward",
3820
+ "view": "no-op"
3821
+ },
3822
+ {
3823
+ "move": "go forward",
3824
+ "view": "no-op"
3825
+ },
3826
+ {
3827
+ "move": "go forward",
3828
+ "view": "no-op"
3829
+ },
3830
+ {
3831
+ "move": "go forward",
3832
+ "view": "no-op"
3833
+ },
3834
+ {
3835
+ "move": "go forward",
3836
+ "view": "no-op"
3837
+ },
3838
+ {
3839
+ "move": "go forward",
3840
+ "view": "no-op"
3841
+ },
3842
+ {
3843
+ "move": "go forward",
3844
+ "view": "no-op"
3845
+ },
3846
+ {
3847
+ "move": "go forward",
3848
+ "view": "no-op"
3849
+ },
3850
+ {
3851
+ "move": "go forward",
3852
+ "view": "no-op"
3853
+ },
3854
+ {
3855
+ "move": "go forward",
3856
+ "view": "no-op"
3857
+ },
3858
+ {
3859
+ "move": "go forward",
3860
+ "view": "no-op"
3861
+ },
3862
+ {
3863
+ "move": "go forward",
3864
+ "view": "no-op"
3865
+ },
3866
+ {
3867
+ "move": "go forward",
3868
+ "view": "no-op"
3869
+ },
3870
+ {
3871
+ "move": "go forward",
3872
+ "view": "no-op"
3873
+ },
3874
+ {
3875
+ "move": "go forward",
3876
+ "view": "no-op"
3877
+ },
3878
+ {
3879
+ "move": "go forward",
3880
+ "view": "no-op"
3881
+ },
3882
+ {
3883
+ "move": "go forward",
3884
+ "view": "no-op"
3885
+ },
3886
+ {
3887
+ "move": "go forward",
3888
+ "view": "no-op"
3889
+ },
3890
+ {
3891
+ "move": "go forward",
3892
+ "view": "no-op"
3893
+ },
3894
+ {
3895
+ "move": "go forward",
3896
+ "view": "no-op"
3897
+ },
3898
+ {
3899
+ "move": "go forward",
3900
+ "view": "no-op"
3901
+ },
3902
+ {
3903
+ "move": "go forward",
3904
+ "view": "no-op"
3905
+ },
3906
+ {
3907
+ "move": "go forward",
3908
+ "view": "no-op"
3909
+ },
3910
+ {
3911
+ "move": "go forward",
3912
+ "view": "no-op"
3913
+ },
3914
+ {
3915
+ "move": "go forward",
3916
+ "view": "no-op"
3917
+ },
3918
+ {
3919
+ "move": "go forward",
3920
+ "view": "no-op"
3921
+ },
3922
+ {
3923
+ "move": "go forward",
3924
+ "view": "no-op"
3925
+ },
3926
+ {
3927
+ "move": "go forward",
3928
+ "view": "no-op"
3929
+ },
3930
+ {
3931
+ "move": "go forward",
3932
+ "view": "no-op"
3933
+ },
3934
+ {
3935
+ "move": "go forward",
3936
+ "view": "no-op"
3937
+ },
3938
+ {
3939
+ "move": "go forward",
3940
+ "view": "no-op"
3941
+ },
3942
+ {
3943
+ "move": "go forward",
3944
+ "view": "no-op"
3945
+ },
3946
+ {
3947
+ "move": "go forward",
3948
+ "view": "no-op"
3949
+ },
3950
+ {
3951
+ "move": "go forward",
3952
+ "view": "no-op"
3953
+ },
3954
+ {
3955
+ "move": "go forward",
3956
+ "view": "no-op"
3957
+ },
3958
+ {
3959
+ "move": "go forward",
3960
+ "view": "no-op"
3961
+ },
3962
+ {
3963
+ "move": "go forward",
3964
+ "view": "no-op"
3965
+ },
3966
+ {
3967
+ "move": "go forward",
3968
+ "view": "no-op"
3969
+ },
3970
+ {
3971
+ "move": "go forward",
3972
+ "view": "no-op"
3973
+ },
3974
+ {
3975
+ "move": "go forward",
3976
+ "view": "no-op"
3977
+ },
3978
+ {
3979
+ "move": "go forward",
3980
+ "view": "no-op"
3981
+ },
3982
+ {
3983
+ "move": "go forward",
3984
+ "view": "no-op"
3985
+ },
3986
+ {
3987
+ "move": "go forward",
3988
+ "view": "no-op"
3989
+ },
3990
+ {
3991
+ "move": "go forward",
3992
+ "view": "no-op"
3993
+ },
3994
+ {
3995
+ "move": "go forward",
3996
+ "view": "no-op"
3997
+ },
3998
+ {
3999
+ "move": "go forward",
4000
+ "view": "no-op"
4001
+ },
4002
+ {
4003
+ "move": "go forward",
4004
+ "view": "no-op"
4005
+ },
4006
+ {
4007
+ "move": "go forward",
4008
+ "view": "no-op"
4009
+ },
4010
+ {
4011
+ "move": "go forward",
4012
+ "view": "no-op"
4013
+ },
4014
+ {
4015
+ "move": "go forward",
4016
+ "view": "no-op"
4017
+ },
4018
+ {
4019
+ "move": "go forward",
4020
+ "view": "no-op"
4021
+ },
4022
+ {
4023
+ "move": "go forward",
4024
+ "view": "no-op"
4025
+ },
4026
+ {
4027
+ "move": "go forward",
4028
+ "view": "no-op"
4029
+ },
4030
+ {
4031
+ "move": "go forward",
4032
+ "view": "no-op"
4033
+ },
4034
+ {
4035
+ "move": "go forward",
4036
+ "view": "no-op"
4037
+ },
4038
+ {
4039
+ "move": "go forward",
4040
+ "view": "no-op"
4041
+ },
4042
+ {
4043
+ "move": "go forward",
4044
+ "view": "no-op"
4045
+ },
4046
+ {
4047
+ "move": "go forward",
4048
+ "view": "no-op"
4049
+ },
4050
+ {
4051
+ "move": "go forward",
4052
+ "view": "no-op"
4053
+ },
4054
+ {
4055
+ "move": "go forward",
4056
+ "view": "no-op"
4057
+ },
4058
+ {
4059
+ "move": "go forward",
4060
+ "view": "no-op"
4061
+ },
4062
+ {
4063
+ "move": "go forward",
4064
+ "view": "no-op"
4065
+ },
4066
+ {
4067
+ "move": "go forward",
4068
+ "view": "no-op"
4069
+ },
4070
+ {
4071
+ "move": "go forward",
4072
+ "view": "no-op"
4073
+ },
4074
+ {
4075
+ "move": "go forward",
4076
+ "view": "no-op"
4077
+ },
4078
+ {
4079
+ "move": "go forward",
4080
+ "view": "no-op"
4081
+ },
4082
+ {
4083
+ "move": "go forward",
4084
+ "view": "no-op"
4085
+ },
4086
+ {
4087
+ "move": "go forward",
4088
+ "view": "no-op"
4089
+ },
4090
+ {
4091
+ "move": "go forward",
4092
+ "view": "no-op"
4093
+ },
4094
+ {
4095
+ "move": "go forward",
4096
+ "view": "no-op"
4097
+ },
4098
+ {
4099
+ "move": "go forward",
4100
+ "view": "no-op"
4101
+ },
4102
+ {
4103
+ "move": "go forward",
4104
+ "view": "no-op"
4105
+ },
4106
+ {
4107
+ "move": "go forward",
4108
+ "view": "no-op"
4109
+ },
4110
+ {
4111
+ "move": "go forward",
4112
+ "view": "no-op"
4113
+ },
4114
+ {
4115
+ "move": "go forward",
4116
+ "view": "no-op"
4117
+ },
4118
+ {
4119
+ "move": "go forward",
4120
+ "view": "no-op"
4121
+ },
4122
+ {
4123
+ "move": "go forward",
4124
+ "view": "no-op"
4125
+ },
4126
+ {
4127
+ "move": "go forward",
4128
+ "view": "no-op"
4129
+ },
4130
+ {
4131
+ "move": "go forward",
4132
+ "view": "no-op"
4133
+ },
4134
+ {
4135
+ "move": "go forward",
4136
+ "view": "no-op"
4137
+ },
4138
+ {
4139
+ "move": "go forward",
4140
+ "view": "no-op"
4141
+ },
4142
+ {
4143
+ "move": "go forward",
4144
+ "view": "no-op"
4145
+ },
4146
+ {
4147
+ "move": "go forward",
4148
+ "view": "no-op"
4149
+ },
4150
+ {
4151
+ "move": "go forward",
4152
+ "view": "no-op"
4153
+ },
4154
+ {
4155
+ "move": "go forward",
4156
+ "view": "no-op"
4157
+ },
4158
+ {
4159
+ "move": "go forward",
4160
+ "view": "no-op"
4161
+ },
4162
+ {
4163
+ "move": "go forward",
4164
+ "view": "no-op"
4165
+ },
4166
+ {
4167
+ "move": "go forward",
4168
+ "view": "no-op"
4169
+ },
4170
+ {
4171
+ "move": "go forward",
4172
+ "view": "no-op"
4173
+ },
4174
+ {
4175
+ "move": "go forward",
4176
+ "view": "no-op"
4177
+ },
4178
+ {
4179
+ "move": "go forward",
4180
+ "view": "no-op"
4181
+ },
4182
+ {
4183
+ "move": "go forward",
4184
+ "view": "no-op"
4185
+ },
4186
+ {
4187
+ "move": "go forward",
4188
+ "view": "no-op"
4189
+ },
4190
+ {
4191
+ "move": "go forward",
4192
+ "view": "no-op"
4193
+ },
4194
+ {
4195
+ "move": "go forward",
4196
+ "view": "no-op"
4197
+ },
4198
+ {
4199
+ "move": "go forward",
4200
+ "view": "no-op"
4201
+ },
4202
+ {
4203
+ "move": "go forward",
4204
+ "view": "no-op"
4205
+ },
4206
+ {
4207
+ "move": "go forward",
4208
+ "view": "no-op"
4209
+ },
4210
+ {
4211
+ "move": "go forward",
4212
+ "view": "no-op"
4213
+ },
4214
+ {
4215
+ "move": "no-op",
4216
+ "view": "turn right"
4217
+ },
4218
+ {
4219
+ "move": "no-op",
4220
+ "view": "turn right"
4221
+ },
4222
+ {
4223
+ "move": "no-op",
4224
+ "view": "turn right"
4225
+ },
4226
+ {
4227
+ "move": "no-op",
4228
+ "view": "turn right"
4229
+ },
4230
+ {
4231
+ "move": "no-op",
4232
+ "view": "turn right"
4233
+ },
4234
+ {
4235
+ "move": "no-op",
4236
+ "view": "turn right"
4237
+ },
4238
+ {
4239
+ "move": "no-op",
4240
+ "view": "turn right"
4241
+ },
4242
+ {
4243
+ "move": "no-op",
4244
+ "view": "turn right"
4245
+ },
4246
+ {
4247
+ "move": "no-op",
4248
+ "view": "turn right"
4249
+ },
4250
+ {
4251
+ "move": "no-op",
4252
+ "view": "turn right"
4253
+ },
4254
+ {
4255
+ "move": "no-op",
4256
+ "view": "turn right"
4257
+ },
4258
+ {
4259
+ "move": "no-op",
4260
+ "view": "turn right"
4261
+ },
4262
+ {
4263
+ "move": "no-op",
4264
+ "view": "turn right"
4265
+ },
4266
+ {
4267
+ "move": "no-op",
4268
+ "view": "turn right"
4269
+ },
4270
+ {
4271
+ "move": "no-op",
4272
+ "view": "turn right"
4273
+ },
4274
+ {
4275
+ "move": "no-op",
4276
+ "view": "turn right"
4277
+ },
4278
+ {
4279
+ "move": "no-op",
4280
+ "view": "turn right"
4281
+ },
4282
+ {
4283
+ "move": "no-op",
4284
+ "view": "turn right"
4285
+ },
4286
+ {
4287
+ "move": "no-op",
4288
+ "view": "turn right"
4289
+ },
4290
+ {
4291
+ "move": "no-op",
4292
+ "view": "turn right"
4293
+ },
4294
+ {
4295
+ "move": "no-op",
4296
+ "view": "turn right"
4297
+ },
4298
+ {
4299
+ "move": "no-op",
4300
+ "view": "turn right"
4301
+ },
4302
+ {
4303
+ "move": "no-op",
4304
+ "view": "turn right"
4305
+ },
4306
+ {
4307
+ "move": "no-op",
4308
+ "view": "turn right"
4309
+ },
4310
+ {
4311
+ "move": "no-op",
4312
+ "view": "turn right"
4313
+ },
4314
+ {
4315
+ "move": "no-op",
4316
+ "view": "turn right"
4317
+ },
4318
+ {
4319
+ "move": "no-op",
4320
+ "view": "turn right"
4321
+ },
4322
+ {
4323
+ "move": "no-op",
4324
+ "view": "turn right"
4325
+ },
4326
+ {
4327
+ "move": "no-op",
4328
+ "view": "turn right"
4329
+ },
4330
+ {
4331
+ "move": "no-op",
4332
+ "view": "turn right"
4333
+ },
4334
+ {
4335
+ "move": "no-op",
4336
+ "view": "turn right"
4337
+ },
4338
+ {
4339
+ "move": "no-op",
4340
+ "view": "turn right"
4341
+ },
4342
+ {
4343
+ "move": "no-op",
4344
+ "view": "turn right"
4345
+ },
4346
+ {
4347
+ "move": "no-op",
4348
+ "view": "turn right"
4349
+ },
4350
+ {
4351
+ "move": "no-op",
4352
+ "view": "turn right"
4353
+ },
4354
+ {
4355
+ "move": "no-op",
4356
+ "view": "turn right"
4357
+ },
4358
+ {
4359
+ "move": "no-op",
4360
+ "view": "turn right"
4361
+ },
4362
+ {
4363
+ "move": "no-op",
4364
+ "view": "turn right"
4365
+ },
4366
+ {
4367
+ "move": "no-op",
4368
+ "view": "turn right"
4369
+ },
4370
+ {
4371
+ "move": "no-op",
4372
+ "view": "turn right"
4373
+ },
4374
+ {
4375
+ "move": "no-op",
4376
+ "view": "turn right"
4377
+ },
4378
+ {
4379
+ "move": "no-op",
4380
+ "view": "turn right"
4381
+ },
4382
+ {
4383
+ "move": "no-op",
4384
+ "view": "turn right"
4385
+ },
4386
+ {
4387
+ "move": "no-op",
4388
+ "view": "turn right"
4389
+ },
4390
+ {
4391
+ "move": "no-op",
4392
+ "view": "turn right"
4393
+ },
4394
+ {
4395
+ "move": "no-op",
4396
+ "view": "turn right"
4397
+ },
4398
+ {
4399
+ "move": "no-op",
4400
+ "view": "turn right"
4401
+ },
4402
+ {
4403
+ "move": "no-op",
4404
+ "view": "turn right"
4405
+ },
4406
+ {
4407
+ "move": "no-op",
4408
+ "view": "turn right"
4409
+ },
4410
+ {
4411
+ "move": "no-op",
4412
+ "view": "turn right"
4413
+ },
4414
+ {
4415
+ "move": "no-op",
4416
+ "view": "turn right"
4417
+ },
4418
+ {
4419
+ "move": "no-op",
4420
+ "view": "turn right"
4421
+ },
4422
+ {
4423
+ "move": "no-op",
4424
+ "view": "turn right"
4425
+ },
4426
+ {
4427
+ "move": "no-op",
4428
+ "view": "turn right"
4429
+ },
4430
+ {
4431
+ "move": "no-op",
4432
+ "view": "turn right"
4433
+ },
4434
+ {
4435
+ "move": "no-op",
4436
+ "view": "turn right"
4437
+ },
4438
+ {
4439
+ "move": "no-op",
4440
+ "view": "turn right"
4441
+ },
4442
+ {
4443
+ "move": "no-op",
4444
+ "view": "turn right"
4445
+ },
4446
+ {
4447
+ "move": "no-op",
4448
+ "view": "turn right"
4449
+ },
4450
+ {
4451
+ "move": "no-op",
4452
+ "view": "turn right"
4453
+ },
4454
+ {
4455
+ "move": "no-op",
4456
+ "view": "turn right"
4457
+ },
4458
+ {
4459
+ "move": "no-op",
4460
+ "view": "turn right"
4461
+ },
4462
+ {
4463
+ "move": "no-op",
4464
+ "view": "turn right"
4465
+ },
4466
+ {
4467
+ "move": "no-op",
4468
+ "view": "turn right"
4469
+ },
4470
+ {
4471
+ "move": "no-op",
4472
+ "view": "turn right"
4473
+ },
4474
+ {
4475
+ "move": "no-op",
4476
+ "view": "turn right"
4477
+ },
4478
+ {
4479
+ "move": "no-op",
4480
+ "view": "turn right"
4481
+ },
4482
+ {
4483
+ "move": "no-op",
4484
+ "view": "turn right"
4485
+ },
4486
+ {
4487
+ "move": "no-op",
4488
+ "view": "turn right"
4489
+ },
4490
+ {
4491
+ "move": "no-op",
4492
+ "view": "turn right"
4493
+ },
4494
+ {
4495
+ "move": "no-op",
4496
+ "view": "turn right"
4497
+ },
4498
+ {
4499
+ "move": "no-op",
4500
+ "view": "turn right"
4501
+ },
4502
+ {
4503
+ "move": "no-op",
4504
+ "view": "turn right"
4505
+ },
4506
+ {
4507
+ "move": "no-op",
4508
+ "view": "turn right"
4509
+ },
4510
+ {
4511
+ "move": "no-op",
4512
+ "view": "turn right"
4513
+ },
4514
+ {
4515
+ "move": "no-op",
4516
+ "view": "turn right"
4517
+ },
4518
+ {
4519
+ "move": "no-op",
4520
+ "view": "turn right"
4521
+ },
4522
+ {
4523
+ "move": "no-op",
4524
+ "view": "turn right"
4525
+ },
4526
+ {
4527
+ "move": "no-op",
4528
+ "view": "turn right"
4529
+ },
4530
+ {
4531
+ "move": "no-op",
4532
+ "view": "turn right"
4533
+ },
4534
+ {
4535
+ "move": "no-op",
4536
+ "view": "turn right"
4537
+ },
4538
+ {
4539
+ "move": "no-op",
4540
+ "view": "turn right"
4541
+ },
4542
+ {
4543
+ "move": "no-op",
4544
+ "view": "turn right"
4545
+ },
4546
+ {
4547
+ "move": "no-op",
4548
+ "view": "turn right"
4549
+ },
4550
+ {
4551
+ "move": "no-op",
4552
+ "view": "turn right"
4553
+ },
4554
+ {
4555
+ "move": "no-op",
4556
+ "view": "turn right"
4557
+ },
4558
+ {
4559
+ "move": "no-op",
4560
+ "view": "turn right"
4561
+ },
4562
+ {
4563
+ "move": "no-op",
4564
+ "view": "turn right"
4565
+ },
4566
+ {
4567
+ "move": "no-op",
4568
+ "view": "turn right"
4569
+ },
4570
+ {
4571
+ "move": "no-op",
4572
+ "view": "turn right"
4573
+ },
4574
+ {
4575
+ "move": "no-op",
4576
+ "view": "turn right"
4577
+ },
4578
+ {
4579
+ "move": "no-op",
4580
+ "view": "turn right"
4581
+ },
4582
+ {
4583
+ "move": "no-op",
4584
+ "view": "turn right"
4585
+ },
4586
+ {
4587
+ "move": "no-op",
4588
+ "view": "turn right"
4589
+ },
4590
+ {
4591
+ "move": "no-op",
4592
+ "view": "turn right"
4593
+ },
4594
+ {
4595
+ "move": "no-op",
4596
+ "view": "turn right"
4597
+ },
4598
+ {
4599
+ "move": "no-op",
4600
+ "view": "turn right"
4601
+ },
4602
+ {
4603
+ "move": "no-op",
4604
+ "view": "turn right"
4605
+ },
4606
+ {
4607
+ "move": "no-op",
4608
+ "view": "turn right"
4609
+ },
4610
+ {
4611
+ "move": "no-op",
4612
+ "view": "turn right"
4613
+ },
4614
+ {
4615
+ "move": "no-op",
4616
+ "view": "turn right"
4617
+ },
4618
+ {
4619
+ "move": "no-op",
4620
+ "view": "turn right"
4621
+ },
4622
+ {
4623
+ "move": "no-op",
4624
+ "view": "turn right"
4625
+ },
4626
+ {
4627
+ "move": "no-op",
4628
+ "view": "turn right"
4629
+ },
4630
+ {
4631
+ "move": "no-op",
4632
+ "view": "turn right"
4633
+ },
4634
+ {
4635
+ "move": "no-op",
4636
+ "view": "turn right"
4637
+ },
4638
+ {
4639
+ "move": "no-op",
4640
+ "view": "turn right"
4641
+ },
4642
+ {
4643
+ "move": "no-op",
4644
+ "view": "turn right"
4645
+ },
4646
+ {
4647
+ "move": "no-op",
4648
+ "view": "turn right"
4649
+ },
4650
+ {
4651
+ "move": "no-op",
4652
+ "view": "turn right"
4653
+ },
4654
+ {
4655
+ "move": "no-op",
4656
+ "view": "turn right"
4657
+ },
4658
+ {
4659
+ "move": "no-op",
4660
+ "view": "turn right"
4661
+ },
4662
+ {
4663
+ "move": "no-op",
4664
+ "view": "turn right"
4665
+ },
4666
+ {
4667
+ "move": "no-op",
4668
+ "view": "turn right"
4669
+ },
4670
+ {
4671
+ "move": "no-op",
4672
+ "view": "turn right"
4673
+ },
4674
+ {
4675
+ "move": "no-op",
4676
+ "view": "turn right"
4677
+ },
4678
+ {
4679
+ "move": "no-op",
4680
+ "view": "turn right"
4681
+ },
4682
+ {
4683
+ "move": "no-op",
4684
+ "view": "turn right"
4685
+ },
4686
+ {
4687
+ "move": "no-op",
4688
+ "view": "turn right"
4689
+ },
4690
+ {
4691
+ "move": "no-op",
4692
+ "view": "turn right"
4693
+ },
4694
+ {
4695
+ "move": "no-op",
4696
+ "view": "turn right"
4697
+ },
4698
+ {
4699
+ "move": "no-op",
4700
+ "view": "turn right"
4701
+ },
4702
+ {
4703
+ "move": "no-op",
4704
+ "view": "turn right"
4705
+ },
4706
+ {
4707
+ "move": "no-op",
4708
+ "view": "turn right"
4709
+ },
4710
+ {
4711
+ "move": "no-op",
4712
+ "view": "turn right"
4713
+ },
4714
+ {
4715
+ "move": "no-op",
4716
+ "view": "turn right"
4717
+ },
4718
+ {
4719
+ "move": "no-op",
4720
+ "view": "turn right"
4721
+ },
4722
+ {
4723
+ "move": "no-op",
4724
+ "view": "turn right"
4725
+ },
4726
+ {
4727
+ "move": "no-op",
4728
+ "view": "turn right"
4729
+ },
4730
+ {
4731
+ "move": "no-op",
4732
+ "view": "turn right"
4733
+ },
4734
+ {
4735
+ "move": "no-op",
4736
+ "view": "turn right"
4737
+ },
4738
+ {
4739
+ "move": "no-op",
4740
+ "view": "turn right"
4741
+ },
4742
+ {
4743
+ "move": "no-op",
4744
+ "view": "turn right"
4745
+ },
4746
+ {
4747
+ "move": "no-op",
4748
+ "view": "turn right"
4749
+ },
4750
+ {
4751
+ "move": "no-op",
4752
+ "view": "turn right"
4753
+ },
4754
+ {
4755
+ "move": "no-op",
4756
+ "view": "turn right"
4757
+ },
4758
+ {
4759
+ "move": "no-op",
4760
+ "view": "turn right"
4761
+ },
4762
+ {
4763
+ "move": "no-op",
4764
+ "view": "turn right"
4765
+ },
4766
+ {
4767
+ "move": "no-op",
4768
+ "view": "turn right"
4769
+ },
4770
+ {
4771
+ "move": "no-op",
4772
+ "view": "turn right"
4773
+ },
4774
+ {
4775
+ "move": "no-op",
4776
+ "view": "turn right"
4777
+ },
4778
+ {
4779
+ "move": "no-op",
4780
+ "view": "turn right"
4781
+ },
4782
+ {
4783
+ "move": "no-op",
4784
+ "view": "turn right"
4785
+ },
4786
+ {
4787
+ "move": "no-op",
4788
+ "view": "turn right"
4789
+ },
4790
+ {
4791
+ "move": "no-op",
4792
+ "view": "turn right"
4793
+ },
4794
+ {
4795
+ "move": "no-op",
4796
+ "view": "turn right"
4797
+ },
4798
+ {
4799
+ "move": "no-op",
4800
+ "view": "turn right"
4801
+ },
4802
+ {
4803
+ "move": "no-op",
4804
+ "view": "turn right"
4805
+ },
4806
+ {
4807
+ "move": "no-op",
4808
+ "view": "turn right"
4809
+ },
4810
+ {
4811
+ "move": "no-op",
4812
+ "view": "turn right"
4813
+ },
4814
+ {
4815
+ "move": "no-op",
4816
+ "view": "turn right"
4817
+ },
4818
+ {
4819
+ "move": "no-op",
4820
+ "view": "turn right"
4821
+ },
4822
+ {
4823
+ "move": "no-op",
4824
+ "view": "turn right"
4825
+ },
4826
+ {
4827
+ "move": "no-op",
4828
+ "view": "turn right"
4829
+ },
4830
+ {
4831
+ "move": "no-op",
4832
+ "view": "turn right"
4833
+ },
4834
+ {
4835
+ "move": "no-op",
4836
+ "view": "turn right"
4837
+ },
4838
+ {
4839
+ "move": "no-op",
4840
+ "view": "turn right"
4841
+ },
4842
+ {
4843
+ "move": "no-op",
4844
+ "view": "turn right"
4845
+ },
4846
+ {
4847
+ "move": "no-op",
4848
+ "view": "turn right"
4849
+ },
4850
+ {
4851
+ "move": "no-op",
4852
+ "view": "turn right"
4853
+ },
4854
+ {
4855
+ "move": "no-op",
4856
+ "view": "turn right"
4857
+ },
4858
+ {
4859
+ "move": "no-op",
4860
+ "view": "turn right"
4861
+ },
4862
+ {
4863
+ "move": "no-op",
4864
+ "view": "turn right"
4865
+ },
4866
+ {
4867
+ "move": "no-op",
4868
+ "view": "turn right"
4869
+ },
4870
+ {
4871
+ "move": "no-op",
4872
+ "view": "turn right"
4873
+ },
4874
+ {
4875
+ "move": "no-op",
4876
+ "view": "turn right"
4877
+ },
4878
+ {
4879
+ "move": "no-op",
4880
+ "view": "turn right"
4881
+ },
4882
+ {
4883
+ "move": "no-op",
4884
+ "view": "turn right"
4885
+ },
4886
+ {
4887
+ "move": "no-op",
4888
+ "view": "turn right"
4889
+ },
4890
+ {
4891
+ "move": "no-op",
4892
+ "view": "turn right"
4893
+ },
4894
+ {
4895
+ "move": "no-op",
4896
+ "view": "turn right"
4897
+ },
4898
+ {
4899
+ "move": "no-op",
4900
+ "view": "turn right"
4901
+ },
4902
+ {
4903
+ "move": "no-op",
4904
+ "view": "turn right"
4905
+ },
4906
+ {
4907
+ "move": "no-op",
4908
+ "view": "turn right"
4909
+ },
4910
+ {
4911
+ "move": "no-op",
4912
+ "view": "turn right"
4913
+ },
4914
+ {
4915
+ "move": "no-op",
4916
+ "view": "turn right"
4917
+ },
4918
+ {
4919
+ "move": "no-op",
4920
+ "view": "turn right"
4921
+ },
4922
+ {
4923
+ "move": "no-op",
4924
+ "view": "turn right"
4925
+ },
4926
+ {
4927
+ "move": "no-op",
4928
+ "view": "turn right"
4929
+ },
4930
+ {
4931
+ "move": "no-op",
4932
+ "view": "turn right"
4933
+ },
4934
+ {
4935
+ "move": "no-op",
4936
+ "view": "turn right"
4937
+ },
4938
+ {
4939
+ "move": "no-op",
4940
+ "view": "turn right"
4941
+ },
4942
+ {
4943
+ "move": "no-op",
4944
+ "view": "turn right"
4945
+ },
4946
+ {
4947
+ "move": "no-op",
4948
+ "view": "turn right"
4949
+ },
4950
+ {
4951
+ "move": "no-op",
4952
+ "view": "turn right"
4953
+ },
4954
+ {
4955
+ "move": "no-op",
4956
+ "view": "turn right"
4957
+ },
4958
+ {
4959
+ "move": "no-op",
4960
+ "view": "turn right"
4961
+ },
4962
+ {
4963
+ "move": "no-op",
4964
+ "view": "turn right"
4965
+ },
4966
+ {
4967
+ "move": "no-op",
4968
+ "view": "turn right"
4969
+ },
4970
+ {
4971
+ "move": "no-op",
4972
+ "view": "turn right"
4973
+ },
4974
+ {
4975
+ "move": "no-op",
4976
+ "view": "turn right"
4977
+ },
4978
+ {
4979
+ "move": "no-op",
4980
+ "view": "turn right"
4981
+ },
4982
+ {
4983
+ "move": "no-op",
4984
+ "view": "turn right"
4985
+ },
4986
+ {
4987
+ "move": "no-op",
4988
+ "view": "turn right"
4989
+ },
4990
+ {
4991
+ "move": "no-op",
4992
+ "view": "turn right"
4993
+ },
4994
+ {
4995
+ "move": "no-op",
4996
+ "view": "turn right"
4997
+ },
4998
+ {
4999
+ "move": "no-op",
5000
+ "view": "turn right"
5001
+ },
5002
+ {
5003
+ "move": "no-op",
5004
+ "view": "turn right"
5005
+ },
5006
+ {
5007
+ "move": "no-op",
5008
+ "view": "turn right"
5009
+ },
5010
+ {
5011
+ "move": "no-op",
5012
+ "view": "turn right"
5013
+ },
5014
+ {
5015
+ "move": "no-op",
5016
+ "view": "turn right"
5017
+ },
5018
+ {
5019
+ "move": "no-op",
5020
+ "view": "turn right"
5021
+ },
5022
+ {
5023
+ "move": "no-op",
5024
+ "view": "turn right"
5025
+ },
5026
+ {
5027
+ "move": "no-op",
5028
+ "view": "turn right"
5029
+ },
5030
+ {
5031
+ "move": "no-op",
5032
+ "view": "turn right"
5033
+ },
5034
+ {
5035
+ "move": "no-op",
5036
+ "view": "turn right"
5037
+ },
5038
+ {
5039
+ "move": "no-op",
5040
+ "view": "turn right"
5041
+ },
5042
+ {
5043
+ "move": "no-op",
5044
+ "view": "turn right"
5045
+ },
5046
+ {
5047
+ "move": "no-op",
5048
+ "view": "turn right"
5049
+ },
5050
+ {
5051
+ "move": "no-op",
5052
+ "view": "turn right"
5053
+ },
5054
+ {
5055
+ "move": "no-op",
5056
+ "view": "turn right"
5057
+ },
5058
+ {
5059
+ "move": "no-op",
5060
+ "view": "turn right"
5061
+ },
5062
+ {
5063
+ "move": "no-op",
5064
+ "view": "turn right"
5065
+ },
5066
+ {
5067
+ "move": "no-op",
5068
+ "view": "turn right"
5069
+ },
5070
+ {
5071
+ "move": "no-op",
5072
+ "view": "turn right"
5073
+ },
5074
+ {
5075
+ "move": "no-op",
5076
+ "view": "turn right"
5077
+ },
5078
+ {
5079
+ "move": "no-op",
5080
+ "view": "turn right"
5081
+ },
5082
+ {
5083
+ "move": "no-op",
5084
+ "view": "turn right"
5085
+ },
5086
+ {
5087
+ "move": "no-op",
5088
+ "view": "turn right"
5089
+ },
5090
+ {
5091
+ "move": "no-op",
5092
+ "view": "turn right"
5093
+ },
5094
+ {
5095
+ "move": "no-op",
5096
+ "view": "turn right"
5097
+ },
5098
+ {
5099
+ "move": "no-op",
5100
+ "view": "turn right"
5101
+ },
5102
+ {
5103
+ "move": "no-op",
5104
+ "view": "turn right"
5105
+ },
5106
+ {
5107
+ "move": "no-op",
5108
+ "view": "turn right"
5109
+ },
5110
+ {
5111
+ "move": "no-op",
5112
+ "view": "turn right"
5113
+ },
5114
+ {
5115
+ "move": "no-op",
5116
+ "view": "turn right"
5117
+ },
5118
+ {
5119
+ "move": "no-op",
5120
+ "view": "turn right"
5121
+ },
5122
+ {
5123
+ "move": "no-op",
5124
+ "view": "turn right"
5125
+ },
5126
+ {
5127
+ "move": "no-op",
5128
+ "view": "turn right"
5129
+ },
5130
+ {
5131
+ "move": "no-op",
5132
+ "view": "turn right"
5133
+ },
5134
+ {
5135
+ "move": "no-op",
5136
+ "view": "turn right"
5137
+ },
5138
+ {
5139
+ "move": "no-op",
5140
+ "view": "turn right"
5141
+ },
5142
+ {
5143
+ "move": "no-op",
5144
+ "view": "turn right"
5145
+ },
5146
+ {
5147
+ "move": "no-op",
5148
+ "view": "turn right"
5149
+ },
5150
+ {
5151
+ "move": "no-op",
5152
+ "view": "turn right"
5153
+ },
5154
+ {
5155
+ "move": "no-op",
5156
+ "view": "turn right"
5157
+ },
5158
+ {
5159
+ "move": "no-op",
5160
+ "view": "turn right"
5161
+ },
5162
+ {
5163
+ "move": "no-op",
5164
+ "view": "turn right"
5165
+ },
5166
+ {
5167
+ "move": "no-op",
5168
+ "view": "turn right"
5169
+ },
5170
+ {
5171
+ "move": "no-op",
5172
+ "view": "turn right"
5173
+ },
5174
+ {
5175
+ "move": "no-op",
5176
+ "view": "turn right"
5177
+ },
5178
+ {
5179
+ "move": "no-op",
5180
+ "view": "turn right"
5181
+ },
5182
+ {
5183
+ "move": "no-op",
5184
+ "view": "turn right"
5185
+ },
5186
+ {
5187
+ "move": "no-op",
5188
+ "view": "turn right"
5189
+ },
5190
+ {
5191
+ "move": "no-op",
5192
+ "view": "turn right"
5193
+ },
5194
+ {
5195
+ "move": "no-op",
5196
+ "view": "turn right"
5197
+ },
5198
+ {
5199
+ "move": "no-op",
5200
+ "view": "turn right"
5201
+ },
5202
+ {
5203
+ "move": "no-op",
5204
+ "view": "turn right"
5205
+ },
5206
+ {
5207
+ "move": "no-op",
5208
+ "view": "turn right"
5209
+ },
5210
+ {
5211
+ "move": "no-op",
5212
+ "view": "turn right"
5213
+ },
5214
+ {
5215
+ "move": "no-op",
5216
+ "view": "turn right"
5217
+ },
5218
+ {
5219
+ "move": "no-op",
5220
+ "view": "turn right"
5221
+ },
5222
+ {
5223
+ "move": "no-op",
5224
+ "view": "turn right"
5225
+ },
5226
+ {
5227
+ "move": "no-op",
5228
+ "view": "turn right"
5229
+ },
5230
+ {
5231
+ "move": "no-op",
5232
+ "view": "turn right"
5233
+ },
5234
+ {
5235
+ "move": "no-op",
5236
+ "view": "turn right"
5237
+ },
5238
+ {
5239
+ "move": "no-op",
5240
+ "view": "turn right"
5241
+ },
5242
+ {
5243
+ "move": "no-op",
5244
+ "view": "turn right"
5245
+ },
5246
+ {
5247
+ "move": "no-op",
5248
+ "view": "turn right"
5249
+ },
5250
+ {
5251
+ "move": "no-op",
5252
+ "view": "turn right"
5253
+ },
5254
+ {
5255
+ "move": "no-op",
5256
+ "view": "turn right"
5257
+ },
5258
+ {
5259
+ "move": "no-op",
5260
+ "view": "turn right"
5261
+ },
5262
+ {
5263
+ "move": "no-op",
5264
+ "view": "turn right"
5265
+ },
5266
+ {
5267
+ "move": "no-op",
5268
+ "view": "turn right"
5269
+ },
5270
+ {
5271
+ "move": "no-op",
5272
+ "view": "turn right"
5273
+ },
5274
+ {
5275
+ "move": "no-op",
5276
+ "view": "turn right"
5277
+ },
5278
+ {
5279
+ "move": "no-op",
5280
+ "view": "turn right"
5281
+ },
5282
+ {
5283
+ "move": "no-op",
5284
+ "view": "turn right"
5285
+ },
5286
+ {
5287
+ "move": "no-op",
5288
+ "view": "turn right"
5289
+ },
5290
+ {
5291
+ "move": "no-op",
5292
+ "view": "turn right"
5293
+ },
5294
+ {
5295
+ "move": "no-op",
5296
+ "view": "turn right"
5297
+ },
5298
+ {
5299
+ "move": "no-op",
5300
+ "view": "turn right"
5301
+ },
5302
+ {
5303
+ "move": "no-op",
5304
+ "view": "turn right"
5305
+ },
5306
+ {
5307
+ "move": "no-op",
5308
+ "view": "turn right"
5309
+ },
5310
+ {
5311
+ "move": "no-op",
5312
+ "view": "turn right"
5313
+ },
5314
+ {
5315
+ "move": "no-op",
5316
+ "view": "turn right"
5317
+ },
5318
+ {
5319
+ "move": "no-op",
5320
+ "view": "turn right"
5321
+ },
5322
+ {
5323
+ "move": "no-op",
5324
+ "view": "turn right"
5325
+ },
5326
+ {
5327
+ "move": "no-op",
5328
+ "view": "turn right"
5329
+ },
5330
+ {
5331
+ "move": "no-op",
5332
+ "view": "turn right"
5333
+ },
5334
+ {
5335
+ "move": "no-op",
5336
+ "view": "turn right"
5337
+ },
5338
+ {
5339
+ "move": "no-op",
5340
+ "view": "turn right"
5341
+ },
5342
+ {
5343
+ "move": "no-op",
5344
+ "view": "turn right"
5345
+ },
5346
+ {
5347
+ "move": "no-op",
5348
+ "view": "turn right"
5349
+ },
5350
+ {
5351
+ "move": "no-op",
5352
+ "view": "turn right"
5353
+ },
5354
+ {
5355
+ "move": "no-op",
5356
+ "view": "turn right"
5357
+ },
5358
+ {
5359
+ "move": "no-op",
5360
+ "view": "turn right"
5361
+ },
5362
+ {
5363
+ "move": "no-op",
5364
+ "view": "turn right"
5365
+ },
5366
+ {
5367
+ "move": "no-op",
5368
+ "view": "turn right"
5369
+ },
5370
+ {
5371
+ "move": "no-op",
5372
+ "view": "turn right"
5373
+ },
5374
+ {
5375
+ "move": "no-op",
5376
+ "view": "turn right"
5377
+ },
5378
+ {
5379
+ "move": "no-op",
5380
+ "view": "turn right"
5381
+ },
5382
+ {
5383
+ "move": "no-op",
5384
+ "view": "turn right"
5385
+ },
5386
+ {
5387
+ "move": "no-op",
5388
+ "view": "turn right"
5389
+ },
5390
+ {
5391
+ "move": "no-op",
5392
+ "view": "turn right"
5393
+ },
5394
+ {
5395
+ "move": "no-op",
5396
+ "view": "turn right"
5397
+ },
5398
+ {
5399
+ "move": "no-op",
5400
+ "view": "turn right"
5401
+ },
5402
+ {
5403
+ "move": "no-op",
5404
+ "view": "turn right"
5405
+ },
5406
+ {
5407
+ "move": "no-op",
5408
+ "view": "turn right"
5409
+ },
5410
+ {
5411
+ "move": "no-op",
5412
+ "view": "turn right"
5413
+ },
5414
+ {
5415
+ "move": "no-op",
5416
+ "view": "turn right"
5417
+ },
5418
+ {
5419
+ "move": "no-op",
5420
+ "view": "turn right"
5421
+ },
5422
+ {
5423
+ "move": "no-op",
5424
+ "view": "turn right"
5425
+ },
5426
+ {
5427
+ "move": "no-op",
5428
+ "view": "turn right"
5429
+ },
5430
+ {
5431
+ "move": "no-op",
5432
+ "view": "turn right"
5433
+ },
5434
+ {
5435
+ "move": "no-op",
5436
+ "view": "turn right"
5437
+ },
5438
+ {
5439
+ "move": "no-op",
5440
+ "view": "turn right"
5441
+ },
5442
+ {
5443
+ "move": "no-op",
5444
+ "view": "turn right"
5445
+ },
5446
+ {
5447
+ "move": "no-op",
5448
+ "view": "turn right"
5449
+ },
5450
+ {
5451
+ "move": "no-op",
5452
+ "view": "turn right"
5453
+ },
5454
+ {
5455
+ "move": "no-op",
5456
+ "view": "turn right"
5457
+ },
5458
+ {
5459
+ "move": "no-op",
5460
+ "view": "turn right"
5461
+ },
5462
+ {
5463
+ "move": "no-op",
5464
+ "view": "turn right"
5465
+ },
5466
+ {
5467
+ "move": "no-op",
5468
+ "view": "turn right"
5469
+ },
5470
+ {
5471
+ "move": "no-op",
5472
+ "view": "turn right"
5473
+ },
5474
+ {
5475
+ "move": "no-op",
5476
+ "view": "turn right"
5477
+ },
5478
+ {
5479
+ "move": "no-op",
5480
+ "view": "turn right"
5481
+ },
5482
+ {
5483
+ "move": "no-op",
5484
+ "view": "turn right"
5485
+ },
5486
+ {
5487
+ "move": "no-op",
5488
+ "view": "turn right"
5489
+ },
5490
+ {
5491
+ "move": "no-op",
5492
+ "view": "turn right"
5493
+ },
5494
+ {
5495
+ "move": "no-op",
5496
+ "view": "turn right"
5497
+ },
5498
+ {
5499
+ "move": "no-op",
5500
+ "view": "turn right"
5501
+ },
5502
+ {
5503
+ "move": "no-op",
5504
+ "view": "turn right"
5505
+ },
5506
+ {
5507
+ "move": "no-op",
5508
+ "view": "turn right"
5509
+ },
5510
+ {
5511
+ "move": "no-op",
5512
+ "view": "turn right"
5513
+ },
5514
+ {
5515
+ "move": "no-op",
5516
+ "view": "turn right"
5517
+ },
5518
+ {
5519
+ "move": "no-op",
5520
+ "view": "turn right"
5521
+ },
5522
+ {
5523
+ "move": "no-op",
5524
+ "view": "turn right"
5525
+ },
5526
+ {
5527
+ "move": "no-op",
5528
+ "view": "turn right"
5529
+ },
5530
+ {
5531
+ "move": "no-op",
5532
+ "view": "turn right"
5533
+ },
5534
+ {
5535
+ "move": "no-op",
5536
+ "view": "turn right"
5537
+ },
5538
+ {
5539
+ "move": "no-op",
5540
+ "view": "turn right"
5541
+ },
5542
+ {
5543
+ "move": "no-op",
5544
+ "view": "turn right"
5545
+ },
5546
+ {
5547
+ "move": "no-op",
5548
+ "view": "turn right"
5549
+ },
5550
+ {
5551
+ "move": "no-op",
5552
+ "view": "turn right"
5553
+ },
5554
+ {
5555
+ "move": "no-op",
5556
+ "view": "turn right"
5557
+ },
5558
+ {
5559
+ "move": "no-op",
5560
+ "view": "turn right"
5561
+ },
5562
+ {
5563
+ "move": "no-op",
5564
+ "view": "turn right"
5565
+ },
5566
+ {
5567
+ "move": "no-op",
5568
+ "view": "turn right"
5569
+ },
5570
+ {
5571
+ "move": "no-op",
5572
+ "view": "turn right"
5573
+ },
5574
+ {
5575
+ "move": "no-op",
5576
+ "view": "turn right"
5577
+ },
5578
+ {
5579
+ "move": "no-op",
5580
+ "view": "turn right"
5581
+ },
5582
+ {
5583
+ "move": "no-op",
5584
+ "view": "turn right"
5585
+ }
5586
+ ]
assets/example_case/0002.jpg ADDED

Git LFS Details

  • SHA256: 52b3e8048f19afa36a2103b2799d5753676ea65169a40a85abdbbd737af24b34
  • Pointer size: 132 Bytes
  • Size of remote file: 2.75 MB
assets/example_case/0002.json ADDED
@@ -0,0 +1,6234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "move": "no-op",
4
+ "view": "turn right"
5
+ },
6
+ {
7
+ "move": "no-op",
8
+ "view": "turn right"
9
+ },
10
+ {
11
+ "move": "no-op",
12
+ "view": "turn right"
13
+ },
14
+ {
15
+ "move": "no-op",
16
+ "view": "turn right"
17
+ },
18
+ {
19
+ "move": "no-op",
20
+ "view": "turn right"
21
+ },
22
+ {
23
+ "move": "no-op",
24
+ "view": "turn right"
25
+ },
26
+ {
27
+ "move": "no-op",
28
+ "view": "turn right"
29
+ },
30
+ {
31
+ "move": "no-op",
32
+ "view": "turn right"
33
+ },
34
+ {
35
+ "move": "no-op",
36
+ "view": "turn right"
37
+ },
38
+ {
39
+ "move": "no-op",
40
+ "view": "turn right"
41
+ },
42
+ {
43
+ "move": "no-op",
44
+ "view": "turn right"
45
+ },
46
+ {
47
+ "move": "no-op",
48
+ "view": "turn right"
49
+ },
50
+ {
51
+ "move": "no-op",
52
+ "view": "turn right"
53
+ },
54
+ {
55
+ "move": "no-op",
56
+ "view": "turn right"
57
+ },
58
+ {
59
+ "move": "no-op",
60
+ "view": "turn right"
61
+ },
62
+ {
63
+ "move": "no-op",
64
+ "view": "turn right"
65
+ },
66
+ {
67
+ "move": "no-op",
68
+ "view": "turn right"
69
+ },
70
+ {
71
+ "move": "no-op",
72
+ "view": "turn right"
73
+ },
74
+ {
75
+ "move": "no-op",
76
+ "view": "turn right"
77
+ },
78
+ {
79
+ "move": "no-op",
80
+ "view": "turn right"
81
+ },
82
+ {
83
+ "move": "no-op",
84
+ "view": "turn right"
85
+ },
86
+ {
87
+ "move": "no-op",
88
+ "view": "turn right"
89
+ },
90
+ {
91
+ "move": "no-op",
92
+ "view": "turn right"
93
+ },
94
+ {
95
+ "move": "no-op",
96
+ "view": "turn right"
97
+ },
98
+ {
99
+ "move": "no-op",
100
+ "view": "turn right"
101
+ },
102
+ {
103
+ "move": "no-op",
104
+ "view": "turn right"
105
+ },
106
+ {
107
+ "move": "no-op",
108
+ "view": "turn right"
109
+ },
110
+ {
111
+ "move": "no-op",
112
+ "view": "turn right"
113
+ },
114
+ {
115
+ "move": "no-op",
116
+ "view": "turn right"
117
+ },
118
+ {
119
+ "move": "no-op",
120
+ "view": "turn right"
121
+ },
122
+ {
123
+ "move": "no-op",
124
+ "view": "turn right"
125
+ },
126
+ {
127
+ "move": "no-op",
128
+ "view": "turn right"
129
+ },
130
+ {
131
+ "move": "no-op",
132
+ "view": "turn right"
133
+ },
134
+ {
135
+ "move": "no-op",
136
+ "view": "turn right"
137
+ },
138
+ {
139
+ "move": "no-op",
140
+ "view": "turn right"
141
+ },
142
+ {
143
+ "move": "no-op",
144
+ "view": "turn right"
145
+ },
146
+ {
147
+ "move": "no-op",
148
+ "view": "turn right"
149
+ },
150
+ {
151
+ "move": "no-op",
152
+ "view": "turn right"
153
+ },
154
+ {
155
+ "move": "no-op",
156
+ "view": "turn right"
157
+ },
158
+ {
159
+ "move": "no-op",
160
+ "view": "turn right"
161
+ },
162
+ {
163
+ "move": "no-op",
164
+ "view": "turn right"
165
+ },
166
+ {
167
+ "move": "no-op",
168
+ "view": "turn right"
169
+ },
170
+ {
171
+ "move": "no-op",
172
+ "view": "turn right"
173
+ },
174
+ {
175
+ "move": "no-op",
176
+ "view": "turn right"
177
+ },
178
+ {
179
+ "move": "no-op",
180
+ "view": "turn right"
181
+ },
182
+ {
183
+ "move": "no-op",
184
+ "view": "turn right"
185
+ },
186
+ {
187
+ "move": "no-op",
188
+ "view": "turn right"
189
+ },
190
+ {
191
+ "move": "no-op",
192
+ "view": "turn right"
193
+ },
194
+ {
195
+ "move": "no-op",
196
+ "view": "turn right"
197
+ },
198
+ {
199
+ "move": "no-op",
200
+ "view": "turn right"
201
+ },
202
+ {
203
+ "move": "no-op",
204
+ "view": "turn right"
205
+ },
206
+ {
207
+ "move": "no-op",
208
+ "view": "turn right"
209
+ },
210
+ {
211
+ "move": "no-op",
212
+ "view": "turn right"
213
+ },
214
+ {
215
+ "move": "no-op",
216
+ "view": "turn right"
217
+ },
218
+ {
219
+ "move": "no-op",
220
+ "view": "turn right"
221
+ },
222
+ {
223
+ "move": "no-op",
224
+ "view": "turn right"
225
+ },
226
+ {
227
+ "move": "no-op",
228
+ "view": "turn right"
229
+ },
230
+ {
231
+ "move": "no-op",
232
+ "view": "turn right"
233
+ },
234
+ {
235
+ "move": "no-op",
236
+ "view": "turn right"
237
+ },
238
+ {
239
+ "move": "no-op",
240
+ "view": "turn right"
241
+ },
242
+ {
243
+ "move": "no-op",
244
+ "view": "turn right"
245
+ },
246
+ {
247
+ "move": "no-op",
248
+ "view": "turn right"
249
+ },
250
+ {
251
+ "move": "no-op",
252
+ "view": "turn right"
253
+ },
254
+ {
255
+ "move": "no-op",
256
+ "view": "turn right"
257
+ },
258
+ {
259
+ "move": "no-op",
260
+ "view": "turn right"
261
+ },
262
+ {
263
+ "move": "no-op",
264
+ "view": "turn right"
265
+ },
266
+ {
267
+ "move": "no-op",
268
+ "view": "turn right"
269
+ },
270
+ {
271
+ "move": "no-op",
272
+ "view": "turn right"
273
+ },
274
+ {
275
+ "move": "no-op",
276
+ "view": "turn right"
277
+ },
278
+ {
279
+ "move": "no-op",
280
+ "view": "turn right"
281
+ },
282
+ {
283
+ "move": "no-op",
284
+ "view": "turn right"
285
+ },
286
+ {
287
+ "move": "no-op",
288
+ "view": "turn right"
289
+ },
290
+ {
291
+ "move": "no-op",
292
+ "view": "turn right"
293
+ },
294
+ {
295
+ "move": "no-op",
296
+ "view": "turn right"
297
+ },
298
+ {
299
+ "move": "no-op",
300
+ "view": "turn right"
301
+ },
302
+ {
303
+ "move": "no-op",
304
+ "view": "turn right"
305
+ },
306
+ {
307
+ "move": "no-op",
308
+ "view": "turn right"
309
+ },
310
+ {
311
+ "move": "no-op",
312
+ "view": "turn right"
313
+ },
314
+ {
315
+ "move": "no-op",
316
+ "view": "turn right"
317
+ },
318
+ {
319
+ "move": "no-op",
320
+ "view": "turn right"
321
+ },
322
+ {
323
+ "move": "no-op",
324
+ "view": "turn right"
325
+ },
326
+ {
327
+ "move": "go forward",
328
+ "view": "no-op"
329
+ },
330
+ {
331
+ "move": "go forward",
332
+ "view": "no-op"
333
+ },
334
+ {
335
+ "move": "go forward",
336
+ "view": "no-op"
337
+ },
338
+ {
339
+ "move": "go forward",
340
+ "view": "no-op"
341
+ },
342
+ {
343
+ "move": "go forward",
344
+ "view": "no-op"
345
+ },
346
+ {
347
+ "move": "go forward",
348
+ "view": "no-op"
349
+ },
350
+ {
351
+ "move": "go forward",
352
+ "view": "no-op"
353
+ },
354
+ {
355
+ "move": "go forward",
356
+ "view": "no-op"
357
+ },
358
+ {
359
+ "move": "go forward",
360
+ "view": "no-op"
361
+ },
362
+ {
363
+ "move": "go forward",
364
+ "view": "no-op"
365
+ },
366
+ {
367
+ "move": "go forward",
368
+ "view": "no-op"
369
+ },
370
+ {
371
+ "move": "go forward",
372
+ "view": "no-op"
373
+ },
374
+ {
375
+ "move": "go forward",
376
+ "view": "no-op"
377
+ },
378
+ {
379
+ "move": "go forward",
380
+ "view": "no-op"
381
+ },
382
+ {
383
+ "move": "go forward",
384
+ "view": "no-op"
385
+ },
386
+ {
387
+ "move": "go forward",
388
+ "view": "no-op"
389
+ },
390
+ {
391
+ "move": "go forward",
392
+ "view": "no-op"
393
+ },
394
+ {
395
+ "move": "go forward",
396
+ "view": "no-op"
397
+ },
398
+ {
399
+ "move": "go forward",
400
+ "view": "no-op"
401
+ },
402
+ {
403
+ "move": "go forward",
404
+ "view": "no-op"
405
+ },
406
+ {
407
+ "move": "go forward",
408
+ "view": "no-op"
409
+ },
410
+ {
411
+ "move": "go forward",
412
+ "view": "no-op"
413
+ },
414
+ {
415
+ "move": "go forward",
416
+ "view": "no-op"
417
+ },
418
+ {
419
+ "move": "go forward",
420
+ "view": "no-op"
421
+ },
422
+ {
423
+ "move": "go forward",
424
+ "view": "no-op"
425
+ },
426
+ {
427
+ "move": "go forward",
428
+ "view": "no-op"
429
+ },
430
+ {
431
+ "move": "go forward",
432
+ "view": "no-op"
433
+ },
434
+ {
435
+ "move": "go forward",
436
+ "view": "no-op"
437
+ },
438
+ {
439
+ "move": "go forward",
440
+ "view": "no-op"
441
+ },
442
+ {
443
+ "move": "go forward",
444
+ "view": "no-op"
445
+ },
446
+ {
447
+ "move": "go forward",
448
+ "view": "no-op"
449
+ },
450
+ {
451
+ "move": "go forward",
452
+ "view": "no-op"
453
+ },
454
+ {
455
+ "move": "go forward",
456
+ "view": "no-op"
457
+ },
458
+ {
459
+ "move": "go forward",
460
+ "view": "no-op"
461
+ },
462
+ {
463
+ "move": "go forward",
464
+ "view": "no-op"
465
+ },
466
+ {
467
+ "move": "go forward",
468
+ "view": "no-op"
469
+ },
470
+ {
471
+ "move": "go forward",
472
+ "view": "no-op"
473
+ },
474
+ {
475
+ "move": "go forward",
476
+ "view": "no-op"
477
+ },
478
+ {
479
+ "move": "go forward",
480
+ "view": "no-op"
481
+ },
482
+ {
483
+ "move": "go forward",
484
+ "view": "no-op"
485
+ },
486
+ {
487
+ "move": "go forward",
488
+ "view": "no-op"
489
+ },
490
+ {
491
+ "move": "go forward",
492
+ "view": "no-op"
493
+ },
494
+ {
495
+ "move": "go forward",
496
+ "view": "no-op"
497
+ },
498
+ {
499
+ "move": "go forward",
500
+ "view": "no-op"
501
+ },
502
+ {
503
+ "move": "go forward",
504
+ "view": "no-op"
505
+ },
506
+ {
507
+ "move": "go forward",
508
+ "view": "no-op"
509
+ },
510
+ {
511
+ "move": "go forward",
512
+ "view": "no-op"
513
+ },
514
+ {
515
+ "move": "go forward",
516
+ "view": "no-op"
517
+ },
518
+ {
519
+ "move": "go forward",
520
+ "view": "no-op"
521
+ },
522
+ {
523
+ "move": "go forward",
524
+ "view": "no-op"
525
+ },
526
+ {
527
+ "move": "go forward",
528
+ "view": "no-op"
529
+ },
530
+ {
531
+ "move": "go forward",
532
+ "view": "no-op"
533
+ },
534
+ {
535
+ "move": "go forward",
536
+ "view": "no-op"
537
+ },
538
+ {
539
+ "move": "go forward",
540
+ "view": "no-op"
541
+ },
542
+ {
543
+ "move": "go forward",
544
+ "view": "no-op"
545
+ },
546
+ {
547
+ "move": "go forward",
548
+ "view": "no-op"
549
+ },
550
+ {
551
+ "move": "go forward",
552
+ "view": "no-op"
553
+ },
554
+ {
555
+ "move": "go forward",
556
+ "view": "no-op"
557
+ },
558
+ {
559
+ "move": "go forward",
560
+ "view": "no-op"
561
+ },
562
+ {
563
+ "move": "go forward",
564
+ "view": "no-op"
565
+ },
566
+ {
567
+ "move": "go forward",
568
+ "view": "no-op"
569
+ },
570
+ {
571
+ "move": "go forward",
572
+ "view": "no-op"
573
+ },
574
+ {
575
+ "move": "go forward",
576
+ "view": "no-op"
577
+ },
578
+ {
579
+ "move": "go forward",
580
+ "view": "no-op"
581
+ },
582
+ {
583
+ "move": "go forward",
584
+ "view": "no-op"
585
+ },
586
+ {
587
+ "move": "go forward",
588
+ "view": "no-op"
589
+ },
590
+ {
591
+ "move": "go forward",
592
+ "view": "no-op"
593
+ },
594
+ {
595
+ "move": "go forward",
596
+ "view": "no-op"
597
+ },
598
+ {
599
+ "move": "go forward",
600
+ "view": "no-op"
601
+ },
602
+ {
603
+ "move": "go forward",
604
+ "view": "no-op"
605
+ },
606
+ {
607
+ "move": "go forward",
608
+ "view": "no-op"
609
+ },
610
+ {
611
+ "move": "go forward",
612
+ "view": "no-op"
613
+ },
614
+ {
615
+ "move": "go forward",
616
+ "view": "no-op"
617
+ },
618
+ {
619
+ "move": "go forward",
620
+ "view": "no-op"
621
+ },
622
+ {
623
+ "move": "go forward",
624
+ "view": "no-op"
625
+ },
626
+ {
627
+ "move": "go forward",
628
+ "view": "no-op"
629
+ },
630
+ {
631
+ "move": "go forward",
632
+ "view": "no-op"
633
+ },
634
+ {
635
+ "move": "go forward",
636
+ "view": "no-op"
637
+ },
638
+ {
639
+ "move": "go forward",
640
+ "view": "no-op"
641
+ },
642
+ {
643
+ "move": "go forward",
644
+ "view": "no-op"
645
+ },
646
+ {
647
+ "move": "go forward",
648
+ "view": "no-op"
649
+ },
650
+ {
651
+ "move": "no-op",
652
+ "view": "turn left"
653
+ },
654
+ {
655
+ "move": "no-op",
656
+ "view": "turn left"
657
+ },
658
+ {
659
+ "move": "no-op",
660
+ "view": "turn left"
661
+ },
662
+ {
663
+ "move": "no-op",
664
+ "view": "turn left"
665
+ },
666
+ {
667
+ "move": "no-op",
668
+ "view": "turn left"
669
+ },
670
+ {
671
+ "move": "no-op",
672
+ "view": "turn left"
673
+ },
674
+ {
675
+ "move": "no-op",
676
+ "view": "turn left"
677
+ },
678
+ {
679
+ "move": "no-op",
680
+ "view": "turn left"
681
+ },
682
+ {
683
+ "move": "no-op",
684
+ "view": "turn left"
685
+ },
686
+ {
687
+ "move": "no-op",
688
+ "view": "turn left"
689
+ },
690
+ {
691
+ "move": "no-op",
692
+ "view": "turn left"
693
+ },
694
+ {
695
+ "move": "no-op",
696
+ "view": "turn left"
697
+ },
698
+ {
699
+ "move": "no-op",
700
+ "view": "turn left"
701
+ },
702
+ {
703
+ "move": "no-op",
704
+ "view": "turn left"
705
+ },
706
+ {
707
+ "move": "no-op",
708
+ "view": "turn left"
709
+ },
710
+ {
711
+ "move": "no-op",
712
+ "view": "turn left"
713
+ },
714
+ {
715
+ "move": "no-op",
716
+ "view": "turn left"
717
+ },
718
+ {
719
+ "move": "no-op",
720
+ "view": "turn left"
721
+ },
722
+ {
723
+ "move": "no-op",
724
+ "view": "turn left"
725
+ },
726
+ {
727
+ "move": "no-op",
728
+ "view": "turn left"
729
+ },
730
+ {
731
+ "move": "no-op",
732
+ "view": "turn left"
733
+ },
734
+ {
735
+ "move": "no-op",
736
+ "view": "turn left"
737
+ },
738
+ {
739
+ "move": "no-op",
740
+ "view": "turn left"
741
+ },
742
+ {
743
+ "move": "no-op",
744
+ "view": "turn left"
745
+ },
746
+ {
747
+ "move": "no-op",
748
+ "view": "turn left"
749
+ },
750
+ {
751
+ "move": "no-op",
752
+ "view": "turn left"
753
+ },
754
+ {
755
+ "move": "no-op",
756
+ "view": "turn left"
757
+ },
758
+ {
759
+ "move": "no-op",
760
+ "view": "turn left"
761
+ },
762
+ {
763
+ "move": "no-op",
764
+ "view": "turn left"
765
+ },
766
+ {
767
+ "move": "no-op",
768
+ "view": "turn left"
769
+ },
770
+ {
771
+ "move": "no-op",
772
+ "view": "turn left"
773
+ },
774
+ {
775
+ "move": "no-op",
776
+ "view": "turn left"
777
+ },
778
+ {
779
+ "move": "no-op",
780
+ "view": "turn left"
781
+ },
782
+ {
783
+ "move": "no-op",
784
+ "view": "turn left"
785
+ },
786
+ {
787
+ "move": "no-op",
788
+ "view": "turn left"
789
+ },
790
+ {
791
+ "move": "no-op",
792
+ "view": "turn left"
793
+ },
794
+ {
795
+ "move": "no-op",
796
+ "view": "turn left"
797
+ },
798
+ {
799
+ "move": "no-op",
800
+ "view": "turn left"
801
+ },
802
+ {
803
+ "move": "no-op",
804
+ "view": "turn left"
805
+ },
806
+ {
807
+ "move": "no-op",
808
+ "view": "turn left"
809
+ },
810
+ {
811
+ "move": "no-op",
812
+ "view": "turn left"
813
+ },
814
+ {
815
+ "move": "no-op",
816
+ "view": "turn left"
817
+ },
818
+ {
819
+ "move": "no-op",
820
+ "view": "turn left"
821
+ },
822
+ {
823
+ "move": "no-op",
824
+ "view": "turn left"
825
+ },
826
+ {
827
+ "move": "no-op",
828
+ "view": "turn left"
829
+ },
830
+ {
831
+ "move": "no-op",
832
+ "view": "turn left"
833
+ },
834
+ {
835
+ "move": "no-op",
836
+ "view": "turn left"
837
+ },
838
+ {
839
+ "move": "no-op",
840
+ "view": "turn left"
841
+ },
842
+ {
843
+ "move": "no-op",
844
+ "view": "turn left"
845
+ },
846
+ {
847
+ "move": "no-op",
848
+ "view": "turn left"
849
+ },
850
+ {
851
+ "move": "no-op",
852
+ "view": "turn left"
853
+ },
854
+ {
855
+ "move": "no-op",
856
+ "view": "turn left"
857
+ },
858
+ {
859
+ "move": "no-op",
860
+ "view": "turn left"
861
+ },
862
+ {
863
+ "move": "no-op",
864
+ "view": "turn left"
865
+ },
866
+ {
867
+ "move": "no-op",
868
+ "view": "turn left"
869
+ },
870
+ {
871
+ "move": "no-op",
872
+ "view": "turn left"
873
+ },
874
+ {
875
+ "move": "no-op",
876
+ "view": "turn left"
877
+ },
878
+ {
879
+ "move": "no-op",
880
+ "view": "turn left"
881
+ },
882
+ {
883
+ "move": "no-op",
884
+ "view": "turn left"
885
+ },
886
+ {
887
+ "move": "no-op",
888
+ "view": "turn left"
889
+ },
890
+ {
891
+ "move": "no-op",
892
+ "view": "turn left"
893
+ },
894
+ {
895
+ "move": "no-op",
896
+ "view": "turn left"
897
+ },
898
+ {
899
+ "move": "no-op",
900
+ "view": "turn left"
901
+ },
902
+ {
903
+ "move": "no-op",
904
+ "view": "turn left"
905
+ },
906
+ {
907
+ "move": "no-op",
908
+ "view": "turn left"
909
+ },
910
+ {
911
+ "move": "no-op",
912
+ "view": "turn left"
913
+ },
914
+ {
915
+ "move": "no-op",
916
+ "view": "turn left"
917
+ },
918
+ {
919
+ "move": "no-op",
920
+ "view": "turn left"
921
+ },
922
+ {
923
+ "move": "no-op",
924
+ "view": "turn left"
925
+ },
926
+ {
927
+ "move": "no-op",
928
+ "view": "turn left"
929
+ },
930
+ {
931
+ "move": "no-op",
932
+ "view": "turn left"
933
+ },
934
+ {
935
+ "move": "no-op",
936
+ "view": "turn left"
937
+ },
938
+ {
939
+ "move": "no-op",
940
+ "view": "turn left"
941
+ },
942
+ {
943
+ "move": "no-op",
944
+ "view": "turn left"
945
+ },
946
+ {
947
+ "move": "no-op",
948
+ "view": "turn left"
949
+ },
950
+ {
951
+ "move": "no-op",
952
+ "view": "turn left"
953
+ },
954
+ {
955
+ "move": "no-op",
956
+ "view": "turn left"
957
+ },
958
+ {
959
+ "move": "no-op",
960
+ "view": "turn left"
961
+ },
962
+ {
963
+ "move": "no-op",
964
+ "view": "turn left"
965
+ },
966
+ {
967
+ "move": "no-op",
968
+ "view": "turn left"
969
+ },
970
+ {
971
+ "move": "no-op",
972
+ "view": "turn left"
973
+ },
974
+ {
975
+ "move": "no-op",
976
+ "view": "turn left"
977
+ },
978
+ {
979
+ "move": "no-op",
980
+ "view": "turn left"
981
+ },
982
+ {
983
+ "move": "no-op",
984
+ "view": "turn left"
985
+ },
986
+ {
987
+ "move": "no-op",
988
+ "view": "turn left"
989
+ },
990
+ {
991
+ "move": "no-op",
992
+ "view": "turn left"
993
+ },
994
+ {
995
+ "move": "no-op",
996
+ "view": "turn left"
997
+ },
998
+ {
999
+ "move": "no-op",
1000
+ "view": "turn left"
1001
+ },
1002
+ {
1003
+ "move": "no-op",
1004
+ "view": "turn left"
1005
+ },
1006
+ {
1007
+ "move": "no-op",
1008
+ "view": "turn left"
1009
+ },
1010
+ {
1011
+ "move": "no-op",
1012
+ "view": "turn left"
1013
+ },
1014
+ {
1015
+ "move": "no-op",
1016
+ "view": "turn left"
1017
+ },
1018
+ {
1019
+ "move": "no-op",
1020
+ "view": "turn left"
1021
+ },
1022
+ {
1023
+ "move": "no-op",
1024
+ "view": "turn left"
1025
+ },
1026
+ {
1027
+ "move": "no-op",
1028
+ "view": "turn left"
1029
+ },
1030
+ {
1031
+ "move": "no-op",
1032
+ "view": "turn left"
1033
+ },
1034
+ {
1035
+ "move": "no-op",
1036
+ "view": "turn left"
1037
+ },
1038
+ {
1039
+ "move": "no-op",
1040
+ "view": "turn left"
1041
+ },
1042
+ {
1043
+ "move": "no-op",
1044
+ "view": "turn left"
1045
+ },
1046
+ {
1047
+ "move": "no-op",
1048
+ "view": "turn left"
1049
+ },
1050
+ {
1051
+ "move": "no-op",
1052
+ "view": "turn left"
1053
+ },
1054
+ {
1055
+ "move": "no-op",
1056
+ "view": "turn left"
1057
+ },
1058
+ {
1059
+ "move": "no-op",
1060
+ "view": "turn left"
1061
+ },
1062
+ {
1063
+ "move": "no-op",
1064
+ "view": "turn left"
1065
+ },
1066
+ {
1067
+ "move": "no-op",
1068
+ "view": "turn left"
1069
+ },
1070
+ {
1071
+ "move": "no-op",
1072
+ "view": "turn left"
1073
+ },
1074
+ {
1075
+ "move": "no-op",
1076
+ "view": "turn left"
1077
+ },
1078
+ {
1079
+ "move": "no-op",
1080
+ "view": "turn left"
1081
+ },
1082
+ {
1083
+ "move": "no-op",
1084
+ "view": "turn left"
1085
+ },
1086
+ {
1087
+ "move": "no-op",
1088
+ "view": "turn left"
1089
+ },
1090
+ {
1091
+ "move": "no-op",
1092
+ "view": "turn left"
1093
+ },
1094
+ {
1095
+ "move": "no-op",
1096
+ "view": "turn left"
1097
+ },
1098
+ {
1099
+ "move": "no-op",
1100
+ "view": "turn left"
1101
+ },
1102
+ {
1103
+ "move": "no-op",
1104
+ "view": "turn left"
1105
+ },
1106
+ {
1107
+ "move": "no-op",
1108
+ "view": "turn left"
1109
+ },
1110
+ {
1111
+ "move": "no-op",
1112
+ "view": "turn left"
1113
+ },
1114
+ {
1115
+ "move": "no-op",
1116
+ "view": "turn left"
1117
+ },
1118
+ {
1119
+ "move": "no-op",
1120
+ "view": "turn left"
1121
+ },
1122
+ {
1123
+ "move": "no-op",
1124
+ "view": "turn left"
1125
+ },
1126
+ {
1127
+ "move": "no-op",
1128
+ "view": "turn left"
1129
+ },
1130
+ {
1131
+ "move": "no-op",
1132
+ "view": "turn left"
1133
+ },
1134
+ {
1135
+ "move": "no-op",
1136
+ "view": "turn left"
1137
+ },
1138
+ {
1139
+ "move": "no-op",
1140
+ "view": "turn left"
1141
+ },
1142
+ {
1143
+ "move": "no-op",
1144
+ "view": "turn left"
1145
+ },
1146
+ {
1147
+ "move": "no-op",
1148
+ "view": "turn left"
1149
+ },
1150
+ {
1151
+ "move": "no-op",
1152
+ "view": "turn left"
1153
+ },
1154
+ {
1155
+ "move": "no-op",
1156
+ "view": "turn left"
1157
+ },
1158
+ {
1159
+ "move": "no-op",
1160
+ "view": "turn left"
1161
+ },
1162
+ {
1163
+ "move": "no-op",
1164
+ "view": "turn left"
1165
+ },
1166
+ {
1167
+ "move": "no-op",
1168
+ "view": "turn left"
1169
+ },
1170
+ {
1171
+ "move": "no-op",
1172
+ "view": "turn left"
1173
+ },
1174
+ {
1175
+ "move": "no-op",
1176
+ "view": "turn left"
1177
+ },
1178
+ {
1179
+ "move": "no-op",
1180
+ "view": "turn left"
1181
+ },
1182
+ {
1183
+ "move": "no-op",
1184
+ "view": "turn left"
1185
+ },
1186
+ {
1187
+ "move": "no-op",
1188
+ "view": "turn left"
1189
+ },
1190
+ {
1191
+ "move": "no-op",
1192
+ "view": "turn left"
1193
+ },
1194
+ {
1195
+ "move": "no-op",
1196
+ "view": "turn left"
1197
+ },
1198
+ {
1199
+ "move": "no-op",
1200
+ "view": "turn left"
1201
+ },
1202
+ {
1203
+ "move": "no-op",
1204
+ "view": "turn left"
1205
+ },
1206
+ {
1207
+ "move": "no-op",
1208
+ "view": "turn left"
1209
+ },
1210
+ {
1211
+ "move": "no-op",
1212
+ "view": "turn left"
1213
+ },
1214
+ {
1215
+ "move": "no-op",
1216
+ "view": "turn left"
1217
+ },
1218
+ {
1219
+ "move": "no-op",
1220
+ "view": "turn left"
1221
+ },
1222
+ {
1223
+ "move": "no-op",
1224
+ "view": "turn left"
1225
+ },
1226
+ {
1227
+ "move": "no-op",
1228
+ "view": "turn left"
1229
+ },
1230
+ {
1231
+ "move": "no-op",
1232
+ "view": "turn left"
1233
+ },
1234
+ {
1235
+ "move": "no-op",
1236
+ "view": "turn left"
1237
+ },
1238
+ {
1239
+ "move": "no-op",
1240
+ "view": "turn left"
1241
+ },
1242
+ {
1243
+ "move": "no-op",
1244
+ "view": "turn left"
1245
+ },
1246
+ {
1247
+ "move": "no-op",
1248
+ "view": "turn left"
1249
+ },
1250
+ {
1251
+ "move": "no-op",
1252
+ "view": "turn left"
1253
+ },
1254
+ {
1255
+ "move": "no-op",
1256
+ "view": "turn left"
1257
+ },
1258
+ {
1259
+ "move": "no-op",
1260
+ "view": "turn left"
1261
+ },
1262
+ {
1263
+ "move": "no-op",
1264
+ "view": "turn left"
1265
+ },
1266
+ {
1267
+ "move": "no-op",
1268
+ "view": "turn left"
1269
+ },
1270
+ {
1271
+ "move": "no-op",
1272
+ "view": "turn left"
1273
+ },
1274
+ {
1275
+ "move": "no-op",
1276
+ "view": "turn left"
1277
+ },
1278
+ {
1279
+ "move": "no-op",
1280
+ "view": "turn left"
1281
+ },
1282
+ {
1283
+ "move": "no-op",
1284
+ "view": "turn left"
1285
+ },
1286
+ {
1287
+ "move": "no-op",
1288
+ "view": "turn left"
1289
+ },
1290
+ {
1291
+ "move": "no-op",
1292
+ "view": "turn left"
1293
+ },
1294
+ {
1295
+ "move": "no-op",
1296
+ "view": "turn left"
1297
+ },
1298
+ {
1299
+ "move": "go forward",
1300
+ "view": "no-op"
1301
+ },
1302
+ {
1303
+ "move": "go forward",
1304
+ "view": "no-op"
1305
+ },
1306
+ {
1307
+ "move": "go forward",
1308
+ "view": "no-op"
1309
+ },
1310
+ {
1311
+ "move": "go forward",
1312
+ "view": "no-op"
1313
+ },
1314
+ {
1315
+ "move": "go forward",
1316
+ "view": "no-op"
1317
+ },
1318
+ {
1319
+ "move": "go forward",
1320
+ "view": "no-op"
1321
+ },
1322
+ {
1323
+ "move": "go forward",
1324
+ "view": "no-op"
1325
+ },
1326
+ {
1327
+ "move": "go forward",
1328
+ "view": "no-op"
1329
+ },
1330
+ {
1331
+ "move": "go forward",
1332
+ "view": "no-op"
1333
+ },
1334
+ {
1335
+ "move": "go forward",
1336
+ "view": "no-op"
1337
+ },
1338
+ {
1339
+ "move": "go forward",
1340
+ "view": "no-op"
1341
+ },
1342
+ {
1343
+ "move": "go forward",
1344
+ "view": "no-op"
1345
+ },
1346
+ {
1347
+ "move": "go forward",
1348
+ "view": "no-op"
1349
+ },
1350
+ {
1351
+ "move": "go forward",
1352
+ "view": "no-op"
1353
+ },
1354
+ {
1355
+ "move": "go forward",
1356
+ "view": "no-op"
1357
+ },
1358
+ {
1359
+ "move": "go forward",
1360
+ "view": "no-op"
1361
+ },
1362
+ {
1363
+ "move": "go forward",
1364
+ "view": "no-op"
1365
+ },
1366
+ {
1367
+ "move": "go forward",
1368
+ "view": "no-op"
1369
+ },
1370
+ {
1371
+ "move": "go forward",
1372
+ "view": "no-op"
1373
+ },
1374
+ {
1375
+ "move": "go forward",
1376
+ "view": "no-op"
1377
+ },
1378
+ {
1379
+ "move": "go forward",
1380
+ "view": "no-op"
1381
+ },
1382
+ {
1383
+ "move": "go forward",
1384
+ "view": "no-op"
1385
+ },
1386
+ {
1387
+ "move": "go forward",
1388
+ "view": "no-op"
1389
+ },
1390
+ {
1391
+ "move": "go forward",
1392
+ "view": "no-op"
1393
+ },
1394
+ {
1395
+ "move": "go forward",
1396
+ "view": "no-op"
1397
+ },
1398
+ {
1399
+ "move": "go forward",
1400
+ "view": "no-op"
1401
+ },
1402
+ {
1403
+ "move": "go forward",
1404
+ "view": "no-op"
1405
+ },
1406
+ {
1407
+ "move": "go forward",
1408
+ "view": "no-op"
1409
+ },
1410
+ {
1411
+ "move": "go forward",
1412
+ "view": "no-op"
1413
+ },
1414
+ {
1415
+ "move": "go forward",
1416
+ "view": "no-op"
1417
+ },
1418
+ {
1419
+ "move": "go forward",
1420
+ "view": "no-op"
1421
+ },
1422
+ {
1423
+ "move": "go forward",
1424
+ "view": "no-op"
1425
+ },
1426
+ {
1427
+ "move": "go forward",
1428
+ "view": "no-op"
1429
+ },
1430
+ {
1431
+ "move": "go forward",
1432
+ "view": "no-op"
1433
+ },
1434
+ {
1435
+ "move": "go forward",
1436
+ "view": "no-op"
1437
+ },
1438
+ {
1439
+ "move": "go forward",
1440
+ "view": "no-op"
1441
+ },
1442
+ {
1443
+ "move": "go forward",
1444
+ "view": "no-op"
1445
+ },
1446
+ {
1447
+ "move": "go forward",
1448
+ "view": "no-op"
1449
+ },
1450
+ {
1451
+ "move": "go forward",
1452
+ "view": "no-op"
1453
+ },
1454
+ {
1455
+ "move": "go forward",
1456
+ "view": "no-op"
1457
+ },
1458
+ {
1459
+ "move": "go forward",
1460
+ "view": "no-op"
1461
+ },
1462
+ {
1463
+ "move": "go forward",
1464
+ "view": "no-op"
1465
+ },
1466
+ {
1467
+ "move": "go forward",
1468
+ "view": "no-op"
1469
+ },
1470
+ {
1471
+ "move": "go forward",
1472
+ "view": "no-op"
1473
+ },
1474
+ {
1475
+ "move": "go forward",
1476
+ "view": "no-op"
1477
+ },
1478
+ {
1479
+ "move": "go forward",
1480
+ "view": "no-op"
1481
+ },
1482
+ {
1483
+ "move": "go forward",
1484
+ "view": "no-op"
1485
+ },
1486
+ {
1487
+ "move": "go forward",
1488
+ "view": "no-op"
1489
+ },
1490
+ {
1491
+ "move": "go forward",
1492
+ "view": "no-op"
1493
+ },
1494
+ {
1495
+ "move": "go forward",
1496
+ "view": "no-op"
1497
+ },
1498
+ {
1499
+ "move": "go forward",
1500
+ "view": "no-op"
1501
+ },
1502
+ {
1503
+ "move": "go forward",
1504
+ "view": "no-op"
1505
+ },
1506
+ {
1507
+ "move": "go forward",
1508
+ "view": "no-op"
1509
+ },
1510
+ {
1511
+ "move": "go forward",
1512
+ "view": "no-op"
1513
+ },
1514
+ {
1515
+ "move": "go forward",
1516
+ "view": "no-op"
1517
+ },
1518
+ {
1519
+ "move": "go forward",
1520
+ "view": "no-op"
1521
+ },
1522
+ {
1523
+ "move": "go forward",
1524
+ "view": "no-op"
1525
+ },
1526
+ {
1527
+ "move": "go forward",
1528
+ "view": "no-op"
1529
+ },
1530
+ {
1531
+ "move": "go forward",
1532
+ "view": "no-op"
1533
+ },
1534
+ {
1535
+ "move": "go forward",
1536
+ "view": "no-op"
1537
+ },
1538
+ {
1539
+ "move": "go forward",
1540
+ "view": "no-op"
1541
+ },
1542
+ {
1543
+ "move": "go forward",
1544
+ "view": "no-op"
1545
+ },
1546
+ {
1547
+ "move": "go forward",
1548
+ "view": "no-op"
1549
+ },
1550
+ {
1551
+ "move": "go forward",
1552
+ "view": "no-op"
1553
+ },
1554
+ {
1555
+ "move": "go forward",
1556
+ "view": "no-op"
1557
+ },
1558
+ {
1559
+ "move": "go forward",
1560
+ "view": "no-op"
1561
+ },
1562
+ {
1563
+ "move": "go forward",
1564
+ "view": "no-op"
1565
+ },
1566
+ {
1567
+ "move": "go forward",
1568
+ "view": "no-op"
1569
+ },
1570
+ {
1571
+ "move": "go forward",
1572
+ "view": "no-op"
1573
+ },
1574
+ {
1575
+ "move": "go forward",
1576
+ "view": "no-op"
1577
+ },
1578
+ {
1579
+ "move": "go forward",
1580
+ "view": "no-op"
1581
+ },
1582
+ {
1583
+ "move": "go forward",
1584
+ "view": "no-op"
1585
+ },
1586
+ {
1587
+ "move": "go forward",
1588
+ "view": "no-op"
1589
+ },
1590
+ {
1591
+ "move": "go forward",
1592
+ "view": "no-op"
1593
+ },
1594
+ {
1595
+ "move": "go forward",
1596
+ "view": "no-op"
1597
+ },
1598
+ {
1599
+ "move": "go forward",
1600
+ "view": "no-op"
1601
+ },
1602
+ {
1603
+ "move": "go forward",
1604
+ "view": "no-op"
1605
+ },
1606
+ {
1607
+ "move": "go forward",
1608
+ "view": "no-op"
1609
+ },
1610
+ {
1611
+ "move": "go forward",
1612
+ "view": "no-op"
1613
+ },
1614
+ {
1615
+ "move": "go forward",
1616
+ "view": "no-op"
1617
+ },
1618
+ {
1619
+ "move": "go forward",
1620
+ "view": "no-op"
1621
+ },
1622
+ {
1623
+ "move": "go forward",
1624
+ "view": "no-op"
1625
+ },
1626
+ {
1627
+ "move": "go forward",
1628
+ "view": "no-op"
1629
+ },
1630
+ {
1631
+ "move": "go forward",
1632
+ "view": "no-op"
1633
+ },
1634
+ {
1635
+ "move": "go forward",
1636
+ "view": "no-op"
1637
+ },
1638
+ {
1639
+ "move": "go forward",
1640
+ "view": "no-op"
1641
+ },
1642
+ {
1643
+ "move": "go forward",
1644
+ "view": "no-op"
1645
+ },
1646
+ {
1647
+ "move": "go forward",
1648
+ "view": "no-op"
1649
+ },
1650
+ {
1651
+ "move": "go forward",
1652
+ "view": "no-op"
1653
+ },
1654
+ {
1655
+ "move": "go forward",
1656
+ "view": "no-op"
1657
+ },
1658
+ {
1659
+ "move": "go forward",
1660
+ "view": "no-op"
1661
+ },
1662
+ {
1663
+ "move": "go forward",
1664
+ "view": "no-op"
1665
+ },
1666
+ {
1667
+ "move": "go forward",
1668
+ "view": "no-op"
1669
+ },
1670
+ {
1671
+ "move": "go forward",
1672
+ "view": "no-op"
1673
+ },
1674
+ {
1675
+ "move": "go forward",
1676
+ "view": "no-op"
1677
+ },
1678
+ {
1679
+ "move": "go forward",
1680
+ "view": "no-op"
1681
+ },
1682
+ {
1683
+ "move": "go forward",
1684
+ "view": "no-op"
1685
+ },
1686
+ {
1687
+ "move": "go forward",
1688
+ "view": "no-op"
1689
+ },
1690
+ {
1691
+ "move": "go forward",
1692
+ "view": "no-op"
1693
+ },
1694
+ {
1695
+ "move": "go forward",
1696
+ "view": "no-op"
1697
+ },
1698
+ {
1699
+ "move": "go forward",
1700
+ "view": "no-op"
1701
+ },
1702
+ {
1703
+ "move": "go forward",
1704
+ "view": "no-op"
1705
+ },
1706
+ {
1707
+ "move": "go forward",
1708
+ "view": "no-op"
1709
+ },
1710
+ {
1711
+ "move": "go forward",
1712
+ "view": "no-op"
1713
+ },
1714
+ {
1715
+ "move": "go forward",
1716
+ "view": "no-op"
1717
+ },
1718
+ {
1719
+ "move": "go forward",
1720
+ "view": "no-op"
1721
+ },
1722
+ {
1723
+ "move": "go forward",
1724
+ "view": "no-op"
1725
+ },
1726
+ {
1727
+ "move": "go forward",
1728
+ "view": "no-op"
1729
+ },
1730
+ {
1731
+ "move": "go forward",
1732
+ "view": "no-op"
1733
+ },
1734
+ {
1735
+ "move": "go forward",
1736
+ "view": "no-op"
1737
+ },
1738
+ {
1739
+ "move": "go forward",
1740
+ "view": "no-op"
1741
+ },
1742
+ {
1743
+ "move": "go forward",
1744
+ "view": "no-op"
1745
+ },
1746
+ {
1747
+ "move": "go forward",
1748
+ "view": "no-op"
1749
+ },
1750
+ {
1751
+ "move": "go forward",
1752
+ "view": "no-op"
1753
+ },
1754
+ {
1755
+ "move": "go forward",
1756
+ "view": "no-op"
1757
+ },
1758
+ {
1759
+ "move": "go forward",
1760
+ "view": "no-op"
1761
+ },
1762
+ {
1763
+ "move": "go forward",
1764
+ "view": "no-op"
1765
+ },
1766
+ {
1767
+ "move": "go forward",
1768
+ "view": "no-op"
1769
+ },
1770
+ {
1771
+ "move": "go forward",
1772
+ "view": "no-op"
1773
+ },
1774
+ {
1775
+ "move": "go forward",
1776
+ "view": "no-op"
1777
+ },
1778
+ {
1779
+ "move": "go forward",
1780
+ "view": "no-op"
1781
+ },
1782
+ {
1783
+ "move": "go forward",
1784
+ "view": "no-op"
1785
+ },
1786
+ {
1787
+ "move": "go forward",
1788
+ "view": "no-op"
1789
+ },
1790
+ {
1791
+ "move": "go forward",
1792
+ "view": "no-op"
1793
+ },
1794
+ {
1795
+ "move": "go forward",
1796
+ "view": "no-op"
1797
+ },
1798
+ {
1799
+ "move": "go forward",
1800
+ "view": "no-op"
1801
+ },
1802
+ {
1803
+ "move": "go forward",
1804
+ "view": "no-op"
1805
+ },
1806
+ {
1807
+ "move": "go forward",
1808
+ "view": "no-op"
1809
+ },
1810
+ {
1811
+ "move": "go forward",
1812
+ "view": "no-op"
1813
+ },
1814
+ {
1815
+ "move": "go forward",
1816
+ "view": "no-op"
1817
+ },
1818
+ {
1819
+ "move": "go forward",
1820
+ "view": "no-op"
1821
+ },
1822
+ {
1823
+ "move": "go forward",
1824
+ "view": "no-op"
1825
+ },
1826
+ {
1827
+ "move": "go forward",
1828
+ "view": "no-op"
1829
+ },
1830
+ {
1831
+ "move": "go forward",
1832
+ "view": "no-op"
1833
+ },
1834
+ {
1835
+ "move": "go forward",
1836
+ "view": "no-op"
1837
+ },
1838
+ {
1839
+ "move": "go forward",
1840
+ "view": "no-op"
1841
+ },
1842
+ {
1843
+ "move": "go forward",
1844
+ "view": "no-op"
1845
+ },
1846
+ {
1847
+ "move": "go forward",
1848
+ "view": "no-op"
1849
+ },
1850
+ {
1851
+ "move": "go forward",
1852
+ "view": "no-op"
1853
+ },
1854
+ {
1855
+ "move": "go forward",
1856
+ "view": "no-op"
1857
+ },
1858
+ {
1859
+ "move": "go forward",
1860
+ "view": "no-op"
1861
+ },
1862
+ {
1863
+ "move": "go forward",
1864
+ "view": "no-op"
1865
+ },
1866
+ {
1867
+ "move": "go forward",
1868
+ "view": "no-op"
1869
+ },
1870
+ {
1871
+ "move": "go forward",
1872
+ "view": "no-op"
1873
+ },
1874
+ {
1875
+ "move": "go forward",
1876
+ "view": "no-op"
1877
+ },
1878
+ {
1879
+ "move": "go forward",
1880
+ "view": "no-op"
1881
+ },
1882
+ {
1883
+ "move": "go forward",
1884
+ "view": "no-op"
1885
+ },
1886
+ {
1887
+ "move": "go forward",
1888
+ "view": "no-op"
1889
+ },
1890
+ {
1891
+ "move": "go forward",
1892
+ "view": "no-op"
1893
+ },
1894
+ {
1895
+ "move": "go forward",
1896
+ "view": "no-op"
1897
+ },
1898
+ {
1899
+ "move": "go forward",
1900
+ "view": "no-op"
1901
+ },
1902
+ {
1903
+ "move": "go forward",
1904
+ "view": "no-op"
1905
+ },
1906
+ {
1907
+ "move": "go forward",
1908
+ "view": "no-op"
1909
+ },
1910
+ {
1911
+ "move": "go forward",
1912
+ "view": "no-op"
1913
+ },
1914
+ {
1915
+ "move": "go forward",
1916
+ "view": "no-op"
1917
+ },
1918
+ {
1919
+ "move": "go forward",
1920
+ "view": "no-op"
1921
+ },
1922
+ {
1923
+ "move": "go forward",
1924
+ "view": "no-op"
1925
+ },
1926
+ {
1927
+ "move": "go forward",
1928
+ "view": "no-op"
1929
+ },
1930
+ {
1931
+ "move": "go forward",
1932
+ "view": "no-op"
1933
+ },
1934
+ {
1935
+ "move": "go forward",
1936
+ "view": "no-op"
1937
+ },
1938
+ {
1939
+ "move": "go forward",
1940
+ "view": "no-op"
1941
+ },
1942
+ {
1943
+ "move": "go forward",
1944
+ "view": "no-op"
1945
+ },
1946
+ {
1947
+ "move": "no-op",
1948
+ "view": "turn left"
1949
+ },
1950
+ {
1951
+ "move": "no-op",
1952
+ "view": "turn left"
1953
+ },
1954
+ {
1955
+ "move": "no-op",
1956
+ "view": "turn left"
1957
+ },
1958
+ {
1959
+ "move": "no-op",
1960
+ "view": "turn left"
1961
+ },
1962
+ {
1963
+ "move": "no-op",
1964
+ "view": "turn left"
1965
+ },
1966
+ {
1967
+ "move": "no-op",
1968
+ "view": "turn left"
1969
+ },
1970
+ {
1971
+ "move": "no-op",
1972
+ "view": "turn left"
1973
+ },
1974
+ {
1975
+ "move": "no-op",
1976
+ "view": "turn left"
1977
+ },
1978
+ {
1979
+ "move": "no-op",
1980
+ "view": "turn left"
1981
+ },
1982
+ {
1983
+ "move": "no-op",
1984
+ "view": "turn left"
1985
+ },
1986
+ {
1987
+ "move": "no-op",
1988
+ "view": "turn left"
1989
+ },
1990
+ {
1991
+ "move": "no-op",
1992
+ "view": "turn left"
1993
+ },
1994
+ {
1995
+ "move": "no-op",
1996
+ "view": "turn left"
1997
+ },
1998
+ {
1999
+ "move": "no-op",
2000
+ "view": "turn left"
2001
+ },
2002
+ {
2003
+ "move": "no-op",
2004
+ "view": "turn left"
2005
+ },
2006
+ {
2007
+ "move": "no-op",
2008
+ "view": "turn left"
2009
+ },
2010
+ {
2011
+ "move": "no-op",
2012
+ "view": "turn left"
2013
+ },
2014
+ {
2015
+ "move": "no-op",
2016
+ "view": "turn left"
2017
+ },
2018
+ {
2019
+ "move": "no-op",
2020
+ "view": "turn left"
2021
+ },
2022
+ {
2023
+ "move": "no-op",
2024
+ "view": "turn left"
2025
+ },
2026
+ {
2027
+ "move": "no-op",
2028
+ "view": "turn left"
2029
+ },
2030
+ {
2031
+ "move": "no-op",
2032
+ "view": "turn left"
2033
+ },
2034
+ {
2035
+ "move": "no-op",
2036
+ "view": "turn left"
2037
+ },
2038
+ {
2039
+ "move": "no-op",
2040
+ "view": "turn left"
2041
+ },
2042
+ {
2043
+ "move": "no-op",
2044
+ "view": "turn left"
2045
+ },
2046
+ {
2047
+ "move": "no-op",
2048
+ "view": "turn left"
2049
+ },
2050
+ {
2051
+ "move": "no-op",
2052
+ "view": "turn left"
2053
+ },
2054
+ {
2055
+ "move": "no-op",
2056
+ "view": "turn left"
2057
+ },
2058
+ {
2059
+ "move": "no-op",
2060
+ "view": "turn left"
2061
+ },
2062
+ {
2063
+ "move": "no-op",
2064
+ "view": "turn left"
2065
+ },
2066
+ {
2067
+ "move": "no-op",
2068
+ "view": "turn left"
2069
+ },
2070
+ {
2071
+ "move": "no-op",
2072
+ "view": "turn left"
2073
+ },
2074
+ {
2075
+ "move": "no-op",
2076
+ "view": "turn left"
2077
+ },
2078
+ {
2079
+ "move": "no-op",
2080
+ "view": "turn left"
2081
+ },
2082
+ {
2083
+ "move": "no-op",
2084
+ "view": "turn left"
2085
+ },
2086
+ {
2087
+ "move": "no-op",
2088
+ "view": "turn left"
2089
+ },
2090
+ {
2091
+ "move": "no-op",
2092
+ "view": "turn left"
2093
+ },
2094
+ {
2095
+ "move": "no-op",
2096
+ "view": "turn left"
2097
+ },
2098
+ {
2099
+ "move": "no-op",
2100
+ "view": "turn left"
2101
+ },
2102
+ {
2103
+ "move": "no-op",
2104
+ "view": "turn left"
2105
+ },
2106
+ {
2107
+ "move": "no-op",
2108
+ "view": "turn left"
2109
+ },
2110
+ {
2111
+ "move": "no-op",
2112
+ "view": "turn left"
2113
+ },
2114
+ {
2115
+ "move": "no-op",
2116
+ "view": "turn left"
2117
+ },
2118
+ {
2119
+ "move": "no-op",
2120
+ "view": "turn left"
2121
+ },
2122
+ {
2123
+ "move": "no-op",
2124
+ "view": "turn left"
2125
+ },
2126
+ {
2127
+ "move": "no-op",
2128
+ "view": "turn left"
2129
+ },
2130
+ {
2131
+ "move": "no-op",
2132
+ "view": "turn left"
2133
+ },
2134
+ {
2135
+ "move": "no-op",
2136
+ "view": "turn left"
2137
+ },
2138
+ {
2139
+ "move": "no-op",
2140
+ "view": "turn left"
2141
+ },
2142
+ {
2143
+ "move": "no-op",
2144
+ "view": "turn left"
2145
+ },
2146
+ {
2147
+ "move": "no-op",
2148
+ "view": "turn left"
2149
+ },
2150
+ {
2151
+ "move": "no-op",
2152
+ "view": "turn left"
2153
+ },
2154
+ {
2155
+ "move": "no-op",
2156
+ "view": "turn left"
2157
+ },
2158
+ {
2159
+ "move": "no-op",
2160
+ "view": "turn left"
2161
+ },
2162
+ {
2163
+ "move": "no-op",
2164
+ "view": "turn left"
2165
+ },
2166
+ {
2167
+ "move": "no-op",
2168
+ "view": "turn left"
2169
+ },
2170
+ {
2171
+ "move": "no-op",
2172
+ "view": "turn left"
2173
+ },
2174
+ {
2175
+ "move": "no-op",
2176
+ "view": "turn left"
2177
+ },
2178
+ {
2179
+ "move": "no-op",
2180
+ "view": "turn left"
2181
+ },
2182
+ {
2183
+ "move": "no-op",
2184
+ "view": "turn left"
2185
+ },
2186
+ {
2187
+ "move": "no-op",
2188
+ "view": "turn left"
2189
+ },
2190
+ {
2191
+ "move": "no-op",
2192
+ "view": "turn left"
2193
+ },
2194
+ {
2195
+ "move": "no-op",
2196
+ "view": "turn left"
2197
+ },
2198
+ {
2199
+ "move": "no-op",
2200
+ "view": "turn left"
2201
+ },
2202
+ {
2203
+ "move": "no-op",
2204
+ "view": "turn left"
2205
+ },
2206
+ {
2207
+ "move": "no-op",
2208
+ "view": "turn left"
2209
+ },
2210
+ {
2211
+ "move": "no-op",
2212
+ "view": "turn left"
2213
+ },
2214
+ {
2215
+ "move": "no-op",
2216
+ "view": "turn left"
2217
+ },
2218
+ {
2219
+ "move": "no-op",
2220
+ "view": "turn left"
2221
+ },
2222
+ {
2223
+ "move": "no-op",
2224
+ "view": "turn left"
2225
+ },
2226
+ {
2227
+ "move": "no-op",
2228
+ "view": "turn left"
2229
+ },
2230
+ {
2231
+ "move": "no-op",
2232
+ "view": "turn left"
2233
+ },
2234
+ {
2235
+ "move": "no-op",
2236
+ "view": "turn left"
2237
+ },
2238
+ {
2239
+ "move": "no-op",
2240
+ "view": "turn left"
2241
+ },
2242
+ {
2243
+ "move": "no-op",
2244
+ "view": "turn left"
2245
+ },
2246
+ {
2247
+ "move": "no-op",
2248
+ "view": "turn left"
2249
+ },
2250
+ {
2251
+ "move": "no-op",
2252
+ "view": "turn left"
2253
+ },
2254
+ {
2255
+ "move": "no-op",
2256
+ "view": "turn left"
2257
+ },
2258
+ {
2259
+ "move": "no-op",
2260
+ "view": "turn left"
2261
+ },
2262
+ {
2263
+ "move": "no-op",
2264
+ "view": "turn left"
2265
+ },
2266
+ {
2267
+ "move": "no-op",
2268
+ "view": "turn left"
2269
+ },
2270
+ {
2271
+ "move": "no-op",
2272
+ "view": "turn left"
2273
+ },
2274
+ {
2275
+ "move": "no-op",
2276
+ "view": "turn left"
2277
+ },
2278
+ {
2279
+ "move": "no-op",
2280
+ "view": "turn left"
2281
+ },
2282
+ {
2283
+ "move": "no-op",
2284
+ "view": "turn left"
2285
+ },
2286
+ {
2287
+ "move": "no-op",
2288
+ "view": "turn left"
2289
+ },
2290
+ {
2291
+ "move": "no-op",
2292
+ "view": "turn left"
2293
+ },
2294
+ {
2295
+ "move": "no-op",
2296
+ "view": "turn left"
2297
+ },
2298
+ {
2299
+ "move": "no-op",
2300
+ "view": "turn left"
2301
+ },
2302
+ {
2303
+ "move": "no-op",
2304
+ "view": "turn left"
2305
+ },
2306
+ {
2307
+ "move": "no-op",
2308
+ "view": "turn left"
2309
+ },
2310
+ {
2311
+ "move": "no-op",
2312
+ "view": "turn left"
2313
+ },
2314
+ {
2315
+ "move": "no-op",
2316
+ "view": "turn left"
2317
+ },
2318
+ {
2319
+ "move": "no-op",
2320
+ "view": "turn left"
2321
+ },
2322
+ {
2323
+ "move": "no-op",
2324
+ "view": "turn left"
2325
+ },
2326
+ {
2327
+ "move": "no-op",
2328
+ "view": "turn left"
2329
+ },
2330
+ {
2331
+ "move": "no-op",
2332
+ "view": "turn left"
2333
+ },
2334
+ {
2335
+ "move": "no-op",
2336
+ "view": "turn left"
2337
+ },
2338
+ {
2339
+ "move": "no-op",
2340
+ "view": "turn left"
2341
+ },
2342
+ {
2343
+ "move": "no-op",
2344
+ "view": "turn left"
2345
+ },
2346
+ {
2347
+ "move": "no-op",
2348
+ "view": "turn left"
2349
+ },
2350
+ {
2351
+ "move": "no-op",
2352
+ "view": "turn left"
2353
+ },
2354
+ {
2355
+ "move": "no-op",
2356
+ "view": "turn left"
2357
+ },
2358
+ {
2359
+ "move": "no-op",
2360
+ "view": "turn left"
2361
+ },
2362
+ {
2363
+ "move": "no-op",
2364
+ "view": "turn left"
2365
+ },
2366
+ {
2367
+ "move": "no-op",
2368
+ "view": "turn left"
2369
+ },
2370
+ {
2371
+ "move": "no-op",
2372
+ "view": "turn left"
2373
+ },
2374
+ {
2375
+ "move": "no-op",
2376
+ "view": "turn left"
2377
+ },
2378
+ {
2379
+ "move": "no-op",
2380
+ "view": "turn left"
2381
+ },
2382
+ {
2383
+ "move": "no-op",
2384
+ "view": "turn left"
2385
+ },
2386
+ {
2387
+ "move": "no-op",
2388
+ "view": "turn left"
2389
+ },
2390
+ {
2391
+ "move": "no-op",
2392
+ "view": "turn left"
2393
+ },
2394
+ {
2395
+ "move": "no-op",
2396
+ "view": "turn left"
2397
+ },
2398
+ {
2399
+ "move": "no-op",
2400
+ "view": "turn left"
2401
+ },
2402
+ {
2403
+ "move": "no-op",
2404
+ "view": "turn left"
2405
+ },
2406
+ {
2407
+ "move": "no-op",
2408
+ "view": "turn left"
2409
+ },
2410
+ {
2411
+ "move": "no-op",
2412
+ "view": "turn left"
2413
+ },
2414
+ {
2415
+ "move": "no-op",
2416
+ "view": "turn left"
2417
+ },
2418
+ {
2419
+ "move": "no-op",
2420
+ "view": "turn left"
2421
+ },
2422
+ {
2423
+ "move": "no-op",
2424
+ "view": "turn left"
2425
+ },
2426
+ {
2427
+ "move": "no-op",
2428
+ "view": "turn left"
2429
+ },
2430
+ {
2431
+ "move": "no-op",
2432
+ "view": "turn left"
2433
+ },
2434
+ {
2435
+ "move": "no-op",
2436
+ "view": "turn left"
2437
+ },
2438
+ {
2439
+ "move": "no-op",
2440
+ "view": "turn left"
2441
+ },
2442
+ {
2443
+ "move": "no-op",
2444
+ "view": "turn left"
2445
+ },
2446
+ {
2447
+ "move": "no-op",
2448
+ "view": "turn left"
2449
+ },
2450
+ {
2451
+ "move": "no-op",
2452
+ "view": "turn left"
2453
+ },
2454
+ {
2455
+ "move": "no-op",
2456
+ "view": "turn left"
2457
+ },
2458
+ {
2459
+ "move": "no-op",
2460
+ "view": "turn left"
2461
+ },
2462
+ {
2463
+ "move": "no-op",
2464
+ "view": "turn left"
2465
+ },
2466
+ {
2467
+ "move": "no-op",
2468
+ "view": "turn left"
2469
+ },
2470
+ {
2471
+ "move": "no-op",
2472
+ "view": "turn left"
2473
+ },
2474
+ {
2475
+ "move": "no-op",
2476
+ "view": "turn left"
2477
+ },
2478
+ {
2479
+ "move": "no-op",
2480
+ "view": "turn left"
2481
+ },
2482
+ {
2483
+ "move": "no-op",
2484
+ "view": "turn left"
2485
+ },
2486
+ {
2487
+ "move": "no-op",
2488
+ "view": "turn left"
2489
+ },
2490
+ {
2491
+ "move": "no-op",
2492
+ "view": "turn left"
2493
+ },
2494
+ {
2495
+ "move": "no-op",
2496
+ "view": "turn left"
2497
+ },
2498
+ {
2499
+ "move": "no-op",
2500
+ "view": "turn left"
2501
+ },
2502
+ {
2503
+ "move": "no-op",
2504
+ "view": "turn left"
2505
+ },
2506
+ {
2507
+ "move": "no-op",
2508
+ "view": "turn left"
2509
+ },
2510
+ {
2511
+ "move": "no-op",
2512
+ "view": "turn left"
2513
+ },
2514
+ {
2515
+ "move": "no-op",
2516
+ "view": "turn left"
2517
+ },
2518
+ {
2519
+ "move": "no-op",
2520
+ "view": "turn left"
2521
+ },
2522
+ {
2523
+ "move": "no-op",
2524
+ "view": "turn left"
2525
+ },
2526
+ {
2527
+ "move": "no-op",
2528
+ "view": "turn left"
2529
+ },
2530
+ {
2531
+ "move": "no-op",
2532
+ "view": "turn left"
2533
+ },
2534
+ {
2535
+ "move": "no-op",
2536
+ "view": "turn left"
2537
+ },
2538
+ {
2539
+ "move": "no-op",
2540
+ "view": "turn left"
2541
+ },
2542
+ {
2543
+ "move": "no-op",
2544
+ "view": "turn left"
2545
+ },
2546
+ {
2547
+ "move": "no-op",
2548
+ "view": "turn left"
2549
+ },
2550
+ {
2551
+ "move": "no-op",
2552
+ "view": "turn left"
2553
+ },
2554
+ {
2555
+ "move": "no-op",
2556
+ "view": "turn left"
2557
+ },
2558
+ {
2559
+ "move": "no-op",
2560
+ "view": "turn left"
2561
+ },
2562
+ {
2563
+ "move": "no-op",
2564
+ "view": "turn left"
2565
+ },
2566
+ {
2567
+ "move": "no-op",
2568
+ "view": "turn left"
2569
+ },
2570
+ {
2571
+ "move": "no-op",
2572
+ "view": "turn left"
2573
+ },
2574
+ {
2575
+ "move": "no-op",
2576
+ "view": "turn left"
2577
+ },
2578
+ {
2579
+ "move": "no-op",
2580
+ "view": "turn left"
2581
+ },
2582
+ {
2583
+ "move": "no-op",
2584
+ "view": "turn left"
2585
+ },
2586
+ {
2587
+ "move": "no-op",
2588
+ "view": "turn left"
2589
+ },
2590
+ {
2591
+ "move": "no-op",
2592
+ "view": "turn left"
2593
+ },
2594
+ {
2595
+ "move": "no-op",
2596
+ "view": "turn right"
2597
+ },
2598
+ {
2599
+ "move": "no-op",
2600
+ "view": "turn right"
2601
+ },
2602
+ {
2603
+ "move": "no-op",
2604
+ "view": "turn right"
2605
+ },
2606
+ {
2607
+ "move": "no-op",
2608
+ "view": "turn right"
2609
+ },
2610
+ {
2611
+ "move": "no-op",
2612
+ "view": "turn right"
2613
+ },
2614
+ {
2615
+ "move": "no-op",
2616
+ "view": "turn right"
2617
+ },
2618
+ {
2619
+ "move": "no-op",
2620
+ "view": "turn right"
2621
+ },
2622
+ {
2623
+ "move": "no-op",
2624
+ "view": "turn right"
2625
+ },
2626
+ {
2627
+ "move": "no-op",
2628
+ "view": "turn right"
2629
+ },
2630
+ {
2631
+ "move": "no-op",
2632
+ "view": "turn right"
2633
+ },
2634
+ {
2635
+ "move": "no-op",
2636
+ "view": "turn right"
2637
+ },
2638
+ {
2639
+ "move": "no-op",
2640
+ "view": "turn right"
2641
+ },
2642
+ {
2643
+ "move": "no-op",
2644
+ "view": "turn right"
2645
+ },
2646
+ {
2647
+ "move": "no-op",
2648
+ "view": "turn right"
2649
+ },
2650
+ {
2651
+ "move": "no-op",
2652
+ "view": "turn right"
2653
+ },
2654
+ {
2655
+ "move": "no-op",
2656
+ "view": "turn right"
2657
+ },
2658
+ {
2659
+ "move": "no-op",
2660
+ "view": "turn right"
2661
+ },
2662
+ {
2663
+ "move": "no-op",
2664
+ "view": "turn right"
2665
+ },
2666
+ {
2667
+ "move": "no-op",
2668
+ "view": "turn right"
2669
+ },
2670
+ {
2671
+ "move": "no-op",
2672
+ "view": "turn right"
2673
+ },
2674
+ {
2675
+ "move": "no-op",
2676
+ "view": "turn right"
2677
+ },
2678
+ {
2679
+ "move": "no-op",
2680
+ "view": "turn right"
2681
+ },
2682
+ {
2683
+ "move": "no-op",
2684
+ "view": "turn right"
2685
+ },
2686
+ {
2687
+ "move": "no-op",
2688
+ "view": "turn right"
2689
+ },
2690
+ {
2691
+ "move": "no-op",
2692
+ "view": "turn right"
2693
+ },
2694
+ {
2695
+ "move": "no-op",
2696
+ "view": "turn right"
2697
+ },
2698
+ {
2699
+ "move": "no-op",
2700
+ "view": "turn right"
2701
+ },
2702
+ {
2703
+ "move": "no-op",
2704
+ "view": "turn right"
2705
+ },
2706
+ {
2707
+ "move": "no-op",
2708
+ "view": "turn right"
2709
+ },
2710
+ {
2711
+ "move": "no-op",
2712
+ "view": "turn right"
2713
+ },
2714
+ {
2715
+ "move": "no-op",
2716
+ "view": "turn right"
2717
+ },
2718
+ {
2719
+ "move": "no-op",
2720
+ "view": "turn right"
2721
+ },
2722
+ {
2723
+ "move": "no-op",
2724
+ "view": "turn right"
2725
+ },
2726
+ {
2727
+ "move": "no-op",
2728
+ "view": "turn right"
2729
+ },
2730
+ {
2731
+ "move": "no-op",
2732
+ "view": "turn right"
2733
+ },
2734
+ {
2735
+ "move": "no-op",
2736
+ "view": "turn right"
2737
+ },
2738
+ {
2739
+ "move": "no-op",
2740
+ "view": "turn right"
2741
+ },
2742
+ {
2743
+ "move": "no-op",
2744
+ "view": "turn right"
2745
+ },
2746
+ {
2747
+ "move": "no-op",
2748
+ "view": "turn right"
2749
+ },
2750
+ {
2751
+ "move": "no-op",
2752
+ "view": "turn right"
2753
+ },
2754
+ {
2755
+ "move": "no-op",
2756
+ "view": "turn right"
2757
+ },
2758
+ {
2759
+ "move": "no-op",
2760
+ "view": "turn right"
2761
+ },
2762
+ {
2763
+ "move": "no-op",
2764
+ "view": "turn right"
2765
+ },
2766
+ {
2767
+ "move": "no-op",
2768
+ "view": "turn right"
2769
+ },
2770
+ {
2771
+ "move": "no-op",
2772
+ "view": "turn right"
2773
+ },
2774
+ {
2775
+ "move": "no-op",
2776
+ "view": "turn right"
2777
+ },
2778
+ {
2779
+ "move": "no-op",
2780
+ "view": "turn right"
2781
+ },
2782
+ {
2783
+ "move": "no-op",
2784
+ "view": "turn right"
2785
+ },
2786
+ {
2787
+ "move": "no-op",
2788
+ "view": "turn right"
2789
+ },
2790
+ {
2791
+ "move": "no-op",
2792
+ "view": "turn right"
2793
+ },
2794
+ {
2795
+ "move": "no-op",
2796
+ "view": "turn right"
2797
+ },
2798
+ {
2799
+ "move": "no-op",
2800
+ "view": "turn right"
2801
+ },
2802
+ {
2803
+ "move": "no-op",
2804
+ "view": "turn right"
2805
+ },
2806
+ {
2807
+ "move": "no-op",
2808
+ "view": "turn right"
2809
+ },
2810
+ {
2811
+ "move": "no-op",
2812
+ "view": "turn right"
2813
+ },
2814
+ {
2815
+ "move": "no-op",
2816
+ "view": "turn right"
2817
+ },
2818
+ {
2819
+ "move": "no-op",
2820
+ "view": "turn right"
2821
+ },
2822
+ {
2823
+ "move": "no-op",
2824
+ "view": "turn right"
2825
+ },
2826
+ {
2827
+ "move": "no-op",
2828
+ "view": "turn right"
2829
+ },
2830
+ {
2831
+ "move": "no-op",
2832
+ "view": "turn right"
2833
+ },
2834
+ {
2835
+ "move": "no-op",
2836
+ "view": "turn right"
2837
+ },
2838
+ {
2839
+ "move": "no-op",
2840
+ "view": "turn right"
2841
+ },
2842
+ {
2843
+ "move": "no-op",
2844
+ "view": "turn right"
2845
+ },
2846
+ {
2847
+ "move": "no-op",
2848
+ "view": "turn right"
2849
+ },
2850
+ {
2851
+ "move": "no-op",
2852
+ "view": "turn right"
2853
+ },
2854
+ {
2855
+ "move": "no-op",
2856
+ "view": "turn right"
2857
+ },
2858
+ {
2859
+ "move": "no-op",
2860
+ "view": "turn right"
2861
+ },
2862
+ {
2863
+ "move": "no-op",
2864
+ "view": "turn right"
2865
+ },
2866
+ {
2867
+ "move": "no-op",
2868
+ "view": "turn right"
2869
+ },
2870
+ {
2871
+ "move": "no-op",
2872
+ "view": "turn right"
2873
+ },
2874
+ {
2875
+ "move": "no-op",
2876
+ "view": "turn right"
2877
+ },
2878
+ {
2879
+ "move": "no-op",
2880
+ "view": "turn right"
2881
+ },
2882
+ {
2883
+ "move": "no-op",
2884
+ "view": "turn right"
2885
+ },
2886
+ {
2887
+ "move": "no-op",
2888
+ "view": "turn right"
2889
+ },
2890
+ {
2891
+ "move": "no-op",
2892
+ "view": "turn right"
2893
+ },
2894
+ {
2895
+ "move": "no-op",
2896
+ "view": "turn right"
2897
+ },
2898
+ {
2899
+ "move": "no-op",
2900
+ "view": "turn right"
2901
+ },
2902
+ {
2903
+ "move": "no-op",
2904
+ "view": "turn right"
2905
+ },
2906
+ {
2907
+ "move": "no-op",
2908
+ "view": "turn right"
2909
+ },
2910
+ {
2911
+ "move": "no-op",
2912
+ "view": "turn right"
2913
+ },
2914
+ {
2915
+ "move": "no-op",
2916
+ "view": "turn right"
2917
+ },
2918
+ {
2919
+ "move": "no-op",
2920
+ "view": "turn right"
2921
+ },
2922
+ {
2923
+ "move": "no-op",
2924
+ "view": "turn right"
2925
+ },
2926
+ {
2927
+ "move": "no-op",
2928
+ "view": "turn right"
2929
+ },
2930
+ {
2931
+ "move": "no-op",
2932
+ "view": "turn right"
2933
+ },
2934
+ {
2935
+ "move": "no-op",
2936
+ "view": "turn right"
2937
+ },
2938
+ {
2939
+ "move": "no-op",
2940
+ "view": "turn right"
2941
+ },
2942
+ {
2943
+ "move": "no-op",
2944
+ "view": "turn right"
2945
+ },
2946
+ {
2947
+ "move": "no-op",
2948
+ "view": "turn right"
2949
+ },
2950
+ {
2951
+ "move": "no-op",
2952
+ "view": "turn right"
2953
+ },
2954
+ {
2955
+ "move": "no-op",
2956
+ "view": "turn right"
2957
+ },
2958
+ {
2959
+ "move": "no-op",
2960
+ "view": "turn right"
2961
+ },
2962
+ {
2963
+ "move": "no-op",
2964
+ "view": "turn right"
2965
+ },
2966
+ {
2967
+ "move": "no-op",
2968
+ "view": "turn right"
2969
+ },
2970
+ {
2971
+ "move": "no-op",
2972
+ "view": "turn right"
2973
+ },
2974
+ {
2975
+ "move": "no-op",
2976
+ "view": "turn right"
2977
+ },
2978
+ {
2979
+ "move": "no-op",
2980
+ "view": "turn right"
2981
+ },
2982
+ {
2983
+ "move": "no-op",
2984
+ "view": "turn right"
2985
+ },
2986
+ {
2987
+ "move": "no-op",
2988
+ "view": "turn right"
2989
+ },
2990
+ {
2991
+ "move": "no-op",
2992
+ "view": "turn right"
2993
+ },
2994
+ {
2995
+ "move": "no-op",
2996
+ "view": "turn right"
2997
+ },
2998
+ {
2999
+ "move": "no-op",
3000
+ "view": "turn right"
3001
+ },
3002
+ {
3003
+ "move": "no-op",
3004
+ "view": "turn right"
3005
+ },
3006
+ {
3007
+ "move": "no-op",
3008
+ "view": "turn right"
3009
+ },
3010
+ {
3011
+ "move": "no-op",
3012
+ "view": "turn right"
3013
+ },
3014
+ {
3015
+ "move": "no-op",
3016
+ "view": "turn right"
3017
+ },
3018
+ {
3019
+ "move": "no-op",
3020
+ "view": "turn right"
3021
+ },
3022
+ {
3023
+ "move": "no-op",
3024
+ "view": "turn right"
3025
+ },
3026
+ {
3027
+ "move": "no-op",
3028
+ "view": "turn right"
3029
+ },
3030
+ {
3031
+ "move": "no-op",
3032
+ "view": "turn right"
3033
+ },
3034
+ {
3035
+ "move": "no-op",
3036
+ "view": "turn right"
3037
+ },
3038
+ {
3039
+ "move": "no-op",
3040
+ "view": "turn right"
3041
+ },
3042
+ {
3043
+ "move": "no-op",
3044
+ "view": "turn right"
3045
+ },
3046
+ {
3047
+ "move": "no-op",
3048
+ "view": "turn right"
3049
+ },
3050
+ {
3051
+ "move": "no-op",
3052
+ "view": "turn right"
3053
+ },
3054
+ {
3055
+ "move": "no-op",
3056
+ "view": "turn right"
3057
+ },
3058
+ {
3059
+ "move": "no-op",
3060
+ "view": "turn right"
3061
+ },
3062
+ {
3063
+ "move": "no-op",
3064
+ "view": "turn right"
3065
+ },
3066
+ {
3067
+ "move": "no-op",
3068
+ "view": "turn right"
3069
+ },
3070
+ {
3071
+ "move": "no-op",
3072
+ "view": "turn right"
3073
+ },
3074
+ {
3075
+ "move": "no-op",
3076
+ "view": "turn right"
3077
+ },
3078
+ {
3079
+ "move": "no-op",
3080
+ "view": "turn right"
3081
+ },
3082
+ {
3083
+ "move": "no-op",
3084
+ "view": "turn right"
3085
+ },
3086
+ {
3087
+ "move": "no-op",
3088
+ "view": "turn right"
3089
+ },
3090
+ {
3091
+ "move": "no-op",
3092
+ "view": "turn right"
3093
+ },
3094
+ {
3095
+ "move": "no-op",
3096
+ "view": "turn right"
3097
+ },
3098
+ {
3099
+ "move": "no-op",
3100
+ "view": "turn right"
3101
+ },
3102
+ {
3103
+ "move": "no-op",
3104
+ "view": "turn right"
3105
+ },
3106
+ {
3107
+ "move": "no-op",
3108
+ "view": "turn right"
3109
+ },
3110
+ {
3111
+ "move": "no-op",
3112
+ "view": "turn right"
3113
+ },
3114
+ {
3115
+ "move": "no-op",
3116
+ "view": "turn right"
3117
+ },
3118
+ {
3119
+ "move": "no-op",
3120
+ "view": "turn right"
3121
+ },
3122
+ {
3123
+ "move": "no-op",
3124
+ "view": "turn right"
3125
+ },
3126
+ {
3127
+ "move": "no-op",
3128
+ "view": "turn right"
3129
+ },
3130
+ {
3131
+ "move": "no-op",
3132
+ "view": "turn right"
3133
+ },
3134
+ {
3135
+ "move": "no-op",
3136
+ "view": "turn right"
3137
+ },
3138
+ {
3139
+ "move": "no-op",
3140
+ "view": "turn right"
3141
+ },
3142
+ {
3143
+ "move": "no-op",
3144
+ "view": "turn right"
3145
+ },
3146
+ {
3147
+ "move": "no-op",
3148
+ "view": "turn right"
3149
+ },
3150
+ {
3151
+ "move": "no-op",
3152
+ "view": "turn right"
3153
+ },
3154
+ {
3155
+ "move": "no-op",
3156
+ "view": "turn right"
3157
+ },
3158
+ {
3159
+ "move": "no-op",
3160
+ "view": "turn right"
3161
+ },
3162
+ {
3163
+ "move": "no-op",
3164
+ "view": "turn right"
3165
+ },
3166
+ {
3167
+ "move": "no-op",
3168
+ "view": "turn right"
3169
+ },
3170
+ {
3171
+ "move": "no-op",
3172
+ "view": "turn right"
3173
+ },
3174
+ {
3175
+ "move": "no-op",
3176
+ "view": "turn right"
3177
+ },
3178
+ {
3179
+ "move": "no-op",
3180
+ "view": "turn right"
3181
+ },
3182
+ {
3183
+ "move": "no-op",
3184
+ "view": "turn right"
3185
+ },
3186
+ {
3187
+ "move": "no-op",
3188
+ "view": "turn right"
3189
+ },
3190
+ {
3191
+ "move": "no-op",
3192
+ "view": "turn right"
3193
+ },
3194
+ {
3195
+ "move": "no-op",
3196
+ "view": "turn right"
3197
+ },
3198
+ {
3199
+ "move": "no-op",
3200
+ "view": "turn right"
3201
+ },
3202
+ {
3203
+ "move": "no-op",
3204
+ "view": "turn right"
3205
+ },
3206
+ {
3207
+ "move": "no-op",
3208
+ "view": "turn right"
3209
+ },
3210
+ {
3211
+ "move": "no-op",
3212
+ "view": "turn right"
3213
+ },
3214
+ {
3215
+ "move": "no-op",
3216
+ "view": "turn right"
3217
+ },
3218
+ {
3219
+ "move": "no-op",
3220
+ "view": "turn right"
3221
+ },
3222
+ {
3223
+ "move": "no-op",
3224
+ "view": "turn right"
3225
+ },
3226
+ {
3227
+ "move": "no-op",
3228
+ "view": "turn right"
3229
+ },
3230
+ {
3231
+ "move": "no-op",
3232
+ "view": "turn right"
3233
+ },
3234
+ {
3235
+ "move": "no-op",
3236
+ "view": "turn right"
3237
+ },
3238
+ {
3239
+ "move": "no-op",
3240
+ "view": "turn right"
3241
+ },
3242
+ {
3243
+ "move": "no-op",
3244
+ "view": "turn right"
3245
+ },
3246
+ {
3247
+ "move": "no-op",
3248
+ "view": "turn right"
3249
+ },
3250
+ {
3251
+ "move": "no-op",
3252
+ "view": "turn right"
3253
+ },
3254
+ {
3255
+ "move": "no-op",
3256
+ "view": "turn right"
3257
+ },
3258
+ {
3259
+ "move": "no-op",
3260
+ "view": "turn right"
3261
+ },
3262
+ {
3263
+ "move": "no-op",
3264
+ "view": "turn right"
3265
+ },
3266
+ {
3267
+ "move": "no-op",
3268
+ "view": "turn right"
3269
+ },
3270
+ {
3271
+ "move": "no-op",
3272
+ "view": "turn right"
3273
+ },
3274
+ {
3275
+ "move": "no-op",
3276
+ "view": "turn right"
3277
+ },
3278
+ {
3279
+ "move": "no-op",
3280
+ "view": "turn right"
3281
+ },
3282
+ {
3283
+ "move": "no-op",
3284
+ "view": "turn right"
3285
+ },
3286
+ {
3287
+ "move": "no-op",
3288
+ "view": "turn right"
3289
+ },
3290
+ {
3291
+ "move": "no-op",
3292
+ "view": "turn right"
3293
+ },
3294
+ {
3295
+ "move": "no-op",
3296
+ "view": "turn right"
3297
+ },
3298
+ {
3299
+ "move": "no-op",
3300
+ "view": "turn right"
3301
+ },
3302
+ {
3303
+ "move": "no-op",
3304
+ "view": "turn right"
3305
+ },
3306
+ {
3307
+ "move": "no-op",
3308
+ "view": "turn right"
3309
+ },
3310
+ {
3311
+ "move": "no-op",
3312
+ "view": "turn right"
3313
+ },
3314
+ {
3315
+ "move": "no-op",
3316
+ "view": "turn right"
3317
+ },
3318
+ {
3319
+ "move": "no-op",
3320
+ "view": "turn right"
3321
+ },
3322
+ {
3323
+ "move": "no-op",
3324
+ "view": "turn right"
3325
+ },
3326
+ {
3327
+ "move": "no-op",
3328
+ "view": "turn right"
3329
+ },
3330
+ {
3331
+ "move": "no-op",
3332
+ "view": "turn right"
3333
+ },
3334
+ {
3335
+ "move": "no-op",
3336
+ "view": "turn right"
3337
+ },
3338
+ {
3339
+ "move": "no-op",
3340
+ "view": "turn right"
3341
+ },
3342
+ {
3343
+ "move": "no-op",
3344
+ "view": "turn right"
3345
+ },
3346
+ {
3347
+ "move": "no-op",
3348
+ "view": "turn right"
3349
+ },
3350
+ {
3351
+ "move": "no-op",
3352
+ "view": "turn right"
3353
+ },
3354
+ {
3355
+ "move": "no-op",
3356
+ "view": "turn right"
3357
+ },
3358
+ {
3359
+ "move": "no-op",
3360
+ "view": "turn right"
3361
+ },
3362
+ {
3363
+ "move": "no-op",
3364
+ "view": "turn right"
3365
+ },
3366
+ {
3367
+ "move": "no-op",
3368
+ "view": "turn right"
3369
+ },
3370
+ {
3371
+ "move": "no-op",
3372
+ "view": "turn right"
3373
+ },
3374
+ {
3375
+ "move": "no-op",
3376
+ "view": "turn right"
3377
+ },
3378
+ {
3379
+ "move": "no-op",
3380
+ "view": "turn right"
3381
+ },
3382
+ {
3383
+ "move": "no-op",
3384
+ "view": "turn right"
3385
+ },
3386
+ {
3387
+ "move": "no-op",
3388
+ "view": "turn right"
3389
+ },
3390
+ {
3391
+ "move": "no-op",
3392
+ "view": "turn right"
3393
+ },
3394
+ {
3395
+ "move": "no-op",
3396
+ "view": "turn right"
3397
+ },
3398
+ {
3399
+ "move": "no-op",
3400
+ "view": "turn right"
3401
+ },
3402
+ {
3403
+ "move": "no-op",
3404
+ "view": "turn right"
3405
+ },
3406
+ {
3407
+ "move": "no-op",
3408
+ "view": "turn right"
3409
+ },
3410
+ {
3411
+ "move": "no-op",
3412
+ "view": "turn right"
3413
+ },
3414
+ {
3415
+ "move": "no-op",
3416
+ "view": "turn right"
3417
+ },
3418
+ {
3419
+ "move": "no-op",
3420
+ "view": "turn right"
3421
+ },
3422
+ {
3423
+ "move": "no-op",
3424
+ "view": "turn right"
3425
+ },
3426
+ {
3427
+ "move": "no-op",
3428
+ "view": "turn right"
3429
+ },
3430
+ {
3431
+ "move": "no-op",
3432
+ "view": "turn right"
3433
+ },
3434
+ {
3435
+ "move": "no-op",
3436
+ "view": "turn right"
3437
+ },
3438
+ {
3439
+ "move": "no-op",
3440
+ "view": "turn right"
3441
+ },
3442
+ {
3443
+ "move": "no-op",
3444
+ "view": "turn right"
3445
+ },
3446
+ {
3447
+ "move": "no-op",
3448
+ "view": "turn right"
3449
+ },
3450
+ {
3451
+ "move": "no-op",
3452
+ "view": "turn right"
3453
+ },
3454
+ {
3455
+ "move": "no-op",
3456
+ "view": "turn right"
3457
+ },
3458
+ {
3459
+ "move": "no-op",
3460
+ "view": "turn right"
3461
+ },
3462
+ {
3463
+ "move": "no-op",
3464
+ "view": "turn right"
3465
+ },
3466
+ {
3467
+ "move": "no-op",
3468
+ "view": "turn right"
3469
+ },
3470
+ {
3471
+ "move": "no-op",
3472
+ "view": "turn right"
3473
+ },
3474
+ {
3475
+ "move": "no-op",
3476
+ "view": "turn right"
3477
+ },
3478
+ {
3479
+ "move": "no-op",
3480
+ "view": "turn right"
3481
+ },
3482
+ {
3483
+ "move": "no-op",
3484
+ "view": "turn right"
3485
+ },
3486
+ {
3487
+ "move": "no-op",
3488
+ "view": "turn right"
3489
+ },
3490
+ {
3491
+ "move": "no-op",
3492
+ "view": "turn right"
3493
+ },
3494
+ {
3495
+ "move": "no-op",
3496
+ "view": "turn right"
3497
+ },
3498
+ {
3499
+ "move": "no-op",
3500
+ "view": "turn right"
3501
+ },
3502
+ {
3503
+ "move": "no-op",
3504
+ "view": "turn right"
3505
+ },
3506
+ {
3507
+ "move": "no-op",
3508
+ "view": "turn right"
3509
+ },
3510
+ {
3511
+ "move": "no-op",
3512
+ "view": "turn right"
3513
+ },
3514
+ {
3515
+ "move": "no-op",
3516
+ "view": "turn right"
3517
+ },
3518
+ {
3519
+ "move": "no-op",
3520
+ "view": "turn right"
3521
+ },
3522
+ {
3523
+ "move": "no-op",
3524
+ "view": "turn right"
3525
+ },
3526
+ {
3527
+ "move": "no-op",
3528
+ "view": "turn right"
3529
+ },
3530
+ {
3531
+ "move": "no-op",
3532
+ "view": "turn right"
3533
+ },
3534
+ {
3535
+ "move": "no-op",
3536
+ "view": "turn right"
3537
+ },
3538
+ {
3539
+ "move": "no-op",
3540
+ "view": "turn right"
3541
+ },
3542
+ {
3543
+ "move": "no-op",
3544
+ "view": "turn right"
3545
+ },
3546
+ {
3547
+ "move": "no-op",
3548
+ "view": "turn right"
3549
+ },
3550
+ {
3551
+ "move": "no-op",
3552
+ "view": "turn right"
3553
+ },
3554
+ {
3555
+ "move": "no-op",
3556
+ "view": "turn right"
3557
+ },
3558
+ {
3559
+ "move": "no-op",
3560
+ "view": "turn right"
3561
+ },
3562
+ {
3563
+ "move": "no-op",
3564
+ "view": "turn right"
3565
+ },
3566
+ {
3567
+ "move": "no-op",
3568
+ "view": "turn up"
3569
+ },
3570
+ {
3571
+ "move": "no-op",
3572
+ "view": "turn up"
3573
+ },
3574
+ {
3575
+ "move": "no-op",
3576
+ "view": "turn up"
3577
+ },
3578
+ {
3579
+ "move": "no-op",
3580
+ "view": "turn up"
3581
+ },
3582
+ {
3583
+ "move": "no-op",
3584
+ "view": "turn up"
3585
+ },
3586
+ {
3587
+ "move": "no-op",
3588
+ "view": "turn up"
3589
+ },
3590
+ {
3591
+ "move": "no-op",
3592
+ "view": "turn up"
3593
+ },
3594
+ {
3595
+ "move": "no-op",
3596
+ "view": "turn up"
3597
+ },
3598
+ {
3599
+ "move": "no-op",
3600
+ "view": "turn up"
3601
+ },
3602
+ {
3603
+ "move": "no-op",
3604
+ "view": "turn up"
3605
+ },
3606
+ {
3607
+ "move": "no-op",
3608
+ "view": "turn up"
3609
+ },
3610
+ {
3611
+ "move": "no-op",
3612
+ "view": "turn up"
3613
+ },
3614
+ {
3615
+ "move": "no-op",
3616
+ "view": "turn up"
3617
+ },
3618
+ {
3619
+ "move": "no-op",
3620
+ "view": "turn up"
3621
+ },
3622
+ {
3623
+ "move": "no-op",
3624
+ "view": "turn up"
3625
+ },
3626
+ {
3627
+ "move": "no-op",
3628
+ "view": "turn up"
3629
+ },
3630
+ {
3631
+ "move": "no-op",
3632
+ "view": "turn up"
3633
+ },
3634
+ {
3635
+ "move": "no-op",
3636
+ "view": "turn up"
3637
+ },
3638
+ {
3639
+ "move": "no-op",
3640
+ "view": "turn up"
3641
+ },
3642
+ {
3643
+ "move": "no-op",
3644
+ "view": "turn up"
3645
+ },
3646
+ {
3647
+ "move": "no-op",
3648
+ "view": "turn up"
3649
+ },
3650
+ {
3651
+ "move": "no-op",
3652
+ "view": "turn up"
3653
+ },
3654
+ {
3655
+ "move": "no-op",
3656
+ "view": "turn up"
3657
+ },
3658
+ {
3659
+ "move": "no-op",
3660
+ "view": "turn up"
3661
+ },
3662
+ {
3663
+ "move": "no-op",
3664
+ "view": "turn up"
3665
+ },
3666
+ {
3667
+ "move": "no-op",
3668
+ "view": "turn up"
3669
+ },
3670
+ {
3671
+ "move": "no-op",
3672
+ "view": "turn up"
3673
+ },
3674
+ {
3675
+ "move": "no-op",
3676
+ "view": "turn up"
3677
+ },
3678
+ {
3679
+ "move": "no-op",
3680
+ "view": "turn up"
3681
+ },
3682
+ {
3683
+ "move": "no-op",
3684
+ "view": "turn up"
3685
+ },
3686
+ {
3687
+ "move": "no-op",
3688
+ "view": "turn up"
3689
+ },
3690
+ {
3691
+ "move": "no-op",
3692
+ "view": "turn up"
3693
+ },
3694
+ {
3695
+ "move": "no-op",
3696
+ "view": "turn up"
3697
+ },
3698
+ {
3699
+ "move": "no-op",
3700
+ "view": "turn up"
3701
+ },
3702
+ {
3703
+ "move": "no-op",
3704
+ "view": "turn up"
3705
+ },
3706
+ {
3707
+ "move": "no-op",
3708
+ "view": "turn up"
3709
+ },
3710
+ {
3711
+ "move": "no-op",
3712
+ "view": "turn up"
3713
+ },
3714
+ {
3715
+ "move": "no-op",
3716
+ "view": "turn up"
3717
+ },
3718
+ {
3719
+ "move": "no-op",
3720
+ "view": "turn up"
3721
+ },
3722
+ {
3723
+ "move": "no-op",
3724
+ "view": "turn up"
3725
+ },
3726
+ {
3727
+ "move": "no-op",
3728
+ "view": "turn up"
3729
+ },
3730
+ {
3731
+ "move": "no-op",
3732
+ "view": "turn up"
3733
+ },
3734
+ {
3735
+ "move": "no-op",
3736
+ "view": "turn up"
3737
+ },
3738
+ {
3739
+ "move": "no-op",
3740
+ "view": "turn up"
3741
+ },
3742
+ {
3743
+ "move": "no-op",
3744
+ "view": "turn up"
3745
+ },
3746
+ {
3747
+ "move": "no-op",
3748
+ "view": "turn up"
3749
+ },
3750
+ {
3751
+ "move": "no-op",
3752
+ "view": "turn up"
3753
+ },
3754
+ {
3755
+ "move": "no-op",
3756
+ "view": "turn up"
3757
+ },
3758
+ {
3759
+ "move": "no-op",
3760
+ "view": "turn up"
3761
+ },
3762
+ {
3763
+ "move": "no-op",
3764
+ "view": "turn up"
3765
+ },
3766
+ {
3767
+ "move": "no-op",
3768
+ "view": "turn up"
3769
+ },
3770
+ {
3771
+ "move": "no-op",
3772
+ "view": "turn up"
3773
+ },
3774
+ {
3775
+ "move": "no-op",
3776
+ "view": "turn up"
3777
+ },
3778
+ {
3779
+ "move": "no-op",
3780
+ "view": "turn up"
3781
+ },
3782
+ {
3783
+ "move": "no-op",
3784
+ "view": "turn up"
3785
+ },
3786
+ {
3787
+ "move": "no-op",
3788
+ "view": "turn up"
3789
+ },
3790
+ {
3791
+ "move": "no-op",
3792
+ "view": "turn up"
3793
+ },
3794
+ {
3795
+ "move": "no-op",
3796
+ "view": "turn up"
3797
+ },
3798
+ {
3799
+ "move": "no-op",
3800
+ "view": "turn up"
3801
+ },
3802
+ {
3803
+ "move": "no-op",
3804
+ "view": "turn up"
3805
+ },
3806
+ {
3807
+ "move": "no-op",
3808
+ "view": "turn up"
3809
+ },
3810
+ {
3811
+ "move": "no-op",
3812
+ "view": "turn up"
3813
+ },
3814
+ {
3815
+ "move": "no-op",
3816
+ "view": "turn up"
3817
+ },
3818
+ {
3819
+ "move": "no-op",
3820
+ "view": "turn up"
3821
+ },
3822
+ {
3823
+ "move": "no-op",
3824
+ "view": "turn up"
3825
+ },
3826
+ {
3827
+ "move": "no-op",
3828
+ "view": "turn up"
3829
+ },
3830
+ {
3831
+ "move": "no-op",
3832
+ "view": "turn up"
3833
+ },
3834
+ {
3835
+ "move": "no-op",
3836
+ "view": "turn up"
3837
+ },
3838
+ {
3839
+ "move": "no-op",
3840
+ "view": "turn up"
3841
+ },
3842
+ {
3843
+ "move": "no-op",
3844
+ "view": "turn up"
3845
+ },
3846
+ {
3847
+ "move": "no-op",
3848
+ "view": "turn up"
3849
+ },
3850
+ {
3851
+ "move": "no-op",
3852
+ "view": "turn up"
3853
+ },
3854
+ {
3855
+ "move": "no-op",
3856
+ "view": "turn up"
3857
+ },
3858
+ {
3859
+ "move": "no-op",
3860
+ "view": "turn up"
3861
+ },
3862
+ {
3863
+ "move": "no-op",
3864
+ "view": "turn up"
3865
+ },
3866
+ {
3867
+ "move": "no-op",
3868
+ "view": "turn up"
3869
+ },
3870
+ {
3871
+ "move": "no-op",
3872
+ "view": "turn up"
3873
+ },
3874
+ {
3875
+ "move": "no-op",
3876
+ "view": "turn up"
3877
+ },
3878
+ {
3879
+ "move": "no-op",
3880
+ "view": "turn up"
3881
+ },
3882
+ {
3883
+ "move": "no-op",
3884
+ "view": "turn up"
3885
+ },
3886
+ {
3887
+ "move": "no-op",
3888
+ "view": "turn up"
3889
+ },
3890
+ {
3891
+ "move": "no-op",
3892
+ "view": "turn down"
3893
+ },
3894
+ {
3895
+ "move": "no-op",
3896
+ "view": "turn down"
3897
+ },
3898
+ {
3899
+ "move": "no-op",
3900
+ "view": "turn down"
3901
+ },
3902
+ {
3903
+ "move": "no-op",
3904
+ "view": "turn down"
3905
+ },
3906
+ {
3907
+ "move": "no-op",
3908
+ "view": "turn down"
3909
+ },
3910
+ {
3911
+ "move": "no-op",
3912
+ "view": "turn down"
3913
+ },
3914
+ {
3915
+ "move": "no-op",
3916
+ "view": "turn down"
3917
+ },
3918
+ {
3919
+ "move": "no-op",
3920
+ "view": "turn down"
3921
+ },
3922
+ {
3923
+ "move": "no-op",
3924
+ "view": "turn down"
3925
+ },
3926
+ {
3927
+ "move": "no-op",
3928
+ "view": "turn down"
3929
+ },
3930
+ {
3931
+ "move": "no-op",
3932
+ "view": "turn down"
3933
+ },
3934
+ {
3935
+ "move": "no-op",
3936
+ "view": "turn down"
3937
+ },
3938
+ {
3939
+ "move": "no-op",
3940
+ "view": "turn down"
3941
+ },
3942
+ {
3943
+ "move": "no-op",
3944
+ "view": "turn down"
3945
+ },
3946
+ {
3947
+ "move": "no-op",
3948
+ "view": "turn down"
3949
+ },
3950
+ {
3951
+ "move": "no-op",
3952
+ "view": "turn down"
3953
+ },
3954
+ {
3955
+ "move": "no-op",
3956
+ "view": "turn down"
3957
+ },
3958
+ {
3959
+ "move": "no-op",
3960
+ "view": "turn down"
3961
+ },
3962
+ {
3963
+ "move": "no-op",
3964
+ "view": "turn down"
3965
+ },
3966
+ {
3967
+ "move": "no-op",
3968
+ "view": "turn down"
3969
+ },
3970
+ {
3971
+ "move": "no-op",
3972
+ "view": "turn down"
3973
+ },
3974
+ {
3975
+ "move": "no-op",
3976
+ "view": "turn down"
3977
+ },
3978
+ {
3979
+ "move": "no-op",
3980
+ "view": "turn down"
3981
+ },
3982
+ {
3983
+ "move": "no-op",
3984
+ "view": "turn down"
3985
+ },
3986
+ {
3987
+ "move": "no-op",
3988
+ "view": "turn down"
3989
+ },
3990
+ {
3991
+ "move": "no-op",
3992
+ "view": "turn down"
3993
+ },
3994
+ {
3995
+ "move": "no-op",
3996
+ "view": "turn down"
3997
+ },
3998
+ {
3999
+ "move": "no-op",
4000
+ "view": "turn down"
4001
+ },
4002
+ {
4003
+ "move": "no-op",
4004
+ "view": "turn down"
4005
+ },
4006
+ {
4007
+ "move": "no-op",
4008
+ "view": "turn down"
4009
+ },
4010
+ {
4011
+ "move": "no-op",
4012
+ "view": "turn down"
4013
+ },
4014
+ {
4015
+ "move": "no-op",
4016
+ "view": "turn down"
4017
+ },
4018
+ {
4019
+ "move": "no-op",
4020
+ "view": "turn down"
4021
+ },
4022
+ {
4023
+ "move": "no-op",
4024
+ "view": "turn down"
4025
+ },
4026
+ {
4027
+ "move": "no-op",
4028
+ "view": "turn down"
4029
+ },
4030
+ {
4031
+ "move": "no-op",
4032
+ "view": "turn down"
4033
+ },
4034
+ {
4035
+ "move": "no-op",
4036
+ "view": "turn down"
4037
+ },
4038
+ {
4039
+ "move": "no-op",
4040
+ "view": "turn down"
4041
+ },
4042
+ {
4043
+ "move": "no-op",
4044
+ "view": "turn down"
4045
+ },
4046
+ {
4047
+ "move": "no-op",
4048
+ "view": "turn down"
4049
+ },
4050
+ {
4051
+ "move": "no-op",
4052
+ "view": "turn down"
4053
+ },
4054
+ {
4055
+ "move": "no-op",
4056
+ "view": "turn down"
4057
+ },
4058
+ {
4059
+ "move": "no-op",
4060
+ "view": "turn down"
4061
+ },
4062
+ {
4063
+ "move": "no-op",
4064
+ "view": "turn down"
4065
+ },
4066
+ {
4067
+ "move": "no-op",
4068
+ "view": "turn down"
4069
+ },
4070
+ {
4071
+ "move": "no-op",
4072
+ "view": "turn down"
4073
+ },
4074
+ {
4075
+ "move": "no-op",
4076
+ "view": "turn down"
4077
+ },
4078
+ {
4079
+ "move": "no-op",
4080
+ "view": "turn down"
4081
+ },
4082
+ {
4083
+ "move": "no-op",
4084
+ "view": "turn down"
4085
+ },
4086
+ {
4087
+ "move": "no-op",
4088
+ "view": "turn down"
4089
+ },
4090
+ {
4091
+ "move": "no-op",
4092
+ "view": "turn down"
4093
+ },
4094
+ {
4095
+ "move": "no-op",
4096
+ "view": "turn down"
4097
+ },
4098
+ {
4099
+ "move": "no-op",
4100
+ "view": "turn down"
4101
+ },
4102
+ {
4103
+ "move": "no-op",
4104
+ "view": "turn down"
4105
+ },
4106
+ {
4107
+ "move": "no-op",
4108
+ "view": "turn down"
4109
+ },
4110
+ {
4111
+ "move": "no-op",
4112
+ "view": "turn down"
4113
+ },
4114
+ {
4115
+ "move": "no-op",
4116
+ "view": "turn down"
4117
+ },
4118
+ {
4119
+ "move": "no-op",
4120
+ "view": "turn down"
4121
+ },
4122
+ {
4123
+ "move": "no-op",
4124
+ "view": "turn down"
4125
+ },
4126
+ {
4127
+ "move": "no-op",
4128
+ "view": "turn down"
4129
+ },
4130
+ {
4131
+ "move": "no-op",
4132
+ "view": "turn down"
4133
+ },
4134
+ {
4135
+ "move": "no-op",
4136
+ "view": "turn down"
4137
+ },
4138
+ {
4139
+ "move": "no-op",
4140
+ "view": "turn down"
4141
+ },
4142
+ {
4143
+ "move": "no-op",
4144
+ "view": "turn down"
4145
+ },
4146
+ {
4147
+ "move": "no-op",
4148
+ "view": "turn down"
4149
+ },
4150
+ {
4151
+ "move": "no-op",
4152
+ "view": "turn down"
4153
+ },
4154
+ {
4155
+ "move": "no-op",
4156
+ "view": "turn down"
4157
+ },
4158
+ {
4159
+ "move": "no-op",
4160
+ "view": "turn down"
4161
+ },
4162
+ {
4163
+ "move": "no-op",
4164
+ "view": "turn down"
4165
+ },
4166
+ {
4167
+ "move": "no-op",
4168
+ "view": "turn down"
4169
+ },
4170
+ {
4171
+ "move": "no-op",
4172
+ "view": "turn down"
4173
+ },
4174
+ {
4175
+ "move": "no-op",
4176
+ "view": "turn down"
4177
+ },
4178
+ {
4179
+ "move": "no-op",
4180
+ "view": "turn down"
4181
+ },
4182
+ {
4183
+ "move": "no-op",
4184
+ "view": "turn down"
4185
+ },
4186
+ {
4187
+ "move": "no-op",
4188
+ "view": "turn down"
4189
+ },
4190
+ {
4191
+ "move": "no-op",
4192
+ "view": "turn down"
4193
+ },
4194
+ {
4195
+ "move": "no-op",
4196
+ "view": "turn down"
4197
+ },
4198
+ {
4199
+ "move": "no-op",
4200
+ "view": "turn down"
4201
+ },
4202
+ {
4203
+ "move": "no-op",
4204
+ "view": "turn down"
4205
+ },
4206
+ {
4207
+ "move": "no-op",
4208
+ "view": "turn down"
4209
+ },
4210
+ {
4211
+ "move": "no-op",
4212
+ "view": "turn down"
4213
+ },
4214
+ {
4215
+ "move": "no-op",
4216
+ "view": "turn left"
4217
+ },
4218
+ {
4219
+ "move": "no-op",
4220
+ "view": "turn left"
4221
+ },
4222
+ {
4223
+ "move": "no-op",
4224
+ "view": "turn left"
4225
+ },
4226
+ {
4227
+ "move": "no-op",
4228
+ "view": "turn left"
4229
+ },
4230
+ {
4231
+ "move": "no-op",
4232
+ "view": "turn left"
4233
+ },
4234
+ {
4235
+ "move": "no-op",
4236
+ "view": "turn left"
4237
+ },
4238
+ {
4239
+ "move": "no-op",
4240
+ "view": "turn left"
4241
+ },
4242
+ {
4243
+ "move": "no-op",
4244
+ "view": "turn left"
4245
+ },
4246
+ {
4247
+ "move": "no-op",
4248
+ "view": "turn left"
4249
+ },
4250
+ {
4251
+ "move": "no-op",
4252
+ "view": "turn left"
4253
+ },
4254
+ {
4255
+ "move": "no-op",
4256
+ "view": "turn left"
4257
+ },
4258
+ {
4259
+ "move": "no-op",
4260
+ "view": "turn left"
4261
+ },
4262
+ {
4263
+ "move": "no-op",
4264
+ "view": "turn left"
4265
+ },
4266
+ {
4267
+ "move": "no-op",
4268
+ "view": "turn left"
4269
+ },
4270
+ {
4271
+ "move": "no-op",
4272
+ "view": "turn left"
4273
+ },
4274
+ {
4275
+ "move": "no-op",
4276
+ "view": "turn left"
4277
+ },
4278
+ {
4279
+ "move": "no-op",
4280
+ "view": "turn left"
4281
+ },
4282
+ {
4283
+ "move": "no-op",
4284
+ "view": "turn left"
4285
+ },
4286
+ {
4287
+ "move": "no-op",
4288
+ "view": "turn left"
4289
+ },
4290
+ {
4291
+ "move": "no-op",
4292
+ "view": "turn left"
4293
+ },
4294
+ {
4295
+ "move": "no-op",
4296
+ "view": "turn left"
4297
+ },
4298
+ {
4299
+ "move": "no-op",
4300
+ "view": "turn left"
4301
+ },
4302
+ {
4303
+ "move": "no-op",
4304
+ "view": "turn left"
4305
+ },
4306
+ {
4307
+ "move": "no-op",
4308
+ "view": "turn left"
4309
+ },
4310
+ {
4311
+ "move": "no-op",
4312
+ "view": "turn left"
4313
+ },
4314
+ {
4315
+ "move": "no-op",
4316
+ "view": "turn left"
4317
+ },
4318
+ {
4319
+ "move": "no-op",
4320
+ "view": "turn left"
4321
+ },
4322
+ {
4323
+ "move": "no-op",
4324
+ "view": "turn left"
4325
+ },
4326
+ {
4327
+ "move": "no-op",
4328
+ "view": "turn left"
4329
+ },
4330
+ {
4331
+ "move": "no-op",
4332
+ "view": "turn left"
4333
+ },
4334
+ {
4335
+ "move": "no-op",
4336
+ "view": "turn left"
4337
+ },
4338
+ {
4339
+ "move": "no-op",
4340
+ "view": "turn left"
4341
+ },
4342
+ {
4343
+ "move": "no-op",
4344
+ "view": "turn left"
4345
+ },
4346
+ {
4347
+ "move": "no-op",
4348
+ "view": "turn left"
4349
+ },
4350
+ {
4351
+ "move": "no-op",
4352
+ "view": "turn left"
4353
+ },
4354
+ {
4355
+ "move": "no-op",
4356
+ "view": "turn left"
4357
+ },
4358
+ {
4359
+ "move": "no-op",
4360
+ "view": "turn left"
4361
+ },
4362
+ {
4363
+ "move": "no-op",
4364
+ "view": "turn left"
4365
+ },
4366
+ {
4367
+ "move": "no-op",
4368
+ "view": "turn left"
4369
+ },
4370
+ {
4371
+ "move": "no-op",
4372
+ "view": "turn left"
4373
+ },
4374
+ {
4375
+ "move": "no-op",
4376
+ "view": "turn left"
4377
+ },
4378
+ {
4379
+ "move": "no-op",
4380
+ "view": "turn left"
4381
+ },
4382
+ {
4383
+ "move": "no-op",
4384
+ "view": "turn left"
4385
+ },
4386
+ {
4387
+ "move": "no-op",
4388
+ "view": "turn left"
4389
+ },
4390
+ {
4391
+ "move": "no-op",
4392
+ "view": "turn left"
4393
+ },
4394
+ {
4395
+ "move": "no-op",
4396
+ "view": "turn left"
4397
+ },
4398
+ {
4399
+ "move": "no-op",
4400
+ "view": "turn left"
4401
+ },
4402
+ {
4403
+ "move": "no-op",
4404
+ "view": "turn left"
4405
+ },
4406
+ {
4407
+ "move": "no-op",
4408
+ "view": "turn left"
4409
+ },
4410
+ {
4411
+ "move": "no-op",
4412
+ "view": "turn left"
4413
+ },
4414
+ {
4415
+ "move": "no-op",
4416
+ "view": "turn left"
4417
+ },
4418
+ {
4419
+ "move": "no-op",
4420
+ "view": "turn left"
4421
+ },
4422
+ {
4423
+ "move": "no-op",
4424
+ "view": "turn left"
4425
+ },
4426
+ {
4427
+ "move": "no-op",
4428
+ "view": "turn left"
4429
+ },
4430
+ {
4431
+ "move": "no-op",
4432
+ "view": "turn left"
4433
+ },
4434
+ {
4435
+ "move": "no-op",
4436
+ "view": "turn left"
4437
+ },
4438
+ {
4439
+ "move": "no-op",
4440
+ "view": "turn left"
4441
+ },
4442
+ {
4443
+ "move": "no-op",
4444
+ "view": "turn left"
4445
+ },
4446
+ {
4447
+ "move": "no-op",
4448
+ "view": "turn left"
4449
+ },
4450
+ {
4451
+ "move": "no-op",
4452
+ "view": "turn left"
4453
+ },
4454
+ {
4455
+ "move": "no-op",
4456
+ "view": "turn left"
4457
+ },
4458
+ {
4459
+ "move": "no-op",
4460
+ "view": "turn left"
4461
+ },
4462
+ {
4463
+ "move": "no-op",
4464
+ "view": "turn left"
4465
+ },
4466
+ {
4467
+ "move": "no-op",
4468
+ "view": "turn left"
4469
+ },
4470
+ {
4471
+ "move": "no-op",
4472
+ "view": "turn left"
4473
+ },
4474
+ {
4475
+ "move": "no-op",
4476
+ "view": "turn left"
4477
+ },
4478
+ {
4479
+ "move": "no-op",
4480
+ "view": "turn left"
4481
+ },
4482
+ {
4483
+ "move": "no-op",
4484
+ "view": "turn left"
4485
+ },
4486
+ {
4487
+ "move": "no-op",
4488
+ "view": "turn left"
4489
+ },
4490
+ {
4491
+ "move": "no-op",
4492
+ "view": "turn left"
4493
+ },
4494
+ {
4495
+ "move": "no-op",
4496
+ "view": "turn left"
4497
+ },
4498
+ {
4499
+ "move": "no-op",
4500
+ "view": "turn left"
4501
+ },
4502
+ {
4503
+ "move": "no-op",
4504
+ "view": "turn left"
4505
+ },
4506
+ {
4507
+ "move": "no-op",
4508
+ "view": "turn left"
4509
+ },
4510
+ {
4511
+ "move": "no-op",
4512
+ "view": "turn left"
4513
+ },
4514
+ {
4515
+ "move": "no-op",
4516
+ "view": "turn left"
4517
+ },
4518
+ {
4519
+ "move": "no-op",
4520
+ "view": "turn left"
4521
+ },
4522
+ {
4523
+ "move": "no-op",
4524
+ "view": "turn left"
4525
+ },
4526
+ {
4527
+ "move": "no-op",
4528
+ "view": "turn left"
4529
+ },
4530
+ {
4531
+ "move": "no-op",
4532
+ "view": "turn left"
4533
+ },
4534
+ {
4535
+ "move": "no-op",
4536
+ "view": "turn left"
4537
+ },
4538
+ {
4539
+ "move": "go forward",
4540
+ "view": "no-op"
4541
+ },
4542
+ {
4543
+ "move": "go forward",
4544
+ "view": "no-op"
4545
+ },
4546
+ {
4547
+ "move": "go forward",
4548
+ "view": "no-op"
4549
+ },
4550
+ {
4551
+ "move": "go forward",
4552
+ "view": "no-op"
4553
+ },
4554
+ {
4555
+ "move": "go forward",
4556
+ "view": "no-op"
4557
+ },
4558
+ {
4559
+ "move": "go forward",
4560
+ "view": "no-op"
4561
+ },
4562
+ {
4563
+ "move": "go forward",
4564
+ "view": "no-op"
4565
+ },
4566
+ {
4567
+ "move": "go forward",
4568
+ "view": "no-op"
4569
+ },
4570
+ {
4571
+ "move": "go forward",
4572
+ "view": "no-op"
4573
+ },
4574
+ {
4575
+ "move": "go forward",
4576
+ "view": "no-op"
4577
+ },
4578
+ {
4579
+ "move": "go forward",
4580
+ "view": "no-op"
4581
+ },
4582
+ {
4583
+ "move": "go forward",
4584
+ "view": "no-op"
4585
+ },
4586
+ {
4587
+ "move": "go forward",
4588
+ "view": "no-op"
4589
+ },
4590
+ {
4591
+ "move": "go forward",
4592
+ "view": "no-op"
4593
+ },
4594
+ {
4595
+ "move": "go forward",
4596
+ "view": "no-op"
4597
+ },
4598
+ {
4599
+ "move": "go forward",
4600
+ "view": "no-op"
4601
+ },
4602
+ {
4603
+ "move": "go forward",
4604
+ "view": "no-op"
4605
+ },
4606
+ {
4607
+ "move": "go forward",
4608
+ "view": "no-op"
4609
+ },
4610
+ {
4611
+ "move": "go forward",
4612
+ "view": "no-op"
4613
+ },
4614
+ {
4615
+ "move": "go forward",
4616
+ "view": "no-op"
4617
+ },
4618
+ {
4619
+ "move": "go forward",
4620
+ "view": "no-op"
4621
+ },
4622
+ {
4623
+ "move": "go forward",
4624
+ "view": "no-op"
4625
+ },
4626
+ {
4627
+ "move": "go forward",
4628
+ "view": "no-op"
4629
+ },
4630
+ {
4631
+ "move": "go forward",
4632
+ "view": "no-op"
4633
+ },
4634
+ {
4635
+ "move": "go forward",
4636
+ "view": "no-op"
4637
+ },
4638
+ {
4639
+ "move": "go forward",
4640
+ "view": "no-op"
4641
+ },
4642
+ {
4643
+ "move": "go forward",
4644
+ "view": "no-op"
4645
+ },
4646
+ {
4647
+ "move": "go forward",
4648
+ "view": "no-op"
4649
+ },
4650
+ {
4651
+ "move": "go forward",
4652
+ "view": "no-op"
4653
+ },
4654
+ {
4655
+ "move": "go forward",
4656
+ "view": "no-op"
4657
+ },
4658
+ {
4659
+ "move": "go forward",
4660
+ "view": "no-op"
4661
+ },
4662
+ {
4663
+ "move": "go forward",
4664
+ "view": "no-op"
4665
+ },
4666
+ {
4667
+ "move": "go forward",
4668
+ "view": "no-op"
4669
+ },
4670
+ {
4671
+ "move": "go forward",
4672
+ "view": "no-op"
4673
+ },
4674
+ {
4675
+ "move": "go forward",
4676
+ "view": "no-op"
4677
+ },
4678
+ {
4679
+ "move": "go forward",
4680
+ "view": "no-op"
4681
+ },
4682
+ {
4683
+ "move": "go forward",
4684
+ "view": "no-op"
4685
+ },
4686
+ {
4687
+ "move": "go forward",
4688
+ "view": "no-op"
4689
+ },
4690
+ {
4691
+ "move": "go forward",
4692
+ "view": "no-op"
4693
+ },
4694
+ {
4695
+ "move": "go forward",
4696
+ "view": "no-op"
4697
+ },
4698
+ {
4699
+ "move": "go forward",
4700
+ "view": "no-op"
4701
+ },
4702
+ {
4703
+ "move": "go forward",
4704
+ "view": "no-op"
4705
+ },
4706
+ {
4707
+ "move": "go forward",
4708
+ "view": "no-op"
4709
+ },
4710
+ {
4711
+ "move": "go forward",
4712
+ "view": "no-op"
4713
+ },
4714
+ {
4715
+ "move": "go forward",
4716
+ "view": "no-op"
4717
+ },
4718
+ {
4719
+ "move": "go forward",
4720
+ "view": "no-op"
4721
+ },
4722
+ {
4723
+ "move": "go forward",
4724
+ "view": "no-op"
4725
+ },
4726
+ {
4727
+ "move": "go forward",
4728
+ "view": "no-op"
4729
+ },
4730
+ {
4731
+ "move": "go forward",
4732
+ "view": "no-op"
4733
+ },
4734
+ {
4735
+ "move": "go forward",
4736
+ "view": "no-op"
4737
+ },
4738
+ {
4739
+ "move": "go forward",
4740
+ "view": "no-op"
4741
+ },
4742
+ {
4743
+ "move": "go forward",
4744
+ "view": "no-op"
4745
+ },
4746
+ {
4747
+ "move": "go forward",
4748
+ "view": "no-op"
4749
+ },
4750
+ {
4751
+ "move": "go forward",
4752
+ "view": "no-op"
4753
+ },
4754
+ {
4755
+ "move": "go forward",
4756
+ "view": "no-op"
4757
+ },
4758
+ {
4759
+ "move": "go forward",
4760
+ "view": "no-op"
4761
+ },
4762
+ {
4763
+ "move": "go forward",
4764
+ "view": "no-op"
4765
+ },
4766
+ {
4767
+ "move": "go forward",
4768
+ "view": "no-op"
4769
+ },
4770
+ {
4771
+ "move": "go forward",
4772
+ "view": "no-op"
4773
+ },
4774
+ {
4775
+ "move": "go forward",
4776
+ "view": "no-op"
4777
+ },
4778
+ {
4779
+ "move": "go forward",
4780
+ "view": "no-op"
4781
+ },
4782
+ {
4783
+ "move": "go forward",
4784
+ "view": "no-op"
4785
+ },
4786
+ {
4787
+ "move": "go forward",
4788
+ "view": "no-op"
4789
+ },
4790
+ {
4791
+ "move": "go forward",
4792
+ "view": "no-op"
4793
+ },
4794
+ {
4795
+ "move": "go forward",
4796
+ "view": "no-op"
4797
+ },
4798
+ {
4799
+ "move": "go forward",
4800
+ "view": "no-op"
4801
+ },
4802
+ {
4803
+ "move": "go forward",
4804
+ "view": "no-op"
4805
+ },
4806
+ {
4807
+ "move": "go forward",
4808
+ "view": "no-op"
4809
+ },
4810
+ {
4811
+ "move": "go forward",
4812
+ "view": "no-op"
4813
+ },
4814
+ {
4815
+ "move": "go forward",
4816
+ "view": "no-op"
4817
+ },
4818
+ {
4819
+ "move": "go forward",
4820
+ "view": "no-op"
4821
+ },
4822
+ {
4823
+ "move": "go forward",
4824
+ "view": "no-op"
4825
+ },
4826
+ {
4827
+ "move": "go forward",
4828
+ "view": "no-op"
4829
+ },
4830
+ {
4831
+ "move": "go forward",
4832
+ "view": "no-op"
4833
+ },
4834
+ {
4835
+ "move": "go forward",
4836
+ "view": "no-op"
4837
+ },
4838
+ {
4839
+ "move": "go forward",
4840
+ "view": "no-op"
4841
+ },
4842
+ {
4843
+ "move": "go forward",
4844
+ "view": "no-op"
4845
+ },
4846
+ {
4847
+ "move": "go forward",
4848
+ "view": "no-op"
4849
+ },
4850
+ {
4851
+ "move": "go forward",
4852
+ "view": "no-op"
4853
+ },
4854
+ {
4855
+ "move": "go forward",
4856
+ "view": "no-op"
4857
+ },
4858
+ {
4859
+ "move": "go forward",
4860
+ "view": "no-op"
4861
+ },
4862
+ {
4863
+ "move": "go forward",
4864
+ "view": "no-op"
4865
+ },
4866
+ {
4867
+ "move": "go forward",
4868
+ "view": "no-op"
4869
+ },
4870
+ {
4871
+ "move": "go forward",
4872
+ "view": "no-op"
4873
+ },
4874
+ {
4875
+ "move": "go forward",
4876
+ "view": "no-op"
4877
+ },
4878
+ {
4879
+ "move": "go forward",
4880
+ "view": "no-op"
4881
+ },
4882
+ {
4883
+ "move": "go forward",
4884
+ "view": "no-op"
4885
+ },
4886
+ {
4887
+ "move": "go forward",
4888
+ "view": "no-op"
4889
+ },
4890
+ {
4891
+ "move": "go forward",
4892
+ "view": "no-op"
4893
+ },
4894
+ {
4895
+ "move": "go forward",
4896
+ "view": "no-op"
4897
+ },
4898
+ {
4899
+ "move": "go forward",
4900
+ "view": "no-op"
4901
+ },
4902
+ {
4903
+ "move": "go forward",
4904
+ "view": "no-op"
4905
+ },
4906
+ {
4907
+ "move": "go forward",
4908
+ "view": "no-op"
4909
+ },
4910
+ {
4911
+ "move": "go forward",
4912
+ "view": "no-op"
4913
+ },
4914
+ {
4915
+ "move": "go forward",
4916
+ "view": "no-op"
4917
+ },
4918
+ {
4919
+ "move": "go forward",
4920
+ "view": "no-op"
4921
+ },
4922
+ {
4923
+ "move": "go forward",
4924
+ "view": "no-op"
4925
+ },
4926
+ {
4927
+ "move": "go forward",
4928
+ "view": "no-op"
4929
+ },
4930
+ {
4931
+ "move": "go forward",
4932
+ "view": "no-op"
4933
+ },
4934
+ {
4935
+ "move": "go forward",
4936
+ "view": "no-op"
4937
+ },
4938
+ {
4939
+ "move": "go forward",
4940
+ "view": "no-op"
4941
+ },
4942
+ {
4943
+ "move": "go forward",
4944
+ "view": "no-op"
4945
+ },
4946
+ {
4947
+ "move": "go forward",
4948
+ "view": "no-op"
4949
+ },
4950
+ {
4951
+ "move": "go forward",
4952
+ "view": "no-op"
4953
+ },
4954
+ {
4955
+ "move": "go forward",
4956
+ "view": "no-op"
4957
+ },
4958
+ {
4959
+ "move": "go forward",
4960
+ "view": "no-op"
4961
+ },
4962
+ {
4963
+ "move": "go forward",
4964
+ "view": "no-op"
4965
+ },
4966
+ {
4967
+ "move": "go forward",
4968
+ "view": "no-op"
4969
+ },
4970
+ {
4971
+ "move": "go forward",
4972
+ "view": "no-op"
4973
+ },
4974
+ {
4975
+ "move": "go forward",
4976
+ "view": "no-op"
4977
+ },
4978
+ {
4979
+ "move": "go forward",
4980
+ "view": "no-op"
4981
+ },
4982
+ {
4983
+ "move": "go forward",
4984
+ "view": "no-op"
4985
+ },
4986
+ {
4987
+ "move": "go forward",
4988
+ "view": "no-op"
4989
+ },
4990
+ {
4991
+ "move": "go forward",
4992
+ "view": "no-op"
4993
+ },
4994
+ {
4995
+ "move": "go forward",
4996
+ "view": "no-op"
4997
+ },
4998
+ {
4999
+ "move": "go forward",
5000
+ "view": "no-op"
5001
+ },
5002
+ {
5003
+ "move": "go forward",
5004
+ "view": "no-op"
5005
+ },
5006
+ {
5007
+ "move": "go forward",
5008
+ "view": "no-op"
5009
+ },
5010
+ {
5011
+ "move": "go forward",
5012
+ "view": "no-op"
5013
+ },
5014
+ {
5015
+ "move": "go forward",
5016
+ "view": "no-op"
5017
+ },
5018
+ {
5019
+ "move": "go forward",
5020
+ "view": "no-op"
5021
+ },
5022
+ {
5023
+ "move": "go forward",
5024
+ "view": "no-op"
5025
+ },
5026
+ {
5027
+ "move": "go forward",
5028
+ "view": "no-op"
5029
+ },
5030
+ {
5031
+ "move": "go forward",
5032
+ "view": "no-op"
5033
+ },
5034
+ {
5035
+ "move": "go forward",
5036
+ "view": "no-op"
5037
+ },
5038
+ {
5039
+ "move": "go forward",
5040
+ "view": "no-op"
5041
+ },
5042
+ {
5043
+ "move": "go forward",
5044
+ "view": "no-op"
5045
+ },
5046
+ {
5047
+ "move": "go forward",
5048
+ "view": "no-op"
5049
+ },
5050
+ {
5051
+ "move": "go forward",
5052
+ "view": "no-op"
5053
+ },
5054
+ {
5055
+ "move": "go forward",
5056
+ "view": "no-op"
5057
+ },
5058
+ {
5059
+ "move": "go forward",
5060
+ "view": "no-op"
5061
+ },
5062
+ {
5063
+ "move": "go forward",
5064
+ "view": "no-op"
5065
+ },
5066
+ {
5067
+ "move": "go forward",
5068
+ "view": "no-op"
5069
+ },
5070
+ {
5071
+ "move": "go forward",
5072
+ "view": "no-op"
5073
+ },
5074
+ {
5075
+ "move": "go forward",
5076
+ "view": "no-op"
5077
+ },
5078
+ {
5079
+ "move": "go forward",
5080
+ "view": "no-op"
5081
+ },
5082
+ {
5083
+ "move": "go forward",
5084
+ "view": "no-op"
5085
+ },
5086
+ {
5087
+ "move": "go forward",
5088
+ "view": "no-op"
5089
+ },
5090
+ {
5091
+ "move": "go forward",
5092
+ "view": "no-op"
5093
+ },
5094
+ {
5095
+ "move": "go forward",
5096
+ "view": "no-op"
5097
+ },
5098
+ {
5099
+ "move": "go forward",
5100
+ "view": "no-op"
5101
+ },
5102
+ {
5103
+ "move": "go forward",
5104
+ "view": "no-op"
5105
+ },
5106
+ {
5107
+ "move": "go forward",
5108
+ "view": "no-op"
5109
+ },
5110
+ {
5111
+ "move": "go forward",
5112
+ "view": "no-op"
5113
+ },
5114
+ {
5115
+ "move": "go forward",
5116
+ "view": "no-op"
5117
+ },
5118
+ {
5119
+ "move": "go forward",
5120
+ "view": "no-op"
5121
+ },
5122
+ {
5123
+ "move": "go forward",
5124
+ "view": "no-op"
5125
+ },
5126
+ {
5127
+ "move": "go forward",
5128
+ "view": "no-op"
5129
+ },
5130
+ {
5131
+ "move": "go forward",
5132
+ "view": "no-op"
5133
+ },
5134
+ {
5135
+ "move": "go forward",
5136
+ "view": "no-op"
5137
+ },
5138
+ {
5139
+ "move": "go forward",
5140
+ "view": "no-op"
5141
+ },
5142
+ {
5143
+ "move": "go forward",
5144
+ "view": "no-op"
5145
+ },
5146
+ {
5147
+ "move": "go forward",
5148
+ "view": "no-op"
5149
+ },
5150
+ {
5151
+ "move": "go forward",
5152
+ "view": "no-op"
5153
+ },
5154
+ {
5155
+ "move": "go forward",
5156
+ "view": "no-op"
5157
+ },
5158
+ {
5159
+ "move": "go forward",
5160
+ "view": "no-op"
5161
+ },
5162
+ {
5163
+ "move": "go forward",
5164
+ "view": "no-op"
5165
+ },
5166
+ {
5167
+ "move": "go forward",
5168
+ "view": "no-op"
5169
+ },
5170
+ {
5171
+ "move": "go forward",
5172
+ "view": "no-op"
5173
+ },
5174
+ {
5175
+ "move": "go forward",
5176
+ "view": "no-op"
5177
+ },
5178
+ {
5179
+ "move": "go forward",
5180
+ "view": "no-op"
5181
+ },
5182
+ {
5183
+ "move": "go forward",
5184
+ "view": "no-op"
5185
+ },
5186
+ {
5187
+ "move": "no-op",
5188
+ "view": "turn left"
5189
+ },
5190
+ {
5191
+ "move": "no-op",
5192
+ "view": "turn left"
5193
+ },
5194
+ {
5195
+ "move": "no-op",
5196
+ "view": "turn left"
5197
+ },
5198
+ {
5199
+ "move": "no-op",
5200
+ "view": "turn left"
5201
+ },
5202
+ {
5203
+ "move": "no-op",
5204
+ "view": "turn left"
5205
+ },
5206
+ {
5207
+ "move": "no-op",
5208
+ "view": "turn left"
5209
+ },
5210
+ {
5211
+ "move": "no-op",
5212
+ "view": "turn left"
5213
+ },
5214
+ {
5215
+ "move": "no-op",
5216
+ "view": "turn left"
5217
+ },
5218
+ {
5219
+ "move": "no-op",
5220
+ "view": "turn left"
5221
+ },
5222
+ {
5223
+ "move": "no-op",
5224
+ "view": "turn left"
5225
+ },
5226
+ {
5227
+ "move": "no-op",
5228
+ "view": "turn left"
5229
+ },
5230
+ {
5231
+ "move": "no-op",
5232
+ "view": "turn left"
5233
+ },
5234
+ {
5235
+ "move": "no-op",
5236
+ "view": "turn left"
5237
+ },
5238
+ {
5239
+ "move": "no-op",
5240
+ "view": "turn left"
5241
+ },
5242
+ {
5243
+ "move": "no-op",
5244
+ "view": "turn left"
5245
+ },
5246
+ {
5247
+ "move": "no-op",
5248
+ "view": "turn left"
5249
+ },
5250
+ {
5251
+ "move": "no-op",
5252
+ "view": "turn left"
5253
+ },
5254
+ {
5255
+ "move": "no-op",
5256
+ "view": "turn left"
5257
+ },
5258
+ {
5259
+ "move": "no-op",
5260
+ "view": "turn left"
5261
+ },
5262
+ {
5263
+ "move": "no-op",
5264
+ "view": "turn left"
5265
+ },
5266
+ {
5267
+ "move": "no-op",
5268
+ "view": "turn left"
5269
+ },
5270
+ {
5271
+ "move": "no-op",
5272
+ "view": "turn left"
5273
+ },
5274
+ {
5275
+ "move": "no-op",
5276
+ "view": "turn left"
5277
+ },
5278
+ {
5279
+ "move": "no-op",
5280
+ "view": "turn left"
5281
+ },
5282
+ {
5283
+ "move": "no-op",
5284
+ "view": "turn left"
5285
+ },
5286
+ {
5287
+ "move": "no-op",
5288
+ "view": "turn left"
5289
+ },
5290
+ {
5291
+ "move": "no-op",
5292
+ "view": "turn left"
5293
+ },
5294
+ {
5295
+ "move": "no-op",
5296
+ "view": "turn left"
5297
+ },
5298
+ {
5299
+ "move": "no-op",
5300
+ "view": "turn left"
5301
+ },
5302
+ {
5303
+ "move": "no-op",
5304
+ "view": "turn left"
5305
+ },
5306
+ {
5307
+ "move": "no-op",
5308
+ "view": "turn left"
5309
+ },
5310
+ {
5311
+ "move": "no-op",
5312
+ "view": "turn left"
5313
+ },
5314
+ {
5315
+ "move": "no-op",
5316
+ "view": "turn left"
5317
+ },
5318
+ {
5319
+ "move": "no-op",
5320
+ "view": "turn left"
5321
+ },
5322
+ {
5323
+ "move": "no-op",
5324
+ "view": "turn left"
5325
+ },
5326
+ {
5327
+ "move": "no-op",
5328
+ "view": "turn left"
5329
+ },
5330
+ {
5331
+ "move": "no-op",
5332
+ "view": "turn left"
5333
+ },
5334
+ {
5335
+ "move": "no-op",
5336
+ "view": "turn left"
5337
+ },
5338
+ {
5339
+ "move": "no-op",
5340
+ "view": "turn left"
5341
+ },
5342
+ {
5343
+ "move": "no-op",
5344
+ "view": "turn left"
5345
+ },
5346
+ {
5347
+ "move": "no-op",
5348
+ "view": "turn left"
5349
+ },
5350
+ {
5351
+ "move": "no-op",
5352
+ "view": "turn left"
5353
+ },
5354
+ {
5355
+ "move": "no-op",
5356
+ "view": "turn left"
5357
+ },
5358
+ {
5359
+ "move": "no-op",
5360
+ "view": "turn left"
5361
+ },
5362
+ {
5363
+ "move": "no-op",
5364
+ "view": "turn left"
5365
+ },
5366
+ {
5367
+ "move": "no-op",
5368
+ "view": "turn left"
5369
+ },
5370
+ {
5371
+ "move": "no-op",
5372
+ "view": "turn left"
5373
+ },
5374
+ {
5375
+ "move": "no-op",
5376
+ "view": "turn left"
5377
+ },
5378
+ {
5379
+ "move": "no-op",
5380
+ "view": "turn left"
5381
+ },
5382
+ {
5383
+ "move": "no-op",
5384
+ "view": "turn left"
5385
+ },
5386
+ {
5387
+ "move": "no-op",
5388
+ "view": "turn left"
5389
+ },
5390
+ {
5391
+ "move": "no-op",
5392
+ "view": "turn left"
5393
+ },
5394
+ {
5395
+ "move": "no-op",
5396
+ "view": "turn left"
5397
+ },
5398
+ {
5399
+ "move": "no-op",
5400
+ "view": "turn left"
5401
+ },
5402
+ {
5403
+ "move": "no-op",
5404
+ "view": "turn left"
5405
+ },
5406
+ {
5407
+ "move": "no-op",
5408
+ "view": "turn left"
5409
+ },
5410
+ {
5411
+ "move": "no-op",
5412
+ "view": "turn left"
5413
+ },
5414
+ {
5415
+ "move": "no-op",
5416
+ "view": "turn left"
5417
+ },
5418
+ {
5419
+ "move": "no-op",
5420
+ "view": "turn left"
5421
+ },
5422
+ {
5423
+ "move": "no-op",
5424
+ "view": "turn left"
5425
+ },
5426
+ {
5427
+ "move": "no-op",
5428
+ "view": "turn left"
5429
+ },
5430
+ {
5431
+ "move": "no-op",
5432
+ "view": "turn left"
5433
+ },
5434
+ {
5435
+ "move": "no-op",
5436
+ "view": "turn left"
5437
+ },
5438
+ {
5439
+ "move": "no-op",
5440
+ "view": "turn left"
5441
+ },
5442
+ {
5443
+ "move": "no-op",
5444
+ "view": "turn left"
5445
+ },
5446
+ {
5447
+ "move": "no-op",
5448
+ "view": "turn left"
5449
+ },
5450
+ {
5451
+ "move": "no-op",
5452
+ "view": "turn left"
5453
+ },
5454
+ {
5455
+ "move": "no-op",
5456
+ "view": "turn left"
5457
+ },
5458
+ {
5459
+ "move": "no-op",
5460
+ "view": "turn left"
5461
+ },
5462
+ {
5463
+ "move": "no-op",
5464
+ "view": "turn left"
5465
+ },
5466
+ {
5467
+ "move": "no-op",
5468
+ "view": "turn left"
5469
+ },
5470
+ {
5471
+ "move": "no-op",
5472
+ "view": "turn left"
5473
+ },
5474
+ {
5475
+ "move": "no-op",
5476
+ "view": "turn left"
5477
+ },
5478
+ {
5479
+ "move": "no-op",
5480
+ "view": "turn left"
5481
+ },
5482
+ {
5483
+ "move": "no-op",
5484
+ "view": "turn left"
5485
+ },
5486
+ {
5487
+ "move": "no-op",
5488
+ "view": "turn left"
5489
+ },
5490
+ {
5491
+ "move": "no-op",
5492
+ "view": "turn left"
5493
+ },
5494
+ {
5495
+ "move": "no-op",
5496
+ "view": "turn left"
5497
+ },
5498
+ {
5499
+ "move": "no-op",
5500
+ "view": "turn left"
5501
+ },
5502
+ {
5503
+ "move": "no-op",
5504
+ "view": "turn left"
5505
+ },
5506
+ {
5507
+ "move": "no-op",
5508
+ "view": "turn left"
5509
+ },
5510
+ {
5511
+ "move": "no-op",
5512
+ "view": "turn right"
5513
+ },
5514
+ {
5515
+ "move": "no-op",
5516
+ "view": "turn right"
5517
+ },
5518
+ {
5519
+ "move": "no-op",
5520
+ "view": "turn right"
5521
+ },
5522
+ {
5523
+ "move": "no-op",
5524
+ "view": "turn right"
5525
+ },
5526
+ {
5527
+ "move": "no-op",
5528
+ "view": "turn right"
5529
+ },
5530
+ {
5531
+ "move": "no-op",
5532
+ "view": "turn right"
5533
+ },
5534
+ {
5535
+ "move": "no-op",
5536
+ "view": "turn right"
5537
+ },
5538
+ {
5539
+ "move": "no-op",
5540
+ "view": "turn right"
5541
+ },
5542
+ {
5543
+ "move": "no-op",
5544
+ "view": "turn right"
5545
+ },
5546
+ {
5547
+ "move": "no-op",
5548
+ "view": "turn right"
5549
+ },
5550
+ {
5551
+ "move": "no-op",
5552
+ "view": "turn right"
5553
+ },
5554
+ {
5555
+ "move": "no-op",
5556
+ "view": "turn right"
5557
+ },
5558
+ {
5559
+ "move": "no-op",
5560
+ "view": "turn right"
5561
+ },
5562
+ {
5563
+ "move": "no-op",
5564
+ "view": "turn right"
5565
+ },
5566
+ {
5567
+ "move": "no-op",
5568
+ "view": "turn right"
5569
+ },
5570
+ {
5571
+ "move": "no-op",
5572
+ "view": "turn right"
5573
+ },
5574
+ {
5575
+ "move": "no-op",
5576
+ "view": "turn right"
5577
+ },
5578
+ {
5579
+ "move": "no-op",
5580
+ "view": "turn right"
5581
+ },
5582
+ {
5583
+ "move": "no-op",
5584
+ "view": "turn right"
5585
+ },
5586
+ {
5587
+ "move": "no-op",
5588
+ "view": "turn right"
5589
+ },
5590
+ {
5591
+ "move": "no-op",
5592
+ "view": "turn right"
5593
+ },
5594
+ {
5595
+ "move": "no-op",
5596
+ "view": "turn right"
5597
+ },
5598
+ {
5599
+ "move": "no-op",
5600
+ "view": "turn right"
5601
+ },
5602
+ {
5603
+ "move": "no-op",
5604
+ "view": "turn right"
5605
+ },
5606
+ {
5607
+ "move": "no-op",
5608
+ "view": "turn right"
5609
+ },
5610
+ {
5611
+ "move": "no-op",
5612
+ "view": "turn right"
5613
+ },
5614
+ {
5615
+ "move": "no-op",
5616
+ "view": "turn right"
5617
+ },
5618
+ {
5619
+ "move": "no-op",
5620
+ "view": "turn right"
5621
+ },
5622
+ {
5623
+ "move": "no-op",
5624
+ "view": "turn right"
5625
+ },
5626
+ {
5627
+ "move": "no-op",
5628
+ "view": "turn right"
5629
+ },
5630
+ {
5631
+ "move": "no-op",
5632
+ "view": "turn right"
5633
+ },
5634
+ {
5635
+ "move": "no-op",
5636
+ "view": "turn right"
5637
+ },
5638
+ {
5639
+ "move": "no-op",
5640
+ "view": "turn right"
5641
+ },
5642
+ {
5643
+ "move": "no-op",
5644
+ "view": "turn right"
5645
+ },
5646
+ {
5647
+ "move": "no-op",
5648
+ "view": "turn right"
5649
+ },
5650
+ {
5651
+ "move": "no-op",
5652
+ "view": "turn right"
5653
+ },
5654
+ {
5655
+ "move": "no-op",
5656
+ "view": "turn right"
5657
+ },
5658
+ {
5659
+ "move": "no-op",
5660
+ "view": "turn right"
5661
+ },
5662
+ {
5663
+ "move": "no-op",
5664
+ "view": "turn right"
5665
+ },
5666
+ {
5667
+ "move": "no-op",
5668
+ "view": "turn right"
5669
+ },
5670
+ {
5671
+ "move": "no-op",
5672
+ "view": "turn right"
5673
+ },
5674
+ {
5675
+ "move": "no-op",
5676
+ "view": "turn right"
5677
+ },
5678
+ {
5679
+ "move": "no-op",
5680
+ "view": "turn right"
5681
+ },
5682
+ {
5683
+ "move": "no-op",
5684
+ "view": "turn right"
5685
+ },
5686
+ {
5687
+ "move": "no-op",
5688
+ "view": "turn right"
5689
+ },
5690
+ {
5691
+ "move": "no-op",
5692
+ "view": "turn right"
5693
+ },
5694
+ {
5695
+ "move": "no-op",
5696
+ "view": "turn right"
5697
+ },
5698
+ {
5699
+ "move": "no-op",
5700
+ "view": "turn right"
5701
+ },
5702
+ {
5703
+ "move": "no-op",
5704
+ "view": "turn right"
5705
+ },
5706
+ {
5707
+ "move": "no-op",
5708
+ "view": "turn right"
5709
+ },
5710
+ {
5711
+ "move": "no-op",
5712
+ "view": "turn right"
5713
+ },
5714
+ {
5715
+ "move": "no-op",
5716
+ "view": "turn right"
5717
+ },
5718
+ {
5719
+ "move": "no-op",
5720
+ "view": "turn right"
5721
+ },
5722
+ {
5723
+ "move": "no-op",
5724
+ "view": "turn right"
5725
+ },
5726
+ {
5727
+ "move": "no-op",
5728
+ "view": "turn right"
5729
+ },
5730
+ {
5731
+ "move": "no-op",
5732
+ "view": "turn right"
5733
+ },
5734
+ {
5735
+ "move": "no-op",
5736
+ "view": "turn right"
5737
+ },
5738
+ {
5739
+ "move": "no-op",
5740
+ "view": "turn right"
5741
+ },
5742
+ {
5743
+ "move": "no-op",
5744
+ "view": "turn right"
5745
+ },
5746
+ {
5747
+ "move": "no-op",
5748
+ "view": "turn right"
5749
+ },
5750
+ {
5751
+ "move": "no-op",
5752
+ "view": "turn right"
5753
+ },
5754
+ {
5755
+ "move": "no-op",
5756
+ "view": "turn right"
5757
+ },
5758
+ {
5759
+ "move": "no-op",
5760
+ "view": "turn right"
5761
+ },
5762
+ {
5763
+ "move": "no-op",
5764
+ "view": "turn right"
5765
+ },
5766
+ {
5767
+ "move": "no-op",
5768
+ "view": "turn right"
5769
+ },
5770
+ {
5771
+ "move": "no-op",
5772
+ "view": "turn right"
5773
+ },
5774
+ {
5775
+ "move": "no-op",
5776
+ "view": "turn right"
5777
+ },
5778
+ {
5779
+ "move": "no-op",
5780
+ "view": "turn right"
5781
+ },
5782
+ {
5783
+ "move": "no-op",
5784
+ "view": "turn right"
5785
+ },
5786
+ {
5787
+ "move": "no-op",
5788
+ "view": "turn right"
5789
+ },
5790
+ {
5791
+ "move": "no-op",
5792
+ "view": "turn right"
5793
+ },
5794
+ {
5795
+ "move": "no-op",
5796
+ "view": "turn right"
5797
+ },
5798
+ {
5799
+ "move": "no-op",
5800
+ "view": "turn right"
5801
+ },
5802
+ {
5803
+ "move": "no-op",
5804
+ "view": "turn right"
5805
+ },
5806
+ {
5807
+ "move": "no-op",
5808
+ "view": "turn right"
5809
+ },
5810
+ {
5811
+ "move": "no-op",
5812
+ "view": "turn right"
5813
+ },
5814
+ {
5815
+ "move": "no-op",
5816
+ "view": "turn right"
5817
+ },
5818
+ {
5819
+ "move": "no-op",
5820
+ "view": "turn right"
5821
+ },
5822
+ {
5823
+ "move": "no-op",
5824
+ "view": "turn right"
5825
+ },
5826
+ {
5827
+ "move": "no-op",
5828
+ "view": "turn right"
5829
+ },
5830
+ {
5831
+ "move": "no-op",
5832
+ "view": "turn right"
5833
+ },
5834
+ {
5835
+ "move": "no-op",
5836
+ "view": "turn right"
5837
+ },
5838
+ {
5839
+ "move": "no-op",
5840
+ "view": "turn right"
5841
+ },
5842
+ {
5843
+ "move": "no-op",
5844
+ "view": "turn right"
5845
+ },
5846
+ {
5847
+ "move": "no-op",
5848
+ "view": "turn right"
5849
+ },
5850
+ {
5851
+ "move": "no-op",
5852
+ "view": "turn right"
5853
+ },
5854
+ {
5855
+ "move": "no-op",
5856
+ "view": "turn right"
5857
+ },
5858
+ {
5859
+ "move": "no-op",
5860
+ "view": "turn right"
5861
+ },
5862
+ {
5863
+ "move": "no-op",
5864
+ "view": "turn right"
5865
+ },
5866
+ {
5867
+ "move": "no-op",
5868
+ "view": "turn right"
5869
+ },
5870
+ {
5871
+ "move": "no-op",
5872
+ "view": "turn right"
5873
+ },
5874
+ {
5875
+ "move": "no-op",
5876
+ "view": "turn right"
5877
+ },
5878
+ {
5879
+ "move": "no-op",
5880
+ "view": "turn right"
5881
+ },
5882
+ {
5883
+ "move": "no-op",
5884
+ "view": "turn right"
5885
+ },
5886
+ {
5887
+ "move": "no-op",
5888
+ "view": "turn right"
5889
+ },
5890
+ {
5891
+ "move": "no-op",
5892
+ "view": "turn right"
5893
+ },
5894
+ {
5895
+ "move": "no-op",
5896
+ "view": "turn right"
5897
+ },
5898
+ {
5899
+ "move": "no-op",
5900
+ "view": "turn right"
5901
+ },
5902
+ {
5903
+ "move": "no-op",
5904
+ "view": "turn right"
5905
+ },
5906
+ {
5907
+ "move": "no-op",
5908
+ "view": "turn right"
5909
+ },
5910
+ {
5911
+ "move": "no-op",
5912
+ "view": "turn right"
5913
+ },
5914
+ {
5915
+ "move": "no-op",
5916
+ "view": "turn right"
5917
+ },
5918
+ {
5919
+ "move": "no-op",
5920
+ "view": "turn right"
5921
+ },
5922
+ {
5923
+ "move": "no-op",
5924
+ "view": "turn right"
5925
+ },
5926
+ {
5927
+ "move": "no-op",
5928
+ "view": "turn right"
5929
+ },
5930
+ {
5931
+ "move": "no-op",
5932
+ "view": "turn right"
5933
+ },
5934
+ {
5935
+ "move": "no-op",
5936
+ "view": "turn right"
5937
+ },
5938
+ {
5939
+ "move": "no-op",
5940
+ "view": "turn right"
5941
+ },
5942
+ {
5943
+ "move": "no-op",
5944
+ "view": "turn right"
5945
+ },
5946
+ {
5947
+ "move": "no-op",
5948
+ "view": "turn right"
5949
+ },
5950
+ {
5951
+ "move": "no-op",
5952
+ "view": "turn right"
5953
+ },
5954
+ {
5955
+ "move": "no-op",
5956
+ "view": "turn right"
5957
+ },
5958
+ {
5959
+ "move": "no-op",
5960
+ "view": "turn right"
5961
+ },
5962
+ {
5963
+ "move": "no-op",
5964
+ "view": "turn right"
5965
+ },
5966
+ {
5967
+ "move": "no-op",
5968
+ "view": "turn right"
5969
+ },
5970
+ {
5971
+ "move": "no-op",
5972
+ "view": "turn right"
5973
+ },
5974
+ {
5975
+ "move": "no-op",
5976
+ "view": "turn right"
5977
+ },
5978
+ {
5979
+ "move": "no-op",
5980
+ "view": "turn right"
5981
+ },
5982
+ {
5983
+ "move": "no-op",
5984
+ "view": "turn right"
5985
+ },
5986
+ {
5987
+ "move": "no-op",
5988
+ "view": "turn right"
5989
+ },
5990
+ {
5991
+ "move": "no-op",
5992
+ "view": "turn right"
5993
+ },
5994
+ {
5995
+ "move": "no-op",
5996
+ "view": "turn right"
5997
+ },
5998
+ {
5999
+ "move": "no-op",
6000
+ "view": "turn right"
6001
+ },
6002
+ {
6003
+ "move": "no-op",
6004
+ "view": "turn right"
6005
+ },
6006
+ {
6007
+ "move": "no-op",
6008
+ "view": "turn right"
6009
+ },
6010
+ {
6011
+ "move": "no-op",
6012
+ "view": "turn right"
6013
+ },
6014
+ {
6015
+ "move": "no-op",
6016
+ "view": "turn right"
6017
+ },
6018
+ {
6019
+ "move": "no-op",
6020
+ "view": "turn right"
6021
+ },
6022
+ {
6023
+ "move": "no-op",
6024
+ "view": "turn right"
6025
+ },
6026
+ {
6027
+ "move": "no-op",
6028
+ "view": "turn right"
6029
+ },
6030
+ {
6031
+ "move": "no-op",
6032
+ "view": "turn right"
6033
+ },
6034
+ {
6035
+ "move": "no-op",
6036
+ "view": "turn right"
6037
+ },
6038
+ {
6039
+ "move": "no-op",
6040
+ "view": "turn right"
6041
+ },
6042
+ {
6043
+ "move": "no-op",
6044
+ "view": "turn right"
6045
+ },
6046
+ {
6047
+ "move": "no-op",
6048
+ "view": "turn right"
6049
+ },
6050
+ {
6051
+ "move": "no-op",
6052
+ "view": "turn right"
6053
+ },
6054
+ {
6055
+ "move": "no-op",
6056
+ "view": "turn right"
6057
+ },
6058
+ {
6059
+ "move": "no-op",
6060
+ "view": "turn right"
6061
+ },
6062
+ {
6063
+ "move": "no-op",
6064
+ "view": "turn right"
6065
+ },
6066
+ {
6067
+ "move": "no-op",
6068
+ "view": "turn right"
6069
+ },
6070
+ {
6071
+ "move": "no-op",
6072
+ "view": "turn right"
6073
+ },
6074
+ {
6075
+ "move": "no-op",
6076
+ "view": "turn right"
6077
+ },
6078
+ {
6079
+ "move": "no-op",
6080
+ "view": "turn right"
6081
+ },
6082
+ {
6083
+ "move": "no-op",
6084
+ "view": "turn right"
6085
+ },
6086
+ {
6087
+ "move": "no-op",
6088
+ "view": "turn right"
6089
+ },
6090
+ {
6091
+ "move": "no-op",
6092
+ "view": "turn right"
6093
+ },
6094
+ {
6095
+ "move": "no-op",
6096
+ "view": "turn right"
6097
+ },
6098
+ {
6099
+ "move": "no-op",
6100
+ "view": "turn right"
6101
+ },
6102
+ {
6103
+ "move": "no-op",
6104
+ "view": "turn right"
6105
+ },
6106
+ {
6107
+ "move": "no-op",
6108
+ "view": "turn right"
6109
+ },
6110
+ {
6111
+ "move": "no-op",
6112
+ "view": "turn right"
6113
+ },
6114
+ {
6115
+ "move": "no-op",
6116
+ "view": "turn right"
6117
+ },
6118
+ {
6119
+ "move": "no-op",
6120
+ "view": "turn right"
6121
+ },
6122
+ {
6123
+ "move": "no-op",
6124
+ "view": "turn right"
6125
+ },
6126
+ {
6127
+ "move": "no-op",
6128
+ "view": "turn right"
6129
+ },
6130
+ {
6131
+ "move": "no-op",
6132
+ "view": "turn right"
6133
+ },
6134
+ {
6135
+ "move": "no-op",
6136
+ "view": "turn right"
6137
+ },
6138
+ {
6139
+ "move": "no-op",
6140
+ "view": "turn right"
6141
+ },
6142
+ {
6143
+ "move": "no-op",
6144
+ "view": "turn right"
6145
+ },
6146
+ {
6147
+ "move": "no-op",
6148
+ "view": "turn right"
6149
+ },
6150
+ {
6151
+ "move": "no-op",
6152
+ "view": "turn right"
6153
+ },
6154
+ {
6155
+ "move": "no-op",
6156
+ "view": "turn right"
6157
+ },
6158
+ {
6159
+ "move": "no-op",
6160
+ "view": "turn right"
6161
+ },
6162
+ {
6163
+ "move": "no-op",
6164
+ "view": "turn right"
6165
+ },
6166
+ {
6167
+ "move": "no-op",
6168
+ "view": "turn right"
6169
+ },
6170
+ {
6171
+ "move": "no-op",
6172
+ "view": "turn right"
6173
+ },
6174
+ {
6175
+ "move": "no-op",
6176
+ "view": "turn right"
6177
+ },
6178
+ {
6179
+ "move": "no-op",
6180
+ "view": "turn right"
6181
+ },
6182
+ {
6183
+ "move": "no-op",
6184
+ "view": "turn right"
6185
+ },
6186
+ {
6187
+ "move": "no-op",
6188
+ "view": "turn right"
6189
+ },
6190
+ {
6191
+ "move": "no-op",
6192
+ "view": "turn right"
6193
+ },
6194
+ {
6195
+ "move": "no-op",
6196
+ "view": "turn right"
6197
+ },
6198
+ {
6199
+ "move": "no-op",
6200
+ "view": "turn right"
6201
+ },
6202
+ {
6203
+ "move": "no-op",
6204
+ "view": "turn right"
6205
+ },
6206
+ {
6207
+ "move": "no-op",
6208
+ "view": "turn right"
6209
+ },
6210
+ {
6211
+ "move": "no-op",
6212
+ "view": "turn right"
6213
+ },
6214
+ {
6215
+ "move": "no-op",
6216
+ "view": "turn right"
6217
+ },
6218
+ {
6219
+ "move": "no-op",
6220
+ "view": "turn right"
6221
+ },
6222
+ {
6223
+ "move": "no-op",
6224
+ "view": "turn right"
6225
+ },
6226
+ {
6227
+ "move": "no-op",
6228
+ "view": "turn right"
6229
+ },
6230
+ {
6231
+ "move": "no-op",
6232
+ "view": "turn right"
6233
+ }
6234
+ ]
assets/framework.png ADDED

Git LFS Details

  • SHA256: 4a4806f577b3871c7a0e370269fe7ca846a0dc09f8b680c9758f2b3325a28115
  • Pointer size: 131 Bytes
  • Size of remote file: 643 kB
configs/infworld_config.yaml ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Infinite World - Model Configuration
2
+ # Download from https://huggingface.co/Wan-AI/Wan2.1-T2V-1.3B and put files under checkpoints/models/
3
+ # Paths below are relative to project root unless absolute.
4
+
5
+ ##########################
6
+ ### DiT checkpoint (from config)
7
+ ##########################
8
+ # HF: diffusion_pytorch_model.safetensors; or your training .ckpt
9
+ checkpoint_path: "checkpoints/infinite_world_model.ckpt"
10
+
11
+ ##########################
12
+ ### text encoder config
13
+ ##########################
14
+
15
+ text_encoder_target: infworld.models.umt5.T5EncoderModel
16
+
17
+ text_encoder_cfg:
18
+ checkpoint_path: "checkpoints/models/models_t5_umt5-xxl-enc-bf16.pth"
19
+ tokenizer_path: "checkpoints/models/google/umt5-xxl"
20
+ model_max_length: 512
21
+
22
+ ##########################
23
+ ### scheduler config
24
+ ##########################
25
+
26
+ scheduler_target: infworld.models.scheduler.RFlowScheduler
27
+
28
+ val_scheduler_cfg:
29
+ shift: 7.0 # PX256: 3, PX627: 7, PX960: 11
30
+ use_reversed_velocity: true
31
+ use_timestep_transform: true
32
+ num_sampling_steps: 30
33
+ audio_cfg_scale: 5.0
34
+ text_cfg_scale: 5.0
35
+
36
+ ##########################
37
+ ### model config
38
+ ##########################
39
+
40
+ model_target: infworld.models.dit_model.WanModel
41
+
42
+ # 1.3B model config
43
+ model_cfg:
44
+ model_type: t2v
45
+ dim: 1536
46
+ in_channels: 20
47
+ ffn_dim: 8960
48
+ freq_dim: 256
49
+ num_heads: 12
50
+ num_layers: 30
51
+
52
+ ##########################
53
+ ### VAE config
54
+ ##########################
55
+
56
+ vae_target: infworld.vae.WanVAEModelWrapper
57
+
58
+ vae_cfg:
59
+ vae_pth: "checkpoints/models/Wan2.1_VAE.pth"
60
+
61
+
62
+ ##########################
63
+ ### validation config
64
+ ##########################
65
+
66
+ validation_data:
67
+ num_frames: 81
68
+
69
+ ##########################
70
+ ### other config
71
+ ##########################
72
+
73
+ amp_dtype: "bfloat16"
infer_local.sh ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Infinite World - Local Inference Script (Single/Multi GPU)
3
+ # Usage: bash infer_local.sh [num_gpus]
4
+ # Example: bash infer_local.sh 1 (single GPU, no torchrun, avoids port conflict)
5
+ # Example: bash infer_local.sh 8 (8 GPUs via torchrun)
6
+ #
7
+ # Single GPU (num_gpus=1): runs "python scripts/..." directly, no port needed.
8
+ # Multi GPU: runs torchrun. If EADDRINUSE, set: export MASTER_PORT=29500
9
+
10
+ NUM_GPUS=${1:-1}
11
+ WORK_DIR="/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/wuruiqi/infinite-world"
12
+
13
+ cd $WORK_DIR
14
+
15
+ echo "=============================================="
16
+ echo "Infinite World - Local Inference"
17
+ echo "=============================================="
18
+ echo "Using $NUM_GPUS GPU(s)"
19
+ echo "Working directory: $WORK_DIR"
20
+
21
+ if [ "$NUM_GPUS" -eq 1 ]; then
22
+ # Single GPU: run directly to avoid torchrun port (EADDRINUSE)
23
+ python scripts/infworld_inference.py
24
+ else
25
+ MASTER_PORT=${MASTER_PORT:-29400}
26
+ echo "MASTER_PORT: $MASTER_PORT"
27
+ torchrun --nnodes=1 --nproc_per_node=$NUM_GPUS \
28
+ --rdzv_id=100 --rdzv_backend=c10d \
29
+ --rdzv_endpoint=localhost:$MASTER_PORT \
30
+ scripts/infworld_inference.py
31
+ fi
infworld/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # infworld package
infworld/clip/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # infworld/clip package
infworld/clip/clip.py ADDED
@@ -0,0 +1,663 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from ``https://github.com/openai/CLIP'' and ``https://github.com/mlfoundations/open_clip''
2
+ # Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
3
+ import logging
4
+ import warnings
5
+ import math
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+ import torchvision.transforms as T
11
+
12
+
13
+ try:
14
+ import flash_attn_interface
15
+ FLASH_ATTN_3_AVAILABLE = True
16
+ except ModuleNotFoundError:
17
+ FLASH_ATTN_3_AVAILABLE = False
18
+
19
+ try:
20
+ import flash_attn
21
+ FLASH_ATTN_2_AVAILABLE = True
22
+ except ModuleNotFoundError:
23
+ FLASH_ATTN_2_AVAILABLE = False
24
+
25
+
26
+ from infworld.clip.tokenizers import HuggingfaceTokenizer
27
+ from infworld.clip.xlm_roberta import XLMRoberta
28
+
29
+ __all__ = [
30
+ 'XLMRobertaCLIP',
31
+ 'clip_xlm_roberta_vit_h_14',
32
+ 'CLIPModel',
33
+ ]
34
+
35
+ def flash_attention(
36
+ q,
37
+ k,
38
+ v,
39
+ q_lens=None,
40
+ k_lens=None,
41
+ dropout_p=0.,
42
+ softmax_scale=None,
43
+ q_scale=None,
44
+ causal=False,
45
+ window_size=(-1, -1),
46
+ deterministic=False,
47
+ dtype=torch.bfloat16,
48
+ version=None,
49
+ ):
50
+ """
51
+ q: [B, Lq, Nq, C1].
52
+ k: [B, Lk, Nk, C1].
53
+ v: [B, Lk, Nk, C2]. Nq must be divisible by Nk.
54
+ q_lens: [B].
55
+ k_lens: [B].
56
+ dropout_p: float. Dropout probability.
57
+ softmax_scale: float. The scaling of QK^T before applying softmax.
58
+ causal: bool. Whether to apply causal attention mask.
59
+ window_size: (left right). If not (-1, -1), apply sliding window local attention.
60
+ deterministic: bool. If True, slightly slower and uses more memory.
61
+ dtype: torch.dtype. Apply when dtype of q/k/v is not float16/bfloat16.
62
+ """
63
+ half_dtypes = (torch.float16, torch.bfloat16)
64
+ assert dtype in half_dtypes
65
+ assert q.device.type == 'cuda' and q.size(-1) <= 256
66
+
67
+ # params
68
+ b, lq, lk, out_dtype = q.size(0), q.size(1), k.size(1), q.dtype
69
+
70
+ def half(x):
71
+ return x if x.dtype in half_dtypes else x.to(dtype)
72
+
73
+ # preprocess query
74
+ if q_lens is None:
75
+ q = half(q.flatten(0, 1))
76
+ q_lens = torch.tensor(
77
+ [lq] * b, dtype=torch.int32).to(
78
+ device=q.device, non_blocking=True)
79
+ else:
80
+ q = half(torch.cat([u[:v] for u, v in zip(q, q_lens)]))
81
+
82
+ # preprocess key, value
83
+ if k_lens is None:
84
+ k = half(k.flatten(0, 1))
85
+ v = half(v.flatten(0, 1))
86
+ k_lens = torch.tensor(
87
+ [lk] * b, dtype=torch.int32).to(
88
+ device=k.device, non_blocking=True)
89
+ else:
90
+ k = half(torch.cat([u[:v] for u, v in zip(k, k_lens)]))
91
+ v = half(torch.cat([u[:v] for u, v in zip(v, k_lens)]))
92
+
93
+ q = q.to(v.dtype)
94
+ k = k.to(v.dtype)
95
+
96
+ if q_scale is not None:
97
+ q = q * q_scale
98
+
99
+ if version is not None and version == 3 and not FLASH_ATTN_3_AVAILABLE:
100
+ warnings.warn(
101
+ 'Flash attention 3 is not available, use flash attention 2 instead.'
102
+ )
103
+
104
+ # apply attention
105
+ if (version is None or version == 3) and FLASH_ATTN_3_AVAILABLE:
106
+ # Note: dropout_p, window_size are not supported in FA3 now.
107
+ x = flash_attn_interface.flash_attn_varlen_func(
108
+ q=q,
109
+ k=k,
110
+ v=v,
111
+ cu_seqlens_q=torch.cat([q_lens.new_zeros([1]), q_lens]).cumsum(
112
+ 0, dtype=torch.int32).to(q.device, non_blocking=True),
113
+ cu_seqlens_k=torch.cat([k_lens.new_zeros([1]), k_lens]).cumsum(
114
+ 0, dtype=torch.int32).to(q.device, non_blocking=True),
115
+ seqused_q=None,
116
+ seqused_k=None,
117
+ max_seqlen_q=lq,
118
+ max_seqlen_k=lk,
119
+ softmax_scale=softmax_scale,
120
+ causal=causal,
121
+ deterministic=deterministic)[0].unflatten(0, (b, lq))
122
+ else:
123
+ assert FLASH_ATTN_2_AVAILABLE
124
+ x = flash_attn.flash_attn_varlen_func(
125
+ q=q,
126
+ k=k,
127
+ v=v,
128
+ cu_seqlens_q=torch.cat([q_lens.new_zeros([1]), q_lens]).cumsum(
129
+ 0, dtype=torch.int32).to(q.device, non_blocking=True),
130
+ cu_seqlens_k=torch.cat([k_lens.new_zeros([1]), k_lens]).cumsum(
131
+ 0, dtype=torch.int32).to(q.device, non_blocking=True),
132
+ max_seqlen_q=lq,
133
+ max_seqlen_k=lk,
134
+ dropout_p=dropout_p,
135
+ softmax_scale=softmax_scale,
136
+ causal=causal,
137
+ window_size=window_size,
138
+ deterministic=deterministic).unflatten(0, (b, lq))
139
+
140
+ # output
141
+ return x.type(out_dtype)
142
+
143
+
144
+ def pos_interpolate(pos, seq_len):
145
+ if pos.size(1) == seq_len:
146
+ return pos
147
+ else:
148
+ src_grid = int(math.sqrt(pos.size(1)))
149
+ tar_grid = int(math.sqrt(seq_len))
150
+ n = pos.size(1) - src_grid * src_grid
151
+ return torch.cat([
152
+ pos[:, :n],
153
+ F.interpolate(
154
+ pos[:, n:].float().reshape(1, src_grid, src_grid, -1).permute(
155
+ 0, 3, 1, 2),
156
+ size=(tar_grid, tar_grid),
157
+ mode='bicubic',
158
+ align_corners=False).flatten(2).transpose(1, 2)
159
+ ],
160
+ dim=1)
161
+
162
+ class QuickGELU(nn.Module):
163
+
164
+ def forward(self, x):
165
+ return x * torch.sigmoid(1.702 * x)
166
+
167
+
168
+ class LayerNorm(nn.LayerNorm):
169
+
170
+ def forward(self, x):
171
+ return super().forward(x.float()).type_as(x)
172
+
173
+
174
+ class SelfAttention(nn.Module):
175
+
176
+ def __init__(self,
177
+ dim,
178
+ num_heads,
179
+ causal=False,
180
+ attn_dropout=0.0,
181
+ proj_dropout=0.0):
182
+ assert dim % num_heads == 0
183
+ super().__init__()
184
+ self.dim = dim
185
+ self.num_heads = num_heads
186
+ self.head_dim = dim // num_heads
187
+ self.causal = causal
188
+ self.attn_dropout = attn_dropout
189
+ self.proj_dropout = proj_dropout
190
+
191
+ # layers
192
+ self.to_qkv = nn.Linear(dim, dim * 3)
193
+ self.proj = nn.Linear(dim, dim)
194
+
195
+ def forward(self, x):
196
+ """
197
+ x: [B, L, C].
198
+ """
199
+ b, s, c, n, d = *x.size(), self.num_heads, self.head_dim
200
+
201
+ # compute query, key, value
202
+ q, k, v = self.to_qkv(x).view(b, s, 3, n, d).unbind(2)
203
+
204
+ # compute attention
205
+ p = self.attn_dropout if self.training else 0.0
206
+ x = flash_attention(q, k, v, dropout_p=p, causal=self.causal, version=2)
207
+ x = x.reshape(b, s, c)
208
+
209
+ # output
210
+ x = self.proj(x)
211
+ x = F.dropout(x, self.proj_dropout, self.training)
212
+ return x
213
+
214
+
215
+ class SwiGLU(nn.Module):
216
+
217
+ def __init__(self, dim, mid_dim):
218
+ super().__init__()
219
+ self.dim = dim
220
+ self.mid_dim = mid_dim
221
+
222
+ # layers
223
+ self.fc1 = nn.Linear(dim, mid_dim)
224
+ self.fc2 = nn.Linear(dim, mid_dim)
225
+ self.fc3 = nn.Linear(mid_dim, dim)
226
+
227
+ def forward(self, x):
228
+ x = F.silu(self.fc1(x)) * self.fc2(x)
229
+ x = self.fc3(x)
230
+ return x
231
+
232
+
233
+ class AttentionBlock(nn.Module):
234
+
235
+ def __init__(self,
236
+ dim,
237
+ mlp_ratio,
238
+ num_heads,
239
+ post_norm=False,
240
+ causal=False,
241
+ activation='quick_gelu',
242
+ attn_dropout=0.0,
243
+ proj_dropout=0.0,
244
+ norm_eps=1e-5):
245
+ assert activation in ['quick_gelu', 'gelu', 'swi_glu']
246
+ super().__init__()
247
+ self.dim = dim
248
+ self.mlp_ratio = mlp_ratio
249
+ self.num_heads = num_heads
250
+ self.post_norm = post_norm
251
+ self.causal = causal
252
+ self.norm_eps = norm_eps
253
+
254
+ # layers
255
+ self.norm1 = LayerNorm(dim, eps=norm_eps)
256
+ self.attn = SelfAttention(dim, num_heads, causal, attn_dropout,
257
+ proj_dropout)
258
+ self.norm2 = LayerNorm(dim, eps=norm_eps)
259
+ if activation == 'swi_glu':
260
+ self.mlp = SwiGLU(dim, int(dim * mlp_ratio))
261
+ else:
262
+ self.mlp = nn.Sequential(
263
+ nn.Linear(dim, int(dim * mlp_ratio)),
264
+ QuickGELU() if activation == 'quick_gelu' else nn.GELU(),
265
+ nn.Linear(int(dim * mlp_ratio), dim), nn.Dropout(proj_dropout))
266
+
267
+ def forward(self, x):
268
+ if self.post_norm:
269
+ x = x + self.norm1(self.attn(x))
270
+ x = x + self.norm2(self.mlp(x))
271
+ else:
272
+ x = x + self.attn(self.norm1(x))
273
+ x = x + self.mlp(self.norm2(x))
274
+ return x
275
+
276
+
277
+ class AttentionPool(nn.Module):
278
+
279
+ def __init__(self,
280
+ dim,
281
+ mlp_ratio,
282
+ num_heads,
283
+ activation='gelu',
284
+ proj_dropout=0.0,
285
+ norm_eps=1e-5):
286
+ assert dim % num_heads == 0
287
+ super().__init__()
288
+ self.dim = dim
289
+ self.mlp_ratio = mlp_ratio
290
+ self.num_heads = num_heads
291
+ self.head_dim = dim // num_heads
292
+ self.proj_dropout = proj_dropout
293
+ self.norm_eps = norm_eps
294
+
295
+ # layers
296
+ gain = 1.0 / math.sqrt(dim)
297
+ self.cls_embedding = nn.Parameter(gain * torch.randn(1, 1, dim))
298
+ self.to_q = nn.Linear(dim, dim)
299
+ self.to_kv = nn.Linear(dim, dim * 2)
300
+ self.proj = nn.Linear(dim, dim)
301
+ self.norm = LayerNorm(dim, eps=norm_eps)
302
+ self.mlp = nn.Sequential(
303
+ nn.Linear(dim, int(dim * mlp_ratio)),
304
+ QuickGELU() if activation == 'quick_gelu' else nn.GELU(),
305
+ nn.Linear(int(dim * mlp_ratio), dim), nn.Dropout(proj_dropout))
306
+
307
+ def forward(self, x):
308
+ """
309
+ x: [B, L, C].
310
+ """
311
+ b, s, c, n, d = *x.size(), self.num_heads, self.head_dim
312
+
313
+ # compute query, key, value
314
+ q = self.to_q(self.cls_embedding).view(1, 1, n, d).expand(b, -1, -1, -1)
315
+ k, v = self.to_kv(x).view(b, s, 2, n, d).unbind(2)
316
+
317
+ # compute attention
318
+ x = flash_attention(q, k, v, version=2)
319
+ x = x.reshape(b, 1, c)
320
+
321
+ # output
322
+ x = self.proj(x)
323
+ x = F.dropout(x, self.proj_dropout, self.training)
324
+
325
+ # mlp
326
+ x = x + self.mlp(self.norm(x))
327
+ return x[:, 0]
328
+
329
+
330
+ class VisionTransformer(nn.Module):
331
+
332
+ def __init__(self,
333
+ image_size=224,
334
+ patch_size=16,
335
+ dim=768,
336
+ mlp_ratio=4,
337
+ out_dim=512,
338
+ num_heads=12,
339
+ num_layers=12,
340
+ pool_type='token',
341
+ pre_norm=True,
342
+ post_norm=False,
343
+ activation='quick_gelu',
344
+ attn_dropout=0.0,
345
+ proj_dropout=0.0,
346
+ embedding_dropout=0.0,
347
+ norm_eps=1e-5):
348
+ if image_size % patch_size != 0:
349
+ print(
350
+ '[WARNING] image_size is not divisible by patch_size',
351
+ flush=True)
352
+ assert pool_type in ('token', 'token_fc', 'attn_pool')
353
+ out_dim = out_dim or dim
354
+ super().__init__()
355
+ self.image_size = image_size
356
+ self.patch_size = patch_size
357
+ self.num_patches = (image_size // patch_size)**2
358
+ self.dim = dim
359
+ self.mlp_ratio = mlp_ratio
360
+ self.out_dim = out_dim
361
+ self.num_heads = num_heads
362
+ self.num_layers = num_layers
363
+ self.pool_type = pool_type
364
+ self.post_norm = post_norm
365
+ self.norm_eps = norm_eps
366
+
367
+ # embeddings
368
+ gain = 1.0 / math.sqrt(dim)
369
+ self.patch_embedding = nn.Conv2d(
370
+ 3,
371
+ dim,
372
+ kernel_size=patch_size,
373
+ stride=patch_size,
374
+ bias=not pre_norm)
375
+ if pool_type in ('token', 'token_fc'):
376
+ self.cls_embedding = nn.Parameter(gain * torch.randn(1, 1, dim))
377
+ self.pos_embedding = nn.Parameter(gain * torch.randn(
378
+ 1, self.num_patches +
379
+ (1 if pool_type in ('token', 'token_fc') else 0), dim))
380
+ self.dropout = nn.Dropout(embedding_dropout)
381
+
382
+ # transformer
383
+ self.pre_norm = LayerNorm(dim, eps=norm_eps) if pre_norm else None
384
+ self.transformer = nn.Sequential(*[
385
+ AttentionBlock(dim, mlp_ratio, num_heads, post_norm, False,
386
+ activation, attn_dropout, proj_dropout, norm_eps)
387
+ for _ in range(num_layers)
388
+ ])
389
+ self.post_norm = LayerNorm(dim, eps=norm_eps)
390
+
391
+ # head
392
+ if pool_type == 'token':
393
+ self.head = nn.Parameter(gain * torch.randn(dim, out_dim))
394
+ elif pool_type == 'token_fc':
395
+ self.head = nn.Linear(dim, out_dim)
396
+ elif pool_type == 'attn_pool':
397
+ self.head = AttentionPool(dim, mlp_ratio, num_heads, activation,
398
+ proj_dropout, norm_eps)
399
+
400
+ def forward(self, x, interpolation=False, use_31_block=False):
401
+ b = x.size(0)
402
+
403
+ # embeddings
404
+ x = self.patch_embedding(x).flatten(2).permute(0, 2, 1)
405
+ if self.pool_type in ('token', 'token_fc'):
406
+ x = torch.cat([self.cls_embedding.expand(b, -1, -1), x], dim=1)
407
+ if interpolation:
408
+ e = pos_interpolate(self.pos_embedding, x.size(1))
409
+ else:
410
+ e = self.pos_embedding
411
+ x = self.dropout(x + e)
412
+ if self.pre_norm is not None:
413
+ x = self.pre_norm(x)
414
+
415
+ # transformer
416
+ if use_31_block:
417
+ x = self.transformer[:-1](x)
418
+ return x
419
+ else:
420
+ x = self.transformer(x)
421
+ return x
422
+
423
+
424
+ class XLMRobertaWithHead(XLMRoberta):
425
+
426
+ def __init__(self, **kwargs):
427
+ self.out_dim = kwargs.pop('out_dim')
428
+ super().__init__(**kwargs)
429
+
430
+ # head
431
+ mid_dim = (self.dim + self.out_dim) // 2
432
+ self.head = nn.Sequential(
433
+ nn.Linear(self.dim, mid_dim, bias=False), nn.GELU(),
434
+ nn.Linear(mid_dim, self.out_dim, bias=False))
435
+
436
+ def forward(self, ids):
437
+ # xlm-roberta
438
+ x = super().forward(ids)
439
+
440
+ # average pooling
441
+ mask = ids.ne(self.pad_id).unsqueeze(-1).to(x)
442
+ x = (x * mask).sum(dim=1) / mask.sum(dim=1)
443
+
444
+ # head
445
+ x = self.head(x)
446
+ return x
447
+
448
+
449
+ class XLMRobertaCLIP(nn.Module):
450
+
451
+ def __init__(self,
452
+ embed_dim=1024,
453
+ image_size=224,
454
+ patch_size=14,
455
+ vision_dim=1280,
456
+ vision_mlp_ratio=4,
457
+ vision_heads=16,
458
+ vision_layers=32,
459
+ vision_pool='token',
460
+ vision_pre_norm=True,
461
+ vision_post_norm=False,
462
+ activation='gelu',
463
+ vocab_size=250002,
464
+ max_text_len=514,
465
+ type_size=1,
466
+ pad_id=1,
467
+ text_dim=1024,
468
+ text_heads=16,
469
+ text_layers=24,
470
+ text_post_norm=True,
471
+ text_dropout=0.1,
472
+ attn_dropout=0.0,
473
+ proj_dropout=0.0,
474
+ embedding_dropout=0.0,
475
+ norm_eps=1e-5):
476
+ super().__init__()
477
+ self.embed_dim = embed_dim
478
+ self.image_size = image_size
479
+ self.patch_size = patch_size
480
+ self.vision_dim = vision_dim
481
+ self.vision_mlp_ratio = vision_mlp_ratio
482
+ self.vision_heads = vision_heads
483
+ self.vision_layers = vision_layers
484
+ self.vision_pre_norm = vision_pre_norm
485
+ self.vision_post_norm = vision_post_norm
486
+ self.activation = activation
487
+ self.vocab_size = vocab_size
488
+ self.max_text_len = max_text_len
489
+ self.type_size = type_size
490
+ self.pad_id = pad_id
491
+ self.text_dim = text_dim
492
+ self.text_heads = text_heads
493
+ self.text_layers = text_layers
494
+ self.text_post_norm = text_post_norm
495
+ self.norm_eps = norm_eps
496
+
497
+ # models
498
+ self.visual = VisionTransformer(
499
+ image_size=image_size,
500
+ patch_size=patch_size,
501
+ dim=vision_dim,
502
+ mlp_ratio=vision_mlp_ratio,
503
+ out_dim=embed_dim,
504
+ num_heads=vision_heads,
505
+ num_layers=vision_layers,
506
+ pool_type=vision_pool,
507
+ pre_norm=vision_pre_norm,
508
+ post_norm=vision_post_norm,
509
+ activation=activation,
510
+ attn_dropout=attn_dropout,
511
+ proj_dropout=proj_dropout,
512
+ embedding_dropout=embedding_dropout,
513
+ norm_eps=norm_eps)
514
+ self.textual = XLMRobertaWithHead(
515
+ vocab_size=vocab_size,
516
+ max_seq_len=max_text_len,
517
+ type_size=type_size,
518
+ pad_id=pad_id,
519
+ dim=text_dim,
520
+ out_dim=embed_dim,
521
+ num_heads=text_heads,
522
+ num_layers=text_layers,
523
+ post_norm=text_post_norm,
524
+ dropout=text_dropout)
525
+ self.log_scale = nn.Parameter(math.log(1 / 0.07) * torch.ones([]))
526
+
527
+ def forward(self, imgs, txt_ids):
528
+ """
529
+ imgs: [B, 3, H, W] of torch.float32.
530
+ - mean: [0.48145466, 0.4578275, 0.40821073]
531
+ - std: [0.26862954, 0.26130258, 0.27577711]
532
+ txt_ids: [B, L] of torch.long.
533
+ Encoded by data.CLIPTokenizer.
534
+ """
535
+ xi = self.visual(imgs)
536
+ xt = self.textual(txt_ids)
537
+ return xi, xt
538
+
539
+ def param_groups(self):
540
+ groups = [{
541
+ 'params': [
542
+ p for n, p in self.named_parameters()
543
+ if 'norm' in n or n.endswith('bias')
544
+ ],
545
+ 'weight_decay': 0.0
546
+ }, {
547
+ 'params': [
548
+ p for n, p in self.named_parameters()
549
+ if not ('norm' in n or n.endswith('bias'))
550
+ ]
551
+ }]
552
+ return groups
553
+
554
+
555
+ def _clip(pretrained=False,
556
+ pretrained_name=None,
557
+ model_cls=XLMRobertaCLIP,
558
+ return_transforms=False,
559
+ return_tokenizer=False,
560
+ tokenizer_padding='eos',
561
+ dtype=torch.float32,
562
+ device='cpu',
563
+ **kwargs):
564
+ # init a model on device
565
+ with torch.device(device):
566
+ model = model_cls(**kwargs)
567
+
568
+ # set device
569
+ model = model.to(dtype=dtype, device=device)
570
+ output = (model,)
571
+
572
+ # init transforms
573
+ if return_transforms:
574
+ # mean and std
575
+ if 'siglip' in pretrained_name.lower():
576
+ mean, std = [0.5, 0.5, 0.5], [0.5, 0.5, 0.5]
577
+ else:
578
+ mean = [0.48145466, 0.4578275, 0.40821073]
579
+ std = [0.26862954, 0.26130258, 0.27577711]
580
+
581
+ # transforms
582
+ transforms = T.Compose([
583
+ T.Resize((model.image_size, model.image_size),
584
+ interpolation=T.InterpolationMode.BICUBIC),
585
+ T.ToTensor(),
586
+ T.Normalize(mean=mean, std=std)
587
+ ])
588
+ output += (transforms,)
589
+ return output[0] if len(output) == 1 else output
590
+
591
+
592
+ def clip_xlm_roberta_vit_h_14(
593
+ pretrained=False,
594
+ pretrained_name='open-clip-xlm-roberta-large-vit-huge-14',
595
+ **kwargs):
596
+ cfg = dict(
597
+ embed_dim=1024,
598
+ image_size=224,
599
+ patch_size=14,
600
+ vision_dim=1280,
601
+ vision_mlp_ratio=4,
602
+ vision_heads=16,
603
+ vision_layers=32,
604
+ vision_pool='token',
605
+ activation='gelu',
606
+ vocab_size=250002,
607
+ max_text_len=514,
608
+ type_size=1,
609
+ pad_id=1,
610
+ text_dim=1024,
611
+ text_heads=16,
612
+ text_layers=24,
613
+ text_post_norm=True,
614
+ text_dropout=0.1,
615
+ attn_dropout=0.0,
616
+ proj_dropout=0.0,
617
+ embedding_dropout=0.0)
618
+ cfg.update(**kwargs)
619
+ return _clip(pretrained, pretrained_name, XLMRobertaCLIP, **cfg)
620
+
621
+
622
+ class CLIPModel:
623
+
624
+ def __init__(self, device, checkpoint_path, tokenizer_path, dtype=torch.float16):
625
+ self.dtype = dtype
626
+ self.device = device
627
+ self.checkpoint_path = checkpoint_path
628
+ self.tokenizer_path = tokenizer_path
629
+
630
+ # init model
631
+ self.model, self.transforms = clip_xlm_roberta_vit_h_14(
632
+ pretrained=False,
633
+ return_transforms=True,
634
+ return_tokenizer=False,
635
+ dtype=dtype,
636
+ device=device)
637
+ self.model = self.model.eval().requires_grad_(False)
638
+ logging.info(f'loading {checkpoint_path}')
639
+ self.model.load_state_dict(
640
+ torch.load(checkpoint_path, map_location='cpu'))
641
+
642
+ # init tokenizer
643
+ self.tokenizer = HuggingfaceTokenizer(
644
+ name=tokenizer_path,
645
+ seq_len=self.model.max_text_len - 2,
646
+ clean='whitespace')
647
+
648
+ def visual(self, videos):
649
+ # preprocess, list, C 1 H W
650
+ size = (self.model.image_size,) * 2 # (224, 224)
651
+ videos = torch.cat([
652
+ F.interpolate(
653
+ u.transpose(0, 1),
654
+ size=size,
655
+ mode='bicubic',
656
+ align_corners=False) for u in videos
657
+ ]) # 1 3 224 224
658
+ videos = self.transforms.transforms[-1](videos.mul_(0.5).add_(0.5)) # 1 3 224 224
659
+
660
+ # forward
661
+ with torch.cuda.amp.autocast(dtype=self.dtype):
662
+ out = self.model.visual(videos, use_31_block=True) # 1 257 1280
663
+ return out
infworld/clip/tokenizers.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
2
+ import html
3
+ import string
4
+
5
+ import ftfy
6
+ import regex as re
7
+ from transformers import AutoTokenizer
8
+
9
+ __all__ = ['HuggingfaceTokenizer']
10
+
11
+
12
+ def basic_clean(text):
13
+ text = ftfy.fix_text(text)
14
+ text = html.unescape(html.unescape(text))
15
+ return text.strip()
16
+
17
+
18
+ def whitespace_clean(text):
19
+ text = re.sub(r'\s+', ' ', text)
20
+ text = text.strip()
21
+ return text
22
+
23
+
24
+ def canonicalize(text, keep_punctuation_exact_string=None):
25
+ text = text.replace('_', ' ')
26
+ if keep_punctuation_exact_string:
27
+ text = keep_punctuation_exact_string.join(
28
+ part.translate(str.maketrans('', '', string.punctuation))
29
+ for part in text.split(keep_punctuation_exact_string))
30
+ else:
31
+ text = text.translate(str.maketrans('', '', string.punctuation))
32
+ text = text.lower()
33
+ text = re.sub(r'\s+', ' ', text)
34
+ return text.strip()
35
+
36
+
37
+ class HuggingfaceTokenizer:
38
+
39
+ def __init__(self, name, seq_len=None, clean=None, **kwargs):
40
+ assert clean in (None, 'whitespace', 'lower', 'canonicalize')
41
+ self.name = name
42
+ self.seq_len = seq_len
43
+ self.clean = clean
44
+
45
+ # init tokenizer
46
+ self.tokenizer = AutoTokenizer.from_pretrained(name, **kwargs)
47
+ self.vocab_size = self.tokenizer.vocab_size
48
+
49
+ def __call__(self, sequence, **kwargs):
50
+ return_mask = kwargs.pop('return_mask', False)
51
+
52
+ # arguments
53
+ _kwargs = {'return_tensors': 'pt'}
54
+ if self.seq_len is not None:
55
+ _kwargs.update({
56
+ 'padding': 'max_length',
57
+ 'truncation': True,
58
+ 'max_length': self.seq_len
59
+ })
60
+ _kwargs.update(**kwargs)
61
+
62
+ # tokenization
63
+ if isinstance(sequence, str):
64
+ sequence = [sequence]
65
+ if self.clean:
66
+ sequence = [self._clean(u) for u in sequence]
67
+ ids = self.tokenizer(sequence, **_kwargs)
68
+
69
+ # output
70
+ if return_mask:
71
+ return ids.input_ids, ids.attention_mask
72
+ else:
73
+ return ids.input_ids
74
+
75
+ def _clean(self, text):
76
+ if self.clean == 'whitespace':
77
+ text = whitespace_clean(basic_clean(text))
78
+ elif self.clean == 'lower':
79
+ text = whitespace_clean(basic_clean(text)).lower()
80
+ elif self.clean == 'canonicalize':
81
+ text = canonicalize(basic_clean(text))
82
+ return text
infworld/clip/xlm_roberta.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from transformers.models.xlm_roberta.modeling_xlm_roberta
2
+ # Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+
7
+ __all__ = ['XLMRoberta', 'xlm_roberta_large']
8
+
9
+
10
+ class SelfAttention(nn.Module):
11
+
12
+ def __init__(self, dim, num_heads, dropout=0.1, eps=1e-5):
13
+ assert dim % num_heads == 0
14
+ super().__init__()
15
+ self.dim = dim
16
+ self.num_heads = num_heads
17
+ self.head_dim = dim // num_heads
18
+ self.eps = eps
19
+
20
+ # layers
21
+ self.q = nn.Linear(dim, dim)
22
+ self.k = nn.Linear(dim, dim)
23
+ self.v = nn.Linear(dim, dim)
24
+ self.o = nn.Linear(dim, dim)
25
+ self.dropout = nn.Dropout(dropout)
26
+
27
+ def forward(self, x, mask):
28
+ """
29
+ x: [B, L, C].
30
+ """
31
+ b, s, c, n, d = *x.size(), self.num_heads, self.head_dim
32
+
33
+ # compute query, key, value
34
+ q = self.q(x).reshape(b, s, n, d).permute(0, 2, 1, 3)
35
+ k = self.k(x).reshape(b, s, n, d).permute(0, 2, 1, 3)
36
+ v = self.v(x).reshape(b, s, n, d).permute(0, 2, 1, 3)
37
+
38
+ # compute attention
39
+ p = self.dropout.p if self.training else 0.0
40
+ x = F.scaled_dot_product_attention(q, k, v, mask, p)
41
+ x = x.permute(0, 2, 1, 3).reshape(b, s, c)
42
+
43
+ # output
44
+ x = self.o(x)
45
+ x = self.dropout(x)
46
+ return x
47
+
48
+
49
+ class AttentionBlock(nn.Module):
50
+
51
+ def __init__(self, dim, num_heads, post_norm, dropout=0.1, eps=1e-5):
52
+ super().__init__()
53
+ self.dim = dim
54
+ self.num_heads = num_heads
55
+ self.post_norm = post_norm
56
+ self.eps = eps
57
+
58
+ # layers
59
+ self.attn = SelfAttention(dim, num_heads, dropout, eps)
60
+ self.norm1 = nn.LayerNorm(dim, eps=eps)
61
+ self.ffn = nn.Sequential(
62
+ nn.Linear(dim, dim * 4), nn.GELU(), nn.Linear(dim * 4, dim),
63
+ nn.Dropout(dropout))
64
+ self.norm2 = nn.LayerNorm(dim, eps=eps)
65
+
66
+ def forward(self, x, mask):
67
+ if self.post_norm:
68
+ x = self.norm1(x + self.attn(x, mask))
69
+ x = self.norm2(x + self.ffn(x))
70
+ else:
71
+ x = x + self.attn(self.norm1(x), mask)
72
+ x = x + self.ffn(self.norm2(x))
73
+ return x
74
+
75
+
76
+ class XLMRoberta(nn.Module):
77
+ """
78
+ XLMRobertaModel with no pooler and no LM head.
79
+ """
80
+
81
+ def __init__(self,
82
+ vocab_size=250002,
83
+ max_seq_len=514,
84
+ type_size=1,
85
+ pad_id=1,
86
+ dim=1024,
87
+ num_heads=16,
88
+ num_layers=24,
89
+ post_norm=True,
90
+ dropout=0.1,
91
+ eps=1e-5):
92
+ super().__init__()
93
+ self.vocab_size = vocab_size
94
+ self.max_seq_len = max_seq_len
95
+ self.type_size = type_size
96
+ self.pad_id = pad_id
97
+ self.dim = dim
98
+ self.num_heads = num_heads
99
+ self.num_layers = num_layers
100
+ self.post_norm = post_norm
101
+ self.eps = eps
102
+
103
+ # embeddings
104
+ self.token_embedding = nn.Embedding(vocab_size, dim, padding_idx=pad_id)
105
+ self.type_embedding = nn.Embedding(type_size, dim)
106
+ self.pos_embedding = nn.Embedding(max_seq_len, dim, padding_idx=pad_id)
107
+ self.dropout = nn.Dropout(dropout)
108
+
109
+ # blocks
110
+ self.blocks = nn.ModuleList([
111
+ AttentionBlock(dim, num_heads, post_norm, dropout, eps)
112
+ for _ in range(num_layers)
113
+ ])
114
+
115
+ # norm layer
116
+ self.norm = nn.LayerNorm(dim, eps=eps)
117
+
118
+ def forward(self, ids):
119
+ """
120
+ ids: [B, L] of torch.LongTensor.
121
+ """
122
+ b, s = ids.shape
123
+ mask = ids.ne(self.pad_id).long()
124
+
125
+ # embeddings
126
+ x = self.token_embedding(ids) + \
127
+ self.type_embedding(torch.zeros_like(ids)) + \
128
+ self.pos_embedding(self.pad_id + torch.cumsum(mask, dim=1) * mask)
129
+ if self.post_norm:
130
+ x = self.norm(x)
131
+ x = self.dropout(x)
132
+
133
+ # blocks
134
+ mask = torch.where(
135
+ mask.view(b, 1, 1, s).gt(0), 0.0,
136
+ torch.finfo(x.dtype).min)
137
+ for block in self.blocks:
138
+ x = block(x, mask)
139
+
140
+ # output
141
+ if not self.post_norm:
142
+ x = self.norm(x)
143
+ return x
144
+
145
+
146
+ def xlm_roberta_large(pretrained=False,
147
+ return_tokenizer=False,
148
+ device='cpu',
149
+ **kwargs):
150
+ """
151
+ XLMRobertaLarge adapted from Huggingface.
152
+ """
153
+ # params
154
+ cfg = dict(
155
+ vocab_size=250002,
156
+ max_seq_len=514,
157
+ type_size=1,
158
+ pad_id=1,
159
+ dim=1024,
160
+ num_heads=16,
161
+ num_layers=24,
162
+ post_norm=True,
163
+ dropout=0.1,
164
+ eps=1e-5)
165
+ cfg.update(**kwargs)
166
+
167
+ # init a model on device
168
+ with torch.device(device):
169
+ model = XLMRoberta(**cfg)
170
+ return model
infworld/configs/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # infworld/configs package
infworld/configs/bucket_config.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ASPECT_RATIO_200 = {
2
+ '0.23': ([96, 416], 1), '0.40': ([128, 320], 1), '0.62': ([160, 256], 1), '0.86': ([192, 224], 1),
3
+ '1.17': ([224, 192], 1), '1.60': ([256, 160], 1), '2.25': ([288, 128], 1), '2.50': ([320, 128], 1),
4
+ '2.75': ([352, 128], 1), '4.00': ([384, 96], 1)
5
+ }
6
+
7
+ ASPECT_RATIO_256 = {
8
+ '0.25': ([128, 512], 1), '0.38': ([160, 416], 1), '0.55': ([192, 352], 1), '0.78': ([224, 288], 1),
9
+ '1.00': ([256, 256], 1), '1.29': ([288, 224], 1), '1.67': ([320, 192], 1), '1.83': ([352, 192], 1),
10
+ '2.40': ([384, 160], 1), '2.60': ([416, 160], 1), '2.80': ([448, 160], 1), '3.75': ([480, 128], 1),
11
+ '4.00': ([512, 128], 1)
12
+ }
13
+
14
+ ASPECT_RATIO_256_SQUARE = {
15
+ '1.00': ([256, 256], 1),
16
+ }
17
+
18
+ ASPECT_RATIO_320 = {
19
+ '0.26': ([160, 608], 1), '0.38': ([192, 512], 1), '0.50': ([224, 448], 1), '0.67': ([256, 384], 1),
20
+ '0.82': ([288, 352], 1), '1.00': ([320, 320], 1), '1.22': ([352, 288], 1), '1.50': ([384, 256], 1),
21
+ '1.86': ([416, 224], 1), '2.00': ([448, 224], 1), '2.50': ([480, 192], 1), '2.83': ([544, 192], 1),
22
+ '3.60': ([576, 160], 1), '3.80': ([608, 160], 1), '4.00': ([640, 160], 1)
23
+ }
24
+
25
+ ASPECT_RATIO_400 = {
26
+ '0.23': ([192, 832], 1), '0.32': ([224, 704], 1), '0.40': ([256, 640], 1), '0.53': ([288, 544], 1),
27
+ '0.62': ([320, 512], 1), '0.79': ([352, 448], 1), '0.92': ([384, 416], 1), '1.08': ([416, 384], 1),
28
+ '1.27': ([448, 352], 1), '1.50': ([480, 320], 1), '1.60': ([512, 320], 1), '1.89': ([544, 288], 1),
29
+ '2.00': ([576, 288], 1), '2.38': ([608, 256], 1), '2.50': ([640, 256], 1), '3.00': ([672, 224], 1),
30
+ '3.14': ([704, 224], 1), '3.43': ([768, 224], 1), '4.17': ([800, 192], 1)
31
+ }
32
+
33
+ ASPECT_RATIO_400_F64 = {
34
+ '0.23': ([192, 832], 1), '0.40': ([256, 640], 1), '0.62': ([320, 512], 1), '0.86': ([384, 448], 1),
35
+ '1.17': ([448, 384], 1), '1.60': ([512, 320], 1), '2.25': ([576, 256], 1), '2.50': ([640, 256], 1),
36
+ '2.75': ([704, 256], 1), '4.00': ([768, 192], 1)
37
+ }
38
+
39
+ ASPECT_RATIO_400_F64_SQUARE = {
40
+ '0.23': ([192, 832], 1), '0.40': ([256, 640], 1), '0.62': ([320, 512], 1), '0.86': ([384, 448], 1), '1.0': ([448, 448], 1),
41
+ '1.17': ([448, 384], 1), '1.60': ([512, 320], 1), '2.25': ([576, 256], 1), '2.50': ([640, 256], 1),
42
+ '2.75': ([704, 256], 1), '4.00': ([768, 192], 1)
43
+ }
44
+
45
+ ASPECT_RATIO_512x512 = {
46
+ '1.0': ([512, 512], 1),
47
+ }
48
+
49
+ ASPECT_RATIO_512 = {
50
+ '0.25': ([256, 1024], 1), '0.26': ([256, 992], 1), '0.27': ([256, 960], 1), '0.28': ([256, 928], 1),
51
+ '0.32': ([288, 896], 1), '0.33': ([288, 864], 1), '0.35': ([288, 832], 1), '0.4': ([320, 800], 1),
52
+ '0.42': ([320, 768], 1), '0.48': ([352, 736], 1), '0.5': ([352, 704], 1), '0.52': ([352, 672], 1),
53
+ '0.57': ([384, 672], 1), '0.6': ([384, 640], 1), '0.68': ([416, 608], 1), '0.72': ([416, 576], 1),
54
+ '0.78': ([448, 576], 1), '0.82': ([448, 544], 1), '0.88': ([480, 544], 1), '0.94': ([480, 512], 1),
55
+ '1.0': ([512, 512], 1), '1.07': ([512, 480], 1), '1.13': ([544, 480], 1), '1.21': ([544, 448], 1),
56
+ '1.29': ([576, 448], 1), '1.38': ([576, 416], 1), '1.46': ([608, 416], 1), '1.67': ([640, 384], 1),
57
+ '1.75': ([672, 384], 1), '2.0': ([704, 352], 1), '2.09': ([736, 352], 1), '2.4': ([768, 320], 1),
58
+ '2.5': ([800, 320], 1), '2.89': ([832, 288], 1), '3.0': ([864, 288], 1), '3.11': ([896, 288], 1),
59
+ '3.62': ([928, 256], 1), '3.75': ([960, 256], 1), '3.88': ([992, 256], 1), '4.0': ([1024, 256], 1),
60
+ }
61
+
62
+ # ASPECT_RATIO_627 = {
63
+ # '0.26': ([320, 1216], 1), '0.31': ([352, 1120], 1), '0.38': ([384, 1024], 1), '0.43': ([416, 960], 1),
64
+ # '0.52': ([448, 864], 1), '0.58': ([448, 768], 1), '0.67': ([512, 768], 1), '0.74': ([544, 736], 1),
65
+ # '0.86': ([576, 672], 1), '0.95': ([608, 640], 1), '1.05': ([640, 608], 1), '1.17': ([672, 576], 1),
66
+ # '1.29': ([704, 544], 1), '1.35': ([736, 544], 1), '1.50': ([768, 512], 1), '1.67': ([800, 480], 1),
67
+ # '1.73': ([832, 480], 1), '2.00': ([896, 448], 1), '2.31': ([960, 416], 1), '2.58': ([992, 384], 1),
68
+ # '2.75': ([1056, 384], 1), '3.09': ([1088, 352], 1), '3.70': ([1184, 320], 1), '3.80': ([1216, 320], 1),
69
+ # '3.90': ([1248, 320], 1), '4.00': ([1280, 320], 1)
70
+ # }
71
+
72
+ ASPECT_RATIO_627 = {
73
+ '0.26': ([320, 1216], 1), '0.31': ([352, 1120], 1), '0.38': ([384, 1024], 1), '0.43': ([416, 960], 1),
74
+ '0.52': ([448, 864], 1), '0.58': ([480, 832], 1), '0.67': ([512, 768], 1), '0.74': ([544, 736], 1),
75
+ '0.86': ([576, 672], 1), '0.95': ([608, 640], 1), '1.05': ([640, 608], 1), '1.17': ([672, 576], 1),
76
+ '1.29': ([704, 544], 1), '1.35': ([736, 544], 1), '1.50': ([768, 512], 1), '1.67': ([800, 480], 1),
77
+ '1.73': ([832, 480], 1), '2.00': ([896, 448], 1), '2.31': ([960, 416], 1), '2.58': ([992, 384], 1),
78
+ '2.75': ([1056, 384], 1), '3.09': ([1088, 352], 1), '3.70': ([1184, 320], 1), '3.80': ([1216, 320], 1),
79
+ '3.90': ([1248, 320], 1), '4.00': ([1280, 320], 1)
80
+ }
81
+
82
+
83
+ ASPECT_RATIO_627_F64 = {
84
+ '0.26': ([320, 1216], 1), '0.38': ([384, 1024], 1), '0.50': ([448, 896], 1), '0.67': ([512, 768], 1),
85
+ '0.82': ([576, 704], 1), '1.00': ([640, 640], 1), '1.22': ([704, 576], 1), '1.50': ([768, 512], 1),
86
+ '1.86': ([832, 448], 1), '2.00': ([896, 448], 1), '2.50': ([960, 384], 1), '2.83': ([1088, 384], 1),
87
+ '3.60': ([1152, 320], 1), '3.80': ([1216, 320], 1), '4.00': ([1280, 320], 1)}
88
+
89
+ ASPECT_RATIO_960 = {
90
+ '0.25': ([480, 1920], 1), '0.29': ([512, 1792], 1), '0.32': ([544, 1696], 1), '0.36': ([576, 1600], 1),
91
+ '0.40': ([608, 1504], 1), '0.49': ([672, 1376], 1), '0.54': ([704, 1312], 1), '0.59': ([736, 1248], 1),
92
+ '0.69': ([800, 1152], 1), '0.74': ([832, 1120], 1), '0.82': ([864, 1056], 1), '0.88': ([896, 1024], 1),
93
+ '0.94': ([928, 992], 1), '1.00': ([960, 960], 1), '1.07': ([992, 928], 1), '1.14': ([1024, 896], 1),
94
+ '1.22': ([1056, 864], 1), '1.31': ([1088, 832], 1), '1.35': ([1120, 832], 1), '1.44': ([1152, 800], 1),
95
+ '1.70': ([1248, 736], 1), '2.00': ([1344, 672], 1), '2.05': ([1376, 672], 1), '2.47': ([1504, 608], 1),
96
+ '2.53': ([1536, 608], 1), '2.83': ([1632, 576], 1), '3.06': ([1664, 544], 1), '3.12': ([1696, 544], 1),
97
+ '3.62': ([1856, 512], 1), '3.93': ([1888, 480], 1), '4.00': ([1920, 480], 1)
98
+ }
99
+
100
+ ASPECT_RATIO_960_F64 = {
101
+ '0.22': ([448, 2048], 1), '0.29': ([512, 1792], 1), '0.36': ([576, 1600], 1), '0.45': ([640, 1408], 1),
102
+ '0.55': ([704, 1280], 1), '0.63': ([768, 1216], 1), '0.76': ([832, 1088], 1), '0.88': ([896, 1024], 1),
103
+ '1.00': ([960, 960], 1), '1.14': ([1024, 896], 1), '1.31': ([1088, 832], 1), '1.50': ([1152, 768], 1),
104
+ '1.58': ([1216, 768], 1), '1.82': ([1280, 704], 1), '1.91': ([1344, 704], 1), '2.20': ([1408, 640], 1),
105
+ '2.30': ([1472, 640], 1), '2.67': ([1536, 576], 1), '2.89': ([1664, 576], 1), '3.62': ([1856, 512], 1),
106
+ '3.75': ([1920, 512], 1)}
107
+
108
+
109
+ ASPECT_RATIO_1440_F64 = {
110
+ '0.24': ([704, 2944], 1), '0.29': ([768, 2688], 1), '0.33': ([832, 2496], 1), '0.39': ([896, 2304], 1),
111
+ '0.44': ([960, 2176], 1), '0.50': ([1024, 2048], 1), '0.57': ([1088, 1920], 1), '0.70': ([1216, 1728], 1),
112
+ '0.80': ([1280, 1600], 1), '0.88': ([1344, 1536], 1), '0.96': ([1408, 1472], 1), '1.05': ([1472, 1408], 1),
113
+ '1.14': ([1536, 1344], 1), '1.25': ([1600, 1280], 1), '1.37': ([1664, 1216], 1), '1.42': ([1728, 1216], 1),
114
+ '1.71': ([1856, 1088], 1), '1.76': ([1920, 1088], 1), '2.00': ([2048, 1024], 1), '2.50': ([2240, 896], 1),
115
+ '2.92': ([2432, 832], 1), '3.00': ([2496, 832], 1), '3.08': ([2560, 832], 1), '3.58': ([2752, 768], 1),
116
+ '3.67': ([2816, 768], 1), '4.09': ([2880, 704], 1)
117
+ }
118
+
119
+ # this func is only used for bucket config generation
120
+ def find_hw(target_area, target_ratio, factor=32):
121
+
122
+ min_side = factor
123
+ max_side = target_area // factor // factor * factor + factor
124
+ min_error = float('inf')
125
+ best_solution = None
126
+
127
+ for height in range(max_side, min_side-1, -factor):
128
+ width = round(target_area / height / factor) * factor
129
+ if width < min_side:
130
+ continue
131
+
132
+ ratio = height / width
133
+
134
+ ratio_error = abs(ratio - target_ratio)
135
+
136
+ if ratio_error < min_error:
137
+ min_error = ratio_error
138
+ best_solution = (height, width)
139
+
140
+ if ratio_error == 0:
141
+ break
142
+
143
+ return best_solution
144
+
145
+ if __name__ == "__main__":
146
+ ratios = list(map(float, ASPECT_RATIO_512.keys()))
147
+ res = {}
148
+ for ratio in ratios:
149
+ h,w = find_hw(400**2, ratio, 64)
150
+ res[f"{h/w:.2f}"] = ([h,w], 1)
151
+ print((h*w)**0.5)
152
+
153
+ print(res)
154
+
155
+
infworld/context_parallel/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # infworld/context_parallel package
infworld/context_parallel/context_parallel_util.py ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import math
3
+ import random
4
+ import argparse
5
+ import datetime
6
+ import logging
7
+ import inspect
8
+ import subprocess
9
+
10
+ import torch
11
+ import torch.distributed as dist
12
+ from torch.distributed.device_mesh import init_device_mesh
13
+ from einops import rearrange, repeat
14
+
15
+
16
+ dp_size = None
17
+ cp_size = None
18
+ dp_group = None
19
+ cp_group = None
20
+ cp_stream = None
21
+ dp_ranks = None
22
+ cp_ranks = None
23
+ dp_rank = None
24
+ cp_rank = None
25
+
26
+
27
+ def init_context_parallel(context_parallel_size: int = 1,
28
+ global_rank: int = 1,
29
+ world_size: int = 1,):
30
+
31
+ global dp_size
32
+ global cp_size
33
+ global dp_group
34
+ global cp_group
35
+ global dp_ranks
36
+ global cp_ranks
37
+ global dp_rank
38
+ global cp_rank
39
+
40
+
41
+ if world_size%context_parallel_size != 0:
42
+ raise RuntimeError(f'world_size {world_size} must be multiple of context_parallel_size {context_parallel_size}!!!')
43
+
44
+
45
+ cp_size = context_parallel_size
46
+ dp_size = world_size//context_parallel_size
47
+
48
+
49
+ print(f'[rank {global_rank}] init_device_mesh [dp_size x cp_size]: [{dp_size} x {cp_size}]')
50
+
51
+ mesh_2d = init_device_mesh("cuda", (dp_size, cp_size), mesh_dim_names=("dp", "cp"))
52
+
53
+ print(f'[rank {global_rank}] mesh_2d: {mesh_2d}')
54
+
55
+ dp_group = mesh_2d.get_group(mesh_dim="dp")
56
+ cp_group = mesh_2d.get_group(mesh_dim="cp")
57
+
58
+ dp_ranks = torch.distributed.get_process_group_ranks(dp_group)
59
+ cp_ranks = torch.distributed.get_process_group_ranks(cp_group)
60
+
61
+ dp_rank = dist.get_rank(group=dp_group)
62
+ cp_rank = dist.get_rank(group=cp_group)
63
+
64
+ global_rank_1 = torch.distributed.get_rank()
65
+ print(f'[rank {global_rank_1}] [dp_rank, cp_rank]: [{dp_rank}, {cp_rank}], dp_ranks: {dp_ranks}, cp_ranks: {cp_ranks}')
66
+
67
+
68
+ def get_cp_size():
69
+
70
+ global cp_size
71
+ return cp_size
72
+
73
+ def get_dp_size():
74
+
75
+ global dp_size
76
+ return dp_size
77
+
78
+ def get_cp_stream():
79
+
80
+ global cp_stream
81
+ if cp_stream == None:
82
+ cp_stream = torch.cuda.Stream()
83
+
84
+ return cp_stream
85
+
86
+ def get_dp_group():
87
+
88
+ global dp_group
89
+ return dp_group
90
+
91
+ def get_cp_group():
92
+
93
+ global cp_group
94
+ return cp_group
95
+
96
+
97
+ def get_dp_rank():
98
+
99
+ global dp_rank
100
+ global cp_rank
101
+
102
+ return dp_rank
103
+
104
+
105
+ def get_cp_rank():
106
+
107
+ global dp_rank
108
+ global cp_rank
109
+
110
+ return cp_rank
111
+
112
+
113
+
114
+ def get_cp_rank_list():
115
+
116
+ global cp_ranks
117
+ if cp_ranks == None:
118
+ cp_ranks = torch.distributed.get_process_group_ranks(cp_group)
119
+ return cp_ranks
120
+
121
+
122
+ def cp_broadcast(tensor, cp_index=0):
123
+
124
+ global dp_group
125
+ global cp_group
126
+
127
+ cp_ranks = get_cp_rank_list()
128
+
129
+ torch.distributed.broadcast(tensor, cp_ranks[cp_index], group=cp_group)
130
+
131
+
132
+
133
+
134
+ def cp_broadcast_objects(tensor):
135
+
136
+ global dp_group
137
+ global cp_group
138
+
139
+ raise NotImplementedError("cp_broadcast_objects method is not yet implemented!!!")
140
+
141
+
142
+
143
+
144
+ def split_tensor_in_cp(input, seq_dim):
145
+
146
+ global cp_size
147
+
148
+ seq_size = input.shape[seq_dim]
149
+
150
+ if seq_size%cp_size != 0:
151
+ raise RuntimeError(f'seq_length {seq_size} in dim {seq_dim} must be multiple of cp_size {cp_size}!!!')
152
+
153
+ split_seq_size = seq_size//cp_size
154
+
155
+ tensor_splits = input.split(split_seq_size, dim=seq_dim)
156
+
157
+ cp_rank = get_cp_rank()
158
+
159
+ split_tensor = tensor_splits[cp_rank]
160
+
161
+ return split_tensor
162
+
163
+
164
+
165
+
166
+
167
+ class GatherFunction(torch.autograd.Function):
168
+
169
+ @staticmethod
170
+ def forward(ctx, input, process_group, seq_dim, frames):
171
+ ctx.cp_group = process_group
172
+ ctx.seq_dim = seq_dim
173
+ ctx.frames = frames
174
+ ctx.cp_size = get_cp_size()
175
+
176
+ input = rearrange(input, "B (T S) C -> B T S C", T=frames)
177
+
178
+ with torch.no_grad():
179
+
180
+ input = input.contiguous()
181
+
182
+ output_tensors = [torch.zeros_like(input) for _ in range(ctx.cp_size)]
183
+
184
+ dist.all_gather(output_tensors, input, group=ctx.cp_group)
185
+
186
+ output_tensor = torch.cat(output_tensors, dim=seq_dim)
187
+
188
+
189
+
190
+ output_tensor = rearrange(output_tensor, "B T S C -> B (T S) C", T=frames)
191
+
192
+
193
+ return output_tensor
194
+
195
+ @staticmethod
196
+ def backward(ctx, grad_output):
197
+
198
+
199
+ with torch.no_grad():
200
+
201
+ grad_output = grad_output * ctx.cp_size
202
+
203
+ grad_output = rearrange(grad_output, "B (T S) C -> B T S C", T=ctx.frames)
204
+
205
+ grad_input = split_tensor_in_cp(grad_output, ctx.seq_dim)
206
+
207
+ grad_input = rearrange(grad_input, "B T S C -> B (T S) C", T=ctx.frames)
208
+
209
+
210
+ return grad_input, None, None, None
211
+
212
+
213
+
214
+
215
+ class SplitFunction(torch.autograd.Function):
216
+
217
+ @staticmethod
218
+ def forward(ctx, input, process_group, seq_dim):
219
+ ctx.cp_group = process_group
220
+ ctx.seq_dim = seq_dim
221
+ ctx.cp_size = get_cp_size()
222
+
223
+ output_tensor = split_tensor_in_cp(input, ctx.seq_dim)
224
+
225
+ return output_tensor
226
+
227
+ @staticmethod
228
+ def backward(ctx, grad_output):
229
+
230
+
231
+ with torch.no_grad():
232
+
233
+
234
+ grad_output = grad_output / ctx.cp_size
235
+
236
+ output_tensors = [torch.zeros_like(grad_output) for _ in range(ctx.cp_size)]
237
+
238
+ dist.all_gather(output_tensors, grad_output, group=ctx.cp_group)
239
+
240
+ grad_input = torch.cat(output_tensors, dim=ctx.seq_dim)
241
+
242
+
243
+ return grad_input, None, None
244
+
245
+
246
+
247
+ def gather_cp(input, frames):
248
+
249
+ cp_process_group = get_cp_group()
250
+
251
+ output_tensor = GatherFunction.apply(input, cp_process_group, 2, frames)
252
+
253
+ return output_tensor
254
+
255
+
256
+ def split_cp(input, seq_dim):
257
+
258
+ cp_process_group = get_cp_group()
259
+
260
+ output_tensor = SplitFunction.apply(input, cp_process_group, seq_dim)
261
+
262
+ return output_tensor
263
+
264
+
265
+
266
+
267
+ class ReduceFunction(torch.autograd.Function):
268
+
269
+ @staticmethod
270
+ def forward(ctx, input, process_group):
271
+ ctx.cp_group = process_group
272
+
273
+ output = input.detach().clone()
274
+
275
+ dist.all_reduce(output, group=ctx.cp_group)
276
+
277
+ return output
278
+
279
+ @staticmethod
280
+ def backward(ctx, grad_output):
281
+
282
+ grad_input = grad_output.detach().clone()
283
+
284
+ return grad_input, None
285
+
286
+
287
+
288
+ class ReplicateFunction(torch.autograd.Function):
289
+
290
+ @staticmethod
291
+ def forward(ctx, input, process_group):
292
+ ctx.cp_group = process_group
293
+
294
+ output = input.detach().clone()
295
+
296
+
297
+ return output
298
+
299
+ @staticmethod
300
+ def backward(ctx, grad_output):
301
+
302
+ grad_input = grad_output.detach().clone()
303
+
304
+ dist.all_reduce(grad_input, group=ctx.cp_group)
305
+
306
+
307
+ return grad_input, None
308
+
309
+
310
+ def reduce_cp(partial_sum, partial_square_sum):
311
+
312
+ cp_process_group = get_cp_group()
313
+
314
+ all_sum = ReduceFunction.apply(partial_sum, cp_process_group)
315
+ all_square_sum = ReduceFunction.apply(partial_square_sum, cp_process_group)
316
+
317
+ return all_sum, all_square_sum
318
+
319
+
320
+ def replicate_cp(all_mean, all_var):
321
+
322
+ cp_process_group = get_cp_group()
323
+
324
+ all_mean = ReplicateFunction.apply(all_mean, cp_process_group)
325
+ all_var = ReplicateFunction.apply(all_var, cp_process_group)
326
+
327
+ return all_mean, all_var
328
+
329
+
330
+
331
+ def _all_to_all_func(input_, world_size, group, scatter_dim, gather_dim):
332
+ input_list = [t.contiguous() for t in torch.tensor_split(input_, world_size, scatter_dim)]
333
+ output_list = [torch.empty_like(input_list[0]) for _ in range(world_size)]
334
+ dist.all_to_all(output_list, input_list, group=group)
335
+ return torch.cat(output_list, dim=gather_dim).contiguous()
336
+
337
+
338
+ class _AllToAll(torch.autograd.Function):
339
+ """All-to-all communication.
340
+
341
+ Args:
342
+ input_: input matrix
343
+ process_group: communication group
344
+ scatter_dim: scatter dimension
345
+ gather_dim: gather dimension
346
+ """
347
+
348
+ @staticmethod
349
+ def forward(ctx, input_, process_group, scatter_dim, gather_dim):
350
+ ctx.process_group = process_group
351
+ ctx.scatter_dim = scatter_dim
352
+ ctx.gather_dim = gather_dim
353
+ world_size = dist.get_world_size(process_group)
354
+
355
+ return _all_to_all_func(input_, world_size, process_group, scatter_dim, gather_dim)
356
+
357
+ @staticmethod
358
+ def backward(ctx, *grad_output):
359
+ process_group = ctx.process_group
360
+ scatter_dim = ctx.gather_dim
361
+ gather_dim = ctx.scatter_dim
362
+ return_grad = _AllToAll.apply(*grad_output, process_group, scatter_dim, gather_dim)
363
+ return (return_grad, None, None, None)
364
+
365
+
366
+ def all_to_all_with_pad(
367
+ input_: torch.Tensor,
368
+ process_group: dist.ProcessGroup,
369
+ scatter_dim: int = 2,
370
+ gather_dim: int = 1,
371
+ scatter_pad: int = 0,
372
+ gather_pad: int = 0,
373
+ ):
374
+ if scatter_pad > 0:
375
+ pad_shape = list(input_.shape)
376
+ pad_shape[scatter_dim] = scatter_pad
377
+ pad_tensor = torch.zeros(pad_shape, device=input_.device, dtype=input_.dtype)
378
+ input_ = torch.cat([input_, pad_tensor], dim=scatter_dim)
379
+
380
+ assert (
381
+ input_.shape[scatter_dim] % dist.get_world_size(process_group) == 0
382
+ ), f"Dimension to scatter ({input_.shape[scatter_dim]}) is not divisible by world size ({dist.get_world_size(process_group)})"
383
+ input_ = _AllToAll.apply(input_, process_group, scatter_dim, gather_dim)
384
+
385
+ if gather_pad > 0:
386
+ input_ = input_.narrow(gather_dim, 0, input_.size(gather_dim) - gather_pad)
387
+
388
+ return input_
389
+
390
+
391
+ def dynamic_switch(x, scatter_dim, gather_dim):
392
+
393
+ scatter_pad = 0
394
+ gather_pad = 0
395
+ cp_process_group = get_cp_group()
396
+
397
+ x = all_to_all_with_pad(
398
+ x,
399
+ cp_process_group,
400
+ scatter_dim=scatter_dim,
401
+ gather_dim=gather_dim,
402
+ scatter_pad=scatter_pad,
403
+ gather_pad=gather_pad,
404
+ )
405
+ return x
infworld/models/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # infworld/models package
infworld/models/checkpoint.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections.abc import Iterable
2
+
3
+ import torch.nn as nn
4
+ from torch.utils.checkpoint import checkpoint, checkpoint_sequential
5
+
6
+
7
+ def set_grad_checkpoint(model, use_fp32_attention=False, gc_step=1):
8
+ assert isinstance(model, nn.Module)
9
+
10
+ def set_attr(module):
11
+ module.grad_checkpointing = True
12
+ module.fp32_attention = use_fp32_attention
13
+ module.grad_checkpointing_step = gc_step
14
+
15
+ model.apply(set_attr)
16
+
17
+
18
+ def auto_grad_checkpoint(module, *args, **kwargs):
19
+ if getattr(module, "grad_checkpointing", False):
20
+ if not isinstance(module, Iterable):
21
+ return checkpoint(module, *args, **kwargs)
22
+ gc_step = module[0].grad_checkpointing_step
23
+ return checkpoint_sequential(module, gc_step, *args, **kwargs)
24
+ return module(*args, **kwargs)
infworld/models/dit_model.py ADDED
@@ -0,0 +1,1285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
2
+ import math
3
+ import os
4
+ import torch
5
+ import torch.cuda.amp as amp
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+
9
+ from einops import rearrange
10
+ from infworld.context_parallel import context_parallel_util
11
+
12
+ from infworld.models.checkpoint import auto_grad_checkpoint
13
+
14
+ try:
15
+ from transformer_engine.pytorch.attention import DotProductAttention
16
+ except:
17
+ print("Import transformer_engine failed, may cause bug.")
18
+
19
+ try:
20
+ import flash_attn_interface
21
+ FLASH_ATTN_3_AVAILABLE = True
22
+ except ModuleNotFoundError:
23
+ FLASH_ATTN_3_AVAILABLE = False
24
+
25
+ try:
26
+ import flash_attn
27
+ FLASH_ATTN_2_AVAILABLE = True
28
+ except ModuleNotFoundError:
29
+ FLASH_ATTN_2_AVAILABLE = False
30
+
31
+ import warnings
32
+
33
+ __all__ = ['WanModel']
34
+
35
+ class ResnetBlock3D(nn.Module):
36
+ def __init__(self, in_channels, out_channels=None, dropout=0.0):
37
+ super().__init__()
38
+ out_channels = out_channels or in_channels
39
+
40
+ self.norm1 = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
41
+ self.conv1 = nn.Conv3d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
42
+
43
+ self.norm2 = nn.GroupNorm(num_groups=32, num_channels=out_channels, eps=1e-6, affine=True)
44
+ self.dropout = nn.Dropout(dropout)
45
+ self.conv2 = nn.Conv3d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
46
+
47
+ self.nonlinearity = nn.SiLU()
48
+
49
+ # Shortcut connection
50
+ if in_channels != out_channels:
51
+ self.shortcut = nn.Conv3d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
52
+ else:
53
+ self.shortcut = nn.Identity()
54
+
55
+ def forward(self, x):
56
+ h = x
57
+ h = self.norm1(h)
58
+ h = self.nonlinearity(h)
59
+ h = self.conv1(h)
60
+
61
+ h = self.norm2(h)
62
+ h = self.nonlinearity(h)
63
+ h = self.dropout(h)
64
+ h = self.conv2(h)
65
+
66
+ return h + self.shortcut(x)
67
+
68
+
69
+ class TemporalDownsample(nn.Module):
70
+ def __init__(self, channels):
71
+ super().__init__()
72
+ # 时序下采样: kernel=3, stride=(2,1,1), padding=(1,1,1)
73
+ # T -> T/2, H, W 保持不变
74
+ self.conv = nn.Conv3d(channels, channels, kernel_size=3, stride=(2, 1, 1), padding=(1, 1, 1))
75
+
76
+ def forward(self, x):
77
+ return self.conv(x)
78
+
79
+
80
+ class WanEncoderAttentionBlock(nn.Module):
81
+ def __init__(self, dim, num_heads=8, window_size=(-1, -1), eps=1e-6):
82
+ super().__init__()
83
+ self.dim = dim
84
+ self.num_heads = num_heads
85
+ self.head_dim = dim // num_heads
86
+
87
+ # 内部使用 WanSelfAttention,保持与主干网络一致的 3D RoPE 和 FlashAttention
88
+ self.attn = WanSelfAttention(
89
+ dim,
90
+ num_heads,
91
+ window_size=window_size,
92
+ qk_norm=True,
93
+ eps=eps
94
+ )
95
+
96
+ # Pre-Norm
97
+ self.norm = WanLayerNorm(dim, eps)
98
+
99
+ def _build_freqs(self, device):
100
+ # 构建 RoPE 频率参数
101
+ d = self.head_dim
102
+ freqs = torch.cat([
103
+ rope_params(1024, d - 4 * (d // 6)),
104
+ rope_params(1024, 2 * (d // 6)),
105
+ rope_params(1024, 2 * (d // 6))
106
+ ], dim=1)
107
+ return freqs.to(device)
108
+
109
+ def forward(self, x):
110
+ # Input: (B, C, T, H, W)
111
+ B, C, T, H, W = x.shape
112
+
113
+ # 1. 转换格式: (B, C, T, H, W) -> (B, L, C)
114
+ # 先 permute 到 (B, T, H, W, C),再 flatten
115
+ x_in = x.permute(0, 2, 3, 4, 1).flatten(1, 3)
116
+
117
+ # 2. Norm
118
+ x_norm = self.norm(x_in)
119
+
120
+ # 3. 构造 Metadata
121
+ # grid_sizes: [B, 3] -> [[T, H, W], ...]
122
+ grid_sizes = torch.tensor([T, H, W], device=x.device).unsqueeze(0).repeat(B, 1)
123
+
124
+ # seq_lens: [B]
125
+ seq_lens = torch.tensor([T * H * W] * B, device=x.device, dtype=torch.long)
126
+
127
+ # freqs: RoPE (可以考虑缓存,这里为了独立性实时生成)
128
+ freqs = self._build_freqs(x.device)
129
+
130
+ # 4. Attention Forward
131
+ # Encoder 内部通常不需要 causal mask 或 ignore mask
132
+ x_out = self.attn(
133
+ x_norm,
134
+ seq_lens=seq_lens,
135
+ grid_sizes=grid_sizes,
136
+ freqs=freqs,
137
+ token_ignore_mask=None
138
+ )
139
+
140
+ # 5. Residual + 恢复形状
141
+ x_out = x_in + x_out
142
+
143
+ # (B, L, C) -> (B, T, H, W, C) -> (B, C, T, H, W)
144
+ x_out = x_out.view(B, T, H, W, C).permute(0, 4, 1, 2, 3)
145
+
146
+ return x_out
147
+
148
+
149
+ class TemporalLatentEncoder(nn.Module):
150
+ def __init__(self, in_channels=16, hidden_dim=256, num_heads=8, use_checkpoint=True):
151
+ """
152
+ 高配版时序 Encoder
153
+ 结构: ConvIn -> ResBlock*2 -> Down -> ResBlock*2 -> Down -> ResBlock -> WanAttn -> ResBlock -> ConvOut
154
+ 输入输出: (B, 16, T, H, W) -> (B, 16, T/4, H, W)
155
+
156
+ Args:
157
+ use_checkpoint: 是否使用 gradient checkpointing 节省显存(默认开启)
158
+ """
159
+ super().__init__()
160
+ self.use_checkpoint = use_checkpoint
161
+
162
+ # 1. Initial Conv
163
+ self.conv_in = nn.Conv3d(in_channels, hidden_dim, kernel_size=3, stride=1, padding=1)
164
+
165
+ # 2. Down Block 1 (T -> T/2)
166
+ self.down_block1 = nn.Sequential(
167
+ ResnetBlock3D(hidden_dim, hidden_dim),
168
+ ResnetBlock3D(hidden_dim, hidden_dim),
169
+ TemporalDownsample(hidden_dim)
170
+ )
171
+
172
+ # 3. Down Block 2 (T/2 -> T/4)
173
+ self.down_block2 = nn.Sequential(
174
+ ResnetBlock3D(hidden_dim, hidden_dim),
175
+ ResnetBlock3D(hidden_dim, hidden_dim),
176
+ TemporalDownsample(hidden_dim)
177
+ )
178
+
179
+ # 4. Mid Block (Res + WanAttention + Res)
180
+ self.mid_block = nn.Sequential(
181
+ ResnetBlock3D(hidden_dim, hidden_dim),
182
+ WanEncoderAttentionBlock(dim=hidden_dim, num_heads=num_heads), # 使用 Wanx 风格 Attention
183
+ ResnetBlock3D(hidden_dim, hidden_dim),
184
+ )
185
+
186
+ # 5. Output Projection
187
+ self.norm_out = nn.GroupNorm(num_groups=32, num_channels=hidden_dim, eps=1e-6, affine=True)
188
+ self.act_out = nn.SiLU()
189
+ self.conv_out = nn.Conv3d(hidden_dim, in_channels, kernel_size=3, stride=1, padding=1)
190
+
191
+ def _forward_down_block1(self, x):
192
+ return self.down_block1(x)
193
+
194
+ def _forward_down_block2(self, x):
195
+ return self.down_block2(x)
196
+
197
+ def _forward_mid_block(self, x):
198
+ return self.mid_block(x)
199
+
200
+ def forward(self, x):
201
+ # x: (B, C, T, H, W)
202
+ from torch.utils.checkpoint import checkpoint
203
+
204
+ x = self.conv_in(x)
205
+
206
+ # 🔴 使用 gradient checkpointing 节省显存
207
+ if self.use_checkpoint and self.training:
208
+ x = checkpoint(self._forward_down_block1, x, use_reentrant=False)
209
+ x = checkpoint(self._forward_down_block2, x, use_reentrant=False)
210
+ x = checkpoint(self._forward_mid_block, x, use_reentrant=False)
211
+ else:
212
+ x = self.down_block1(x)
213
+ x = self.down_block2(x)
214
+ x = self.mid_block(x)
215
+
216
+ x = self.norm_out(x)
217
+ x = self.act_out(x)
218
+ x = self.conv_out(x)
219
+
220
+ return x
221
+
222
+ def temporal_sample(x: torch.Tensor, rate: int, dim: int = 2) -> torch.Tensor:
223
+ """
224
+ 在指定维度采样,首尾必保留
225
+
226
+ Args:
227
+ x (torch.Tensor): 输入张量,默认 shape = (B, C, T, H, W)
228
+ rate (int): 采样率(步长)
229
+ dim (int): 采样的维度,默认=2 (T维)
230
+
231
+ Returns:
232
+ torch.Tensor: 采样后的张量
233
+ """
234
+ assert x.dim() >= dim + 1, f"输入维度 {x.dim()} 小于 dim={dim}"
235
+ N = x.shape[dim]
236
+
237
+ # 初步采样下标
238
+ indices = torch.arange(0, N, step=rate, device=x.device)
239
+
240
+ # 确保首尾都在
241
+ if indices[0] != 0:
242
+ indices = torch.cat([torch.tensor([0], device=x.device), indices])
243
+ if indices[-1] != N - 1:
244
+ indices = torch.cat([indices, torch.tensor([N - 1], device=x.device)])
245
+
246
+ # 去重并排序
247
+ indices = torch.unique(indices, sorted=True)
248
+
249
+ return torch.index_select(x, dim, indices)
250
+
251
+ def flash_attention(
252
+ q,
253
+ k,
254
+ v,
255
+ q_lens=None,
256
+ k_lens=None,
257
+ dropout_p=0.,
258
+ softmax_scale=None,
259
+ q_scale=None,
260
+ causal=False,
261
+ window_size=(-1, -1),
262
+ deterministic=False,
263
+ dtype=torch.bfloat16,
264
+ version=None,
265
+ ):
266
+ """
267
+ q: [B, Lq, Nq, C1].
268
+ k: [B, Lk, Nk, C1].
269
+ v: [B, Lk, Nk, C2]. Nq must be divisible by Nk.
270
+ q_lens: [B].
271
+ k_lens: [B].
272
+ dropout_p: float. Dropout probability.
273
+ softmax_scale: float. The scaling of QK^T before applying softmax.
274
+ causal: bool. Whether to apply causal attention mask.
275
+ window_size: (left right). If not (-1, -1), apply sliding window local attention.
276
+ deterministic: bool. If True, slightly slower and uses more memory.
277
+ dtype: torch.dtype. Apply when dtype of q/k/v is not float16/bfloat16.
278
+ """
279
+ half_dtypes = (torch.float16, torch.bfloat16)
280
+ assert dtype in half_dtypes
281
+ assert q.device.type == 'cuda' and q.size(-1) <= 256
282
+
283
+ # params
284
+ b, lq, lk, out_dtype = q.size(0), q.size(1), k.size(1), q.dtype
285
+
286
+ def half(x):
287
+ return x if x.dtype in half_dtypes else x.to(dtype)
288
+
289
+ # preprocess query
290
+ if q_lens is None:
291
+ q = half(q.flatten(0, 1))
292
+ q_lens = torch.tensor(
293
+ [lq] * b, dtype=torch.int32).to(
294
+ device=q.device, non_blocking=True)
295
+ else:
296
+ q = half(torch.cat([u[:v] for u, v in zip(q, q_lens)]))
297
+
298
+ # preprocess key, value
299
+ if k_lens is None:
300
+ k = half(k.flatten(0, 1))
301
+ v = half(v.flatten(0, 1))
302
+ k_lens = torch.tensor(
303
+ [lk] * b, dtype=torch.int32).to(
304
+ device=k.device, non_blocking=True)
305
+ else:
306
+ k = half(torch.cat([u[:v] for u, v in zip(k, k_lens)]))
307
+ v = half(torch.cat([u[:v] for u, v in zip(v, k_lens)]))
308
+
309
+ q = q.to(v.dtype)
310
+ k = k.to(v.dtype)
311
+
312
+ if q_scale is not None:
313
+ q = q * q_scale
314
+
315
+ if version is not None and version == 3 and not FLASH_ATTN_3_AVAILABLE:
316
+ warnings.warn(
317
+ 'Flash attention 3 is not available, use flash attention 2 instead.'
318
+ )
319
+
320
+ # apply attention
321
+ if (version is None or version == 3) and FLASH_ATTN_3_AVAILABLE:
322
+ # Note: dropout_p, window_size are not supported in FA3 now.
323
+ x = flash_attn_interface.flash_attn_varlen_func(
324
+ q=q,
325
+ k=k,
326
+ v=v,
327
+ cu_seqlens_q=torch.cat([q_lens.new_zeros([1]), q_lens]).cumsum(
328
+ 0, dtype=torch.int32).to(q.device, non_blocking=True),
329
+ cu_seqlens_k=torch.cat([k_lens.new_zeros([1]), k_lens]).cumsum(
330
+ 0, dtype=torch.int32).to(q.device, non_blocking=True),
331
+ seqused_q=None,
332
+ seqused_k=None,
333
+ max_seqlen_q=lq,
334
+ max_seqlen_k=lk,
335
+ softmax_scale=softmax_scale,
336
+ causal=causal,
337
+ deterministic=deterministic)[0].unflatten(0, (b, lq))
338
+ else:
339
+ assert FLASH_ATTN_2_AVAILABLE
340
+ x = flash_attn.flash_attn_varlen_func(
341
+ q=q,
342
+ k=k,
343
+ v=v,
344
+ cu_seqlens_q=torch.cat([q_lens.new_zeros([1]), q_lens]).cumsum(
345
+ 0, dtype=torch.int32).to(q.device, non_blocking=True),
346
+ cu_seqlens_k=torch.cat([k_lens.new_zeros([1]), k_lens]).cumsum(
347
+ 0, dtype=torch.int32).to(q.device, non_blocking=True),
348
+ max_seqlen_q=lq,
349
+ max_seqlen_k=lk,
350
+ dropout_p=dropout_p,
351
+ softmax_scale=softmax_scale,
352
+ causal=causal,
353
+ window_size=window_size,
354
+ deterministic=deterministic).unflatten(0, (b, lq))
355
+
356
+ # output
357
+ return x.type(out_dtype)
358
+
359
+ def sinusoidal_embedding_1d(dim, position):
360
+ # preprocess
361
+ assert dim % 2 == 0
362
+ half = dim // 2
363
+ position = position.type(torch.float64)
364
+
365
+ # calculation
366
+ sinusoid = torch.outer(
367
+ position, torch.pow(10000, -torch.arange(half).to(position).div(half)))
368
+ x = torch.cat([torch.cos(sinusoid), torch.sin(sinusoid)], dim=1)
369
+ return x
370
+
371
+
372
+ @amp.autocast(enabled=False)
373
+ def rope_params(max_seq_len, dim, theta=10000):
374
+ assert dim % 2 == 0
375
+ freqs = torch.outer(
376
+ torch.arange(max_seq_len),
377
+ 1.0 / torch.pow(theta,
378
+ torch.arange(0, dim, 2).to(torch.float64).div(dim)))
379
+ freqs = torch.polar(torch.ones_like(freqs), freqs)
380
+ return freqs
381
+
382
+
383
+ @amp.autocast(enabled=False)
384
+ def rope_apply(x, grid_sizes, freqs, enable_context_parallel=False):
385
+ s, n, c = x.size(1), x.size(2), x.size(3) // 2
386
+
387
+ # split freqs
388
+ freqs = freqs.split([c - 2 * (c // 3), c // 3, c // 3], dim=1)
389
+
390
+ # loop over samples
391
+ output = []
392
+ for i, (f, h, w) in enumerate(grid_sizes.tolist()):
393
+ seq_len = f * h * w
394
+
395
+ # precompute multipliers
396
+ x_i = torch.view_as_complex(x[i, :s].to(torch.float64).reshape(
397
+ s, n, -1, 2))
398
+ freqs_i = torch.cat([
399
+ freqs[0][:f].view(f, 1, 1, -1).expand(f, h, w, -1),
400
+ freqs[1][:h].view(1, h, 1, -1).expand(f, h, w, -1),
401
+ freqs[2][:w].view(1, 1, w, -1).expand(f, h, w, -1)
402
+ ],
403
+ dim=-1).reshape(seq_len, 1, -1)
404
+
405
+ if enable_context_parallel:
406
+ freqs_i = rearrange(freqs_i, "(T S) B C -> T S B C", T=f)
407
+ freqs_i = context_parallel_util.split_cp(freqs_i, seq_dim=1)
408
+ freqs_i = rearrange(freqs_i, "T S B C -> (T S) B C")
409
+
410
+ # apply rotary embedding
411
+ x_i = torch.view_as_real(x_i * freqs_i).flatten(2)
412
+ x_i = torch.cat([x_i, x[i, seq_len:]])
413
+
414
+ # append to collection
415
+ output.append(x_i)
416
+ return torch.stack(output).float()
417
+
418
+
419
+ class WanRMSNorm(nn.Module):
420
+
421
+ def __init__(self, dim, eps=1e-5):
422
+ super().__init__()
423
+ self.dim = dim
424
+ self.eps = eps
425
+ self.weight = nn.Parameter(torch.ones(dim))
426
+
427
+ def forward(self, x):
428
+ r"""
429
+ Args:
430
+ x(Tensor): Shape [B, L, C]
431
+ """
432
+ return self._norm(x.float()).type_as(x) * self.weight
433
+
434
+ def _norm(self, x):
435
+ return x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
436
+
437
+ class ActionEncoder(nn.Module):
438
+ def __init__(self, vocab_size=10, embed_dim=256, hidden_dim=512, out_dim=1536):
439
+ super().__init__()
440
+ # 将整数映射到向量
441
+ self.embedding_move = nn.Embedding(vocab_size, embed_dim)
442
+ self.embedding_view = nn.Embedding(vocab_size, embed_dim)
443
+
444
+ self.encode_1 = nn.Sequential(
445
+ nn.Conv1d(embed_dim * 2, hidden_dim, kernel_size=3, stride=2, padding=1),
446
+ nn.GroupNorm(2, hidden_dim),
447
+ nn.ReLU(),
448
+ )
449
+
450
+ self.encode_2 = nn.Sequential(
451
+ nn.Conv1d(hidden_dim, hidden_dim, kernel_size=3, stride=2, padding=1),
452
+ nn.GroupNorm(2, hidden_dim),
453
+ nn.ReLU(),
454
+ )
455
+
456
+ self.proj = nn.Linear(hidden_dim, out_dim)
457
+
458
+ def forward(self, move, view):
459
+ # x: (B, L+1),整数输入
460
+ x_move = self.embedding_move(move).transpose(1, 2)
461
+ x_view = self.embedding_view(view).transpose(1, 2)
462
+ x = torch.cat([x_move, x_view], dim=1)
463
+
464
+ x = self.encode_2(self.encode_1(x)) # (B, out_dim, (L+1)/4)
465
+
466
+ x = x.transpose(1, 2) # (B, (L/4)+1, out_dim)
467
+ x = self.proj(x)
468
+ return x
469
+
470
+ class WanLayerNorm(nn.LayerNorm):
471
+
472
+ def __init__(self, dim, eps=1e-6, elementwise_affine=False):
473
+ super().__init__(dim, elementwise_affine=elementwise_affine, eps=eps)
474
+
475
+ def forward(self, inputs: torch.Tensor) -> torch.Tensor:
476
+ origin_dtype = inputs.dtype
477
+ out = F.layer_norm(
478
+ inputs.float(),
479
+ self.normalized_shape,
480
+ None if self.weight is None else self.weight.float(),
481
+ None if self.bias is None else self.bias.float() ,
482
+ self.eps
483
+ ).to(origin_dtype)
484
+ return out
485
+
486
+
487
+ class WanSelfAttention(nn.Module):
488
+
489
+ def __init__(
490
+ self,
491
+ dim,
492
+ num_heads,
493
+ window_size=(-1, -1),
494
+ qk_norm=True,
495
+ eps=1e-6,
496
+ enable_context_parallel=False,
497
+ fp32_infer=False,
498
+ ):
499
+ assert dim % num_heads == 0
500
+ super().__init__()
501
+ self.dim = dim
502
+ self.num_heads = num_heads
503
+ self.head_dim = dim // num_heads
504
+ self.window_size = window_size
505
+ self.qk_norm = qk_norm
506
+ self.eps = eps
507
+ self.enable_context_parallel = enable_context_parallel
508
+
509
+ # layers
510
+ self.q = nn.Linear(dim, dim)
511
+ self.k = nn.Linear(dim, dim)
512
+ self.v = nn.Linear(dim, dim)
513
+ self.o = nn.Linear(dim, dim)
514
+ self.norm_q = WanRMSNorm(dim, eps=eps) if qk_norm else nn.Identity()
515
+ self.norm_k = WanRMSNorm(dim, eps=eps) if qk_norm else nn.Identity()
516
+
517
+ if self.enable_context_parallel:
518
+ qkv_format = "bshd"
519
+ attn_mask_type = "no_mask"
520
+ os.environ["NVTE_FUSED_ATTN"] = "0"
521
+ os.environ["NVTE_FLASH_ATTN"] = "1"
522
+ self.core_attn = DotProductAttention(
523
+ self.num_heads,
524
+ self.head_dim,
525
+ num_gqa_groups=self.num_heads,
526
+ qkv_format=qkv_format,
527
+ attn_mask_type=attn_mask_type,
528
+ )
529
+ self.core_attn.set_context_parallel_group(context_parallel_util.get_cp_group(),
530
+ context_parallel_util.get_cp_rank_list(),
531
+ context_parallel_util.get_cp_stream())
532
+
533
+ self.fp32_infer = fp32_infer
534
+ self.out_c = None
535
+
536
+ def forward(self, x, seq_lens, grid_sizes, freqs, token_ignore_mask=None, dtype=torch.bfloat16):
537
+ r"""
538
+ Args:
539
+ x(Tensor): Shape [B, L, num_heads, C / num_heads]
540
+ seq_lens(Tensor): Shape [B]
541
+ grid_sizes(Tensor): Shape [B, 3], the second dimension contains (F, H, W)
542
+ freqs(Tensor): Rope freqs, shape [1024, C / num_heads / 2]
543
+ token_ignore_mask: [B, N]; bool tensor indicating tokens to be ignored
544
+ """
545
+ b, s, n, d = *x.shape[:2], self.num_heads, self.head_dim
546
+
547
+ # query, key, value function
548
+ def qkv_fn(x):
549
+ q = self.norm_q(self.q(x)).view(b, s, n, d)
550
+ k = self.norm_k(self.k(x)).view(b, s, n, d)
551
+ v = self.v(x).view(b, s, n, d)
552
+ return q, k, v
553
+
554
+ q, k, v = qkv_fn(x)
555
+
556
+ q = rope_apply(q, grid_sizes, freqs, enable_context_parallel=self.enable_context_parallel)
557
+ k = rope_apply(k, grid_sizes, freqs, enable_context_parallel=self.enable_context_parallel)
558
+
559
+ # maks implementation by setting KV to zero
560
+ # this is a hack for the sake of cp support
561
+ if token_ignore_mask is not None:
562
+ select_mask = ~token_ignore_mask
563
+ expanded_select_mask = select_mask.unsqueeze(-1).unsqueeze(-1).expand(-1, -1, self.num_heads, self.head_dim) # [B, N, H, D]
564
+ expanded_select_mask = expanded_select_mask.to(k.dtype)
565
+ k = k * expanded_select_mask
566
+ v = v * expanded_select_mask
567
+
568
+ if self.enable_context_parallel:
569
+ # cp_size = context_parallel_util.get_cp_size()
570
+ # half_dtypes = (torch.float16, torch.bfloat16)
571
+ # def half(x):
572
+ # return x if x.dtype in half_dtypes else x.to(dtype)
573
+
574
+ # max_seqlen_q = s * cp_size
575
+ # max_seqlen_kv = max_seqlen_q
576
+ # x = self.core_attn(
577
+ # half(q) if self.fp32_infer else q.type_as(x),
578
+ # half(k) if self.fp32_infer else k.type_as(x),
579
+ # half(v) if self.fp32_infer else v.type_as(x),
580
+ # core_attention_bias_type="no_bias",
581
+ # core_attention_bias=None,
582
+ # cu_seqlens_q=None,
583
+ # cu_seqlens_kv=None,
584
+ # max_seqlen_q=max_seqlen_q,
585
+ # max_seqlen_kv=max_seqlen_kv,
586
+ # )
587
+ # x = rearrange(x, "B S (H D) -> B S H D", H=self.num_heads)
588
+ raise(NotImplementedError)
589
+ else:
590
+ B, S, H, D = q.shape
591
+ # 👉 你需要提前传入 num_c(或在这里根据场景算出)
592
+ num_c = getattr(self, "num_c", 0)
593
+ if num_c > 0 and num_c < S:
594
+ # 2️⃣ 当前 noisy 帧 Qz 看 [Kc; Kz]
595
+ q_z, k_z, v_z = q[:, num_c:], k, v
596
+ x = flash_attention(q_z, k_z, v_z, window_size=self.window_size).type_as(x)
597
+ else:
598
+ # 没有分段信息,默认用标准路径
599
+ x = flash_attention(q, k, v, k_lens=seq_lens, window_size=self.window_size).type_as(x)
600
+ # output
601
+ x = x.flatten(2)
602
+ x = self.o(x)
603
+ return x
604
+
605
+
606
+ class WanT2VCrossAttention(WanSelfAttention):
607
+
608
+ def forward(self, x, context, context_lens):
609
+ r"""
610
+ Args:
611
+ x(Tensor): Shape [B, L1, C]
612
+ context(Tensor): Shape [B, L2, C]
613
+ context_lens(Tensor): Shape [B]
614
+ """
615
+ b, n, d = x.size(0), self.num_heads, self.head_dim
616
+
617
+ # compute query, key, value
618
+ q = self.norm_q(self.q(x)).view(b, -1, n, d)
619
+ k = self.norm_k(self.k(context)).view(b, -1, n, d)
620
+ v = self.v(context).view(b, -1, n, d)
621
+
622
+ # compute attention
623
+ x = flash_attention(q, k, v, k_lens=context_lens)
624
+
625
+ # output
626
+ x = x.flatten(2)
627
+ x = self.o(x)
628
+ return x
629
+
630
+
631
+ class WanI2VCrossAttention(WanSelfAttention):
632
+
633
+ def __init__(self,
634
+ dim,
635
+ num_heads,
636
+ window_size=(-1, -1),
637
+ qk_norm=True,
638
+ eps=1e-6):
639
+ super().__init__(dim, num_heads, window_size, qk_norm, eps)
640
+
641
+ self.k_img = nn.Linear(dim, dim)
642
+ self.v_img = nn.Linear(dim, dim)
643
+ # self.alpha = nn.Parameter(torch.zeros((1, )))
644
+ self.norm_k_img = WanRMSNorm(dim, eps=eps) if qk_norm else nn.Identity()
645
+
646
+ def forward(self, x, context, context_lens):
647
+ r"""
648
+ Args:
649
+ x(Tensor): Shape [B, L1, C]
650
+ context(Tensor): Shape [B, L2, C]
651
+ context_lens(Tensor): Shape [B]
652
+ """
653
+ context_img = context[:, :257]
654
+ context = context[:, 257:]
655
+ b, n, d = x.size(0), self.num_heads, self.head_dim
656
+
657
+ # compute query, key, value
658
+ q = self.norm_q(self.q(x)).view(b, -1, n, d)
659
+ k = self.norm_k(self.k(context)).view(b, -1, n, d)
660
+ v = self.v(context).view(b, -1, n, d)
661
+ k_img = self.norm_k_img(self.k_img(context_img)).view(b, -1, n, d)
662
+ v_img = self.v_img(context_img).view(b, -1, n, d)
663
+ img_x = flash_attention(q, k_img, v_img, k_lens=None)
664
+ # compute attention
665
+ x = flash_attention(q, k, v, k_lens=context_lens)
666
+
667
+ # output
668
+ x = x.flatten(2)
669
+ img_x = img_x.flatten(2)
670
+ x = x + img_x
671
+ x = self.o(x)
672
+ return x
673
+
674
+
675
+ WAN_CROSSATTENTION_CLASSES = {
676
+ 't2v_cross_attn': WanT2VCrossAttention,
677
+ 'i2v_cross_attn': WanI2VCrossAttention,
678
+ }
679
+
680
+
681
+ class WanAttentionBlock(nn.Module):
682
+
683
+ def __init__(
684
+ self,
685
+ cross_attn_type,
686
+ dim,
687
+ ffn_dim,
688
+ num_heads,
689
+ window_size=(-1, -1),
690
+ qk_norm=True,
691
+ cross_attn_norm=False,
692
+ eps=1e-6,
693
+ enable_context_parallel=False,
694
+ ):
695
+ super().__init__()
696
+ self.dim = dim
697
+ self.ffn_dim = ffn_dim
698
+ self.num_heads = num_heads
699
+ self.window_size = window_size
700
+ self.qk_norm = qk_norm
701
+ self.cross_attn_norm = cross_attn_norm
702
+ self.eps = eps
703
+ self.enable_context_parallel = enable_context_parallel
704
+
705
+ # layers
706
+ self.norm1 = WanLayerNorm(dim, eps)
707
+ self.self_attn = WanSelfAttention(dim, num_heads, window_size, qk_norm,
708
+ eps, enable_context_parallel=enable_context_parallel)
709
+ self.norm3 = WanLayerNorm(
710
+ dim, eps,
711
+ elementwise_affine=True) if cross_attn_norm else nn.Identity()
712
+ self.cross_attn = WAN_CROSSATTENTION_CLASSES[cross_attn_type](dim,
713
+ num_heads,
714
+ (-1, -1),
715
+ qk_norm,
716
+ eps)
717
+ self.norm2 = WanLayerNorm(dim, eps)
718
+ self.ffn = nn.Sequential(
719
+ nn.Linear(dim, ffn_dim), nn.GELU(approximate='tanh'),
720
+ nn.Linear(ffn_dim, dim))
721
+
722
+ # modulation
723
+ self.modulation = nn.Parameter(torch.randn(1, 6, dim) / dim**0.5)
724
+ self.hist = None
725
+ self.hist_cross = None
726
+
727
+ def forward(
728
+ self,
729
+ x,
730
+ e_all,
731
+ seq_lens,
732
+ grid_sizes,
733
+ freqs,
734
+ context,
735
+ context_lens,
736
+ token_ignore_mask=None,
737
+ training=True
738
+ ):
739
+ r"""
740
+ Args:
741
+ x(Tensor): Shape [B, L, C]
742
+ e(Tensor): Shape [B, 6, C]
743
+ seq_lens(Tensor): Shape [B], length of each sequence in batch
744
+ grid_sizes(Tensor): Shape [B, 3], the second dimension contains (F, H, W)
745
+ freqs(Tensor): Rope freqs, shape [1024, C / num_heads / 2]
746
+ token_ignore_mask: [B, N]; bool tensor indicating tokens to be ignored in self attention
747
+ """
748
+ dtype = x.dtype
749
+ e, e_no_noise = e_all[0], e_all[1]
750
+ assert e.dtype == torch.float32
751
+ assert e_no_noise.dtype == torch.float32
752
+ with amp.autocast(dtype=torch.float32):
753
+ e = (self.modulation + e).chunk(6, dim=1)
754
+ e_no_noise = (self.modulation + e_no_noise).chunk(6, dim=1)
755
+ assert e[0].dtype == torch.float32
756
+
757
+ num_hist = getattr(self.self_attn, "num_c", 0)
758
+ hist, noisy = x[:, :num_hist], x[:, num_hist:]
759
+ _, H, W = grid_sizes[0].tolist() # 假设所有样本一致
760
+ B = grid_sizes.shape[0]
761
+ T_noisy = noisy.shape[1] // (H * W)
762
+ T_hist= hist.shape[1] // (H * W)
763
+
764
+ grid_sizes_noisy = torch.tensor([T_noisy, H, W], device=grid_sizes.device).unsqueeze(0).repeat(B, 1)
765
+ grid_sizes_hist = torch.tensor([T_hist, H, W], device=grid_sizes.device).unsqueeze(0).repeat(B, 1)
766
+
767
+ # print(x.shape, e[1].shape, e[0].shape)
768
+ # self-attention
769
+
770
+ seq_len_hist = torch.tensor([u.size(0) for u in hist], dtype=torch.long)
771
+ if training or self.hist is None or self.hist.shape[1] != num_hist:
772
+ if token_ignore_mask is not None:
773
+ hist_token_ignore_mask = token_ignore_mask[:, :num_hist]
774
+ else:
775
+ hist_token_ignore_mask = token_ignore_mask
776
+ y = self.self_attn(
777
+ (self.norm1(hist).float() * (1 + e_no_noise[1]) + e_no_noise[0]).type_as(x), seq_len_hist, grid_sizes_hist,
778
+ freqs, hist_token_ignore_mask)
779
+ with amp.autocast(dtype=torch.float32):
780
+ self.hist = hist + y * e_no_noise[2]
781
+
782
+ # print('recompute condition', x.shape)
783
+ y = self.self_attn(
784
+ (self.norm1(x).float() * (1 + e[1]) + e[0]).type_as(x), seq_lens, grid_sizes,
785
+ freqs, token_ignore_mask)
786
+ with amp.autocast(dtype=torch.float32):
787
+ noisy = noisy + y * e[2]
788
+
789
+ x = torch.cat([self.hist, noisy], dim=1)
790
+ x = x.to(dtype)
791
+ # print('after self attn', x.shape)
792
+
793
+ # cross-attention & ffn function
794
+ def cross_attn_ffn(x, context, context_lens, e):
795
+ # print('before cross attn', x.shape)
796
+ x = x + self.cross_attn(self.norm3(x), context, context_lens)
797
+ # print('after cross attn', x.shape)
798
+ hist, noisy = x[:, :num_hist], x[:, num_hist:]
799
+
800
+ y = self.ffn((self.norm2(noisy).float() * (1 + e[4]) + e[3]).to(dtype))
801
+ with amp.autocast(dtype=torch.float32):
802
+ noisy = noisy + y * e[5]
803
+
804
+ if training or self.hist_cross is None or self.hist_cross.shape[1] != num_hist:
805
+ y = self.ffn((self.norm2(hist).float() * (1 + e_no_noise[4]) + e_no_noise[3]).to(dtype))
806
+ with amp.autocast(dtype=torch.float32):
807
+ self.hist_cross = hist + y * e_no_noise[5]
808
+ # print('compute hist cross', self.hist_cross.shape, hist.shape, noisy.shape, x.shape)
809
+ x = torch.cat([self.hist_cross, noisy], dim=1)
810
+ # print('after ffn', self.hist_cross.shape, hist.shape, noisy.shape, x.shape)
811
+
812
+ return x
813
+
814
+ x = cross_attn_ffn(x, context, context_lens, e)
815
+ x = x.to(dtype)
816
+ return x
817
+
818
+
819
+ class Head(nn.Module):
820
+
821
+ def __init__(self, dim, out_dim, patch_size, eps=1e-6):
822
+ super().__init__()
823
+ self.dim = dim
824
+ self.out_dim = out_dim
825
+ self.patch_size = patch_size
826
+ self.eps = eps
827
+
828
+ # layers
829
+ out_dim = math.prod(patch_size) * out_dim
830
+ self.norm = WanLayerNorm(dim, eps)
831
+ self.head = nn.Linear(dim, out_dim)
832
+
833
+ # modulation
834
+ self.modulation = nn.Parameter(torch.randn(1, 2, dim) / dim**0.5)
835
+
836
+ def forward(self, x, e):
837
+ r"""
838
+ Args:
839
+ x(Tensor): Shape [B, L1, C]
840
+ e(Tensor): Shape [B, C]
841
+ """
842
+ assert e.dtype == torch.float32
843
+ with amp.autocast(dtype=torch.float32):
844
+ e = (self.modulation + e.unsqueeze(1)).chunk(2, dim=1)
845
+ x = (self.head(self.norm(x) * (1 + e[1]) + e[0]))
846
+ return x
847
+
848
+
849
+ class MLPProj(torch.nn.Module):
850
+
851
+ def __init__(self, in_dim, out_dim):
852
+ super().__init__()
853
+
854
+ self.proj = torch.nn.Sequential(
855
+ torch.nn.LayerNorm(in_dim), torch.nn.Linear(in_dim, in_dim),
856
+ torch.nn.GELU(), torch.nn.Linear(in_dim, out_dim),
857
+ torch.nn.LayerNorm(out_dim))
858
+
859
+ def forward(self, image_embeds):
860
+ clip_extra_context_tokens = self.proj(image_embeds)
861
+ return clip_extra_context_tokens
862
+
863
+
864
+ class WanModel(nn.Module):
865
+ r"""
866
+ Wan diffusion backbone supporting both text-to-video and image-to-video.
867
+ """
868
+
869
+ def __init__(
870
+ self,
871
+ model_type='t2v',
872
+ patch_size=(1, 2, 2),
873
+ model_max_length=512,
874
+ in_channels=16,
875
+ dim=2048,
876
+ ffn_dim=8192,
877
+ freq_dim=256,
878
+ caption_channels=4096,
879
+ out_channels=16,
880
+ num_heads=16,
881
+ num_layers=32,
882
+ window_size=(-1, -1),
883
+ qk_norm=True,
884
+ cross_attn_norm=True,
885
+ eps=1e-6,
886
+ enable_context_parallel=False,
887
+ use_convenc=True, # 🔴 新增参数:是否使用卷积编码器进行时序压缩
888
+ ):
889
+ r"""
890
+ Initialize the diffusion model backbone.
891
+
892
+ Args:
893
+ model_type (`str`, *optional*, defaults to 't2v'):
894
+ Model variant - 't2v' (text-to-video) or 'i2v' (image-to-video)
895
+ patch_size (`tuple`, *optional*, defaults to (1, 2, 2)):
896
+ 3D patch dimensions for video embedding (t_patch, h_patch, w_patch)
897
+ model_max_length (`int`, *optional*, defaults to 512):
898
+ Fixed length for text embeddings
899
+ in_channels (`int`, *optional*, defaults to 16):
900
+ Input video channels (C_in)
901
+ dim (`int`, *optional*, defaults to 2048):
902
+ Hidden dimension of the transformer
903
+ ffn_dim (`int`, *optional*, defaults to 8192):
904
+ Intermediate dimension in feed-forward network
905
+ freq_dim (`int`, *optional*, defaults to 256):
906
+ Dimension for sinusoidal time embeddings
907
+ caption_channels (`int`, *optional*, defaults to 4096):
908
+ Input dimension for text embeddings
909
+ out_channels (`int`, *optional*, defaults to 16):
910
+ Output video channels (C_out)
911
+ num_heads (`int`, *optional*, defaults to 16):
912
+ Number of attention heads
913
+ num_layers (`int`, *optional*, defaults to 32):
914
+ Number of transformer blocks
915
+ window_size (`tuple`, *optional*, defaults to (-1, -1)):
916
+ Window size for local attention (-1 indicates global attention)
917
+ qk_norm (`bool`, *optional*, defaults to True):
918
+ Enable query/key normalization
919
+ cross_attn_norm (`bool`, *optional*, defaults to False):
920
+ Enable cross-attention normalization
921
+ eps (`float`, *optional*, defaults to 1e-6):
922
+ Epsilon value for normalization layers
923
+ """
924
+
925
+ super().__init__()
926
+
927
+ assert model_type in ['t2v', 'i2v']
928
+ self.model_type = model_type
929
+
930
+ self.patch_size = patch_size
931
+ self.model_max_length = model_max_length
932
+ self.in_channels = in_channels
933
+ self.dim = dim
934
+ self.ffn_dim = ffn_dim
935
+ self.freq_dim = freq_dim
936
+ self.caption_channels = caption_channels
937
+ self.out_channels = out_channels
938
+ self.num_heads = num_heads
939
+ self.num_layers = num_layers
940
+ self.window_size = window_size
941
+ self.qk_norm = qk_norm
942
+ self.cross_attn_norm = cross_attn_norm
943
+ self.eps = eps
944
+ self.enable_context_parallel = enable_context_parallel
945
+ self.use_convenc = use_convenc # 🔴 保存参数
946
+
947
+ # hack y_embedder, not support uncond training now, pls use negative prompt for uncond
948
+ self.y_embedder = None
949
+
950
+ # embeddings
951
+ self.patch_embedding = nn.Conv3d(
952
+ in_channels, dim, kernel_size=patch_size, stride=patch_size)
953
+ self.text_embedding = nn.Sequential(
954
+ nn.Linear(caption_channels, dim), nn.GELU(approximate='tanh'),
955
+ nn.Linear(dim, dim))
956
+
957
+ self.time_embedding = nn.Sequential(
958
+ nn.Linear(freq_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
959
+ self.time_projection = nn.Sequential(nn.SiLU(), nn.Linear(dim, dim * 6))
960
+
961
+ self.action_encoder = ActionEncoder()
962
+ # 🔴 只在 use_convenc=True 时创建时序编码器
963
+ if self.use_convenc:
964
+ self.latent_encoder = TemporalLatentEncoder()
965
+ else:
966
+ self.latent_encoder = None
967
+ # blocks
968
+ cross_attn_type = 't2v_cross_attn' if model_type == 't2v' else 'i2v_cross_attn'
969
+ self.blocks = nn.ModuleList([
970
+ WanAttentionBlock(cross_attn_type, dim, ffn_dim, num_heads,
971
+ window_size, qk_norm, cross_attn_norm, eps,
972
+ enable_context_parallel=enable_context_parallel,)
973
+ for _ in range(num_layers)
974
+ ])
975
+
976
+ # head
977
+ self.head = Head(dim, out_channels, patch_size, eps)
978
+
979
+ # buffers (don't use register_buffer otherwise dtype will be changed in to())
980
+ assert (dim % num_heads) == 0 and (dim // num_heads) % 2 == 0
981
+ d = dim // num_heads
982
+ self.freqs = torch.cat([
983
+ rope_params(1024, d - 4 * (d // 6)),
984
+ rope_params(1024, 2 * (d // 6)),
985
+ rope_params(1024, 2 * (d // 6))
986
+ ],
987
+ dim=1)
988
+
989
+ if model_type == 'i2v':
990
+ self.img_emb = MLPProj(1280, dim)
991
+
992
+ # initialize weights
993
+ self.init_weights()
994
+
995
+ def forward(
996
+ self,
997
+ x,
998
+ t,
999
+ y,
1000
+ y_mask=None,
1001
+ x_ignore_mask=None,
1002
+ clip_fea=None,
1003
+ image_cond=None,
1004
+ move=None,
1005
+ view=None
1006
+ ):
1007
+ r"""
1008
+ Forward pass through the diffusion model
1009
+ """
1010
+
1011
+ COMPRESSION_RATE = 4
1012
+ MAX_T_OUT = 20
1013
+ TARGET_T_MID = MAX_T_OUT * COMPRESSION_RATE # 80
1014
+ W_IN = 64
1015
+ W_OUT_PER_CHUNK = W_IN // COMPRESSION_RATE # 16
1016
+ TARGET_N_CHUNKS = 5 # 确保 T_mid = 80
1017
+
1018
+ dtype = self.patch_embedding.weight.dtype
1019
+ B, _, T, H, W = x.shape
1020
+ device = x.device # 获取当前设备
1021
+ T_in = image_cond.shape[2] # 原始输入的时间维度长度
1022
+
1023
+ # 1. 提取局部记忆 (Last Frame Memory) - 必须在压缩前进行
1024
+ loc_mem = image_cond[:,:,-1:,:,:].to(dtype)
1025
+
1026
+ # 2. 确保输入数据类型正确
1027
+ image_cond = image_cond.to(dtype)
1028
+
1029
+ # ----------------- [NEW LOGIC START] 时序压缩逻辑 -----------------
1030
+
1031
+ # 🔴 只在 use_convenc=True 时执行时序压缩
1032
+ if T_in <= TARGET_T_MID:
1033
+ # 情况 A: T_in <= 80,直接一次编码
1034
+ image_cond = self.latent_encoder(image_cond)
1035
+
1036
+ else:
1037
+ # 情况 B: T_in > 80,滑动窗口 + 二次压缩
1038
+
1039
+ # --- Step 1: 滑动窗口分块编码 (T_in -> T_mid=80) ---
1040
+
1041
+ # 计算步长 S,确保 5 个 Chunk 覆盖 T_in
1042
+ S_denom = TARGET_N_CHUNKS - 1
1043
+ # S = floor( (T_in - W_IN) / (N_chunks - 1) )
1044
+ S = math.floor((T_in - W_IN) / S_denom)
1045
+ S = max(1, S) # 最小步长为 1
1046
+
1047
+ latent_chunks = []
1048
+
1049
+ for i in range(TARGET_N_CHUNKS):
1050
+ start = i * S
1051
+ end = start + W_IN
1052
+
1053
+ chunk = image_cond[:, :, start:end, :, :]
1054
+
1055
+ # 处理填充:如果 end > T_in,则需要填充
1056
+ padding_len = W_IN - chunk.shape[2]
1057
+ if padding_len > 0:
1058
+ # 在时序维度 (dim=2) 末尾填充 0
1059
+ # F.pad 参数: (W_pad_start, W_pad_end, H_pad_start, H_pad_end, T_pad_start, T_pad_end)
1060
+ chunk = F.pad(chunk, (0, 0, 0, 0, 0, padding_len))
1061
+
1062
+ # 编码块 (W_IN -> W_OUT_PER_CHUNK=16)
1063
+ # 第一次编码通常冻结
1064
+ # with torch.no_grad():
1065
+ # self.latent_encoder.eval()
1066
+ encoded_chunk = self.latent_encoder(chunk)
1067
+ # self.latent_encoder.train()
1068
+
1069
+ # 裁剪到预期的输出长度 (防止 padding 导致的额外输出)
1070
+ encoded_chunk = encoded_chunk[:, :, :W_OUT_PER_CHUNK, :, :]
1071
+ latent_chunks.append(encoded_chunk)
1072
+
1073
+ # 拼接中间序列 T_mid (T_mid = 80)
1074
+ image_cond = torch.cat(latent_chunks, dim=2)
1075
+ T_mid = image_cond.shape[2]
1076
+
1077
+ # --- Step 2: 二次压缩 (T_mid=80 -> T_out=20) ---
1078
+
1079
+ if T_mid > MAX_T_OUT:
1080
+ # 此时 T_mid = 80,是 4 的倍数,直接编码即可
1081
+ image_cond = self.latent_encoder(image_cond)
1082
+ # T_out = 20
1083
+
1084
+ # ----------------- [NEW LOGIC END] -----------------
1085
+
1086
+ # 3. 拼接压缩后的 Condition 和 Loc_Mem
1087
+ image_cond = torch.cat((image_cond, loc_mem), dim=2)
1088
+
1089
+ # 4. 拼接 Condition 和 Noisy Input
1090
+ x = torch.cat((image_cond, x.to(dtype)), dim=2) # B, C, T_all, H, W
1091
+ # print("x init shape: ", x.shape)
1092
+ # print("image_cond init shape: ", image_cond.shape)
1093
+
1094
+ T_all = x.shape[2]
1095
+ mask = torch.ones(B, T_all, H, W, device=x.device, dtype=x.dtype) # B, T_all, H, W
1096
+ mask[:, -T:] = 0
1097
+ mask = mask.unsqueeze(1).expand(-1, 4, -1, -1, -1) # B, 4, T_all, H, W
1098
+
1099
+ x = torch.cat((x, mask), dim=1) # B, C+4, T_all, H, W
1100
+ T_x = T
1101
+ T = T_all
1102
+ N_t = T // self.patch_size[0]
1103
+ N_h = H // self.patch_size[1]
1104
+ N_w = W // self.patch_size[2]
1105
+ T_cond = image_cond.shape[2] # 新的 T_cond 约为 21 (20 + 1 loc_mem)
1106
+ num_c = (T_cond // self.patch_size[0]) * (H // self.patch_size[1]) * (W // self.patch_size[2])
1107
+ for block in self.blocks:
1108
+ block.self_attn.num_c = num_c
1109
+ dtype = self.patch_embedding.weight.dtype
1110
+ x = x.to(dtype)
1111
+ t = t.to(dtype)
1112
+ y = y.to(dtype)
1113
+
1114
+ if self.model_type == 'i2v':
1115
+ assert clip_fea is not None and image_cond is not None
1116
+ # clip_fea = clip_fea.to(dtype)
1117
+
1118
+
1119
+ # params
1120
+ device = self.patch_embedding.weight.device
1121
+ if self.freqs.device != device:
1122
+ self.freqs = self.freqs.to(device)
1123
+
1124
+ if self.model_type == 'i2v' and image_cond is not None:
1125
+ # image_cond = image_cond.to(dtype)
1126
+ x = [torch.cat([u, v], dim=0) for u, v in zip(x, image_cond)]
1127
+
1128
+ # embeddings
1129
+ x = [self.patch_embedding(u.unsqueeze(0)) for u in x] # fp32 -> bf16
1130
+
1131
+ # *******************************************************************
1132
+ # 注意:这里的 action_encoder 调用已经更新为 move 和 view
1133
+ # 假设 self.action_encoder 现在接收 move 和 view 两个参数
1134
+ # *******************************************************************
1135
+
1136
+ # Action Embedding Logic
1137
+ action_embedding_2 = self.action_encoder(move[:, -81:], view[:, -81:]).to(dtype).permute(0, 2, 1).unsqueeze(-1).unsqueeze(-1)
1138
+
1139
+
1140
+ # padding action embedding2 with a tensor of all zeros, the tensor has a same time length of image cond
1141
+ action_shape = list(action_embedding_2.shape)
1142
+ action_shape[2] = T_cond
1143
+ padding_embedding = torch.zeros(action_shape, device=device)
1144
+
1145
+ # make data type and device right with action embedding 1
1146
+ padding_embedding = padding_embedding.to(dtype).to(device)
1147
+
1148
+ # concat action embedding 1 and 2
1149
+ action_embedding = torch.cat((padding_embedding, action_embedding_2), dim=2)
1150
+
1151
+ # 切片 action embedding to meet the length of x (the last action)
1152
+ action_embedding = action_embedding[:, :, -T_all:]
1153
+ # print("action", action_embedding.shape)
1154
+ # print("u shape 1", x[0].shape)
1155
+ x = [u + action_embedding for u in x]
1156
+ grid_sizes = torch.stack(
1157
+ [torch.tensor(u.shape[2:], dtype=torch.long) for u in x])
1158
+ x = [u.flatten(2).transpose(1, 2) for u in x]
1159
+ seq_lens = torch.tensor([u.size(1) for u in x], dtype=torch.long)
1160
+
1161
+ # print("u shape", x[0].shape)
1162
+ # hack seq_len
1163
+ seq_len = seq_lens.max()
1164
+ x = torch.cat([
1165
+ torch.cat([u, u.new_zeros(u.size(0), seq_len - u.size(1), u.size(2))],
1166
+ dim=1) for u in x
1167
+ ])
1168
+ # print("x now", x.shape)
1169
+ # time embeddings
1170
+ with amp.autocast(dtype=torch.float32):
1171
+ e = self.time_embedding(
1172
+ sinusoidal_embedding_1d(self.freq_dim, t).float())
1173
+ e0 = self.time_projection(e).unflatten(1, (6, self.dim))
1174
+ assert e.dtype == torch.float32 and e0.dtype == torch.float32
1175
+ t_no_noise = torch.zeros_like(t) # 对应 t = 0
1176
+
1177
+ with amp.autocast(dtype=torch.float32):
1178
+ e_no_noise = self.time_embedding(
1179
+ sinusoidal_embedding_1d(self.freq_dim, t_no_noise).float())
1180
+ e0_no_noise = self.time_projection(e_no_noise).unflatten(1, (6, self.dim))
1181
+ assert e_no_noise.dtype == torch.float32 and e0_no_noise.dtype == torch.float32
1182
+ y = y[:,0]
1183
+ y = y * y_mask[...,None]
1184
+
1185
+ # context
1186
+ context_lens = None
1187
+ context = self.text_embedding(
1188
+ torch.stack(
1189
+ [torch.cat([u, u.new_zeros(self.model_max_length - u.size(0), u.size(1))]) for u in y] #padding
1190
+ )
1191
+ )
1192
+
1193
+ # # sync context among cp ranks to avoid the following situation:
1194
+ # # cp_rank 0 dropped the context but cp_rank 1 did not, then they have different y embeeding in a forward pass
1195
+ # if context_parallel_util.get_cp_size() > 1:
1196
+ # context_parallel_util.cp_broadcast(context)
1197
+
1198
+ if self.model_type == 'i2v' and clip_fea is not None:
1199
+ context_clip = self.img_emb(clip_fea) # bs x 257 x dim
1200
+ context = torch.concat([context_clip, context], dim=1) # bf16 --> tf32
1201
+
1202
+ if self.enable_context_parallel:
1203
+ x = rearrange(x, "B (T S) C -> B T S C", T=N_t)
1204
+ x = context_parallel_util.split_cp(x, seq_dim=2)
1205
+ x = rearrange(x, "B T S C -> B (T S) C")
1206
+
1207
+ # convert x_mask to token_ignore_mask
1208
+ token_ignore_mask = None
1209
+ if x_ignore_mask is not None:
1210
+ x_ignore_mask = x_ignore_mask.to(torch.float32) # [B, T, H, W]; cast for interpolation
1211
+ # x_ignore_mask_temp_sample_cond = temporal_sample(x_ignore_mask[:, :-T_x], rate=2, dim=1)
1212
+ # print(x_ignore_mask_temp_sample_cond.shape)
1213
+ x_ignore_mask_temp_sample = torch.cat((x_ignore_mask, x_ignore_mask[:, -T_x:]), dim=1)
1214
+ token_ignore_mask = nn.functional.interpolate(x_ignore_mask_temp_sample, size=(N_h, N_w), mode='nearest')[:, -T_all:] # [B, T, N_h, N_w]
1215
+ token_ignore_mask = token_ignore_mask.reshape(B, T * N_h * N_w) # [B, N]
1216
+ token_ignore_mask = (token_ignore_mask > 0)
1217
+
1218
+ if self.enable_context_parallel and x_ignore_mask is not None:
1219
+ token_ignore_mask = rearrange(token_ignore_mask, "B (T S) -> B T S", T=T)
1220
+ token_ignore_mask = context_parallel_util.split_cp(token_ignore_mask, seq_dim=2)
1221
+ token_ignore_mask = rearrange(token_ignore_mask, "B T S -> B (T S)")
1222
+
1223
+ for block in self.blocks:
1224
+ # support grad checkpointing
1225
+ x = auto_grad_checkpoint(block, x, [e0, e0_no_noise], seq_lens, grid_sizes, self.freqs, context, context_lens, token_ignore_mask)
1226
+
1227
+ if self.enable_context_parallel:
1228
+ x = context_parallel_util.gather_cp(x, N_t)
1229
+
1230
+ # head
1231
+ x = self.head(x, e)
1232
+
1233
+ # unpatchify
1234
+ x = self.unpatchify(x, grid_sizes)
1235
+
1236
+ return torch.stack(x).float()
1237
+
1238
+ def unpatchify(self, x, grid_sizes):
1239
+ r"""
1240
+ Reconstruct video tensors from patch embeddings.
1241
+
1242
+ Args:
1243
+ x (List[Tensor]):
1244
+ List of patchified features, each with shape [L, C_out * prod(patch_size)]
1245
+ grid_sizes (Tensor):
1246
+ Original spatial-temporal grid dimensions before patching,
1247
+ shape [B, 3] (3 dimensions correspond to F_patches, H_patches, W_patches)
1248
+
1249
+ Returns:
1250
+ List[Tensor]:
1251
+ Reconstructed video tensors with shape [C_out, F, H / 8, W / 8]
1252
+ """
1253
+
1254
+ c = self.out_channels
1255
+ out = []
1256
+ for u, v in zip(x, grid_sizes.tolist()):
1257
+ u = u[:math.prod(v)].view(*v, *self.patch_size, c)
1258
+ u = torch.einsum('fhwpqrc->cfphqwr', u)
1259
+ u = u.reshape(c, *[i * j for i, j in zip(v, self.patch_size)])
1260
+ out.append(u)
1261
+ return out
1262
+
1263
+ def init_weights(self):
1264
+ r"""
1265
+ Initialize model parameters using Xavier initialization.
1266
+ """
1267
+
1268
+ # basic init
1269
+ for m in self.modules():
1270
+ if isinstance(m, nn.Linear):
1271
+ nn.init.xavier_uniform_(m.weight)
1272
+ if m.bias is not None:
1273
+ nn.init.zeros_(m.bias)
1274
+
1275
+ # init embeddings
1276
+ nn.init.xavier_uniform_(self.patch_embedding.weight.flatten(1))
1277
+ for m in self.text_embedding.modules():
1278
+ if isinstance(m, nn.Linear):
1279
+ nn.init.normal_(m.weight, std=.02)
1280
+ for m in self.time_embedding.modules():
1281
+ if isinstance(m, nn.Linear):
1282
+ nn.init.normal_(m.weight, std=.02)
1283
+
1284
+ # init output layer
1285
+ nn.init.zeros_(self.head.head.weight)
infworld/models/scheduler.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import time
3
+ import numpy as np
4
+
5
+ from tqdm import tqdm
6
+ from typing import Callable
7
+ from einops import rearrange
8
+ from functools import partial
9
+
10
+ import torch
11
+ from torch.distributions import LogisticNormal
12
+
13
+ from infworld.context_parallel import context_parallel_util
14
+
15
+ # some code are inspired by https://github.com/magic-research/piecewise-rectified-flow/blob/main/scripts/train_perflow.py
16
+ # and https://github.com/magic-research/piecewise-rectified-flow/blob/main/src/scheduler_perflow.py
17
+ # and https://github.com/black-forest-labs/flux/blob/main/src/flux/sampling.py
18
+
19
+
20
+ def _extract_into_tensor(arr, timesteps, broadcast_shape):
21
+ """
22
+ Extract values from a 1-D numpy array for a batch of indices.
23
+ :param arr: the 1-D numpy array.
24
+ :param timesteps: a tensor of indices into the array to extract.
25
+ :param broadcast_shape: a larger shape of K dimensions with the batch
26
+ dimension equal to the length of timesteps.
27
+ :return: a tensor of shape [batch_size, 1, ...] where the shape has K dims.
28
+ """
29
+ res = torch.from_numpy(arr).to(device=timesteps.device)[timesteps].float()
30
+ while len(res.shape) < len(broadcast_shape):
31
+ res = res[..., None]
32
+ return res + torch.zeros(broadcast_shape, device=timesteps.device)
33
+
34
+
35
+ def mean_flat(tensor: torch.Tensor, stoploss_mask=None):
36
+ """
37
+ Take the mean over all non-batch dimensions.
38
+ tensor: [B, C, T, H, W]
39
+ stoploss_mask: [B, T, H, W]
40
+ """
41
+ if stoploss_mask is None:
42
+ return tensor.mean(dim=list(range(1, len(tensor.shape))))
43
+ else:
44
+ stoploss_mask = stoploss_mask.unsqueeze(1).expand_as(tensor) # [B, T, H, W] --> [B, C, T, H, W]
45
+ assert tensor.shape == stoploss_mask.shape, f"shape of tensor {tensor.shape} and stoploss_mask {stoploss_mask.shape} should be the same"
46
+ loss_mask = ~stoploss_mask
47
+ masked_loss = tensor * loss_mask
48
+ sum_loss = masked_loss.sum(dim=list(range(1, len(tensor.shape))))
49
+ count_nonzero = loss_mask.sum(dim=list(range(1, len(tensor.shape))))
50
+ mean_loss = sum_loss / count_nonzero.clamp(min=1)
51
+
52
+ return mean_loss
53
+
54
+
55
+ def clamp(value, min_value, max_value):
56
+ return max(min_value, min(value, max_value))
57
+
58
+
59
+
60
+ def timestep_transform(
61
+ t,
62
+ shift=5.0,
63
+ num_timesteps=1000,
64
+ ):
65
+ t = t / num_timesteps
66
+ # shift the timestep based on ratio
67
+ new_t = shift * t / (1 + (shift - 1) * t)
68
+ new_t = new_t * num_timesteps
69
+ return new_t
70
+
71
+
72
+ class RFlowScheduler:
73
+ def __init__(
74
+ self,
75
+ num_timesteps=1000,
76
+ num_sampling_steps=10,
77
+ use_discrete_timesteps=False,
78
+ sample_method="uniform",
79
+ loc=0.0,
80
+ scale=1.0,
81
+ shift=5.0,
82
+ use_timestep_transform=False,
83
+ transform_scale=1.0,
84
+ use_reversed_velocity=False,
85
+ cfg_scale=7.0,
86
+ **kwargs,
87
+ ):
88
+ self.num_timesteps = num_timesteps
89
+ self.num_sampling_steps = num_sampling_steps
90
+ self.use_discrete_timesteps = use_discrete_timesteps
91
+ self.use_reversed_velocity = use_reversed_velocity
92
+ self.cfg_scale = cfg_scale
93
+
94
+ # sample method
95
+ assert sample_method in ["uniform", "logit-normal"]
96
+ assert (
97
+ sample_method == "uniform" or not use_discrete_timesteps
98
+ ), "Only uniform sampling is supported for discrete timesteps"
99
+ self.sample_method = sample_method
100
+ if sample_method == "logit-normal":
101
+ self.distribution = LogisticNormal(torch.tensor([loc]), torch.tensor([scale]))
102
+ self.sample_t = lambda x: self.distribution.sample((x.shape[0],))[:, 0].to(x.device)
103
+
104
+ # timestep transform
105
+ self.use_timestep_transform = use_timestep_transform
106
+ self.transform_scale = transform_scale
107
+
108
+ self.shift = shift
109
+ sigmas = torch.linspace(0, 1, num_timesteps)
110
+ sigmas = shift * sigmas / (1 + (shift - 1) * sigmas)
111
+ self.timesteps = sigmas * num_timesteps
112
+
113
+ y = torch.exp(-2 * ((self.timesteps - num_timesteps/2) / num_timesteps)**2)
114
+ y_shifted = y - y.min()
115
+ self.bsmntw_weighing = y_shifted * (num_timesteps / y_shifted.sum())
116
+
117
+ def training_losses(self, model, x_start, model_kwargs=None, noise=None, x_ignore_mask=None, t=None):
118
+ """
119
+ Compute training losses for a single timestep.
120
+ Arguments format copied from opensora/schedulers/iddpm/gaussian_diffusion.py/training_losses
121
+ Note: t is int tensor and should be rescaled from [0, num_timesteps-1] to [1,0]
122
+ """
123
+
124
+ if t is None:
125
+ if self.use_discrete_timesteps:
126
+ t = torch.randint(0, self.num_timesteps, (x_start.shape[0],), device=x_start.device)
127
+ elif self.sample_method == "uniform":
128
+ t = torch.rand((x_start.shape[0],), device=x_start.device) * self.num_timesteps
129
+ elif self.sample_method == "logit-normal":
130
+ t = self.sample_t(x_start) * self.num_timesteps
131
+
132
+ if self.use_timestep_transform:
133
+ latent_size = x_start.shape[-3:]
134
+ t = timestep_transform(t, shift=self.shift, num_timesteps=self.num_timesteps)
135
+
136
+ if model_kwargs is None:
137
+ model_kwargs = {}
138
+ if noise is None:
139
+ noise = torch.randn_like(x_start)
140
+ assert noise.shape == x_start.shape
141
+
142
+
143
+ if context_parallel_util.get_cp_size() > 1:
144
+ context_parallel_util.cp_broadcast(noise)
145
+ context_parallel_util.cp_broadcast(t)
146
+
147
+ x_t = self.add_noise(x_start, noise, t)
148
+
149
+
150
+ target = x_start - noise
151
+ if self.use_reversed_velocity:
152
+ target = -target
153
+
154
+ terms = {}
155
+ model_output = model(x_t, t, x_ignore_mask=x_ignore_mask, **model_kwargs)
156
+ velocity_pred = model_output
157
+
158
+ T = target.shape[2]
159
+ loss = mean_flat((velocity_pred[:, :, -T:] - target).pow(2), stoploss_mask=x_ignore_mask[:, -T:])
160
+
161
+ # # get loss weight
162
+ # timestep_id = torch.argmin((self.timesteps.unsqueeze(0) - t.unsqueeze(1).to(self.timesteps.device)).abs(), dim=1)
163
+ # weights = self.bsmntw_weighing[timestep_id]
164
+ # loss = weights.to(loss) * loss
165
+
166
+ terms["loss"] = loss
167
+
168
+ return terms
169
+
170
+ def add_noise(
171
+ self,
172
+ original_samples: torch.FloatTensor,
173
+ noise: torch.FloatTensor,
174
+ timesteps: torch.IntTensor,
175
+ ) -> torch.FloatTensor:
176
+ """
177
+ compatible with diffusers add_noise()
178
+ """
179
+ timesteps = timesteps.float() / self.num_timesteps
180
+ timesteps = timesteps.view(timesteps.shape + (1,) * (len(noise.shape)-1))
181
+
182
+ return (1 - timesteps) * original_samples + timesteps * noise
183
+
184
+ def sample(
185
+ self,
186
+ model,
187
+ text_encoder,
188
+ null_embedder,
189
+ z_size,
190
+ prompts,
191
+ device,
192
+ mask=None,
193
+ guidance_scale=None,
194
+ negative_prompts=None,
195
+ additional_args=None,
196
+ progress=True,
197
+ ):
198
+ # if no specific guidance scale is provided, use the default scale when initializing the scheduler
199
+ if guidance_scale is None:
200
+ guidance_scale = self.cfg_scale
201
+
202
+ n = len(prompts)
203
+ z = torch.randn(*z_size, device=device)
204
+
205
+ if context_parallel_util.get_cp_size() > 1:
206
+ context_parallel_util.cp_broadcast(z)
207
+
208
+ # For performance alignment
209
+ # from source.opensora.utils.inference_utils import apply_mask_strategy
210
+ # mask = apply_mask_strategy(z, [[]], [""], 0, align=5)
211
+
212
+ assert negative_prompts is None or len(negative_prompts) in [n, 1], \
213
+ "Invalid negative prompts."
214
+
215
+ if negative_prompts:
216
+ if len(negative_prompts) == 1: negative_prompts *= n
217
+ prompts = prompts + negative_prompts
218
+
219
+ batch_size = len(prompts)
220
+ if context_parallel_util.get_cp_rank() == 0:
221
+ model_args = text_encoder.encode(prompts)
222
+ if context_parallel_util.get_cp_size() > 1:
223
+ context_parallel_util.cp_broadcast(model_args['y'])
224
+ context_parallel_util.cp_broadcast(model_args['y_mask'])
225
+ elif context_parallel_util.get_cp_size() > 1:
226
+ caption_channels = text_encoder.output_dim
227
+ model_max_length = text_encoder.model_max_length
228
+ y_tensor = torch.zeros([batch_size, 1, model_max_length, caption_channels], dtype=torch.float32, device=device)
229
+ y_mask_tensor = torch.zeros([batch_size, model_max_length], dtype=torch.int64, device=device)
230
+ context_parallel_util.cp_broadcast(y_tensor)
231
+ context_parallel_util.cp_broadcast(y_mask_tensor)
232
+ model_args = {
233
+ "y" : y_tensor,
234
+ "y_mask": y_mask_tensor,
235
+ }
236
+
237
+ assert negative_prompts, "Not support uncond training now, pls use negative prompt for uncond."
238
+ if not negative_prompts:
239
+ uncond = null_embedder.y_embedding[None].repeat(n, 1, 1)[:, None]
240
+ model_args["y"] = torch.concat([model_args["y"], uncond])
241
+
242
+ if additional_args is not None:
243
+ model_args.update(additional_args)
244
+
245
+ # prepare timesteps
246
+ timesteps = list(np.linspace(self.num_timesteps, 1, self.num_sampling_steps, dtype=np.float32))
247
+ if self.use_discrete_timesteps:
248
+ timesteps = [int(round(t)) for t in timesteps]
249
+ timesteps = [torch.tensor([t] * z.shape[0], device=device) for t in timesteps]
250
+ if self.use_timestep_transform:
251
+ latent_size = z_size[-3:]
252
+ timesteps = [timestep_transform(t, shift=self.shift, num_timesteps=self.num_timesteps) for t in timesteps]
253
+
254
+ if mask is not None:
255
+ noise_added = torch.zeros_like(mask, dtype=torch.bool)
256
+ noise_added = noise_added | (mask == 1)
257
+
258
+ if context_parallel_util.get_cp_size() > 1:
259
+ torch.distributed.barrier(group=context_parallel_util.get_cp_group())
260
+
261
+ model_args["image_cond"] = model_args["image_cond"].repeat(2, 1, 1, 1, 1)
262
+ progress_wrap = partial(tqdm, total=len(timesteps)) if progress else (lambda x: x)
263
+ for i, t in progress_wrap(enumerate(timesteps)):
264
+ # mask for adding noise
265
+ if mask is not None:
266
+ mask_t = mask * self.num_timesteps
267
+ x0 = z.clone()
268
+
269
+ x0_noise = torch.randn_like(x0)
270
+ if context_parallel_util.get_cp_size() > 1:
271
+ context_parallel_util.cp_broadcast(x0_noise)
272
+
273
+ x_noise = self.scheduler.add_noise(x0, x0_noise, t)
274
+
275
+ mask_t_upper = mask_t >= t.unsqueeze(1)
276
+ model_args["x_mask"] = mask_t_upper.repeat(2, 1)
277
+ mask_add_noise = mask_t_upper & ~noise_added
278
+
279
+ z = torch.where(mask_add_noise[:, None, :, None, None], x_noise, x0)
280
+ noise_added = mask_t_upper
281
+
282
+ # classifier-free guidance
283
+ z_in = torch.cat([z, z], 0)
284
+
285
+ t = torch.cat([t, t], 0)
286
+ start = time.time()
287
+ pred = model(z_in, t, **model_args)
288
+ pred = pred[:, :, -z_in.shape[2]:]
289
+ end = time.time()
290
+
291
+ print(f"Step {i} Forward time: {end - start:.4f} seconds")
292
+ pred_cond, pred_uncond = pred.chunk(2, dim=0)
293
+ v_pred = pred_uncond + guidance_scale * (pred_cond - pred_uncond)
294
+
295
+ # When model predict noise-z0, the actual velocity is (v_pred * -1)
296
+ if self.use_reversed_velocity:
297
+ v_pred = -v_pred
298
+
299
+ # update z
300
+ dt = timesteps[i] - timesteps[i + 1] if i < len(timesteps) - 1 else timesteps[i]
301
+ dt = dt / self.num_timesteps
302
+ z = z + v_pred * dt[:, None, None, None, None]
303
+
304
+ if mask is not None:
305
+ z = torch.where(mask_t_upper[:, None, :, None, None], z, x0)
306
+ return z
infworld/models/t5.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted from PixArt
2
+ #
3
+ # Copyright (C) 2023 PixArt-alpha/PixArt-alpha
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ #
16
+ # This source code is licensed under the license found in the
17
+ # LICENSE file in the root directory of this source tree.
18
+ # --------------------------------------------------------
19
+ # References:
20
+ # PixArt: https://github.com/PixArt-alpha/PixArt-alpha
21
+ # T5: https://github.com/google-research/text-to-text-transfer-transformer
22
+ # --------------------------------------------------------
23
+
24
+
25
+ import html
26
+ import os
27
+ import re
28
+ import urllib.parse as ul
29
+
30
+ import ftfy
31
+ import torch
32
+ from bs4 import BeautifulSoup
33
+ from huggingface_hub import hf_hub_download
34
+ from transformers import AutoTokenizer, T5EncoderModel
35
+
36
+
37
+ class T5Embedder:
38
+ available_models = ["t5-v1_1-xxl"]
39
+ bad_punct_regex = re.compile(
40
+ r"[" + "#®•©™&@·º½¾¿¡§~" + "\)" + "\(" + "\]" + "\[" + "\}" + "\{" + "\|" + "\\" + "\/" + "\*" + r"]{1,}"
41
+ ) # noqa
42
+
43
+ def __init__(
44
+ self,
45
+ device,
46
+ from_pretrained,
47
+ *,
48
+ cache_dir=None,
49
+ hf_token=None,
50
+ use_text_preprocessing=True,
51
+ t5_model_kwargs=None,
52
+ torch_dtype=None,
53
+ use_offload_folder=None,
54
+ model_max_length=120,
55
+ ):
56
+ self.device = torch.device(device)
57
+ self.torch_dtype = torch_dtype or torch.bfloat16
58
+ if t5_model_kwargs is None:
59
+ t5_model_kwargs = {"low_cpu_mem_usage": True, "torch_dtype": self.torch_dtype}
60
+ if use_offload_folder is not None:
61
+ t5_model_kwargs["offload_folder"] = use_offload_folder
62
+ t5_model_kwargs["device_map"] = {
63
+ "shared": self.device,
64
+ "encoder.embed_tokens": self.device,
65
+ "encoder.block.0": self.device,
66
+ "encoder.block.1": self.device,
67
+ "encoder.block.2": self.device,
68
+ "encoder.block.3": self.device,
69
+ "encoder.block.4": self.device,
70
+ "encoder.block.5": self.device,
71
+ "encoder.block.6": self.device,
72
+ "encoder.block.7": self.device,
73
+ "encoder.block.8": self.device,
74
+ "encoder.block.9": self.device,
75
+ "encoder.block.10": self.device,
76
+ "encoder.block.11": self.device,
77
+ "encoder.block.12": "disk",
78
+ "encoder.block.13": "disk",
79
+ "encoder.block.14": "disk",
80
+ "encoder.block.15": "disk",
81
+ "encoder.block.16": "disk",
82
+ "encoder.block.17": "disk",
83
+ "encoder.block.18": "disk",
84
+ "encoder.block.19": "disk",
85
+ "encoder.block.20": "disk",
86
+ "encoder.block.21": "disk",
87
+ "encoder.block.22": "disk",
88
+ "encoder.block.23": "disk",
89
+ "encoder.final_layer_norm": "disk",
90
+ "encoder.dropout": "disk",
91
+ }
92
+ else:
93
+ t5_model_kwargs["device_map"] = {"shared": self.device, "encoder": self.device}
94
+
95
+ self.use_text_preprocessing = use_text_preprocessing
96
+
97
+ tokenizer_path = from_pretrained
98
+ path = from_pretrained
99
+
100
+
101
+ print(tokenizer_path)
102
+ self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path)
103
+ self.model = T5EncoderModel.from_pretrained(path, **t5_model_kwargs).eval()
104
+ self.model_max_length = model_max_length
105
+
106
+ def get_text_embeddings(self, texts):
107
+ texts = [self.text_preprocessing(text) for text in texts]
108
+
109
+ text_tokens_and_mask = self.tokenizer(
110
+ texts,
111
+ max_length=self.model_max_length,
112
+ padding="max_length",
113
+ truncation=True,
114
+ return_attention_mask=True,
115
+ add_special_tokens=True,
116
+ return_tensors="pt",
117
+ )
118
+
119
+ text_tokens_and_mask["input_ids"] = text_tokens_and_mask["input_ids"]
120
+ text_tokens_and_mask["attention_mask"] = text_tokens_and_mask["attention_mask"]
121
+
122
+ with torch.no_grad():
123
+ text_encoder_embs = self.model(
124
+ input_ids=text_tokens_and_mask["input_ids"].to(self.device),
125
+ attention_mask=text_tokens_and_mask["attention_mask"].to(self.device),
126
+ )["last_hidden_state"].detach()
127
+ return text_encoder_embs, text_tokens_and_mask["attention_mask"].to(self.device)
128
+
129
+ def text_preprocessing(self, text):
130
+ if self.use_text_preprocessing:
131
+ # The exact text cleaning as was in the training stage:
132
+ text = self.clean_caption(text)
133
+ text = self.clean_caption(text)
134
+ return text
135
+ else:
136
+ return text.lower().strip()
137
+
138
+ @staticmethod
139
+ def basic_clean(text):
140
+ text = ftfy.fix_text(text)
141
+ text = html.unescape(html.unescape(text))
142
+ return text.strip()
143
+
144
+ def clean_caption(self, caption):
145
+ caption = str(caption)
146
+ caption = ul.unquote_plus(caption)
147
+ caption = caption.strip().lower()
148
+ caption = re.sub("<person>", "person", caption)
149
+ # urls:
150
+ caption = re.sub(
151
+ r"\b((?:https?:(?:\/{1,3}|[a-zA-Z0-9%])|[a-zA-Z0-9.\-]+[.](?:com|co|ru|net|org|edu|gov|it)[\w/-]*\b\/?(?!@)))", # noqa
152
+ "",
153
+ caption,
154
+ ) # regex for urls
155
+ caption = re.sub(
156
+ r"\b((?:www:(?:\/{1,3}|[a-zA-Z0-9%])|[a-zA-Z0-9.\-]+[.](?:com|co|ru|net|org|edu|gov|it)[\w/-]*\b\/?(?!@)))", # noqa
157
+ "",
158
+ caption,
159
+ ) # regex for urls
160
+ # html:
161
+ caption = BeautifulSoup(caption, features="html.parser").text
162
+
163
+ # @<nickname>
164
+ caption = re.sub(r"@[\w\d]+\b", "", caption)
165
+
166
+ # 31C0—31EF CJK Strokes
167
+ # 31F0—31FF Katakana Phonetic Extensions
168
+ # 3200—32FF Enclosed CJK Letters and Months
169
+ # 3300—33FF CJK Compatibility
170
+ # 3400—4DBF CJK Unified Ideographs Extension A
171
+ # 4DC0—4DFF Yijing Hexagram Symbols
172
+ # 4E00—9FFF CJK Unified Ideographs
173
+ caption = re.sub(r"[\u31c0-\u31ef]+", "", caption)
174
+ caption = re.sub(r"[\u31f0-\u31ff]+", "", caption)
175
+ caption = re.sub(r"[\u3200-\u32ff]+", "", caption)
176
+ caption = re.sub(r"[\u3300-\u33ff]+", "", caption)
177
+ caption = re.sub(r"[\u3400-\u4dbf]+", "", caption)
178
+ caption = re.sub(r"[\u4dc0-\u4dff]+", "", caption)
179
+ caption = re.sub(r"[\u4e00-\u9fff]+", "", caption)
180
+ #######################################################
181
+
182
+ # все виды тире / all types of dash --> "-"
183
+ caption = re.sub(
184
+ r"[\u002D\u058A\u05BE\u1400\u1806\u2010-\u2015\u2E17\u2E1A\u2E3A\u2E3B\u2E40\u301C\u3030\u30A0\uFE31\uFE32\uFE58\uFE63\uFF0D]+", # noqa
185
+ "-",
186
+ caption,
187
+ )
188
+
189
+ # кавычки к одному стандарту
190
+ caption = re.sub(r"[`´«»“”¨]", '"', caption)
191
+ caption = re.sub(r"[‘’]", "'", caption)
192
+
193
+ # &quot;
194
+ caption = re.sub(r"&quot;?", "", caption)
195
+ # &amp
196
+ caption = re.sub(r"&amp", "", caption)
197
+
198
+ # ip adresses:
199
+ caption = re.sub(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", " ", caption)
200
+
201
+ # article ids:
202
+ caption = re.sub(r"\d:\d\d\s+$", "", caption)
203
+
204
+ # \n
205
+ caption = re.sub(r"\\n", " ", caption)
206
+
207
+ # "#123"
208
+ caption = re.sub(r"#\d{1,3}\b", "", caption)
209
+ # "#12345.."
210
+ caption = re.sub(r"#\d{5,}\b", "", caption)
211
+ # "123456.."
212
+ caption = re.sub(r"\b\d{6,}\b", "", caption)
213
+ # filenames:
214
+ caption = re.sub(r"[\S]+\.(?:png|jpg|jpeg|bmp|webp|eps|pdf|apk|mp4)", "", caption)
215
+
216
+ #
217
+ caption = re.sub(r"[\"\']{2,}", r'"', caption) # """AUSVERKAUFT"""
218
+ caption = re.sub(r"[\.]{2,}", r" ", caption) # """AUSVERKAUFT"""
219
+
220
+ caption = re.sub(self.bad_punct_regex, r" ", caption) # ***AUSVERKAUFT***, #AUSVERKAUFT
221
+ caption = re.sub(r"\s+\.\s+", r" ", caption) # " . "
222
+
223
+ # this-is-my-cute-cat / this_is_my_cute_cat
224
+ regex2 = re.compile(r"(?:\-|\_)")
225
+ if len(re.findall(regex2, caption)) > 3:
226
+ caption = re.sub(regex2, " ", caption)
227
+
228
+ caption = self.basic_clean(caption)
229
+
230
+ caption = re.sub(r"\b[a-zA-Z]{1,3}\d{3,15}\b", "", caption) # jc6640
231
+ caption = re.sub(r"\b[a-zA-Z]+\d+[a-zA-Z]+\b", "", caption) # jc6640vc
232
+ caption = re.sub(r"\b\d+[a-zA-Z]+\d+\b", "", caption) # 6640vc231
233
+
234
+ caption = re.sub(r"(worldwide\s+)?(free\s+)?shipping", "", caption)
235
+ caption = re.sub(r"(free\s)?download(\sfree)?", "", caption)
236
+ caption = re.sub(r"\bclick\b\s(?:for|on)\s\w+", "", caption)
237
+ caption = re.sub(r"\b(?:png|jpg|jpeg|bmp|webp|eps|pdf|apk|mp4)(\simage[s]?)?", "", caption)
238
+ caption = re.sub(r"\bpage\s+\d+\b", "", caption)
239
+
240
+ caption = re.sub(r"\b\d*[a-zA-Z]+\d+[a-zA-Z]+\d+[a-zA-Z\d]*\b", r" ", caption) # j2d1a2a...
241
+
242
+ caption = re.sub(r"\b\d+\.?\d*[xх×]\d+\.?\d*\b", "", caption)
243
+
244
+ caption = re.sub(r"\b\s+\:\s+", r": ", caption)
245
+ caption = re.sub(r"(\D[,\./])\b", r"\1 ", caption)
246
+ caption = re.sub(r"\s+", " ", caption)
247
+
248
+ caption.strip()
249
+
250
+ caption = re.sub(r"^[\"\']([\w\W]+)[\"\']$", r"\1", caption)
251
+ caption = re.sub(r"^[\'\_,\-\:;]", r"", caption)
252
+ caption = re.sub(r"[\'\_,\-\:\-\+]$", r"", caption)
253
+ caption = re.sub(r"^\.\S+$", "", caption)
254
+
255
+ return caption.strip()
256
+
257
+
258
+ class T5Encoder:
259
+ def __init__(
260
+ self,
261
+ from_pretrained=None,
262
+ model_max_length=120,
263
+ device="cuda",
264
+ dtype=torch.float,
265
+ shardformer=False,
266
+ allow_tf32=True,
267
+ ):
268
+ assert from_pretrained is not None, "Please specify the path to the T5 model"
269
+
270
+ self.t5 = T5Embedder(
271
+ device=device,
272
+ torch_dtype=dtype,
273
+ from_pretrained=from_pretrained,
274
+ model_max_length=model_max_length,
275
+ )
276
+ self.t5.model.to(dtype=dtype)
277
+ self.y_embedder = None
278
+
279
+ self.model_max_length = model_max_length
280
+ self.output_dim = self.t5.model.config.d_model
281
+
282
+ self.allow_tf32 = allow_tf32
283
+
284
+ if shardformer:
285
+ self.shardformer_t5()
286
+
287
+ def shardformer_t5(self):
288
+ from colossalai.shardformer import ShardConfig, ShardFormer
289
+
290
+ from opensora.acceleration.shardformer.policy.t5_encoder import T5EncoderPolicy
291
+ from opensora.utils.misc import requires_grad
292
+
293
+ shard_config = ShardConfig(
294
+ tensor_parallel_process_group=None,
295
+ pipeline_stage_manager=None,
296
+ enable_tensor_parallelism=False,
297
+ enable_fused_normalization=False,
298
+ enable_flash_attention=False,
299
+ enable_jit_fused=True,
300
+ enable_sequence_parallelism=False,
301
+ enable_sequence_overlap=False,
302
+ )
303
+ shard_former = ShardFormer(shard_config=shard_config)
304
+ optim_model, _ = shard_former.optimize(self.t5.model, policy=T5EncoderPolicy())
305
+ self.t5.model = optim_model.half()
306
+
307
+ # ensure the weights are frozen
308
+ requires_grad(self.t5.model, False)
309
+
310
+ def encode(self, text):
311
+ original_value = torch.backends.cuda.matmul.allow_tf32
312
+ if self.allow_tf32:
313
+ torch.backends.cuda.matmul.allow_tf32 = True
314
+ caption_embs, emb_masks = self.t5.get_text_embeddings(text)
315
+ caption_embs = caption_embs[:, None]
316
+ torch.backends.cuda.matmul.allow_tf32 = original_value
317
+ return dict(y=caption_embs, y_mask=emb_masks)
318
+
319
+ def null(self, n):
320
+ null_y = self.y_embedder.y_embedding[None].repeat(n, 1, 1)[:, None]
321
+ return null_y
infworld/models/umt5.py ADDED
@@ -0,0 +1,605 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from transformers.models.t5.modeling_t5
2
+ # Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
3
+ import os
4
+ import html
5
+ import math
6
+ import ftfy
7
+ import string
8
+ import logging
9
+ import regex as re
10
+
11
+ import torch
12
+ import torch.nn as nn
13
+ import torch.nn.functional as F
14
+
15
+ from transformers import AutoTokenizer
16
+
17
+
18
+ __all__ = [
19
+ 'T5Model',
20
+ 'T5Encoder',
21
+ 'T5Decoder',
22
+ 'T5EncoderModel',
23
+ 'HuggingfaceTokenizer',
24
+ ]
25
+
26
+
27
+ def basic_clean(text):
28
+ text = ftfy.fix_text(text)
29
+ text = html.unescape(html.unescape(text))
30
+ return text.strip()
31
+
32
+
33
+ def whitespace_clean(text):
34
+ text = re.sub(r'\s+', ' ', text)
35
+ text = text.strip()
36
+ return text
37
+
38
+
39
+ def canonicalize(text, keep_punctuation_exact_string=None):
40
+ text = text.replace('_', ' ')
41
+ if keep_punctuation_exact_string:
42
+ text = keep_punctuation_exact_string.join(
43
+ part.translate(str.maketrans('', '', string.punctuation))
44
+ for part in text.split(keep_punctuation_exact_string))
45
+ else:
46
+ text = text.translate(str.maketrans('', '', string.punctuation))
47
+ text = text.lower()
48
+ text = re.sub(r'\s+', ' ', text)
49
+ return text.strip()
50
+
51
+
52
+ class HuggingfaceTokenizer:
53
+
54
+ def __init__(self, name, seq_len=None, clean=None, **kwargs):
55
+ assert clean in (None, 'whitespace', 'lower', 'canonicalize')
56
+ self.name = name
57
+ self.seq_len = seq_len
58
+ self.clean = clean
59
+
60
+ # init tokenizer
61
+ self.tokenizer = AutoTokenizer.from_pretrained(name, **kwargs)
62
+ self.vocab_size = self.tokenizer.vocab_size
63
+
64
+ def __call__(self, sequence, **kwargs):
65
+ return_mask = kwargs.pop('return_mask', False)
66
+
67
+ # arguments
68
+ _kwargs = {'return_tensors': 'pt'}
69
+ if self.seq_len is not None:
70
+ _kwargs.update({
71
+ 'padding': 'max_length',
72
+ 'truncation': True,
73
+ 'max_length': self.seq_len
74
+ })
75
+ _kwargs.update(**kwargs)
76
+
77
+ # tokenization
78
+ if isinstance(sequence, str):
79
+ sequence = [sequence]
80
+ if self.clean:
81
+ sequence = [self._clean(u) for u in sequence]
82
+ ids = self.tokenizer(sequence, **_kwargs)
83
+
84
+ # output
85
+ if return_mask:
86
+ return ids.input_ids, ids.attention_mask
87
+ else:
88
+ return ids.input_ids
89
+
90
+ def _clean(self, text):
91
+ if self.clean == 'whitespace':
92
+ text = whitespace_clean(basic_clean(text))
93
+ elif self.clean == 'lower':
94
+ text = whitespace_clean(basic_clean(text)).lower()
95
+ elif self.clean == 'canonicalize':
96
+ text = canonicalize(basic_clean(text))
97
+ return text
98
+
99
+ def fp16_clamp(x):
100
+ if x.dtype == torch.float16 and torch.isinf(x).any():
101
+ clamp = torch.finfo(x.dtype).max - 1000
102
+ x = torch.clamp(x, min=-clamp, max=clamp)
103
+ return x
104
+
105
+
106
+ def init_weights(m):
107
+ if isinstance(m, T5LayerNorm):
108
+ nn.init.ones_(m.weight)
109
+ elif isinstance(m, T5Model):
110
+ nn.init.normal_(m.token_embedding.weight, std=1.0)
111
+ elif isinstance(m, T5FeedForward):
112
+ nn.init.normal_(m.gate[0].weight, std=m.dim**-0.5)
113
+ nn.init.normal_(m.fc1.weight, std=m.dim**-0.5)
114
+ nn.init.normal_(m.fc2.weight, std=m.dim_ffn**-0.5)
115
+ elif isinstance(m, T5Attention):
116
+ nn.init.normal_(m.q.weight, std=(m.dim * m.dim_attn)**-0.5)
117
+ nn.init.normal_(m.k.weight, std=m.dim**-0.5)
118
+ nn.init.normal_(m.v.weight, std=m.dim**-0.5)
119
+ nn.init.normal_(m.o.weight, std=(m.num_heads * m.dim_attn)**-0.5)
120
+ elif isinstance(m, T5RelativeEmbedding):
121
+ nn.init.normal_(
122
+ m.embedding.weight, std=(2 * m.num_buckets * m.num_heads)**-0.5)
123
+
124
+
125
+ class GELU(nn.Module):
126
+
127
+ def forward(self, x):
128
+ return 0.5 * x * (1.0 + torch.tanh(
129
+ math.sqrt(2.0 / math.pi) * (x + 0.044715 * torch.pow(x, 3.0))))
130
+
131
+
132
+ class T5LayerNorm(nn.Module):
133
+
134
+ def __init__(self, dim, eps=1e-6):
135
+ super(T5LayerNorm, self).__init__()
136
+ self.dim = dim
137
+ self.eps = eps
138
+ self.weight = nn.Parameter(torch.ones(dim))
139
+
140
+ def forward(self, x):
141
+ x = x * torch.rsqrt(x.float().pow(2).mean(dim=-1, keepdim=True) +
142
+ self.eps)
143
+ if self.weight.dtype in [torch.float16, torch.bfloat16]:
144
+ x = x.type_as(self.weight)
145
+ return self.weight * x
146
+
147
+
148
+ class T5Attention(nn.Module):
149
+
150
+ def __init__(self, dim, dim_attn, num_heads, dropout=0.1):
151
+ assert dim_attn % num_heads == 0
152
+ super(T5Attention, self).__init__()
153
+ self.dim = dim
154
+ self.dim_attn = dim_attn
155
+ self.num_heads = num_heads
156
+ self.head_dim = dim_attn // num_heads
157
+
158
+ # layers
159
+ self.q = nn.Linear(dim, dim_attn, bias=False)
160
+ self.k = nn.Linear(dim, dim_attn, bias=False)
161
+ self.v = nn.Linear(dim, dim_attn, bias=False)
162
+ self.o = nn.Linear(dim_attn, dim, bias=False)
163
+ self.dropout = nn.Dropout(dropout)
164
+
165
+ def forward(self, x, context=None, mask=None, pos_bias=None):
166
+ """
167
+ x: [B, L1, C].
168
+ context: [B, L2, C] or None.
169
+ mask: [B, L2] or [B, L1, L2] or None.
170
+ """
171
+ # check inputs
172
+ context = x if context is None else context
173
+ b, n, c = x.size(0), self.num_heads, self.head_dim
174
+
175
+ # compute query, key, value
176
+ q = self.q(x).view(b, -1, n, c)
177
+ k = self.k(context).view(b, -1, n, c)
178
+ v = self.v(context).view(b, -1, n, c)
179
+
180
+ # attention bias
181
+ attn_bias = x.new_zeros(b, n, q.size(1), k.size(1))
182
+ if pos_bias is not None:
183
+ attn_bias += pos_bias
184
+ if mask is not None:
185
+ assert mask.ndim in [2, 3]
186
+ mask = mask.view(b, 1, 1,
187
+ -1) if mask.ndim == 2 else mask.unsqueeze(1)
188
+ attn_bias.masked_fill_(mask == 0, torch.finfo(x.dtype).min)
189
+
190
+ # compute attention (T5 does not use scaling)
191
+ attn = torch.einsum('binc,bjnc->bnij', q, k) + attn_bias
192
+ attn = F.softmax(attn.float(), dim=-1).type_as(attn)
193
+ x = torch.einsum('bnij,bjnc->binc', attn, v)
194
+
195
+ # output
196
+ x = x.reshape(b, -1, n * c)
197
+ x = self.o(x)
198
+ x = self.dropout(x)
199
+ return x
200
+
201
+
202
+ class T5FeedForward(nn.Module):
203
+
204
+ def __init__(self, dim, dim_ffn, dropout=0.1):
205
+ super(T5FeedForward, self).__init__()
206
+ self.dim = dim
207
+ self.dim_ffn = dim_ffn
208
+
209
+ # layers
210
+ self.gate = nn.Sequential(nn.Linear(dim, dim_ffn, bias=False), GELU())
211
+ self.fc1 = nn.Linear(dim, dim_ffn, bias=False)
212
+ self.fc2 = nn.Linear(dim_ffn, dim, bias=False)
213
+ self.dropout = nn.Dropout(dropout)
214
+
215
+ def forward(self, x):
216
+ x = self.fc1(x) * self.gate(x)
217
+ x = self.dropout(x)
218
+ x = self.fc2(x)
219
+ x = self.dropout(x)
220
+ return x
221
+
222
+
223
+ class T5SelfAttention(nn.Module):
224
+
225
+ def __init__(self,
226
+ dim,
227
+ dim_attn,
228
+ dim_ffn,
229
+ num_heads,
230
+ num_buckets,
231
+ shared_pos=True,
232
+ dropout=0.1):
233
+ super(T5SelfAttention, self).__init__()
234
+ self.dim = dim
235
+ self.dim_attn = dim_attn
236
+ self.dim_ffn = dim_ffn
237
+ self.num_heads = num_heads
238
+ self.num_buckets = num_buckets
239
+ self.shared_pos = shared_pos
240
+
241
+ # layers
242
+ self.norm1 = T5LayerNorm(dim)
243
+ self.attn = T5Attention(dim, dim_attn, num_heads, dropout)
244
+ self.norm2 = T5LayerNorm(dim)
245
+ self.ffn = T5FeedForward(dim, dim_ffn, dropout)
246
+ self.pos_embedding = None if shared_pos else T5RelativeEmbedding(
247
+ num_buckets, num_heads, bidirectional=True)
248
+
249
+ def forward(self, x, mask=None, pos_bias=None):
250
+ e = pos_bias if self.shared_pos else self.pos_embedding(
251
+ x.size(1), x.size(1))
252
+ x = fp16_clamp(x + self.attn(self.norm1(x), mask=mask, pos_bias=e))
253
+ x = fp16_clamp(x + self.ffn(self.norm2(x)))
254
+ return x
255
+
256
+
257
+ class T5CrossAttention(nn.Module):
258
+
259
+ def __init__(self,
260
+ dim,
261
+ dim_attn,
262
+ dim_ffn,
263
+ num_heads,
264
+ num_buckets,
265
+ shared_pos=True,
266
+ dropout=0.1):
267
+ super(T5CrossAttention, self).__init__()
268
+ self.dim = dim
269
+ self.dim_attn = dim_attn
270
+ self.dim_ffn = dim_ffn
271
+ self.num_heads = num_heads
272
+ self.num_buckets = num_buckets
273
+ self.shared_pos = shared_pos
274
+
275
+ # layers
276
+ self.norm1 = T5LayerNorm(dim)
277
+ self.self_attn = T5Attention(dim, dim_attn, num_heads, dropout)
278
+ self.norm2 = T5LayerNorm(dim)
279
+ self.cross_attn = T5Attention(dim, dim_attn, num_heads, dropout)
280
+ self.norm3 = T5LayerNorm(dim)
281
+ self.ffn = T5FeedForward(dim, dim_ffn, dropout)
282
+ self.pos_embedding = None if shared_pos else T5RelativeEmbedding(
283
+ num_buckets, num_heads, bidirectional=False)
284
+
285
+ def forward(self,
286
+ x,
287
+ mask=None,
288
+ encoder_states=None,
289
+ encoder_mask=None,
290
+ pos_bias=None):
291
+ e = pos_bias if self.shared_pos else self.pos_embedding(
292
+ x.size(1), x.size(1))
293
+ x = fp16_clamp(x + self.self_attn(self.norm1(x), mask=mask, pos_bias=e))
294
+ x = fp16_clamp(x + self.cross_attn(
295
+ self.norm2(x), context=encoder_states, mask=encoder_mask))
296
+ x = fp16_clamp(x + self.ffn(self.norm3(x)))
297
+ return x
298
+
299
+
300
+ class T5RelativeEmbedding(nn.Module):
301
+
302
+ def __init__(self, num_buckets, num_heads, bidirectional, max_dist=128):
303
+ super(T5RelativeEmbedding, self).__init__()
304
+ self.num_buckets = num_buckets
305
+ self.num_heads = num_heads
306
+ self.bidirectional = bidirectional
307
+ self.max_dist = max_dist
308
+
309
+ # layers
310
+ self.embedding = nn.Embedding(num_buckets, num_heads)
311
+
312
+ def forward(self, lq, lk):
313
+ device = self.embedding.weight.device
314
+ # rel_pos = torch.arange(lk).unsqueeze(0).to(device) - \
315
+ # torch.arange(lq).unsqueeze(1).to(device)
316
+ rel_pos = torch.arange(lk, device=device).unsqueeze(0) - \
317
+ torch.arange(lq, device=device).unsqueeze(1)
318
+ rel_pos = self._relative_position_bucket(rel_pos)
319
+ rel_pos_embeds = self.embedding(rel_pos)
320
+ rel_pos_embeds = rel_pos_embeds.permute(2, 0, 1).unsqueeze(
321
+ 0) # [1, N, Lq, Lk]
322
+ return rel_pos_embeds.contiguous()
323
+
324
+ def _relative_position_bucket(self, rel_pos):
325
+ # preprocess
326
+ if self.bidirectional:
327
+ num_buckets = self.num_buckets // 2
328
+ rel_buckets = (rel_pos > 0).long() * num_buckets
329
+ rel_pos = torch.abs(rel_pos)
330
+ else:
331
+ num_buckets = self.num_buckets
332
+ rel_buckets = 0
333
+ rel_pos = -torch.min(rel_pos, torch.zeros_like(rel_pos))
334
+
335
+ # embeddings for small and large positions
336
+ max_exact = num_buckets // 2
337
+ rel_pos_large = max_exact + (torch.log(rel_pos.float() / max_exact) /
338
+ math.log(self.max_dist / max_exact) *
339
+ (num_buckets - max_exact)).long()
340
+ rel_pos_large = torch.min(
341
+ rel_pos_large, torch.full_like(rel_pos_large, num_buckets - 1))
342
+ rel_buckets += torch.where(rel_pos < max_exact, rel_pos, rel_pos_large)
343
+ return rel_buckets
344
+
345
+
346
+ class T5Encoder(nn.Module):
347
+
348
+ def __init__(self,
349
+ vocab,
350
+ dim,
351
+ dim_attn,
352
+ dim_ffn,
353
+ num_heads,
354
+ num_layers,
355
+ num_buckets,
356
+ shared_pos=True,
357
+ dropout=0.1):
358
+ super(T5Encoder, self).__init__()
359
+ self.dim = dim
360
+ self.dim_attn = dim_attn
361
+ self.dim_ffn = dim_ffn
362
+ self.num_heads = num_heads
363
+ self.num_layers = num_layers
364
+ self.num_buckets = num_buckets
365
+ self.shared_pos = shared_pos
366
+
367
+ # layers
368
+ self.token_embedding = vocab if isinstance(vocab, nn.Embedding) \
369
+ else nn.Embedding(vocab, dim)
370
+ self.pos_embedding = T5RelativeEmbedding(
371
+ num_buckets, num_heads, bidirectional=True) if shared_pos else None
372
+ self.dropout = nn.Dropout(dropout)
373
+ self.blocks = nn.ModuleList([
374
+ T5SelfAttention(dim, dim_attn, dim_ffn, num_heads, num_buckets,
375
+ shared_pos, dropout) for _ in range(num_layers)
376
+ ])
377
+ self.norm = T5LayerNorm(dim)
378
+
379
+ # initialize weights
380
+ self.apply(init_weights)
381
+
382
+ def forward(self, ids, mask=None):
383
+ x = self.token_embedding(ids)
384
+ x = self.dropout(x)
385
+ e = self.pos_embedding(x.size(1),
386
+ x.size(1)) if self.shared_pos else None
387
+ for block in self.blocks:
388
+ x = block(x, mask, pos_bias=e)
389
+ x = self.norm(x)
390
+ x = self.dropout(x)
391
+ return x
392
+
393
+
394
+ class T5Decoder(nn.Module):
395
+
396
+ def __init__(self,
397
+ vocab,
398
+ dim,
399
+ dim_attn,
400
+ dim_ffn,
401
+ num_heads,
402
+ num_layers,
403
+ num_buckets,
404
+ shared_pos=True,
405
+ dropout=0.1):
406
+ super(T5Decoder, self).__init__()
407
+ self.dim = dim
408
+ self.dim_attn = dim_attn
409
+ self.dim_ffn = dim_ffn
410
+ self.num_heads = num_heads
411
+ self.num_layers = num_layers
412
+ self.num_buckets = num_buckets
413
+ self.shared_pos = shared_pos
414
+
415
+ # layers
416
+ self.token_embedding = vocab if isinstance(vocab, nn.Embedding) \
417
+ else nn.Embedding(vocab, dim)
418
+ self.pos_embedding = T5RelativeEmbedding(
419
+ num_buckets, num_heads, bidirectional=False) if shared_pos else None
420
+ self.dropout = nn.Dropout(dropout)
421
+ self.blocks = nn.ModuleList([
422
+ T5CrossAttention(dim, dim_attn, dim_ffn, num_heads, num_buckets,
423
+ shared_pos, dropout) for _ in range(num_layers)
424
+ ])
425
+ self.norm = T5LayerNorm(dim)
426
+
427
+ # initialize weights
428
+ self.apply(init_weights)
429
+
430
+ def forward(self, ids, mask=None, encoder_states=None, encoder_mask=None):
431
+ b, s = ids.size()
432
+
433
+ # causal mask
434
+ if mask is None:
435
+ mask = torch.tril(torch.ones(1, s, s).to(ids.device))
436
+ elif mask.ndim == 2:
437
+ mask = torch.tril(mask.unsqueeze(1).expand(-1, s, -1))
438
+
439
+ # layers
440
+ x = self.token_embedding(ids)
441
+ x = self.dropout(x)
442
+ e = self.pos_embedding(x.size(1),
443
+ x.size(1)) if self.shared_pos else None
444
+ for block in self.blocks:
445
+ x = block(x, mask, encoder_states, encoder_mask, pos_bias=e)
446
+ x = self.norm(x)
447
+ x = self.dropout(x)
448
+ return x
449
+
450
+
451
+ class T5Model(nn.Module):
452
+
453
+ def __init__(self,
454
+ vocab_size,
455
+ dim,
456
+ dim_attn,
457
+ dim_ffn,
458
+ num_heads,
459
+ encoder_layers,
460
+ decoder_layers,
461
+ num_buckets,
462
+ shared_pos=True,
463
+ dropout=0.1):
464
+ super(T5Model, self).__init__()
465
+ self.vocab_size = vocab_size
466
+ self.dim = dim
467
+ self.dim_attn = dim_attn
468
+ self.dim_ffn = dim_ffn
469
+ self.num_heads = num_heads
470
+ self.encoder_layers = encoder_layers
471
+ self.decoder_layers = decoder_layers
472
+ self.num_buckets = num_buckets
473
+
474
+ # layers
475
+ self.token_embedding = nn.Embedding(vocab_size, dim)
476
+ self.encoder = T5Encoder(self.token_embedding, dim, dim_attn, dim_ffn,
477
+ num_heads, encoder_layers, num_buckets,
478
+ shared_pos, dropout)
479
+ self.decoder = T5Decoder(self.token_embedding, dim, dim_attn, dim_ffn,
480
+ num_heads, decoder_layers, num_buckets,
481
+ shared_pos, dropout)
482
+ self.head = nn.Linear(dim, vocab_size, bias=False)
483
+
484
+ # initialize weights
485
+ self.apply(init_weights)
486
+
487
+ def forward(self, encoder_ids, encoder_mask, decoder_ids, decoder_mask):
488
+ x = self.encoder(encoder_ids, encoder_mask)
489
+ x = self.decoder(decoder_ids, decoder_mask, x, encoder_mask)
490
+ x = self.head(x)
491
+ return x
492
+
493
+
494
+ def _t5(name,
495
+ encoder_only=False,
496
+ decoder_only=False,
497
+ return_tokenizer=False,
498
+ tokenizer_kwargs={},
499
+ dtype=torch.float32,
500
+ device='cpu',
501
+ **kwargs):
502
+ # sanity check
503
+ assert not (encoder_only and decoder_only)
504
+
505
+ # params
506
+ if encoder_only:
507
+ model_cls = T5Encoder
508
+ kwargs['vocab'] = kwargs.pop('vocab_size')
509
+ kwargs['num_layers'] = kwargs.pop('encoder_layers')
510
+ _ = kwargs.pop('decoder_layers')
511
+ elif decoder_only:
512
+ model_cls = T5Decoder
513
+ kwargs['vocab'] = kwargs.pop('vocab_size')
514
+ kwargs['num_layers'] = kwargs.pop('decoder_layers')
515
+ _ = kwargs.pop('encoder_layers')
516
+ else:
517
+ model_cls = T5Model
518
+
519
+ # init model
520
+ with torch.device(device):
521
+ model = model_cls(**kwargs)
522
+
523
+ # set device
524
+ model = model.to(dtype=dtype, device=device)
525
+
526
+ # init tokenizer
527
+ if return_tokenizer:
528
+ tokenizer = HuggingfaceTokenizer(f'google/{name}', **tokenizer_kwargs)
529
+ return model, tokenizer
530
+ else:
531
+ return model
532
+
533
+
534
+ def umt5_xxl(**kwargs):
535
+ cfg = dict(
536
+ vocab_size=256384,
537
+ dim=4096,
538
+ dim_attn=4096,
539
+ dim_ffn=10240,
540
+ num_heads=64,
541
+ encoder_layers=24,
542
+ decoder_layers=24,
543
+ num_buckets=32,
544
+ shared_pos=False,
545
+ dropout=0.1)
546
+ cfg.update(**kwargs)
547
+ return _t5('umt5-xxl', **cfg)
548
+
549
+
550
+ class T5EncoderModel:
551
+
552
+ def __init__(
553
+ self,
554
+ model_max_length,
555
+ dtype=torch.bfloat16,
556
+ device=torch.cuda.current_device(),
557
+ checkpoint_path=None,
558
+ tokenizer_path=None,
559
+ shard_fn=None,
560
+ ):
561
+
562
+ os.environ["TOKENIZERS_PARALLELISM"]="false"
563
+
564
+ self.model_max_length = model_max_length
565
+ self.dtype = dtype
566
+ self.device = device
567
+ self.checkpoint_path = checkpoint_path
568
+ self.tokenizer_path = tokenizer_path
569
+
570
+ # init model
571
+ model = umt5_xxl(
572
+ encoder_only=True,
573
+ return_tokenizer=False,
574
+ dtype=dtype,
575
+ device=device).eval().requires_grad_(False)
576
+ logging.info(f'loading {checkpoint_path}')
577
+ model.load_state_dict(torch.load(checkpoint_path, map_location='cpu'))
578
+ self.model = model
579
+ if shard_fn is not None:
580
+ self.model = shard_fn(self.model, sync_module_states=False)
581
+ else:
582
+ self.model.to(self.device)
583
+ # init tokenizer
584
+ self.tokenizer = HuggingfaceTokenizer(
585
+ name=tokenizer_path, seq_len=model_max_length, clean='whitespace')
586
+
587
+ self.output_dim = self.model.dim
588
+ self.y_embedder = None
589
+
590
+ @property
591
+ def t5(self,):
592
+ return self
593
+
594
+ def encode(self, texts):
595
+ ids, mask = self.tokenizer(
596
+ texts, return_mask=True, add_special_tokens=True)
597
+ ids = ids.to(self.device)
598
+ mask = mask.to(self.device)
599
+ seq_lens = mask.gt(0).sum(dim=1).long()
600
+ context = self.model(ids, mask).float()
601
+ return dict(y=context[:,None], y_mask=mask)
602
+
603
+ def null(self, n):
604
+ null_y = self.y_embedder.y_embedding[None].repeat(n, 1, 1)[:, None]
605
+ return null_y
infworld/utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # infworld/utils package
infworld/utils/data_utils.py ADDED
@@ -0,0 +1,854 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import re
4
+ import math
5
+ import tempfile
6
+ import imageio
7
+ import random
8
+ from tqdm import tqdm
9
+ import subprocess
10
+
11
+ import cv2
12
+ import numpy as np
13
+ from decord import VideoReader
14
+ from PIL import Image
15
+ from moviepy.editor import AudioFileClip, VideoClip
16
+
17
+
18
+ import torch
19
+ from torchvision.io import write_video
20
+ from torchvision.utils import save_image
21
+ import torchvision.transforms as transforms
22
+
23
+ import binascii
24
+ import torchvision
25
+ import imageio
26
+ import os.path as osp
27
+
28
+
29
+ def infinite_iterator(iter):
30
+ while True:
31
+ for sample in iter:
32
+ yield sample
33
+
34
+ ### Moved from opensora dataset utils
35
+ def save_sample(x, fps=8, save_path=None, normalize=True, value_range=(-1, 1)):
36
+ """
37
+ Args:
38
+ x (Tensor): shape [C, T, H, W]
39
+ Returns:
40
+ x (Tensor): shape [T, H, W, C]
41
+ """
42
+ assert x.ndim == 4
43
+
44
+ os.makedirs(os.path.dirname(save_path),exist_ok=True)
45
+
46
+ if x.shape[1] == 1: # T = 1: save as image
47
+ save_path += ".png"
48
+ x = x.squeeze(1) # [C, H, W]
49
+ save_image([x], save_path, normalize=normalize, value_range=value_range)
50
+ x = x.unsqueeze(0) # [1, C, H, W]
51
+ x = x.permute(0, 2, 3, 1) # [1, H, W, C]
52
+ else:
53
+ save_path += ".mp4"
54
+ if normalize:
55
+ low, high = value_range
56
+ x = x.clamp(min=low, max=high)
57
+ x = x.sub(low).div(max(high - low, 1e-5))
58
+
59
+ x = x.mul(255).add(0.5).clamp(0, 255).permute(1, 2, 3, 0).to("cpu", torch.uint8)
60
+ write_video(save_path, x, fps=fps, video_codec="h264")
61
+ print(f"Saved to {save_path}")
62
+ return x
63
+
64
+
65
+ def video_reader_from_data_meta(datameta, use_tempfile, num_threads_decord):
66
+ """ Get VideoReader from data meta; data meta needs to be video.
67
+ """
68
+ if not datameta.is_video:
69
+ raise NotImplementedError('Unknown data type.')
70
+
71
+ if 'raw_frames' in datameta:
72
+ raw_data = datameta.raw_frames
73
+ if use_tempfile:
74
+ # write raw frames to a temp file before loading
75
+ # this avoids some codec problems
76
+ with tempfile.NamedTemporaryFile() as temp:
77
+ temp.write(raw_data)
78
+ video_reader = VideoReader(temp.name, num_threads=num_threads_decord)
79
+ else:
80
+ # Use io.BytesIO to read image data from memory
81
+ dataBytesIO = io.BytesIO(raw_data)
82
+ # Convert raw data to numpy array
83
+ # Use decord to read video data from memory
84
+ video_reader = VideoReader(dataBytesIO, num_threads=num_threads_decord)
85
+ elif "tar_dir" in datameta and "tar_filename" in datameta and "tar_key" in datameta:
86
+ raw_data = datameta.load_tar_videodata()
87
+ if use_tempfile:
88
+ # write raw frames to a temp file before loading
89
+ # this avoids some codec problems
90
+ with tempfile.NamedTemporaryFile() as temp:
91
+ temp.write(raw_data)
92
+ video_reader = VideoReader(temp.name, num_threads=num_threads_decord)
93
+ else:
94
+ # Use io.BytesIO to read image data from memory
95
+ dataBytesIO = io.BytesIO(raw_data)
96
+ # Convert raw data to numpy array
97
+ # Use decord to read video data from memory
98
+ video_reader = VideoReader(dataBytesIO, num_threads=num_threads_decord)
99
+ elif os.path.exists(datameta.filename):
100
+ video_reader = VideoReader(datameta.filename, num_threads=num_threads_decord)
101
+ else:
102
+ raise NotImplementedError('Not supported data format. rawframes or filename is needed.')
103
+
104
+ return video_reader
105
+
106
+
107
+ def cap_from_data_meta(datameta):
108
+ if not datameta.is_video:
109
+ raise NotImplementedError('Unknown data type.')
110
+
111
+ if 'raw_frames' in datameta:
112
+ raw_data = datameta.raw_frames
113
+ # write raw frames to a temp file before loading
114
+ # this avoids some codec problems
115
+ with tempfile.NamedTemporaryFile() as temp:
116
+ temp.write(raw_data)
117
+ cap = cv2.VideoCapture(temp.name)
118
+ elif "tar_dir" in datameta and "tar_filename" in datameta and "tar_key" in datameta:
119
+ raw_data = datameta.load_tar_videodata()
120
+ # write raw frames to a temp file before loading
121
+ # this avoids some codec problems
122
+ with tempfile.NamedTemporaryFile() as temp:
123
+ temp.write(raw_data)
124
+ cap = cv2.VideoCapture(temp.name)
125
+ elif os.path.exists(datameta.filename):
126
+ cap = cv2.VideoCapture(datameta.filename)
127
+ else:
128
+ raise NotImplementedError('Not supported data format. rawframes or filename is needed.')
129
+
130
+ return cap
131
+
132
+
133
+ def none_node_splitter(src, group=None):
134
+ yield from src
135
+
136
+
137
+ def resize_and_covert_to_gray(np_frames, pixel_value=16, interpolation=cv2.INTER_LINEAR, resize_only=False):
138
+ # Get the dimensions of the first frame
139
+ height, width, *_ = np_frames[0].shape
140
+ # Determine the new dimensions based on the aspect ratio of the original frame
141
+ if width < height:
142
+ new_width = pixel_value
143
+ new_height = int((new_width / width) * height)
144
+ else:
145
+ new_height = pixel_value
146
+ new_width = int((new_height / height) * width)
147
+
148
+ # Function to preprocess each frame
149
+ def transform(frame):
150
+ # Resize the frame
151
+ frame = cv2.resize(frame, (new_width, new_height), interpolation=interpolation)
152
+ # Convert the frame to grayscale
153
+ if not resize_only:
154
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
155
+ return frame
156
+
157
+ # Apply the transformation to each frame
158
+ resize_frames = [transform(frame) for frame in np_frames]
159
+ resize_frames = np.stack(resize_frames)
160
+
161
+ return resize_frames
162
+
163
+ def get_top_m_percent(arr, m_percent):
164
+ B, H, W = arr.shape
165
+ N = int(H * W * m_percent / 100)
166
+ result = np.zeros((B, N))
167
+ for i in range(B):
168
+ flattened_frame = arr[i].flatten()
169
+ flattened_frame = flattened_frame[~np.isnan(flattened_frame)]
170
+ top_m_percent_values = np.partition(flattened_frame, -N)[-N:]
171
+ result[i] = top_m_percent_values
172
+ return np.nanmean(result,axis=1)
173
+
174
+ def compute_optical_flow_score(np_frames, pixel_value=16):
175
+ video_length = np_frames.shape[0]
176
+ # Calculate the optical flow for each pair of frames
177
+ flow_scores = []
178
+ for i in range(1, video_length):
179
+ # Calculate the optical flow between the current and previous frame
180
+ flow = cv2.calcOpticalFlowFarneback(np_frames[i - 1], np_frames[i], None, 0.5, 3, 15, 3, 5, 1.2, 0)
181
+ # Convert the flow vectors to polar coordinates
182
+ magnitude, angle = cv2.cartToPolar(flow[..., 0], flow[..., 1])
183
+ # Append the mean magnitude of the flow vectors to the list of scores
184
+ flow_scores.append(magnitude)
185
+
186
+ # Return the flow score
187
+ return np.array(flow_scores)
188
+
189
+ def get_first_frame_from_video_path(video_path):
190
+ # get cv2 video capture data meta
191
+ cap = cv2.VideoCapture(video_path)
192
+ cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
193
+
194
+ # get first frame, ret will be False if the read operation fails.
195
+ ret, frame = cap.read()
196
+ if ret is False:
197
+ return None
198
+ cap.release()
199
+
200
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
201
+ # convert the numpy frame to Image.
202
+ frame = Image.fromarray(frame)
203
+
204
+ return frame
205
+
206
+ def get_first_clip_from_video(video_path, clip_len=1):
207
+ """
208
+ 获取视频前n帧(默认第1帧)
209
+
210
+ 参数:
211
+ video_path: 视频文件路径
212
+ n: 需要获取的帧数(从第1帧开始)
213
+
214
+ 返回:
215
+ list: 包含前n帧PIL.Image对象的列表,空列表表示读取失败
216
+ """
217
+ frames = []
218
+ cap = cv2.VideoCapture(video_path)
219
+ if not cap.isOpened():
220
+ return frames
221
+
222
+ if clip_len is None:
223
+ clip_len = 100000000
224
+ # 循环读取前n帧
225
+ for frame_idx in range(clip_len):
226
+ # 设置当前帧位置
227
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
228
+ ret, frame = cap.read()
229
+
230
+ if not ret:
231
+ break # 视频长度不足时提前终止
232
+
233
+ # 格式转换
234
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
235
+ frames.append(frame)
236
+
237
+ cap.release()
238
+ return frames
239
+
240
+ def get_last_clip_from_video(video_path, clip_len=1):
241
+ """
242
+ 获取视频最后n帧
243
+
244
+ 参数:
245
+ video_path: 视频文件路径
246
+ clip_len: 需要获取的帧数(从末尾开始)
247
+
248
+ 返回:
249
+ list: 包含最后n帧的RGB帧列表,空列表表示读取失败
250
+ """
251
+ frames = []
252
+ cap = cv2.VideoCapture(video_path)
253
+ if not cap.isOpened():
254
+ return frames
255
+
256
+ # 获取视频总帧数
257
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
258
+
259
+ # 计算起始帧位置
260
+ start_frame = max(0, total_frames - clip_len)
261
+
262
+ # 设置起始位置
263
+ cap.set(cv2.CAP_PROP_POS_FRAMES, start_frame)
264
+
265
+ # 读取剩余所有帧
266
+ while len(frames) < clip_len:
267
+ ret, frame = cap.read()
268
+ if not ret:
269
+ break
270
+
271
+ # 转换颜色空间并存储
272
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
273
+ frames.append(frame)
274
+
275
+ cap.release()
276
+
277
+ # 如果视频长度不足,返回实际能读取的帧
278
+ return frames[-clip_len:] if len(frames) >= clip_len else frames
279
+
280
+
281
+ def pad_to_square_ndarray(image, pad_value=255):
282
+ H, W, C = image.shape
283
+ max_size = max(H, W)
284
+
285
+ padded_image = np.full((max_size, max_size, C), pad_value, dtype=image.dtype)
286
+
287
+ top_left_y = (max_size - H) // 2
288
+ top_left_x = (max_size - W) // 2
289
+
290
+ padded_image[top_left_y:top_left_y + H, top_left_x:top_left_x + W, :] = image
291
+
292
+ return padded_image
293
+
294
+ def pad_to_square_pil(image, pad_value=255):
295
+ width, height = image.size
296
+
297
+ max_size = max(width, height)
298
+
299
+ new_image = Image.new("RGB", (max_size, max_size), (pad_value, pad_value, pad_value))
300
+
301
+ top_left_x = (max_size - width) // 2
302
+ top_left_y = (max_size - height) // 2
303
+
304
+ new_image.paste(image, (top_left_x, top_left_y))
305
+
306
+ return new_image
307
+
308
+ def separate_connected_components(mask):
309
+
310
+ labeled_array, num_features = label(mask)
311
+
312
+ separate_masks = []
313
+ bboxes = []
314
+
315
+ slices = find_objects(labeled_array)
316
+
317
+ for i in range(1, num_features + 1):
318
+
319
+ component_mask = (labeled_array == i).astype(np.uint8)
320
+ separate_masks.append(component_mask)
321
+
322
+ slice_ = slices[i - 1]
323
+
324
+ bbox = (slice_[1].start, slice_[0].start, slice_[1].stop, slice_[0].stop) # (xmin, ymin, xmax, ymax)
325
+ bboxes.append(bbox)
326
+
327
+ return separate_masks, bboxes
328
+
329
+ def bbox_random_crop(bbox):
330
+
331
+ xmin, ymin, xmax, ymax = bbox
332
+
333
+ width = xmax - xmin
334
+ height = ymax - ymin
335
+
336
+ if height > width:
337
+ square_size = width
338
+ max_y_start = ymax - square_size
339
+ y_start = random.randint(ymin, max_y_start)
340
+ return (xmin, y_start, xmin + square_size, y_start + square_size)
341
+ else:
342
+ square_size = height
343
+ max_x_start = xmax - square_size
344
+ x_start = random.randint(xmin, max_x_start)
345
+ return (x_start, ymin, x_start + square_size, ymin + square_size)
346
+
347
+ def inflate_bbox(bbox, d):
348
+
349
+ x_min, y_min, x_max, y_max = bbox
350
+
351
+ original_width = x_max - x_min
352
+ original_height = y_max - y_min
353
+
354
+ new_width = d * original_width
355
+ new_height = new_width
356
+
357
+ center_x = (x_min + x_max) / 2
358
+ center_y = (y_min + y_max) / 2
359
+
360
+ half_new_width = new_width / 2
361
+ half_new_height = new_height / 2
362
+
363
+ new_x_min = int(center_x - half_new_width)
364
+ new_x_max = int(center_x + half_new_width)
365
+ new_y_min = int(center_y - half_new_height)
366
+ new_y_max = int(center_y + half_new_height)
367
+
368
+ return (new_x_min, new_y_min, new_x_max, new_y_max)
369
+
370
+ def get_frame_by_idx(cap, frame_idxs):
371
+ if isinstance(frame_idxs, np.ndarray) or isinstance(frame_idxs, list):
372
+ frames = []
373
+ for frame_idx in frame_idxs:
374
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
375
+
376
+ ret, frame = cap.read()
377
+ assert ret
378
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
379
+ frames.append(frame)
380
+
381
+ return frames
382
+ else:
383
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idxs)
384
+ ret, frame = cap.read()
385
+ assert ret
386
+ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
387
+ return frame
388
+
389
+
390
+ def recover_mask(array, shape):
391
+ size = np.prod(shape)
392
+ mask = np.unpackbits(array)[:size].reshape(shape).astype(np.uint8)
393
+ return mask
394
+
395
+
396
+ def calculate_iou(box1, box2):
397
+ x1_min, y1_min, x1_max, y1_max = box1
398
+ x2_min, y2_min, x2_max, y2_max = box2
399
+
400
+ inter_x_min = max(x1_min, x2_min)
401
+ inter_x_max = min(x1_max, x2_max)
402
+ inter_y_min = max(y1_min, y2_min)
403
+ inter_y_max = min(y1_max, y2_max)
404
+
405
+ if inter_x_max > inter_x_min and inter_y_max > inter_y_min:
406
+ inter_area = (inter_x_max - inter_x_min) * (inter_y_max - inter_y_min)
407
+ else:
408
+ inter_area = 0
409
+
410
+ area1 = (x1_max - x1_min) * (y1_max - y1_min)
411
+ area2 = (x2_max - x2_min) * (y2_max - y2_min)
412
+
413
+ union_area = area1 + area2 - inter_area
414
+ iou = inter_area / union_area if union_area != 0 else 0
415
+ return iou
416
+
417
+ def extract_number_from_suffix(s):
418
+ match = re.search(r'_\[([\d.]+)\]$', s)
419
+ if match:
420
+ return float(match.group(1))
421
+ else:
422
+ return 0
423
+
424
+ def tensor_to_video(tensor, output_video_path, input_audio_path, fps=30, dynamic_fps=True, audio_range=None, video_length=None):
425
+ """
426
+ Converts a Tensor with shape [c, f, h, w] into a video and adds an audio track from the specified audio file.
427
+
428
+ Args:
429
+ tensor (Tensor): The Tensor to be converted, shaped [c, f, h, w].
430
+ output_video_path (str): The file path where the output video will be saved.
431
+ input_audio_path (str): The path to the audio file (WAV file) that contains the audio track to be added.
432
+ fps (int): The frame rate of the output video. Default is 30 fps.
433
+ """
434
+ if tensor.shape[1] == 1:
435
+ output_video_path += '.png'
436
+ else:
437
+ output_video_path += '.mp4'
438
+
439
+ os.makedirs(os.path.dirname(output_video_path), exist_ok=True)
440
+
441
+ tensor = tensor.permute(1, 2, 3, 0).cpu().numpy() # convert to [f, h, w, c]
442
+ tensor = np.clip(tensor * 255, 0, 255).astype(np.uint8) # to [0, 255]
443
+
444
+ def make_frame(t):
445
+ frame_index = min(int(t * fps), tensor.shape[0] - 1)
446
+ return tensor[frame_index]
447
+
448
+ if not dynamic_fps:
449
+ video_duration = tensor.shape[0] / fps
450
+
451
+ audio_clip = AudioFileClip(input_audio_path)
452
+ audio_duration = audio_clip.duration
453
+
454
+ if not dynamic_fps:
455
+ final_duration = min(video_duration, audio_duration)
456
+ audio_clip = audio_clip.subclip(0, final_duration)
457
+ else:
458
+ select_start, select_end = audio_range[0] / video_length, audio_range[1] / video_length
459
+ audio_clip = audio_clip.subclip(select_start * audio_duration, select_end * audio_duration)
460
+ final_duration = (select_end - select_start) * audio_duration
461
+ fps = tensor.shape[0] / final_duration
462
+
463
+ new_video_clip = VideoClip(make_frame, duration=final_duration)
464
+ new_video_clip = new_video_clip.set_audio(audio_clip)
465
+ print(f"video save fps is: {fps}")
466
+ new_video_clip.write_videofile(output_video_path, fps=fps, audio_codec="aac")
467
+
468
+ def resize_and_centercrop(cond_image, target_size):
469
+ """
470
+ Resize image to the target size without padding.
471
+ """
472
+
473
+ # Get the original size
474
+ orig_h, orig_w = cond_image.height, cond_image.width
475
+
476
+ target_h, target_w = target_size
477
+
478
+ # Calculate the scaling factor for resizing
479
+ scale_h = target_h / orig_h
480
+ scale_w = target_w / orig_w
481
+
482
+ # Compute the final size
483
+ scale = max(scale_h, scale_w)
484
+ final_h = math.ceil(scale * orig_h)
485
+ final_w = math.ceil(scale * orig_w)
486
+
487
+ # Resize
488
+ resized_image = cond_image.resize((final_w, final_h), resample=Image.BILINEAR)
489
+ resized_image = np.array(resized_image)
490
+
491
+ # tensor and crop
492
+ resized_tensor = torch.from_numpy(resized_image)[None, ...].permute(0, 3, 1, 2).contiguous()
493
+ cropped_tensor = transforms.functional.center_crop(resized_tensor, target_size) # 1 C H W
494
+ cropped_tensor = cropped_tensor[:, :, None, :, :] # 1 C H W --> 1 C 1 H W
495
+
496
+ return cropped_tensor
497
+
498
+
499
+ def compute_face_to_front_angle(rvec):
500
+ # 参考姿态(正对镜头)
501
+ rvec_ref = np.zeros((3, 1), dtype=np.float32)
502
+ # rvec_ref = np.array([[0], [0], [1]], dtype=np.float32)
503
+ R_ref, _ = cv2.Rodrigues(rvec_ref)
504
+ R_face, _ = cv2.Rodrigues(rvec)
505
+ R_diff = R_face @ R_ref.T
506
+ angle_rad = np.arccos(np.clip((np.trace(R_diff) - 1) / 2, -1.0, 1.0))
507
+ return 180 - angle_rad * 180 / np.pi
508
+
509
+
510
+
511
+ def rotation_vector_to_euler_angles(rvec):
512
+ R, _ = cv2.Rodrigues(rvec)
513
+ sy = np.sqrt(R[0,0] * R[0,0] + R[1,0] * R[1,0])
514
+ singular = sy < 1e-6
515
+
516
+ if not singular:
517
+ pitch = np.arctan2(R[2,1], R[2,2])
518
+ yaw = np.arctan2(-R[2,0], sy)
519
+ roll = np.arctan2(R[1,0], R[0,0])
520
+ else:
521
+ pitch = np.arctan2(-R[1,2], R[1,1])
522
+ yaw = np.arctan2(-R[2,0], sy)
523
+ roll = 0
524
+
525
+ return np.degrees(yaw), np.degrees(pitch), np.degrees(roll)
526
+
527
+
528
+ def head_pose_calculation(face_landmarks, image_size=(720, 480)):
529
+ # ========== 可选:模型中的 3D 点定义 ==========
530
+ # 依照通用五点模型(左眼、右眼、鼻尖、左嘴角、右嘴角)
531
+ model_points = np.array([
532
+ [-30.0, 35.0, 0.0], # 左眼
533
+ [30.0, 35.0, 0.0], # 右眼
534
+ [0.0, 0.0, 0.0], # 鼻尖
535
+ [-25.0, -35.0, 0.0], # 左嘴角
536
+ [25.0, -35.0, 0.0], # 右嘴角
537
+ ])
538
+
539
+ # ========== 相机内参 ==========
540
+ focal_length = image_size[0]
541
+ center = (image_size[0] / 2, image_size[1] / 2)
542
+ camera_matrix = np.array([
543
+ [focal_length, 0, center[0]],
544
+ [0, focal_length, center[1]],
545
+ [0, 0, 1]
546
+ ], dtype=np.float32)
547
+ dist_coeffs = np.zeros((4, 1)) # 假设无畸变
548
+
549
+ success, rvec, tvec = cv2.solvePnP(
550
+ model_points, face_landmarks,
551
+ camera_matrix, dist_coeffs,
552
+ flags=cv2.SOLVEPNP_ITERATIVE
553
+ )
554
+
555
+ # # # 转换为旋转矩阵
556
+ # # R1, _ = cv2.Rodrigues(rvec)
557
+ # angle_face_to_front = compute_face_to_front_angle(rvec)
558
+
559
+
560
+ # 转换为欧拉角(单位:度)
561
+ yaw, pitch, roll = rotation_vector_to_euler_angles(rvec)
562
+
563
+
564
+ return abs(yaw), abs(pitch)
565
+
566
+
567
+
568
+
569
+ def rand_name(length=8, suffix=''):
570
+ name = binascii.b2a_hex(os.urandom(length)).decode('utf-8')
571
+ if suffix:
572
+ if not suffix.startswith('.'):
573
+ suffix = '.' + suffix
574
+ name += suffix
575
+ return name
576
+
577
+
578
+
579
+ def cache_video(tensor,
580
+ save_file=None,
581
+ fps=30,
582
+ suffix='.mp4',
583
+ nrow=8,
584
+ normalize=True,
585
+ value_range=(-1, 1),
586
+ retry=5):
587
+
588
+ # cache file
589
+ cache_file = osp.join('/tmp', rand_name(
590
+ suffix=suffix)) if save_file is None else save_file
591
+
592
+ # save to cache
593
+ error = None
594
+ for _ in range(retry):
595
+
596
+ # preprocess
597
+ tensor = tensor.clamp(min(value_range), max(value_range))
598
+ tensor = torch.stack([
599
+ torchvision.utils.make_grid(
600
+ u, nrow=nrow, normalize=normalize, value_range=value_range)
601
+ for u in tensor.unbind(2)
602
+ ],
603
+ dim=1).permute(1, 2, 3, 0)
604
+ tensor = (tensor * 255).type(torch.uint8).cpu()
605
+
606
+ # write video
607
+ writer = imageio.get_writer(cache_file, fps=fps, codec='libx264', quality=10, ffmpeg_params=["-crf", "10"])
608
+ for frame in tensor.numpy():
609
+ writer.append_data(frame)
610
+ writer.close()
611
+ return cache_file
612
+
613
+ def save_silent_video(gen_video_samples, save_path, fps=25, quality=10, high_quality_save=True):
614
+ """
615
+ 保存无声音视频(支持追加���到已有视频)
616
+
617
+ 参数:
618
+ gen_video_samples: 生成的视频张量 [B,C,T,H,W]
619
+ save_path: 保存路径(不带扩展名)
620
+ fps: 视频帧率
621
+ quality: 视频质量 (0-10)
622
+ high_quality_save: 是否启用高质量模式
623
+ """
624
+ gen_video_samples = gen_video_samples[0] # 取第一个样本
625
+
626
+ # 创建保存目录
627
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
628
+
629
+ # 统一保存为MP4格式
630
+ final_save_path = f"{save_path}.mp4"
631
+
632
+ # 张量转视频帧
633
+ video_frames = (gen_video_samples + 1) / 2 # [-1,1] -> [0,1]
634
+ video_frames = video_frames.permute(1, 2, 3, 0).cpu().numpy() # T H W C
635
+ video_frames = np.clip(video_frames * 255, 0, 255).astype(np.uint8)
636
+
637
+ # 处理已有视频
638
+ all_frames = []
639
+ existing_fps = fps # 默认使用新视频的fps
640
+ if os.path.exists(final_save_path):
641
+ # 读取已有视频信息
642
+ with imageio.get_reader(final_save_path) as reader:
643
+ # 先获取元数据再读取帧
644
+ meta_data = reader.get_meta_data()
645
+ existing_fps = meta_data['fps']
646
+ existing_frames = [frame for frame in reader]
647
+
648
+ # 检查参数一致性
649
+ if existing_fps != fps:
650
+ raise ValueError(f"Existing video fps {existing_fps} conflicts with new fps {fps}")
651
+ if existing_frames[0].shape != video_frames[0].shape:
652
+ raise ValueError("Frame resolution mismatch between existing and new video")
653
+
654
+ all_frames.extend(existing_frames)
655
+
656
+ # 添加新帧
657
+ all_frames.extend(video_frames)
658
+
659
+ # 设置编码参数
660
+ if high_quality_save:
661
+ ffmpeg_params = [
662
+ '-c:v', 'libx264',
663
+ '-crf', '0', # 无损模式
664
+ '-preset', 'veryslow' # 最高压缩率
665
+ ]
666
+ else:
667
+ ffmpeg_params = [
668
+ '-c:v', 'libx264',
669
+ '-crf', '23', # 默认质量 (0-51, 越小质量越高)
670
+ '-preset', 'medium'
671
+ ]
672
+
673
+ # 使用imageio保存
674
+ with imageio.get_writer(
675
+ final_save_path,
676
+ fps=existing_fps, # 使用已有视频的fps(当存在时)
677
+ codec='libx264',
678
+ quality=quality,
679
+ ffmpeg_params=ffmpeg_params
680
+ ) as writer:
681
+ for frame in all_frames:
682
+ writer.append_data(frame)
683
+
684
+ print(f"Silent video saved to: {final_save_path}")
685
+
686
+ def save_silent_video_overwrite(gen_video_samples, save_path, fps=25, quality=5, high_quality_save=False):
687
+ """
688
+ 保存无声音视频(支持追加帧到已有视频)
689
+
690
+ 参数:
691
+ gen_video_samples: 生成的视频张量 [B,C,T,H,W]
692
+ save_path: 保存路径(不带扩展名)
693
+ fps: 视频帧率
694
+ quality: 视频质量 (0-10)
695
+ high_quality_save: 是否启用高质量模式
696
+ """
697
+ gen_video_samples = gen_video_samples[0] # 取第一个样本
698
+
699
+ # 创建保存目录
700
+ os.makedirs(os.path.dirname(save_path), exist_ok=True)
701
+
702
+ # 统一保存为MP4格式
703
+ final_save_path = f"{save_path}.mp4"
704
+
705
+ # 张量转视频帧
706
+ video_frames = (gen_video_samples + 1) / 2 # [-1,1] -> [0,1]
707
+ video_frames = video_frames.permute(1, 2, 3, 0).cpu().numpy() # T H W C
708
+ video_frames = np.clip(video_frames * 255, 0, 255).astype(np.uint8)
709
+
710
+ # 处理已有视频
711
+ all_frames = []
712
+
713
+ # 添加新帧
714
+ all_frames.extend(video_frames)
715
+
716
+ # 设置编码参数
717
+ if high_quality_save:
718
+ ffmpeg_params = [
719
+ '-c:v', 'libx264',
720
+ '-crf', '0', # 无损模式
721
+ '-preset', 'veryslow' # 最高压缩率
722
+ ]
723
+ else:
724
+ ffmpeg_params = [
725
+ '-c:v', 'libx264',
726
+ '-crf', '23', # 默认质量 (0-51, 越小质量越高)
727
+ '-preset', 'medium'
728
+ ]
729
+
730
+ # 使用imageio保存
731
+ with imageio.get_writer(
732
+ final_save_path,
733
+ fps=fps, # 使用已有视频的fps(当存在时)
734
+ codec='libx264',
735
+ quality=quality,
736
+ ffmpeg_params=ffmpeg_params
737
+ ) as writer:
738
+ for frame in all_frames:
739
+ writer.append_data(frame)
740
+
741
+ print(f"Silent video saved to: {final_save_path}")
742
+
743
+ def save_video_ffmpeg(gen_video_samples, save_path, vocal_audio_list, fps=25, quality=5, high_quality_save=False):
744
+
745
+ gen_video_samples = gen_video_samples[0]
746
+
747
+ def save_video(frames, save_path, fps, quality=9, ffmpeg_params=None):
748
+ writer = imageio.get_writer(
749
+ save_path, fps=fps, quality=quality, ffmpeg_params=ffmpeg_params
750
+ )
751
+ for frame in tqdm(frames, desc="Saving video"):
752
+ frame = np.array(frame)
753
+ writer.append_data(frame)
754
+ writer.close()
755
+ save_path_tmp = save_path + "-temp.mp4"
756
+
757
+ os.makedirs(os.path.dirname(save_path_tmp), exist_ok=True)
758
+
759
+
760
+ if high_quality_save:
761
+ # Experiment version
762
+ # NOTE: to be verified effects
763
+ cache_video(
764
+ tensor=gen_video_samples.unsqueeze(0),
765
+ save_file=save_path_tmp,
766
+ fps=fps,
767
+ nrow=1,
768
+ normalize=True,
769
+ value_range=(-1, 1)
770
+ )
771
+ else:
772
+ video_audio = (gen_video_samples+1)/2 # C T H W
773
+ video_audio = video_audio.permute(1, 2, 3, 0).cpu().numpy()
774
+ video_audio = np.clip(video_audio * 255, 0, 255).astype(np.uint8) # to [0, 255]
775
+ save_video(video_audio, save_path_tmp, fps=fps, quality=quality)
776
+
777
+
778
+ # crop audio according to video length
779
+ _, T, _, _ = gen_video_samples.shape
780
+ duration = T / fps
781
+ save_path_crop_audio = save_path + "-cropaudio.wav"
782
+ final_command = [
783
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/gaofeng49/conda/memo/bin/ffmpeg",
784
+ "-i",
785
+ vocal_audio_list[0],
786
+ "-t",
787
+ f'{duration}',
788
+ save_path_crop_audio,
789
+ ]
790
+ subprocess.run(final_command, check=True)
791
+
792
+ # generate video with audio
793
+ save_path = save_path + ".mp4"
794
+ if high_quality_save:
795
+ final_command = [
796
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/gaofeng49/conda/memo/bin/ffmpeg",
797
+ "-y",
798
+ "-i", save_path_tmp,
799
+ "-i", save_path_crop_audio,
800
+ "-c:v", "libx264",
801
+ "-crf", "0",
802
+ "-preset", "veryslow", # 可选,压缩率更高但更慢
803
+ "-c:a", "aac", # mp4下只能用aac或copy
804
+ "-shortest",
805
+ save_path,
806
+ ]
807
+ subprocess.run(final_command, check=True)
808
+ os.remove(save_path_tmp)
809
+ os.remove(save_path_crop_audio)
810
+ else:
811
+ final_command = [
812
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/gaofeng49/conda/memo/bin/ffmpeg",
813
+ "-y",
814
+ "-i",
815
+ save_path_tmp,
816
+ "-i",
817
+ save_path_crop_audio,
818
+ "-c:v",
819
+ "libx264",
820
+ "-c:a",
821
+ "aac",
822
+ "-shortest",
823
+ save_path,
824
+ ]
825
+ subprocess.run(final_command, check=True)
826
+ os.remove(save_path_tmp)
827
+ os.remove(save_path_crop_audio)
828
+
829
+ def audio_move_from_hdfs(src_path):
830
+ map_dict = {
831
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/data_digitalhuman/talkingbody/yt_runway_sub/singlehuman_lipsync/yt_runway_0808_35w_merge/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_chinese":
832
+ "/mnt/hdfs/user/hadoop-vision-data/llm/dataset/videogen_dataset/data/digital_human_video/talkingbody/runway_chinese/singlehuman_lipsync/yt_runway_0808_35w_merge/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_chinese",
833
+
834
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/data_digitalhuman/talkingbody/yt_runway_sub/singlehuman_lipsync/yt_runway_0829_52w_merge/tar_record_caption_qwen2vlm_pose_audioemb_part2_lipsync_camera_face_chinese":
835
+ "/mnt/hdfs/user/hadoop-vision-data/llm/dataset/videogen_dataset/data/digital_human_video/talkingbody/runway_chinese/singlehuman_lipsync/yt_runway_0829_52w_merge/tar_record_caption_qwen2vlm_pose_audioemb_part2_lipsync_camera_face_chinese",
836
+
837
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/data_digitalhuman/talkingbody/yt_runway_sub/singlehuman_lipsync/yt_runway_0912_28w_merge/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_chinese":
838
+ "/mnt/hdfs/user/hadoop-vision-data/llm/dataset/videogen_dataset/data/digital_human_video/talkingbody/runway_chinese/singlehuman_lipsync/yt_runway_0912_28w_merge/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_chinese",
839
+
840
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/data_digitalhuman/talkingbody/yt_runway_sub/singlehuman_lipsync/yt_runway_0926_105w_merge/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_chinese":
841
+ "/mnt/hdfs/user/hadoop-vision-data/llm/dataset/videogen_dataset/data/digital_human_video/talkingbody/runway_chinese/singlehuman_lipsync/yt_runway_0926_105w_merge/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_chinese",
842
+
843
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/data_digitalhuman/talkingbody/yt_runway_sub/singlehuman_lipsync/yt_runway_1129_65w_part1/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_facecropcaption_chinese":
844
+ "/mnt/hdfs/user/hadoop-vision-data/llm/dataset/videogen_dataset/data/digital_human_video/talkingbody/runway_chinese/singlehuman_lipsync/yt_runway_1129_65w_part1/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_facecropcaption_chinese",
845
+
846
+ "/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/data_digitalhuman/talkingbody/yt_runway_sub/singlehuman_lipsync/yt_runway_1129_65w_part2/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_facecropcaption_chinese":
847
+ "/mnt/hdfs/user/hadoop-vision-data/llm/dataset/videogen_dataset/data/digital_human_video/talkingbody/runway_chinese/singlehuman_lipsync/yt_runway_1129_65w_part2/tar_record_caption_qwen2vlm_pose_audioemb_lipsync_camera_face_facecropcaption_chinese"
848
+ }
849
+
850
+ for src_p in map_dict:
851
+ if src_p in src_path:
852
+ src_path = src_path.replace(src_p, map_dict[src_p])
853
+
854
+ return src_path
infworld/utils/dataset_utils.py ADDED
@@ -0,0 +1,665 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ import requests
7
+ import torch
8
+ import torchvision
9
+ import torchvision.transforms as transforms
10
+ from PIL import Image
11
+ from torchvision.datasets.folder import IMG_EXTENSIONS, pil_loader
12
+ from torchvision.io import write_video
13
+ from torchvision.utils import save_image
14
+
15
+
16
+ VID_EXTENSIONS = (".mp4", ".avi", ".mov", ".mkv")
17
+
18
+ regex = re.compile(
19
+ r"^(?:http|ftp)s?://" # http:// or https://
20
+ r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" # domain...
21
+ r"localhost|" # localhost...
22
+ r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # ...or ip
23
+ r"(?::\d+)?" # optional port
24
+ r"(?:/?|[/?]\S+)$",
25
+ re.IGNORECASE,
26
+ )
27
+
28
+ import numbers
29
+ import random
30
+
31
+ import numpy as np
32
+ import torch
33
+
34
+
35
+ def _is_tensor_video_clip(clip):
36
+ if not torch.is_tensor(clip):
37
+ raise TypeError("clip should be Tensor. Got %s" % type(clip))
38
+
39
+ if not clip.ndimension() == 4:
40
+ raise ValueError("clip should be 4D. Got %dD" % clip.dim())
41
+
42
+ return True
43
+
44
+
45
+ def crop(clip, i, j, h, w):
46
+ """
47
+ Args:
48
+ clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
49
+ """
50
+ if len(clip.size()) != 4:
51
+ raise ValueError("clip should be a 4D tensor")
52
+ return clip[..., i : i + h, j : j + w]
53
+
54
+
55
+ def resize(clip, target_size, interpolation_mode):
56
+ if len(target_size) != 2:
57
+ raise ValueError(f"target size should be tuple (height, width), instead got {target_size}")
58
+ return torch.nn.functional.interpolate(clip, size=target_size, mode=interpolation_mode, align_corners=False)
59
+
60
+
61
+ def resize_scale(clip, target_size, interpolation_mode):
62
+ if len(target_size) != 2:
63
+ raise ValueError(f"target size should be tuple (height, width), instead got {target_size}")
64
+ H, W = clip.size(-2), clip.size(-1)
65
+ scale_ = target_size[0] / min(H, W)
66
+ return torch.nn.functional.interpolate(clip, scale_factor=scale_, mode=interpolation_mode, align_corners=False)
67
+
68
+
69
+ def resized_crop(clip, i, j, h, w, size, interpolation_mode="bilinear"):
70
+ """
71
+ Do spatial cropping and resizing to the video clip
72
+ Args:
73
+ clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
74
+ i (int): i in (i,j) i.e coordinates of the upper left corner.
75
+ j (int): j in (i,j) i.e coordinates of the upper left corner.
76
+ h (int): Height of the cropped region.
77
+ w (int): Width of the cropped region.
78
+ size (tuple(int, int)): height and width of resized clip
79
+ Returns:
80
+ clip (torch.tensor): Resized and cropped clip. Size is (T, C, H, W)
81
+ """
82
+ if not _is_tensor_video_clip(clip):
83
+ raise ValueError("clip should be a 4D torch.tensor")
84
+ clip = crop(clip, i, j, h, w)
85
+ clip = resize(clip, size, interpolation_mode)
86
+ return clip
87
+
88
+
89
+ def center_crop(clip, crop_size):
90
+ if not _is_tensor_video_clip(clip):
91
+ raise ValueError("clip should be a 4D torch.tensor")
92
+ h, w = clip.size(-2), clip.size(-1)
93
+ th, tw = crop_size
94
+ if h < th or w < tw:
95
+ raise ValueError("height and width must be no smaller than crop_size")
96
+
97
+ i = int(round((h - th) / 2.0))
98
+ j = int(round((w - tw) / 2.0))
99
+ return crop(clip, i, j, th, tw)
100
+
101
+
102
+ def center_crop_using_short_edge(clip):
103
+ if not _is_tensor_video_clip(clip):
104
+ raise ValueError("clip should be a 4D torch.tensor")
105
+ h, w = clip.size(-2), clip.size(-1)
106
+ if h < w:
107
+ th, tw = h, h
108
+ i = 0
109
+ j = int(round((w - tw) / 2.0))
110
+ else:
111
+ th, tw = w, w
112
+ i = int(round((h - th) / 2.0))
113
+ j = 0
114
+ return crop(clip, i, j, th, tw)
115
+
116
+
117
+ def resize_crop_to_fill(clip, target_size):
118
+ if not _is_tensor_video_clip(clip):
119
+ raise ValueError("clip should be a 4D torch.tensor")
120
+ h, w = clip.size(-2), clip.size(-1)
121
+ th, tw = target_size[0], target_size[1]
122
+ rh, rw = th / h, tw / w
123
+ if rh > rw:
124
+ sh, sw = th, round(w * rh)
125
+ clip = resize(clip, (sh, sw), "bilinear")
126
+ i = 0
127
+ j = int(round(sw - tw) / 2.0)
128
+ else:
129
+ sh, sw = round(h * rw), tw
130
+ clip = resize(clip, (sh, sw), "bilinear")
131
+ i = int(round(sh - th) / 2.0)
132
+ j = 0
133
+ assert i + th <= clip.size(-2) and j + tw <= clip.size(-1)
134
+ return crop(clip, i, j, th, tw)
135
+
136
+
137
+ def random_shift_crop(clip):
138
+ """
139
+ Slide along the long edge, with the short edge as crop size
140
+ """
141
+ if not _is_tensor_video_clip(clip):
142
+ raise ValueError("clip should be a 4D torch.tensor")
143
+ h, w = clip.size(-2), clip.size(-1)
144
+
145
+ if h <= w:
146
+ short_edge = h
147
+ else:
148
+ short_edge = w
149
+
150
+ th, tw = short_edge, short_edge
151
+
152
+ i = torch.randint(0, h - th + 1, size=(1,)).item()
153
+ j = torch.randint(0, w - tw + 1, size=(1,)).item()
154
+ return crop(clip, i, j, th, tw)
155
+
156
+
157
+ def to_tensor(clip):
158
+ """
159
+ Convert tensor data type from uint8 to float, divide value by 255.0 and
160
+ permute the dimensions of clip tensor
161
+ Args:
162
+ clip (torch.tensor, dtype=torch.uint8): Size is (T, C, H, W)
163
+ Return:
164
+ clip (torch.tensor, dtype=torch.float): Size is (T, C, H, W)
165
+ """
166
+ _is_tensor_video_clip(clip)
167
+ if not clip.dtype == torch.uint8:
168
+ raise TypeError("clip tensor should have data type uint8. Got %s" % str(clip.dtype))
169
+ # return clip.float().permute(3, 0, 1, 2) / 255.0
170
+ return clip.float() / 255.0
171
+
172
+
173
+ def normalize(clip, mean, std, inplace=False):
174
+ """
175
+ Args:
176
+ clip (torch.tensor): Video clip to be normalized. Size is (T, C, H, W)
177
+ mean (tuple): pixel RGB mean. Size is (3)
178
+ std (tuple): pixel standard deviation. Size is (3)
179
+ Returns:
180
+ normalized clip (torch.tensor): Size is (T, C, H, W)
181
+ """
182
+ if not _is_tensor_video_clip(clip):
183
+ raise ValueError("clip should be a 4D torch.tensor")
184
+ if not inplace:
185
+ clip = clip.clone()
186
+ mean = torch.as_tensor(mean, dtype=clip.dtype, device=clip.device)
187
+ # print(mean)
188
+ std = torch.as_tensor(std, dtype=clip.dtype, device=clip.device)
189
+ clip.sub_(mean[:, None, None, None]).div_(std[:, None, None, None])
190
+ return clip
191
+
192
+
193
+ def hflip(clip):
194
+ """
195
+ Args:
196
+ clip (torch.tensor): Video clip to be normalized. Size is (T, C, H, W)
197
+ Returns:
198
+ flipped clip (torch.tensor): Size is (T, C, H, W)
199
+ """
200
+ if not _is_tensor_video_clip(clip):
201
+ raise ValueError("clip should be a 4D torch.tensor")
202
+ return clip.flip(-1)
203
+
204
+
205
+ class ResizeCrop:
206
+ def __init__(self, size):
207
+ if isinstance(size, numbers.Number):
208
+ self.size = (int(size), int(size))
209
+ else:
210
+ self.size = size
211
+
212
+ def __call__(self, clip):
213
+ clip = resize_crop_to_fill(clip, self.size)
214
+ return clip
215
+
216
+ def __repr__(self) -> str:
217
+ return f"{self.__class__.__name__}(size={self.size})"
218
+
219
+
220
+ class RandomCropVideo:
221
+ def __init__(self, size):
222
+ if isinstance(size, numbers.Number):
223
+ self.size = (int(size), int(size))
224
+ else:
225
+ self.size = size
226
+
227
+ def __call__(self, clip):
228
+ """
229
+ Args:
230
+ clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
231
+ Returns:
232
+ torch.tensor: randomly cropped video clip.
233
+ size is (T, C, OH, OW)
234
+ """
235
+ i, j, h, w = self.get_params(clip)
236
+ return crop(clip, i, j, h, w)
237
+
238
+ def get_params(self, clip):
239
+ h, w = clip.shape[-2:]
240
+ th, tw = self.size
241
+
242
+ if h < th or w < tw:
243
+ raise ValueError(f"Required crop size {(th, tw)} is larger than input image size {(h, w)}")
244
+
245
+ if w == tw and h == th:
246
+ return 0, 0, h, w
247
+
248
+ i = torch.randint(0, h - th + 1, size=(1,)).item()
249
+ j = torch.randint(0, w - tw + 1, size=(1,)).item()
250
+
251
+ return i, j, th, tw
252
+
253
+ def __repr__(self) -> str:
254
+ return f"{self.__class__.__name__}(size={self.size})"
255
+
256
+
257
+ class CenterCropResizeVideo:
258
+ """
259
+ First use the short side for cropping length,
260
+ center crop video, then resize to the specified size
261
+ """
262
+
263
+ def __init__(
264
+ self,
265
+ size,
266
+ interpolation_mode="bilinear",
267
+ ):
268
+ if isinstance(size, tuple):
269
+ if len(size) != 2:
270
+ raise ValueError(f"size should be tuple (height, width), instead got {size}")
271
+ self.size = size
272
+ else:
273
+ self.size = (size, size)
274
+
275
+ self.interpolation_mode = interpolation_mode
276
+
277
+ def __call__(self, clip):
278
+ """
279
+ Args:
280
+ clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
281
+ Returns:
282
+ torch.tensor: scale resized / center cropped video clip.
283
+ size is (T, C, crop_size, crop_size)
284
+ """
285
+ clip_center_crop = center_crop_using_short_edge(clip)
286
+ clip_center_crop_resize = resize(
287
+ clip_center_crop, target_size=self.size, interpolation_mode=self.interpolation_mode
288
+ )
289
+ return clip_center_crop_resize
290
+
291
+ def __repr__(self) -> str:
292
+ return f"{self.__class__.__name__}(size={self.size}, interpolation_mode={self.interpolation_mode}"
293
+
294
+
295
+ class UCFCenterCropVideo:
296
+ """
297
+ First scale to the specified size in equal proportion to the short edge,
298
+ then center cropping
299
+ """
300
+
301
+ def __init__(
302
+ self,
303
+ size,
304
+ interpolation_mode="bilinear",
305
+ ):
306
+ if isinstance(size, tuple):
307
+ if len(size) != 2:
308
+ raise ValueError(f"size should be tuple (height, width), instead got {size}")
309
+ self.size = size
310
+ else:
311
+ self.size = (size, size)
312
+
313
+ self.interpolation_mode = interpolation_mode
314
+
315
+ def __call__(self, clip):
316
+ """
317
+ Args:
318
+ clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
319
+ Returns:
320
+ torch.tensor: scale resized / center cropped video clip.
321
+ size is (T, C, crop_size, crop_size)
322
+ """
323
+ clip_resize = resize_scale(clip=clip, target_size=self.size, interpolation_mode=self.interpolation_mode)
324
+ clip_center_crop = center_crop(clip_resize, self.size)
325
+ return clip_center_crop
326
+
327
+ def __repr__(self) -> str:
328
+ return f"{self.__class__.__name__}(size={self.size}, interpolation_mode={self.interpolation_mode}"
329
+
330
+
331
+ class KineticsRandomCropResizeVideo:
332
+ """
333
+ Slide along the long edge, with the short edge as crop size. And resie to the desired size.
334
+ """
335
+
336
+ def __init__(
337
+ self,
338
+ size,
339
+ interpolation_mode="bilinear",
340
+ ):
341
+ if isinstance(size, tuple):
342
+ if len(size) != 2:
343
+ raise ValueError(f"size should be tuple (height, width), instead got {size}")
344
+ self.size = size
345
+ else:
346
+ self.size = (size, size)
347
+
348
+ self.interpolation_mode = interpolation_mode
349
+
350
+ def __call__(self, clip):
351
+ clip_random_crop = random_shift_crop(clip)
352
+ clip_resize = resize(clip_random_crop, self.size, self.interpolation_mode)
353
+ return clip_resize
354
+
355
+
356
+ class CenterCropVideo:
357
+ def __init__(
358
+ self,
359
+ size,
360
+ interpolation_mode="bilinear",
361
+ ):
362
+ if isinstance(size, tuple):
363
+ if len(size) != 2:
364
+ raise ValueError(f"size should be tuple (height, width), instead got {size}")
365
+ self.size = size
366
+ else:
367
+ self.size = (size, size)
368
+
369
+ self.interpolation_mode = interpolation_mode
370
+
371
+ def __call__(self, clip):
372
+ """
373
+ Args:
374
+ clip (torch.tensor): Video clip to be cropped. Size is (T, C, H, W)
375
+ Returns:
376
+ torch.tensor: center cropped video clip.
377
+ size is (T, C, crop_size, crop_size)
378
+ """
379
+ clip_center_crop = center_crop(clip, self.size)
380
+ return clip_center_crop
381
+
382
+ def __repr__(self) -> str:
383
+ return f"{self.__class__.__name__}(size={self.size}, interpolation_mode={self.interpolation_mode}"
384
+
385
+
386
+ class NormalizeVideo:
387
+ """
388
+ Normalize the video clip by mean subtraction and division by standard deviation
389
+ Args:
390
+ mean (3-tuple): pixel RGB mean
391
+ std (3-tuple): pixel RGB standard deviation
392
+ inplace (boolean): whether do in-place normalization
393
+ """
394
+
395
+ def __init__(self, mean, std, inplace=False):
396
+ self.mean = mean
397
+ self.std = std
398
+ self.inplace = inplace
399
+
400
+ def __call__(self, clip):
401
+ """
402
+ Args:
403
+ clip (torch.tensor): video clip must be normalized. Size is (C, T, H, W)
404
+ """
405
+ return normalize(clip, self.mean, self.std, self.inplace)
406
+
407
+ def __repr__(self) -> str:
408
+ return f"{self.__class__.__name__}(mean={self.mean}, std={self.std}, inplace={self.inplace})"
409
+
410
+
411
+ class ToTensorVideo:
412
+ """
413
+ Convert tensor data type from uint8 to float, divide value by 255.0 and
414
+ permute the dimensions of clip tensor
415
+ """
416
+
417
+ def __init__(self):
418
+ pass
419
+
420
+ def __call__(self, clip):
421
+ """
422
+ Args:
423
+ clip (torch.tensor, dtype=torch.uint8): Size is (T, C, H, W)
424
+ Return:
425
+ clip (torch.tensor, dtype=torch.float): Size is (T, C, H, W)
426
+ """
427
+ return to_tensor(clip)
428
+
429
+ def __repr__(self) -> str:
430
+ return self.__class__.__name__
431
+
432
+
433
+ class RandomHorizontalFlipVideo:
434
+ """
435
+ Flip the video clip along the horizontal direction with a given probability
436
+ Args:
437
+ p (float): probability of the clip being flipped. Default value is 0.5
438
+ """
439
+
440
+ def __init__(self, p=0.5):
441
+ self.p = p
442
+
443
+ def __call__(self, clip):
444
+ """
445
+ Args:
446
+ clip (torch.tensor): Size is (T, C, H, W)
447
+ Return:
448
+ clip (torch.tensor): Size is (T, C, H, W)
449
+ """
450
+ if random.random() < self.p:
451
+ clip = hflip(clip)
452
+ return clip
453
+
454
+ def __repr__(self) -> str:
455
+ return f"{self.__class__.__name__}(p={self.p})"
456
+
457
+
458
+ # ------------------------------------------------------------
459
+ # --------------------- Sampling ---------------------------
460
+ # ------------------------------------------------------------
461
+ class TemporalRandomCrop(object):
462
+ """Temporally crop the given frame indices at a random location.
463
+
464
+ Args:
465
+ size (int): Desired length of frames will be seen in the model.
466
+ """
467
+
468
+ def __init__(self, size):
469
+ self.size = size
470
+
471
+ def __call__(self, total_frames):
472
+ rand_end = max(0, total_frames - self.size - 1)
473
+ begin_index = random.randint(0, rand_end)
474
+ end_index = min(begin_index + self.size, total_frames)
475
+ return begin_index, end_index
476
+
477
+
478
+ def is_img(path):
479
+ ext = os.path.splitext(path)[-1].lower()
480
+ return ext in IMG_EXTENSIONS
481
+
482
+
483
+ def is_vid(path):
484
+ ext = os.path.splitext(path)[-1].lower()
485
+ return ext in VID_EXTENSIONS
486
+
487
+
488
+ def is_url(url):
489
+ return re.match(regex, url) is not None
490
+
491
+
492
+ def read_file(input_path):
493
+ if input_path.endswith(".csv"):
494
+ return pd.read_csv(input_path)
495
+ elif input_path.endswith(".parquet"):
496
+ return pd.read_parquet(input_path)
497
+ else:
498
+ raise NotImplementedError(f"Unsupported file format: {input_path}")
499
+
500
+
501
+ def download_url(input_path):
502
+ output_dir = "cache"
503
+ os.makedirs(output_dir, exist_ok=True)
504
+ base_name = os.path.basename(input_path)
505
+ output_path = os.path.join(output_dir, base_name)
506
+ img_data = requests.get(input_path).content
507
+ with open(output_path, "wb") as handler:
508
+ handler.write(img_data)
509
+ print(f"URL {input_path} downloaded to {output_path}")
510
+ return output_path
511
+
512
+
513
+ def temporal_random_crop(vframes, num_frames, frame_interval):
514
+ temporal_sample = TemporalRandomCrop(num_frames * frame_interval)
515
+ total_frames = len(vframes)
516
+ start_frame_ind, end_frame_ind = temporal_sample(total_frames)
517
+ assert (
518
+ end_frame_ind - start_frame_ind >= num_frames
519
+ ), f"Not enough frames to sample, {end_frame_ind} - {start_frame_ind} < {num_frames}"
520
+ frame_indice = np.linspace(start_frame_ind, end_frame_ind - 1, num_frames, dtype=int)
521
+ video = vframes[frame_indice]
522
+ return video
523
+
524
+
525
+ def get_transforms_video(name="center", image_size=(256, 256)):
526
+ if name is None:
527
+ return None
528
+ elif name == "center":
529
+ assert image_size[0] == image_size[1], "image_size must be square for center crop"
530
+ transform_video = transforms.Compose(
531
+ [
532
+ ToTensorVideo(), # TCHW
533
+ # video_transforms.RandomHorizontalFlipVideo(),
534
+ UCFCenterCropVideo(image_size[0]),
535
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
536
+ ]
537
+ )
538
+ elif name == "resize_crop":
539
+ transform_video = transforms.Compose(
540
+ [
541
+ ToTensorVideo(), # TCHW
542
+ ResizeCrop(image_size),
543
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
544
+ ]
545
+ )
546
+ else:
547
+ raise NotImplementedError(f"Transform {name} not implemented")
548
+ return transform_video
549
+
550
+
551
+ def get_transforms_image(name="center", image_size=(256, 256)):
552
+ if name is None:
553
+ return None
554
+ elif name == "center":
555
+ assert image_size[0] == image_size[1], "Image size must be square for center crop"
556
+ transform = transforms.Compose(
557
+ [
558
+ transforms.Lambda(lambda pil_image: center_crop_arr(pil_image, image_size[0])),
559
+ # transforms.RandomHorizontalFlip(),
560
+ transforms.ToTensor(),
561
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
562
+ ]
563
+ )
564
+ elif name == "resize_crop":
565
+ transform = transforms.Compose(
566
+ [
567
+ transforms.Lambda(lambda pil_image: resize_crop_to_fill(pil_image, image_size)),
568
+ transforms.ToTensor(),
569
+ transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5], inplace=True),
570
+ ]
571
+ )
572
+ else:
573
+ raise NotImplementedError(f"Transform {name} not implemented")
574
+ return transform
575
+
576
+
577
+ def read_image_from_path(path, transform=None, transform_name="center", num_frames=1, image_size=(256, 256)):
578
+ image = pil_loader(path)
579
+ if transform is None:
580
+ transform = get_transforms_image(image_size=image_size, name=transform_name)
581
+ image = transform(image)
582
+ video = image.unsqueeze(0).repeat(num_frames, 1, 1, 1)
583
+ video = video.permute(1, 0, 2, 3)
584
+ return video
585
+
586
+
587
+ def read_video_from_path(path, transform=None, transform_name="center", image_size=(256, 256)):
588
+ vframes, aframes, info = torchvision.io.read_video(filename=path, pts_unit="sec", output_format="TCHW")
589
+ if transform is None:
590
+ transform = get_transforms_video(image_size=image_size, name=transform_name)
591
+ video = transform(vframes) # T C H W
592
+ video = video.permute(1, 0, 2, 3)
593
+ return video
594
+
595
+
596
+ def read_from_path(path, image_size, transform_name="center"):
597
+ if is_url(path):
598
+ path = download_url(path)
599
+ ext = os.path.splitext(path)[-1].lower()
600
+ if ext.lower() in VID_EXTENSIONS:
601
+ return read_video_from_path(path, image_size=image_size, transform_name=transform_name)
602
+ else:
603
+ assert ext.lower() in IMG_EXTENSIONS, f"Unsupported file format: {ext}"
604
+ return read_image_from_path(path, image_size=image_size, transform_name=transform_name)
605
+
606
+
607
+ def save_sample(x, save_path=None, fps=8, normalize=True, value_range=(-1, 1), force_video=False, verbose=True):
608
+ """
609
+ Args:
610
+ x (Tensor): shape [C, T, H, W]
611
+ """
612
+ assert x.ndim == 4
613
+
614
+ if not force_video and x.shape[1] == 1: # T = 1: save as image
615
+ save_path += ".png"
616
+ x = x.squeeze(1)
617
+ save_image([x], save_path, normalize=normalize, value_range=value_range)
618
+ else:
619
+ save_path += ".mp4"
620
+ if normalize:
621
+ low, high = value_range
622
+ x.clamp_(min=low, max=high)
623
+ x.sub_(low).div_(max(high - low, 1e-5))
624
+
625
+ x = x.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 3, 0).to("cpu", torch.uint8)
626
+ write_video(save_path, x, fps=fps, video_codec="h264")
627
+ if verbose:
628
+ print(f"Saved to {save_path}")
629
+ return save_path
630
+
631
+
632
+ def center_crop_arr(pil_image, image_size):
633
+ """
634
+ Center cropping implementation from ADM.
635
+ https://github.com/openai/guided-diffusion/blob/8fb3ad9197f16bbc40620447b2742e13458d2831/guided_diffusion/image_datasets.py#L126
636
+ """
637
+ while min(*pil_image.size) >= 2 * image_size:
638
+ pil_image = pil_image.resize(tuple(x // 2 for x in pil_image.size), resample=Image.BOX)
639
+
640
+ scale = image_size / min(*pil_image.size)
641
+ pil_image = pil_image.resize(tuple(round(x * scale) for x in pil_image.size), resample=Image.BICUBIC)
642
+
643
+ arr = np.array(pil_image)
644
+ crop_y = (arr.shape[0] - image_size) // 2
645
+ crop_x = (arr.shape[1] - image_size) // 2
646
+ return Image.fromarray(arr[crop_y : crop_y + image_size, crop_x : crop_x + image_size])
647
+
648
+
649
+ def resize_crop_to_fill(pil_image, image_size):
650
+ w, h = pil_image.size # PIL is (W, H)
651
+ th, tw = image_size
652
+ rh, rw = th / h, tw / w
653
+ if rh > rw:
654
+ sh, sw = th, round(w * rh)
655
+ image = pil_image.resize((sw, sh), Image.BICUBIC)
656
+ i = 0
657
+ j = int(round((sw - tw) / 2.0))
658
+ else:
659
+ sh, sw = round(h * rw), tw
660
+ image = pil_image.resize((sw, sh), Image.BICUBIC)
661
+ i = int(round((sh - th) / 2.0))
662
+ j = 0
663
+ arr = np.array(image)
664
+ assert i + th <= arr.shape[0] and j + tw <= arr.shape[1]
665
+ return Image.fromarray(arr[i : i + th, j : j + tw])
infworld/utils/prepare_dataloader.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ import importlib
4
+
5
+ from omegaconf import OmegaConf
6
+ from tqdm.auto import tqdm
7
+
8
+ import torch
9
+
10
+ sys.path.append(os.path.join(os.path.dirname(__file__),'../..'))
11
+
12
+
13
+
14
+ def get_obj_from_str(string, reload=False, invalidate_cache=True):
15
+ module, cls = string.rsplit(".", 1)
16
+ if invalidate_cache:
17
+ importlib.invalidate_caches()
18
+ if reload:
19
+ module_imp = importlib.import_module(module)
20
+ importlib.reload(module_imp)
21
+ return getattr(importlib.import_module(module, package=None), cls)
22
+
23
+
24
+ def prepare_dataloader_for_rank(config, global_rank, num_processes=-1, repeat_cp_size=1):
25
+ """ Get the dataloader given config and the current global rank.
26
+ "dataset_setting" provides the list of dataset configs
27
+ "rank_index_map" provides how to distribute the config across ranks
28
+ """
29
+ # repeat each elements in CP; [a b c] --> [a a ... b b ... c c ...]
30
+ if repeat_cp_size > 1:
31
+ print(f'before repeat config.rank_index_map: {config.rank_index_map}')
32
+ repeated_rank_index_map = [element for element in config.rank_index_map for _ in range(repeat_cp_size)]
33
+ config.rank_index_map = repeated_rank_index_map
34
+ print(f'after repeat repeated_rank_index_map: {config.rank_index_map}')
35
+
36
+ # get the dataset index
37
+ num_total_indices = len(config.rank_index_map)
38
+ dataset_index = config.rank_index_map[global_rank % num_total_indices]
39
+
40
+ # get the correct partition
41
+ num_partitions = 1
42
+ partition_id = 0
43
+ if num_processes > 0:
44
+ rank_to_dataset_index_map = list(config.rank_index_map) * num_processes
45
+ rank_to_dataset_index_map = rank_to_dataset_index_map[:num_processes]
46
+ num_partitions = rank_to_dataset_index_map.count(dataset_index)
47
+ partition_id = rank_to_dataset_index_map[:global_rank].count(dataset_index)
48
+ print(f'rank_to_dataset_index_map: {rank_to_dataset_index_map}')
49
+ print(f'dataset_index: {dataset_index} partition_id: {partition_id} num_partitions: {num_partitions} ')
50
+
51
+ # get the loss weight scale factor to normalize loss weight to 1.0
52
+ sum_loss_weight = 0.0
53
+ for i in range(num_total_indices):
54
+ dataset_setting = config.dataset_setting[config.rank_index_map[i]]
55
+ sum_loss_weight += dataset_setting.get("loss_weight", 1.0)
56
+ loss_weight_scale = float(num_total_indices) / sum_loss_weight
57
+
58
+ # fetch the config
59
+ dataset_setting = config.dataset_setting[dataset_index]
60
+ loss_weight = dataset_setting.get("loss_weight", 1.0) * loss_weight_scale
61
+ print(f'global_rank: {global_rank} -- dataset_index: {dataset_index} - loss_weight_scale: {loss_weight_scale} - loss weight: {loss_weight} - dataset_setting: {dataset_setting}')
62
+
63
+ # set prompt function
64
+ utils_prompt_module = importlib.import_module(dataset_setting.get_prompt_module)
65
+ get_prompt_func = getattr(utils_prompt_module, dataset_setting.get_prompt_func)
66
+ get_prompt_frame_spans_func = None
67
+ if hasattr(dataset_setting, "get_prompt_frame_spans_func"):
68
+ get_prompt_frame_spans_func = getattr(utils_prompt_module, dataset_setting.get_prompt_frame_spans_func)
69
+
70
+ # get dataset from setting
71
+ dataset_kwargs = dataset_setting.get("dataset_kwargs", dict())
72
+
73
+ # get bucket configs
74
+ assert hasattr(dataset_kwargs, "bucket_configs")
75
+ bucket_configs = dataset_kwargs.get("bucket_configs", dict())
76
+
77
+ dataset = get_obj_from_str(dataset_setting.dataset_target)(
78
+ get_prompt_func=get_prompt_func,
79
+ get_prompt_frame_spans_func=get_prompt_frame_spans_func,
80
+ partition_id=partition_id,
81
+ num_partitions=num_partitions,
82
+ **dataset_kwargs
83
+ )
84
+
85
+ # get dataloader from setting
86
+ dataloader_kwargs = dataset_setting.get("dataloader_kwargs", dict())
87
+ dataloader = torch.utils.data.DataLoader(
88
+ dataset,
89
+ **dataloader_kwargs,
90
+ shuffle=False,
91
+ pin_memory=True,
92
+ drop_last=True,
93
+ collate_fn = dataset.collate_fn if hasattr(dataset,"collate_fn") else None,
94
+ )
95
+
96
+ return dataloader, loss_weight, bucket_configs
97
+
98
+
99
+
100
+ if __name__ == '__main__':
101
+ # example_config_path = 'source/dataset/example_config.yaml'
102
+ example_config_path = "configs/train_t2v_opensora_v2_ms_long32_hq400.yaml"
103
+ config = OmegaConf.load(example_config_path)
104
+
105
+ dataloader = prepare_dataloader_for_rank(config.video_training_data_config, global_rank=7, num_processes=28)
106
+
107
+ num_train_steps = 1000
108
+ progress_bar = tqdm(range(0, num_train_steps))
109
+
110
+ # output_dir = "assets/webvid-trimming_aes-tfreader"
111
+ # os.makedirs(output_dir, exist_ok=True)
112
+
113
+ # for step, batch in enumerate(tfreader):
114
+ for step, batch in enumerate(dataloader):
115
+ progress_bar.update(1)
116
+
117
+ # # save data for visualization
118
+ # pixel_values = batch['pixel_values'].cpu()
119
+ # pixel_values = rearrange(pixel_values, "b f c h w -> b c f h w")
120
+ # for idx, pixel_value in enumerate(pixel_values):
121
+ # pixel_value = pixel_value[None, ...]
122
+ # text_value = batch['text'][idx]
123
+ # of_score = batch['of_score'][idx]
124
+ # fps_value = batch['fps'][idx]
125
+ # text_value = (text_value[:70] + '..') if len(text_value) > 70 else text_value
126
+ # output_filename = f"{output_dir}/{f'{fps_value}-{of_score}-{text_value}'}.gif"
127
+ # print(f'saving data to {output_filename}')
128
+ # save_videos_grid(pixel_value, output_filename, rescale=True)
129
+
130
+ # print(f'step: {step} / num_train_steps: {num_train_steps}')
131
+
132
+ if step >= num_train_steps:
133
+ break
infworld/utils/registry.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from copy import deepcopy
2
+
3
+ import torch.nn as nn
4
+ from mmengine.registry import Registry
5
+
6
+
7
+ def build_module(module, builder, **kwargs):
8
+ """Build module from config or return the module itself.
9
+
10
+ Args:
11
+ module (Union[dict, nn.Module]): The module to build.
12
+ builder (Registry): The registry to build module.
13
+ *args, **kwargs: Arguments passed to build function.
14
+
15
+ Returns:
16
+ Any: The built module.
17
+ """
18
+ if isinstance(module, dict):
19
+ cfg = deepcopy(module)
20
+ for k, v in kwargs.items():
21
+ cfg[k] = v
22
+ return builder.build(cfg)
23
+ elif isinstance(module, nn.Module):
24
+ return module
25
+ elif module is None:
26
+ return None
27
+ else:
28
+ raise TypeError(f"Only support dict and nn.Module, but got {type(module)}.")
29
+
30
+
31
+ MODELS = Registry(
32
+ "model",
33
+ locations=["opensora.models"],
34
+ )
35
+
36
+ SCHEDULERS = Registry(
37
+ "scheduler",
38
+ locations=["opensora.schedulers"],
39
+ )
infworld/vae/__init__.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from einops import rearrange
2
+ import torch
3
+ from torch import nn
4
+
5
+ # Standalone: only Wan VAE (used by infworld_config.yaml)
6
+ from .vae import WanVAE
7
+
8
+
9
+ class WanVAEModelWrapper(nn.Module):
10
+ def __init__(self, vae_pth, dtype=torch.float, device="cuda", patch_size=(4, 8, 8)):
11
+ super(WanVAEModelWrapper, self).__init__()
12
+ self.module = WanVAE(
13
+ vae_pth=vae_pth,
14
+ dtype=dtype,
15
+ device=device,
16
+ )
17
+ self.dtype = dtype
18
+ self.device = device
19
+ self.out_channels = 16
20
+ self.patch_size = patch_size
21
+
22
+ def encode(self, x):
23
+ # input: x: B, C, T, H, W or B, C, H, W
24
+ # return: x: B, C, T, H, W
25
+ if len(x.shape) == 4:
26
+ x = rearrange(x, "B C H W -> B C 1 H W")
27
+ x = self.module.encode_batch(x)
28
+ return x
29
+
30
+ def decode(self, x):
31
+ # input: x: B, C, T, H, W or B, C, H, W
32
+ # return: x: B, C, T, H, W
33
+ if len(x.shape) == 4:
34
+ x = rearrange(x, "T C H W -> 1 C T H W")
35
+ x = self.module.decode_batch(x)
36
+ return x
37
+
38
+ def get_latent_size(self, input_size):
39
+ latent_size = []
40
+ for i in range(3):
41
+ if i == 0:
42
+ target_size = 1 + (input_size[i] - 1) // self.patch_size[i]
43
+ latent_size.append(target_size)
44
+ else:
45
+ assert input_size[i] % self.patch_size[i] == 0, "Input spatial size must be divisible by patch size"
46
+ target_size = input_size[i] // self.patch_size[i]
47
+ latent_size.append(target_size)
48
+ return latent_size
infworld/vae/vae.py ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2024-2025 The Alibaba Wan Team Authors. All rights reserved.
2
+ import logging
3
+
4
+ import torch
5
+ import torch.cuda.amp as amp
6
+ import torch.nn as nn
7
+ import torch.nn.functional as F
8
+ from einops import rearrange
9
+
10
+ __all__ = [
11
+ 'WanVAE',
12
+ ]
13
+
14
+ CACHE_T = 2
15
+
16
+
17
+ class CausalConv3d(nn.Conv3d):
18
+ """
19
+ Causal 3d convolusion.
20
+ """
21
+
22
+ def __init__(self, *args, **kwargs):
23
+ super().__init__(*args, **kwargs)
24
+ self._padding = (self.padding[2], self.padding[2], self.padding[1],
25
+ self.padding[1], 2 * self.padding[0], 0)
26
+ self.padding = (0, 0, 0)
27
+
28
+ def forward(self, x, cache_x=None):
29
+ padding = list(self._padding)
30
+ if cache_x is not None and self._padding[4] > 0:
31
+ cache_x = cache_x.to(x.device)
32
+ x = torch.cat([cache_x, x], dim=2)
33
+ padding[4] -= cache_x.shape[2]
34
+ x = F.pad(x, padding)
35
+
36
+ return super().forward(x)
37
+
38
+
39
+ class RMS_norm(nn.Module):
40
+
41
+ def __init__(self, dim, channel_first=True, images=True, bias=False):
42
+ super().__init__()
43
+ broadcastable_dims = (1, 1, 1) if not images else (1, 1)
44
+ shape = (dim, *broadcastable_dims) if channel_first else (dim,)
45
+
46
+ self.channel_first = channel_first
47
+ self.scale = dim**0.5
48
+ self.gamma = nn.Parameter(torch.ones(shape))
49
+ self.bias = nn.Parameter(torch.zeros(shape)) if bias else 0.
50
+
51
+ def forward(self, x):
52
+ return F.normalize(
53
+ x, dim=(1 if self.channel_first else
54
+ -1)) * self.scale * self.gamma + self.bias
55
+
56
+
57
+ class Upsample(nn.Upsample):
58
+
59
+ def forward(self, x):
60
+ """
61
+ Fix bfloat16 support for nearest neighbor interpolation.
62
+ """
63
+ return super().forward(x.float()).type_as(x)
64
+
65
+
66
+ class Resample(nn.Module):
67
+
68
+ def __init__(self, dim, mode):
69
+ assert mode in ('none', 'upsample2d', 'upsample3d', 'downsample2d',
70
+ 'downsample3d')
71
+ super().__init__()
72
+ self.dim = dim
73
+ self.mode = mode
74
+
75
+ # layers
76
+ if mode == 'upsample2d':
77
+ self.resample = nn.Sequential(
78
+ Upsample(scale_factor=(2., 2.), mode='nearest-exact'),
79
+ nn.Conv2d(dim, dim // 2, 3, padding=1))
80
+ elif mode == 'upsample3d':
81
+ self.resample = nn.Sequential(
82
+ Upsample(scale_factor=(2., 2.), mode='nearest-exact'),
83
+ nn.Conv2d(dim, dim // 2, 3, padding=1))
84
+ self.time_conv = CausalConv3d(
85
+ dim, dim * 2, (3, 1, 1), padding=(1, 0, 0))
86
+
87
+ elif mode == 'downsample2d':
88
+ self.resample = nn.Sequential(
89
+ nn.ZeroPad2d((0, 1, 0, 1)),
90
+ nn.Conv2d(dim, dim, 3, stride=(2, 2)))
91
+ elif mode == 'downsample3d':
92
+ self.resample = nn.Sequential(
93
+ nn.ZeroPad2d((0, 1, 0, 1)),
94
+ nn.Conv2d(dim, dim, 3, stride=(2, 2)))
95
+ self.time_conv = CausalConv3d(
96
+ dim, dim, (3, 1, 1), stride=(2, 1, 1), padding=(0, 0, 0))
97
+
98
+ else:
99
+ self.resample = nn.Identity()
100
+
101
+ def forward(self, x, feat_cache=None, feat_idx=[0]):
102
+ b, c, t, h, w = x.size()
103
+ if self.mode == 'upsample3d':
104
+ if feat_cache is not None:
105
+ idx = feat_idx[0]
106
+ if feat_cache[idx] is None:
107
+ feat_cache[idx] = 'Rep'
108
+ feat_idx[0] += 1
109
+ else:
110
+
111
+ cache_x = x[:, :, -CACHE_T:, :, :].clone()
112
+ if cache_x.shape[2] < 2 and feat_cache[
113
+ idx] is not None and feat_cache[idx] != 'Rep':
114
+ # cache last frame of last two chunk
115
+ cache_x = torch.cat([
116
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
117
+ cache_x.device), cache_x
118
+ ],
119
+ dim=2)
120
+ if cache_x.shape[2] < 2 and feat_cache[
121
+ idx] is not None and feat_cache[idx] == 'Rep':
122
+ cache_x = torch.cat([
123
+ torch.zeros_like(cache_x).to(cache_x.device),
124
+ cache_x
125
+ ],
126
+ dim=2)
127
+ if feat_cache[idx] == 'Rep':
128
+ x = self.time_conv(x)
129
+ else:
130
+ x = self.time_conv(x, feat_cache[idx])
131
+ feat_cache[idx] = cache_x
132
+ feat_idx[0] += 1
133
+
134
+ x = x.reshape(b, 2, c, t, h, w)
135
+ x = torch.stack((x[:, 0, :, :, :, :], x[:, 1, :, :, :, :]),
136
+ 3)
137
+ x = x.reshape(b, c, t * 2, h, w)
138
+ t = x.shape[2]
139
+ x = rearrange(x, 'b c t h w -> (b t) c h w')
140
+ x = self.resample(x)
141
+ x = rearrange(x, '(b t) c h w -> b c t h w', t=t)
142
+
143
+ if self.mode == 'downsample3d':
144
+ if feat_cache is not None:
145
+ idx = feat_idx[0]
146
+ if feat_cache[idx] is None:
147
+ feat_cache[idx] = x.clone()
148
+ feat_idx[0] += 1
149
+ else:
150
+
151
+ cache_x = x[:, :, -1:, :, :].clone()
152
+ # if cache_x.shape[2] < 2 and feat_cache[idx] is not None and feat_cache[idx]!='Rep':
153
+ # # cache last frame of last two chunk
154
+ # cache_x = torch.cat([feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(cache_x.device), cache_x], dim=2)
155
+
156
+ x = self.time_conv(
157
+ torch.cat([feat_cache[idx][:, :, -1:, :, :], x], 2))
158
+ feat_cache[idx] = cache_x
159
+ feat_idx[0] += 1
160
+ return x
161
+
162
+ def init_weight(self, conv):
163
+ conv_weight = conv.weight
164
+ nn.init.zeros_(conv_weight)
165
+ c1, c2, t, h, w = conv_weight.size()
166
+ one_matrix = torch.eye(c1, c2)
167
+ init_matrix = one_matrix
168
+ nn.init.zeros_(conv_weight)
169
+ #conv_weight.data[:,:,-1,1,1] = init_matrix * 0.5
170
+ conv_weight.data[:, :, 1, 0, 0] = init_matrix #* 0.5
171
+ conv.weight.data.copy_(conv_weight)
172
+ nn.init.zeros_(conv.bias.data)
173
+
174
+ def init_weight2(self, conv):
175
+ conv_weight = conv.weight.data
176
+ nn.init.zeros_(conv_weight)
177
+ c1, c2, t, h, w = conv_weight.size()
178
+ init_matrix = torch.eye(c1 // 2, c2)
179
+ #init_matrix = repeat(init_matrix, 'o ... -> (o 2) ...').permute(1,0,2).contiguous().reshape(c1,c2)
180
+ conv_weight[:c1 // 2, :, -1, 0, 0] = init_matrix
181
+ conv_weight[c1 // 2:, :, -1, 0, 0] = init_matrix
182
+ conv.weight.data.copy_(conv_weight)
183
+ nn.init.zeros_(conv.bias.data)
184
+
185
+
186
+ class ResidualBlock(nn.Module):
187
+
188
+ def __init__(self, in_dim, out_dim, dropout=0.0):
189
+ super().__init__()
190
+ self.in_dim = in_dim
191
+ self.out_dim = out_dim
192
+
193
+ # layers
194
+ self.residual = nn.Sequential(
195
+ RMS_norm(in_dim, images=False), nn.SiLU(),
196
+ CausalConv3d(in_dim, out_dim, 3, padding=1),
197
+ RMS_norm(out_dim, images=False), nn.SiLU(), nn.Dropout(dropout),
198
+ CausalConv3d(out_dim, out_dim, 3, padding=1))
199
+ self.shortcut = CausalConv3d(in_dim, out_dim, 1) \
200
+ if in_dim != out_dim else nn.Identity()
201
+
202
+ def forward(self, x, feat_cache=None, feat_idx=[0]):
203
+ h = self.shortcut(x)
204
+ for layer in self.residual:
205
+ if isinstance(layer, CausalConv3d) and feat_cache is not None:
206
+ idx = feat_idx[0]
207
+ cache_x = x[:, :, -CACHE_T:, :, :].clone()
208
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
209
+ # cache last frame of last two chunk
210
+ cache_x = torch.cat([
211
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
212
+ cache_x.device), cache_x
213
+ ],
214
+ dim=2)
215
+ x = layer(x, feat_cache[idx])
216
+ feat_cache[idx] = cache_x
217
+ feat_idx[0] += 1
218
+ else:
219
+ x = layer(x)
220
+ return x + h
221
+
222
+
223
+ class AttentionBlock(nn.Module):
224
+ """
225
+ Causal self-attention with a single head.
226
+ """
227
+
228
+ def __init__(self, dim):
229
+ super().__init__()
230
+ self.dim = dim
231
+
232
+ # layers
233
+ self.norm = RMS_norm(dim)
234
+ self.to_qkv = nn.Conv2d(dim, dim * 3, 1)
235
+ self.proj = nn.Conv2d(dim, dim, 1)
236
+
237
+ # zero out the last layer params
238
+ nn.init.zeros_(self.proj.weight)
239
+
240
+ def forward(self, x):
241
+ identity = x
242
+ b, c, t, h, w = x.size()
243
+ x = rearrange(x, 'b c t h w -> (b t) c h w')
244
+ x = self.norm(x)
245
+ # compute query, key, value
246
+ q, k, v = self.to_qkv(x).reshape(b * t, 1, c * 3,
247
+ -1).permute(0, 1, 3,
248
+ 2).contiguous().chunk(
249
+ 3, dim=-1)
250
+
251
+ # apply attention
252
+ x = F.scaled_dot_product_attention(
253
+ q,
254
+ k,
255
+ v,
256
+ )
257
+ x = x.squeeze(1).permute(0, 2, 1).reshape(b * t, c, h, w)
258
+
259
+ # output
260
+ x = self.proj(x)
261
+ x = rearrange(x, '(b t) c h w-> b c t h w', t=t)
262
+ return x + identity
263
+
264
+
265
+ class Encoder3d(nn.Module):
266
+
267
+ def __init__(self,
268
+ dim=128,
269
+ z_dim=4,
270
+ dim_mult=[1, 2, 4, 4],
271
+ num_res_blocks=2,
272
+ attn_scales=[],
273
+ temperal_downsample=[True, True, False],
274
+ dropout=0.0):
275
+ super().__init__()
276
+ self.dim = dim
277
+ self.z_dim = z_dim
278
+ self.dim_mult = dim_mult
279
+ self.num_res_blocks = num_res_blocks
280
+ self.attn_scales = attn_scales
281
+ self.temperal_downsample = temperal_downsample
282
+
283
+ # dimensions
284
+ dims = [dim * u for u in [1] + dim_mult]
285
+ scale = 1.0
286
+
287
+ # init block
288
+ self.conv1 = CausalConv3d(3, dims[0], 3, padding=1)
289
+
290
+ # downsample blocks
291
+ downsamples = []
292
+ for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
293
+ # residual (+attention) blocks
294
+ for _ in range(num_res_blocks):
295
+ downsamples.append(ResidualBlock(in_dim, out_dim, dropout))
296
+ if scale in attn_scales:
297
+ downsamples.append(AttentionBlock(out_dim))
298
+ in_dim = out_dim
299
+
300
+ # downsample block
301
+ if i != len(dim_mult) - 1:
302
+ mode = 'downsample3d' if temperal_downsample[
303
+ i] else 'downsample2d'
304
+ downsamples.append(Resample(out_dim, mode=mode))
305
+ scale /= 2.0
306
+ self.downsamples = nn.Sequential(*downsamples)
307
+
308
+ # middle blocks
309
+ self.middle = nn.Sequential(
310
+ ResidualBlock(out_dim, out_dim, dropout), AttentionBlock(out_dim),
311
+ ResidualBlock(out_dim, out_dim, dropout))
312
+
313
+ # output blocks
314
+ self.head = nn.Sequential(
315
+ RMS_norm(out_dim, images=False), nn.SiLU(),
316
+ CausalConv3d(out_dim, z_dim, 3, padding=1))
317
+
318
+ def forward(self, x, feat_cache=None, feat_idx=[0]):
319
+ if feat_cache is not None:
320
+ idx = feat_idx[0]
321
+ cache_x = x[:, :, -CACHE_T:, :, :].clone()
322
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
323
+ # cache last frame of last two chunk
324
+ cache_x = torch.cat([
325
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
326
+ cache_x.device), cache_x
327
+ ],
328
+ dim=2)
329
+ x = self.conv1(x, feat_cache[idx])
330
+ feat_cache[idx] = cache_x
331
+ feat_idx[0] += 1
332
+ else:
333
+ x = self.conv1(x)
334
+
335
+ ## downsamples
336
+ for layer in self.downsamples:
337
+ if feat_cache is not None:
338
+ x = layer(x, feat_cache, feat_idx)
339
+ else:
340
+ x = layer(x)
341
+
342
+ ## middle
343
+ for layer in self.middle:
344
+ if isinstance(layer, ResidualBlock) and feat_cache is not None:
345
+ x = layer(x, feat_cache, feat_idx)
346
+ else:
347
+ x = layer(x)
348
+
349
+ ## head
350
+ for layer in self.head:
351
+ if isinstance(layer, CausalConv3d) and feat_cache is not None:
352
+ idx = feat_idx[0]
353
+ cache_x = x[:, :, -CACHE_T:, :, :].clone()
354
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
355
+ # cache last frame of last two chunk
356
+ cache_x = torch.cat([
357
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
358
+ cache_x.device), cache_x
359
+ ],
360
+ dim=2)
361
+ x = layer(x, feat_cache[idx])
362
+ feat_cache[idx] = cache_x
363
+ feat_idx[0] += 1
364
+ else:
365
+ x = layer(x)
366
+ return x
367
+
368
+
369
+ class Decoder3d(nn.Module):
370
+
371
+ def __init__(self,
372
+ dim=128,
373
+ z_dim=4,
374
+ dim_mult=[1, 2, 4, 4],
375
+ num_res_blocks=2,
376
+ attn_scales=[],
377
+ temperal_upsample=[False, True, True],
378
+ dropout=0.0):
379
+ super().__init__()
380
+ self.dim = dim
381
+ self.z_dim = z_dim
382
+ self.dim_mult = dim_mult
383
+ self.num_res_blocks = num_res_blocks
384
+ self.attn_scales = attn_scales
385
+ self.temperal_upsample = temperal_upsample
386
+
387
+ # dimensions
388
+ dims = [dim * u for u in [dim_mult[-1]] + dim_mult[::-1]]
389
+ scale = 1.0 / 2**(len(dim_mult) - 2)
390
+
391
+ # init block
392
+ self.conv1 = CausalConv3d(z_dim, dims[0], 3, padding=1)
393
+
394
+ # middle blocks
395
+ self.middle = nn.Sequential(
396
+ ResidualBlock(dims[0], dims[0], dropout), AttentionBlock(dims[0]),
397
+ ResidualBlock(dims[0], dims[0], dropout))
398
+
399
+ # upsample blocks
400
+ upsamples = []
401
+ for i, (in_dim, out_dim) in enumerate(zip(dims[:-1], dims[1:])):
402
+ # residual (+attention) blocks
403
+ if i == 1 or i == 2 or i == 3:
404
+ in_dim = in_dim // 2
405
+ for _ in range(num_res_blocks + 1):
406
+ upsamples.append(ResidualBlock(in_dim, out_dim, dropout))
407
+ if scale in attn_scales:
408
+ upsamples.append(AttentionBlock(out_dim))
409
+ in_dim = out_dim
410
+
411
+ # upsample block
412
+ if i != len(dim_mult) - 1:
413
+ mode = 'upsample3d' if temperal_upsample[i] else 'upsample2d'
414
+ upsamples.append(Resample(out_dim, mode=mode))
415
+ scale *= 2.0
416
+ self.upsamples = nn.Sequential(*upsamples)
417
+
418
+ # output blocks
419
+ self.head = nn.Sequential(
420
+ RMS_norm(out_dim, images=False), nn.SiLU(),
421
+ CausalConv3d(out_dim, 3, 3, padding=1))
422
+
423
+ def forward(self, x, feat_cache=None, feat_idx=[0]):
424
+ ## conv1
425
+ if feat_cache is not None:
426
+ idx = feat_idx[0]
427
+ cache_x = x[:, :, -CACHE_T:, :, :].clone()
428
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
429
+ # cache last frame of last two chunk
430
+ cache_x = torch.cat([
431
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
432
+ cache_x.device), cache_x
433
+ ],
434
+ dim=2)
435
+ x = self.conv1(x, feat_cache[idx])
436
+ feat_cache[idx] = cache_x
437
+ feat_idx[0] += 1
438
+ else:
439
+ x = self.conv1(x)
440
+
441
+ ## middle
442
+ for layer in self.middle:
443
+ if isinstance(layer, ResidualBlock) and feat_cache is not None:
444
+ x = layer(x, feat_cache, feat_idx)
445
+ else:
446
+ x = layer(x)
447
+
448
+ ## upsamples
449
+ for layer in self.upsamples:
450
+ if feat_cache is not None:
451
+ x = layer(x, feat_cache, feat_idx)
452
+ else:
453
+ x = layer(x)
454
+
455
+ ## head
456
+ for layer in self.head:
457
+ if isinstance(layer, CausalConv3d) and feat_cache is not None:
458
+ idx = feat_idx[0]
459
+ cache_x = x[:, :, -CACHE_T:, :, :].clone()
460
+ if cache_x.shape[2] < 2 and feat_cache[idx] is not None:
461
+ # cache last frame of last two chunk
462
+ cache_x = torch.cat([
463
+ feat_cache[idx][:, :, -1, :, :].unsqueeze(2).to(
464
+ cache_x.device), cache_x
465
+ ],
466
+ dim=2)
467
+ x = layer(x, feat_cache[idx])
468
+ feat_cache[idx] = cache_x
469
+ feat_idx[0] += 1
470
+ else:
471
+ x = layer(x)
472
+ return x
473
+
474
+
475
+ def count_conv3d(model):
476
+ count = 0
477
+ for m in model.modules():
478
+ if isinstance(m, CausalConv3d):
479
+ count += 1
480
+ return count
481
+
482
+
483
+ class WanVAE_(nn.Module):
484
+
485
+ def __init__(self,
486
+ dim=128,
487
+ z_dim=4,
488
+ dim_mult=[1, 2, 4, 4],
489
+ num_res_blocks=2,
490
+ attn_scales=[],
491
+ temperal_downsample=[True, True, False],
492
+ dropout=0.0):
493
+ super().__init__()
494
+ self.dim = dim
495
+ self.z_dim = z_dim
496
+ self.dim_mult = dim_mult
497
+ self.num_res_blocks = num_res_blocks
498
+ self.attn_scales = attn_scales
499
+ self.temperal_downsample = temperal_downsample
500
+ self.temperal_upsample = temperal_downsample[::-1]
501
+
502
+ # modules
503
+ self.encoder = Encoder3d(dim, z_dim * 2, dim_mult, num_res_blocks,
504
+ attn_scales, self.temperal_downsample, dropout)
505
+ self.conv1 = CausalConv3d(z_dim * 2, z_dim * 2, 1)
506
+ self.conv2 = CausalConv3d(z_dim, z_dim, 1)
507
+ self.decoder = Decoder3d(dim, z_dim, dim_mult, num_res_blocks,
508
+ attn_scales, self.temperal_upsample, dropout)
509
+
510
+ def forward(self, x):
511
+ mu, log_var = self.encode(x)
512
+ z = self.reparameterize(mu, log_var)
513
+ x_recon = self.decode(z)
514
+ return x_recon, mu, log_var
515
+
516
+ def encode(self, x, scale):
517
+ self.clear_cache()
518
+ ## cache
519
+ t = x.shape[2]
520
+ iter_ = 1 + (t - 1) // 4
521
+ ## 对encode输入的x,按时间拆分为1、4、4、4....
522
+ for i in range(iter_):
523
+ self._enc_conv_idx = [0]
524
+ if i == 0:
525
+ out = self.encoder(
526
+ x[:, :, :1, :, :],
527
+ feat_cache=self._enc_feat_map,
528
+ feat_idx=self._enc_conv_idx)
529
+ else:
530
+ out_ = self.encoder(
531
+ x[:, :, 1 + 4 * (i - 1):1 + 4 * i, :, :],
532
+ feat_cache=self._enc_feat_map,
533
+ feat_idx=self._enc_conv_idx)
534
+ out = torch.cat([out, out_], 2)
535
+ mu, log_var = self.conv1(out).chunk(2, dim=1)
536
+ if isinstance(scale[0], torch.Tensor):
537
+ mu = (mu - scale[0].view(1, self.z_dim, 1, 1, 1)) * scale[1].view(
538
+ 1, self.z_dim, 1, 1, 1)
539
+ else:
540
+ mu = (mu - scale[0]) * scale[1]
541
+ self.clear_cache()
542
+ return mu
543
+
544
+ def decode(self, z, scale):
545
+ self.clear_cache()
546
+ # z: [b,c,t,h,w]
547
+ if isinstance(scale[0], torch.Tensor):
548
+ z = z / scale[1].view(1, self.z_dim, 1, 1, 1) + scale[0].view(
549
+ 1, self.z_dim, 1, 1, 1)
550
+ else:
551
+ z = z / scale[1] + scale[0]
552
+ iter_ = z.shape[2]
553
+ x = self.conv2(z)
554
+ for i in range(iter_):
555
+ self._conv_idx = [0]
556
+ if i == 0:
557
+ out = self.decoder(
558
+ x[:, :, i:i + 1, :, :],
559
+ feat_cache=self._feat_map,
560
+ feat_idx=self._conv_idx)
561
+ else:
562
+ out_ = self.decoder(
563
+ x[:, :, i:i + 1, :, :],
564
+ feat_cache=self._feat_map,
565
+ feat_idx=self._conv_idx)
566
+ out = torch.cat([out, out_], 2)
567
+ self.clear_cache()
568
+ return out
569
+
570
+ def reparameterize(self, mu, log_var):
571
+ std = torch.exp(0.5 * log_var)
572
+ eps = torch.randn_like(std)
573
+ return eps * std + mu
574
+
575
+ def sample(self, imgs, deterministic=False):
576
+ mu, log_var = self.encode(imgs)
577
+ if deterministic:
578
+ return mu
579
+ std = torch.exp(0.5 * log_var.clamp(-30.0, 20.0))
580
+ return mu + std * torch.randn_like(std)
581
+
582
+ def clear_cache(self):
583
+ self._conv_num = count_conv3d(self.decoder)
584
+ self._conv_idx = [0]
585
+ self._feat_map = [None] * self._conv_num
586
+ #cache encode
587
+ self._enc_conv_num = count_conv3d(self.encoder)
588
+ self._enc_conv_idx = [0]
589
+ self._enc_feat_map = [None] * self._enc_conv_num
590
+
591
+
592
+ def _video_vae(pretrained_path=None, z_dim=None, device='cpu', **kwargs):
593
+ """
594
+ Autoencoder3d adapted from Stable Diffusion 1.x, 2.x and XL.
595
+ """
596
+ # params
597
+ cfg = dict(
598
+ dim=96,
599
+ z_dim=z_dim,
600
+ dim_mult=[1, 2, 4, 4],
601
+ num_res_blocks=2,
602
+ attn_scales=[],
603
+ temperal_downsample=[False, True, True],
604
+ dropout=0.0)
605
+ cfg.update(**kwargs)
606
+
607
+ # init model
608
+ with torch.device('meta'):
609
+ model = WanVAE_(**cfg)
610
+
611
+ # load checkpoint
612
+ logging.info(f'loading {pretrained_path}')
613
+ model.load_state_dict(
614
+ torch.load(pretrained_path, map_location=device), assign=True)
615
+
616
+ return model
617
+
618
+
619
+ class WanVAE:
620
+
621
+ def __init__(self,
622
+ z_dim=16,
623
+ vae_pth='cache/vae_step_411000.pth',
624
+ dtype=torch.float,
625
+ device="cuda"):
626
+ self.dtype = dtype
627
+ self.device = device
628
+
629
+ mean = [
630
+ -0.7571, -0.7089, -0.9113, 0.1075, -0.1745, 0.9653, -0.1517, 1.5508,
631
+ 0.4134, -0.0715, 0.5517, -0.3632, -0.1922, -0.9497, 0.2503, -0.2921
632
+ ]
633
+ std = [
634
+ 2.8184, 1.4541, 2.3275, 2.6558, 1.2196, 1.7708, 2.6052, 2.0743,
635
+ 3.2687, 2.1526, 2.8652, 1.5579, 1.6382, 1.1253, 2.8251, 1.9160
636
+ ]
637
+ self.mean = torch.tensor(mean, dtype=dtype, device=device)
638
+ self.std = torch.tensor(std, dtype=dtype, device=device)
639
+ self.scale = [self.mean, 1.0 / self.std]
640
+
641
+ # init model
642
+ self.model = _video_vae(
643
+ pretrained_path=vae_pth,
644
+ z_dim=z_dim,
645
+ ).eval().requires_grad_(False).to(device)
646
+
647
+ def encode(self, videos):
648
+ """
649
+ videos: A list of videos each with shape [C, T, H, W].
650
+ """
651
+ with amp.autocast(dtype=self.dtype):
652
+ return [
653
+ self.model.encode(u.unsqueeze(0), self.scale).float().squeeze(0)
654
+ for u in videos
655
+ ]
656
+
657
+ def decode(self, zs):
658
+ with amp.autocast(dtype=self.dtype):
659
+ return [
660
+ self.model.decode(u.unsqueeze(0),
661
+ self.scale).float().clamp_(-1, 1).squeeze(0)
662
+ for u in zs
663
+ ]
664
+
665
+ def encode_batch(self, videos):
666
+ """
667
+ videos: A list of videos each with shape [C, T, H, W].
668
+ """
669
+ with amp.autocast(dtype=self.dtype):
670
+ return self.model.encode(videos, self.scale).float()
671
+
672
+ def decode_batch(self, zs):
673
+ with amp.autocast(dtype=self.dtype):
674
+ return self.model.decode(zs, self.scale).float().clamp_(-1, 1)
prompts/demo.yaml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Infinite World - Demo Prompts
2
+ # Format: [prompt, condition_image_path, action_json_path]
3
+ prompts:
4
+ - - A serene campus walkway lined with modern glass buildings, green ivy climbing some walls, empty benches, soft dappled sunlight through maple trees.
5
+ - ./assets/example_case/0001.jpg
6
+ - ./assets/example_case/0001.json
7
+
8
+ - - A street in a fantasy city where buildings are carved into gargantuan ancient trees, glowing sap running through bark, misty floor.
9
+ - ./assets/example_case/0002.jpg
10
+ - ./assets/example_case/0002.json
readme.md ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <h1 align="center">Infinite-World</h1>
2
+
3
+ <h3 align="center">Scaling Interactive World Models to 1000-Frame Horizons via Pose-Free Hierarchical Memory</h3>
4
+
5
+ <p align="center">
6
+ <a href="http://arxiv.org/abs/2602.02393"><img src="https://img.shields.io/badge/arXiv-2602.02393-b31b1b.svg" alt="arXiv"></a>
7
+ <a href="https://rq-wu.github.io/projects/infinite_world"><img src="https://img.shields.io/badge/Project-Page-blue.svg" alt="Project Page"></a>
8
+ </p>
9
+
10
+ <p align="center">
11
+ <strong>Ruiqi Wu</strong><sup>1,2,3*</sup>, <strong>Xuanhua He</strong><sup>4,2*</sup>, <strong>Meng Cheng</strong><sup>2*</sup>, <strong>Tianyu Yang</strong><sup>2</sup>, <strong>Yong Zhang</strong><sup>2‡</sup>, <strong>Chunle Guo</strong><sup>1,3†</sup>, <strong>Chongyi Li</strong><sup>1,3</sup>, <strong>Ming-Ming Cheng</strong><sup>1,3</sup>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <sup>1</sup>Nankai University &nbsp; <sup>2</sup>Meituan &nbsp; <sup>3</sup>NKIARI &nbsp; <sup>4</sup>HKUST
16
+ </p>
17
+
18
+ <p align="center">
19
+ <sup>*</sup>Equal Contribution &nbsp; <sup>†</sup>Corresponding Author &nbsp; <sup>‡</sup>Project Leader
20
+ </p>
21
+
22
+ ---
23
+
24
+ ## Highlights
25
+
26
+ **Infinite-World** is a robust interactive world model with:
27
+
28
+ - **Real-World Training** — Trained on real-world videos without requiring perfect pose annotations or synthetic data
29
+ - **1000+ Frame Memory** — Maintains coherent visual memory over 1000+ frames via Hierarchical Pose-free Memory Compressor (HPMC)
30
+ - **Robust Action Control** — Uncertainty-aware action labeling ensures accurate action-response learning from noisy trajectories
31
+
32
+ <p align="center">
33
+ <img src="./assets/framework.png" alt="Infinite-World Framework" width="100%">
34
+ </p>
35
+
36
+
37
+ ## Installation
38
+
39
+ **Environment:** Python 3.10, CUDA 12.4 recommended.
40
+
41
+ ### 1. Create conda environment
42
+
43
+ ```bash
44
+ conda create -n infworld python=3.10
45
+ conda activate infworld
46
+ ```
47
+
48
+ ### 2. Install PyTorch with CUDA 12.4
49
+
50
+ Install from the official PyTorch index (no local whl):
51
+
52
+ ```bash
53
+ pip install torch==2.6.0 torchvision==0.21.0 --index-url https://download.pytorch.org/whl/cu124
54
+ ```
55
+
56
+
57
+ ### 3. Install Python dependencies
58
+
59
+ ```bash
60
+ pip install -r requirements.txt
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Checkpoint Configuration
66
+
67
+ All model paths are configured in **`configs/infworld_config.yaml`**. Paths are relative to the project root unless absolute.
68
+
69
+ ### Download checkpoints
70
+
71
+ Download from [Wan-AI/Wan2.1-T2V-1.3B](https://huggingface.co/Wan-AI/Wan2.1-T2V-1.3B) and place files under `checkpoints/`:
72
+
73
+ | File / directory | Config key | Description |
74
+ |------------------|------------|-------------|
75
+ | `models/Wan2.1_VAE.pth` | `vae_cfg.vae_pth` | VAE weights |
76
+ | `models/models_t5_umt5-xxl-enc-bf16.pth` | `text_encoder_cfg.checkpoint_path` | T5 text encoder |
77
+ | `models/google/umt5-xxl` (folder) | `text_encoder_cfg.tokenizer_path` | T5 tokenizer |
78
+ | `infinite_world_model.ckpt` | `checkpoint_path` | DiT model weights |
79
+
80
+
81
+
82
+ - **DiT checkpoint:** Can be downloaded from [TBD]().
83
+
84
+ ---
85
+
86
+ ## Upload to Hugging Face (including checkpoints)
87
+
88
+ To upload this repo to Hugging Face Hub (code + `checkpoints/`):
89
+
90
+ 1. **Login**
91
+ ```bash
92
+ pip install huggingface_hub
93
+ huggingface-cli login
94
+ ```
95
+ Use a token from [https://huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) (need write permission).
96
+
97
+ 2. **Upload**
98
+ From the project root (`infinite-world/`):
99
+ ```bash
100
+ python scripts/upload_to_hf.py YOUR_USERNAME/infinite-world
101
+ ```
102
+ Or set the repo and run:
103
+ ```bash
104
+ export HF_REPO_ID=YOUR_USERNAME/infinite-world
105
+ python scripts/upload_to_hf.py
106
+ ```
107
+
108
+ The script uploads the whole directory (including `checkpoints/`) and skips `__pycache__`, `outputs`, `.git`, etc. Large checkpoint files are uploaded via the Hub API; the first run may take a while depending on size and network.
109
+
110
+ 3. **Create repo manually (optional)**
111
+ You can create the model repo first at [https://huggingface.co/new](https://huggingface.co/new) (type: **Model**), then run the script with that `repo_id`.
112
+
113
+ ---
114
+
115
+ ## Results
116
+
117
+ ### Quantitative Comparison
118
+
119
+ | Model | Mot. Smo.↑ | Dyn. Deg.↑ | Aes. Qual.↑ | Img. Qual.↑ | Avg. Score↑ | Memory↓ | Fidelity↓ | Action↓ | ELO Rating↑ |
120
+ |:------|:----------:|:----------:|:-----------:|:-----------:|:-----------:|:-------:|:---------:|:-------:|:-----------:|
121
+ | Hunyuan-GameCraft | 0.9855 | 0.9896 | 0.5380 | 0.6010 | 0.7785 | 2.67 | 2.49 | 2.56 | 1311 |
122
+ | Matrix-Game 2.0 | 0.9788 | **1.0000** | 0.5267 | **0.7215** | 0.8068 | 2.98 | 2.91 | 1.78 | 1432 |
123
+ | Yume 1.5 | 0.9861 | 0.9896 | **0.5840** | <u>0.6969</u> | **0.8141** | <u>2.43</u> | <u>1.91</u> | 2.47 | 1495 |
124
+ | HY-World-1.5 | **0.9905** | **1.0000** | 0.5280 | 0.6611 | 0.7949 | 2.59 | 2.78 | **1.50** | <u>1542</u> |
125
+ | **Infinite-World** | <u>0.9876</u> | **1.0000** | <u>0.5440</u> | <u>0.7159</u> | <u>0.8119</u> | **1.92** | **1.67** | <u>1.54</u> | **1719** |
126
+
127
+
128
+ ## Citation
129
+
130
+ If you find this work useful, please consider citing:
131
+
132
+ ```bibtex
133
+ @article{wu2026infiniteworld,
134
+ title={Infinite-World: Scaling Interactive World Models to 1000-Frame Horizons via Pose-Free Hierarchical Memory},
135
+ author={Wu, Ruiqi and He, Xuanhua and Cheng, Meng and Yang, Tianyu and Zhang, Yong and Kang, Zhuoliang and Cai, Xunliang and Wei, Xiaoming and Guo, Chunle and Li, Chongyi and Cheng, Ming-Ming},
136
+ journal={arXiv preprint arXiv:2602.02393},
137
+ year={2026}
138
+ }
139
+ ```
140
+
141
+
142
+ ## License
143
+
144
+ This project is released under the [MIT License](LICENSE).
requirements.txt ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Infinite World - Python dependencies (PyPI only)
2
+ # Install PyTorch with CUDA first (see README), then: pip install -r requirements.txt
3
+
4
+ flash_attn==2.7.4.post1
5
+ absl-py==2.1.0
6
+ accelerate==1.9.0
7
+ addict==2.4.0
8
+ annotated-types==0.7.0
9
+ antlr4-python3-runtime==4.9.3
10
+ av==12.0.0
11
+ beautifulsoup4==4.12.3
12
+ braceexpand==0.1.7
13
+ certifi==2024.8.30
14
+ charset-normalizer==3.3.2
15
+ contourpy==1.3.0
16
+ crc32c==2.7.1
17
+ cycler==0.12.1
18
+ decorator==4.4.2
19
+ decord==0.6.0
20
+ diffusers==0.24.0
21
+ docopt==0.6.2
22
+ einops==0.8.0
23
+ ffmpeg-python==0.2.0
24
+ filelock==3.16.1
25
+ fonttools==4.54.1
26
+ fsspec==2024.9.0
27
+ ftfy==6.2.0
28
+ future==1.0.0
29
+ huggingface-hub==0.25.1
30
+ idna==3.10
31
+ imageio==2.34.1
32
+ imageio-ffmpeg==0.4.9
33
+ importlib_metadata==8.5.0
34
+ Jinja2==3.1.4
35
+ kiwisolver==1.4.7
36
+ loguru==0.7.2
37
+ Markdown==3.7
38
+ markdown-it-py==3.0.0
39
+ MarkupSafe==2.1.5
40
+ matplotlib==3.9.2
41
+ mdurl==0.1.2
42
+ moviepy==1.0.3
43
+ mpmath==1.3.0
44
+ networkx==3.3
45
+ ninja==1.11.1.1
46
+ numpy==1.26.4
47
+ omegaconf==2.3.0
48
+ opencv-python==4.9.0.80
49
+ packaging==24.1
50
+ pillow==10.4.0
51
+ ply==3.11
52
+ prettytable==3.10.0
53
+ proglog==0.1.10
54
+ protobuf==3.20.1
55
+ psutil==6.0.0
56
+ py-cpuinfo==9.0.0
57
+ pybind11==2.13.6
58
+ pydantic==2.9.2
59
+ pydantic_core==2.23.4
60
+ Pygments==2.18.0
61
+ pynvml==11.5.3
62
+ pyparsing==3.1.4
63
+ python-dateutil==2.9.0.post0
64
+ pytz==2024.2
65
+ PyYAML==6.0.2
66
+ regex==2024.9.11
67
+ requests==2.32.3
68
+ rich==13.8.1
69
+ safetensors==0.4.5
70
+ sentencepiece==0.2.0
71
+ six==1.16.0
72
+ soupsieve==2.6
73
+ sympy==1.13.1
74
+ tensorboard==2.16.2
75
+ tensorboard-data-server==0.7.2
76
+ termcolor==2.4.0
77
+ timm==1.0.9
78
+ tokenizers==0.19.1
79
+ tqdm==4.66.4
80
+ transformers==4.41.0
81
+ # triton: do not pin; it is installed with torch and must match your torch version
82
+ typing_extensions==4.12.2
83
+ urllib3==2.2.3
84
+ wcwidth==0.2.13
85
+ Werkzeug==3.0.4
86
+ yapf==0.32.0
87
+ zipp==3.20.2
88
+ pytest==8.3.5
89
+ pandas==2.2.3
scripts/infworld_inference.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Infinite World - Action-Conditioned Video Generation Inference Script
3
+ ======================================================================
4
+ A standalone inference script for generating long videos with action control.
5
+ """
6
+
7
+ import sys
8
+ import os
9
+ import cv2
10
+ import math
11
+ import torch
12
+ import random
13
+ import json
14
+ import datetime
15
+ import importlib
16
+ import numpy as np
17
+ from PIL import Image
18
+ from omegaconf import OmegaConf
19
+ import torch.distributed as dist
20
+ import torchvision.transforms as transforms
21
+ import re
22
+
23
+ # Add project root to path
24
+ PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
25
+ sys.path.insert(0, PROJECT_ROOT)
26
+
27
+ from infworld.utils.prepare_dataloader import get_obj_from_str
28
+ from infworld.utils.data_utils import get_first_clip_from_video, save_silent_video
29
+ from infworld.utils.dataset_utils import is_vid, is_img
30
+
31
+ # ============================================================================
32
+ # Action Mapping Dictionaries
33
+ # ============================================================================
34
+ MOVE_ACTION_MAP = {
35
+ 'no-op': 0,
36
+ 'go forward': 1,
37
+ 'go back': 2,
38
+ 'go left': 3,
39
+ 'go right': 4,
40
+ 'go forward and go left': 5,
41
+ 'go forward and go right': 6,
42
+ 'go back and go left': 7,
43
+ 'go back and go right': 8,
44
+ 'uncertain': 9
45
+ }
46
+
47
+ VIEW_ACTION_MAP = {
48
+ 'no-op': 0,
49
+ 'turn up': 1,
50
+ 'turn down': 2,
51
+ 'turn left': 3,
52
+ 'turn right': 4,
53
+ 'turn up and turn left': 5,
54
+ 'turn up and turn right': 6,
55
+ 'turn down and turn left': 7,
56
+ 'turn down and turn right': 8,
57
+ 'uncertain': 9
58
+ }
59
+
60
+ # ============================================================================
61
+ # Utility Functions
62
+ # ============================================================================
63
+ def extract_ckpt_step(path):
64
+ """Extract checkpoint step number from path."""
65
+ match = re.search(r'checkpoint-(\d+)\.ckpt', path)
66
+ return int(match.group(1)) if match else 0
67
+
68
+ def resize_and_center_crop(image, target_size):
69
+ """Resize image and center crop to target size."""
70
+ orig_h, orig_w = image.shape[:2]
71
+ target_h, target_w = target_size
72
+
73
+ scale = max(target_h / orig_h, target_w / orig_w)
74
+ final_h = math.ceil(scale * orig_h)
75
+ final_w = math.ceil(scale * orig_w)
76
+
77
+ resized = cv2.resize(image, (final_w, final_h), interpolation=cv2.INTER_AREA)
78
+ tensor = torch.from_numpy(resized)[None, ...].permute(0, 3, 1, 2).contiguous()
79
+ cropped = transforms.functional.center_crop(tensor, target_size)
80
+ return cropped[:, :, None, :, :] # [1, C, 1, H, W]
81
+
82
+ def setup_seed(seed):
83
+ """Set random seeds for reproducibility."""
84
+ torch.manual_seed(seed)
85
+ torch.cuda.manual_seed_all(seed)
86
+ np.random.seed(seed)
87
+ random.seed(seed)
88
+ torch.backends.cudnn.deterministic = True
89
+
90
+ def torch_gc():
91
+ """Clear GPU memory cache."""
92
+ torch.cuda.empty_cache()
93
+ torch.cuda.ipc_collect()
94
+
95
+ def load_action_sequence(action_path):
96
+ """Load action sequence from JSON file."""
97
+ with open(action_path, 'r') as f:
98
+ actions = json.load(f)
99
+
100
+ move_indices = [MOVE_ACTION_MAP[a['move']] for a in actions]
101
+ view_indices = [VIEW_ACTION_MAP[a['view']] for a in actions]
102
+ return move_indices, view_indices
103
+
104
+ def load_condition_image(image_path, bucket_config):
105
+ """Load and preprocess condition image."""
106
+ if is_vid(image_path):
107
+ frames = get_first_clip_from_video(image_path, clip_len=1)
108
+ elif is_img(image_path):
109
+ image = cv2.imread(image_path)
110
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
111
+ frames = [image]
112
+ else:
113
+ raise ValueError(f'Unsupported file format: {image_path}')
114
+
115
+ processed_frames = []
116
+ for frame in frames:
117
+ ratio = frame.shape[0] / frame.shape[1]
118
+ closest_bucket = sorted(bucket_config.keys(), key=lambda x: abs(float(x) - ratio))[0]
119
+ target_h, target_w = bucket_config[closest_bucket][0]
120
+
121
+ tensor = resize_and_center_crop(frame, (target_h, target_w))
122
+ tensor = (tensor / 255 - 0.5) * 2 # Normalize to [-1, 1]
123
+ processed_frames.append(tensor)
124
+
125
+ return torch.cat(processed_frames, dim=2)
126
+
127
+ # ============================================================================
128
+ # Distributed Setup (support single-GPU without torchrun to avoid port conflict)
129
+ # ============================================================================
130
+ def setup_distributed():
131
+ """Setup distributed or single-GPU mode."""
132
+ if 'RANK' in os.environ:
133
+ # Launched by torchrun or similar
134
+ rank = int(os.environ['RANK'])
135
+ world_size = int(os.environ.get('WORLD_SIZE', 1))
136
+ local_rank = int(os.environ.get('LOCAL_RANK', rank % torch.cuda.device_count()))
137
+ torch.cuda.set_device(local_rank)
138
+ dist.init_process_group(backend="nccl", timeout=datetime.timedelta(seconds=3600*24))
139
+ global_rank = dist.get_rank()
140
+ num_processes = dist.get_world_size()
141
+ return local_rank, global_rank, num_processes, True # use_cp_init=True
142
+ else:
143
+ # Single process (no torchrun) - avoid port conflict, no dist init
144
+ local_rank = 0
145
+ global_rank = 0
146
+ num_processes = 1
147
+ torch.cuda.set_device(local_rank)
148
+ return local_rank, global_rank, num_processes, False # use_cp_init=False
149
+
150
+ local_rank, global_rank, num_processes, use_dist = setup_distributed()
151
+ print(f"[InfWorld] local_rank: {local_rank} | global_rank: {global_rank} | world_size: {num_processes}")
152
+
153
+ # Context parallel setup
154
+ context_parallel_size = 1
155
+ import infworld.context_parallel.context_parallel_util as cp_util
156
+ if use_dist:
157
+ from infworld.context_parallel.context_parallel_util import init_context_parallel, get_dp_size, get_dp_rank
158
+ init_context_parallel(context_parallel_size=context_parallel_size, global_rank=global_rank, world_size=num_processes)
159
+ dp_rank = get_dp_rank()
160
+ dp_size = get_dp_size()
161
+ else:
162
+ # Single process: set globals so get_dp_rank/get_dp_size work without dist
163
+ cp_util.dp_rank = 0
164
+ cp_util.dp_size = 1
165
+ cp_util.cp_rank = 0
166
+ cp_util.cp_size = 1
167
+ dp_rank = 0
168
+ dp_size = 1
169
+ enable_context_parallel = (context_parallel_size > 1)
170
+
171
+ # ============================================================================
172
+ # Configuration
173
+ # ============================================================================
174
+ # Inference settings
175
+ GLOBAL_SEED = 42
176
+ setup_seed(GLOBAL_SEED + global_rank)
177
+
178
+ TEXT_CFG_SCALE = 5.0
179
+ NUM_SAMPLING_STEPS = 30
180
+ SHIFT = 7 # PX256: 3, PX627: 7, PX960: 11
181
+ NUM_CHUNKS = 13 # Number of video chunks to generate
182
+ HIGH_QUALITY_SAVE = True
183
+
184
+ # Paths - checkpoint_path is read from config (configs/infworld_config.yaml)
185
+ # Model config - use standalone config
186
+ CONFIG_PATH = os.path.join(PROJECT_ROOT, 'configs', 'infworld_config.yaml')
187
+
188
+ PROMPTS_YAML = os.path.join(PROJECT_ROOT, 'prompts', 'demo.yaml')
189
+ BUCKET_CONFIG_NAME = 'ASPECT_RATIO_627_F64'
190
+
191
+ # Output directory
192
+ OUTPUT_BASE = os.path.join(PROJECT_ROOT, 'outputs')
193
+
194
+ # Negative prompt for generation quality
195
+ NEGATIVE_PROMPT = "many cars, crowds, Vivid hues, overexposed, static, blurry details, subtitles, style, work, artwork, image, still, overall grayish, worst quality, low quality, JPEG compression artifacts, ugly, incomplete, extra fingers, poorly drawn hands, poorly drawn face, deformed, disfigured, deformed limbs, fused fingers, motionless image, cluttered background, three legs, crowded background, walking backwards."
196
+
197
+ # ============================================================================
198
+ # Main Inference Loop
199
+ # ============================================================================
200
+ def resolve_path(path, root=PROJECT_ROOT):
201
+ """Resolve path: if relative, join with project root."""
202
+ if path is None:
203
+ return path
204
+ path = str(path).strip()
205
+ if not os.path.isabs(path):
206
+ path = os.path.join(root, path)
207
+ return path
208
+
209
+
210
+ def load_dit_state_dict(checkpoint_path):
211
+ """Load DiT state dict from .ckpt (torch) or .safetensors."""
212
+ checkpoint_path = resolve_path(checkpoint_path)
213
+ if checkpoint_path.endswith(".safetensors"):
214
+ from safetensors.torch import load_file
215
+ state_dict = load_file(checkpoint_path)
216
+ else:
217
+ state_dict = torch.load(checkpoint_path, map_location="cpu")
218
+ if "state_dict" in state_dict:
219
+ state_dict = state_dict["state_dict"]
220
+ return state_dict
221
+
222
+
223
+ def main():
224
+ torch_gc()
225
+
226
+ config_path = CONFIG_PATH
227
+ args = OmegaConf.load(config_path)
228
+ checkpoint_path = resolve_path(args.get("checkpoint_path", "checkpoints/models/diffusion_pytorch_model.safetensors"))
229
+
230
+ ckpt_step = extract_ckpt_step(checkpoint_path)
231
+
232
+ # Create output directory
233
+ output_dir = os.path.join(OUTPUT_BASE, f"infworld-ckpt{ckpt_step}-step{NUM_SAMPLING_STEPS}-cfg{TEXT_CFG_SCALE}")
234
+ os.makedirs(output_dir, exist_ok=True)
235
+
236
+ print(f"[InfWorld] Loading checkpoint: {checkpoint_path}")
237
+ print(f"[InfWorld] Config: {config_path}")
238
+ print(f"[InfWorld] Output directory: {output_dir}")
239
+
240
+ # Resolve relative paths in config for models that load from disk
241
+ if hasattr(args, "vae_cfg") and "vae_pth" in args.vae_cfg:
242
+ args.vae_cfg.vae_pth = resolve_path(args.vae_cfg.vae_pth)
243
+ if hasattr(args, "text_encoder_cfg"):
244
+ if "checkpoint_path" in args.text_encoder_cfg:
245
+ args.text_encoder_cfg.checkpoint_path = resolve_path(args.text_encoder_cfg.checkpoint_path)
246
+ if "tokenizer_path" in args.text_encoder_cfg:
247
+ args.text_encoder_cfg.tokenizer_path = resolve_path(args.text_encoder_cfg.tokenizer_path)
248
+
249
+ # Initialize models
250
+ print("[InfWorld] Loading VAE...")
251
+ vae = get_obj_from_str(args.vae_target)(**args.vae_cfg).to(local_rank)
252
+
253
+ print("[InfWorld] Loading Text Encoder...")
254
+ text_encoder = get_obj_from_str(args.text_encoder_target)(device=local_rank, **args.text_encoder_cfg)
255
+ text_encoder.t5.model.to(local_rank)
256
+
257
+ print("[InfWorld] Loading Scheduler...")
258
+ scheduler = get_obj_from_str(args.scheduler_target)(**args.val_scheduler_cfg)
259
+ scheduler.num_sampling_steps = NUM_SAMPLING_STEPS
260
+ scheduler.shift = SHIFT
261
+
262
+ print("[InfWorld] Loading DiT Model...")
263
+ dtype = getattr(torch, args.amp_dtype)
264
+ dit = get_obj_from_str(args.model_target)(
265
+ out_channels=vae.out_channels,
266
+ caption_channels=text_encoder.output_dim,
267
+ model_max_length=text_encoder.model_max_length,
268
+ enable_context_parallel=enable_context_parallel,
269
+ **args.model_cfg
270
+ ).to(dtype)
271
+ dit.eval()
272
+
273
+ # Load DiT checkpoint (from config)
274
+ state_dict = load_dit_state_dict(args.checkpoint_path)
275
+
276
+ # Remove position embeddings (will be recomputed)
277
+ state_dict.pop("pos_embed_temporal", None)
278
+ state_dict.pop("pos_embed", None)
279
+
280
+ missing, unexpected = dit.load_state_dict(state_dict, strict=False)
281
+ print(f"[InfWorld] Model loaded! Missing: {len(missing)}, Unexpected: {len(unexpected)}")
282
+
283
+ dit.to(local_rank)
284
+
285
+ # Load bucket config
286
+ from infworld.configs import bucket_config as bucket_config_module
287
+ bucket_config = getattr(bucket_config_module, BUCKET_CONFIG_NAME)
288
+
289
+ # Load prompts
290
+ prompts_path = os.path.abspath(PROMPTS_YAML)
291
+ target_prompts = OmegaConf.load(prompts_path).prompts
292
+ print(f"[InfWorld] Loaded {len(target_prompts)} prompts")
293
+
294
+ # Process each prompt
295
+ for task_idx, (prompt, image_path, action_path) in enumerate(target_prompts):
296
+ if task_idx % dp_size != dp_rank:
297
+ continue
298
+
299
+ if not os.path.exists(image_path):
300
+ print(f"[InfWorld] Skipping task {task_idx}: Image not found - {image_path}")
301
+ continue
302
+
303
+ if not os.path.exists(action_path):
304
+ print(f"[InfWorld] Skipping task {task_idx}: Action not found - {action_path}")
305
+ continue
306
+
307
+ print(f"[InfWorld] Task {task_idx}: {prompt[:50]}...")
308
+
309
+ # Load condition image
310
+ cond_video = load_condition_image(image_path, bucket_config).to(local_rank)
311
+
312
+ with torch.no_grad():
313
+ cond_latent = vae.encode(cond_video)
314
+
315
+ # Load action sequence
316
+ move_indices, view_indices = load_action_sequence(action_path)
317
+
318
+ # Initialize video buffer
319
+ video_buffer = cond_video.clone().cpu()
320
+
321
+ # Latent size for generation
322
+ latent_size = list(cond_latent.shape)
323
+ latent_size[2] = 21 # Output frames per chunk
324
+ latent_size = torch.Size(latent_size)
325
+
326
+ # Generate video chunks
327
+ for chunk_idx in range(NUM_CHUNKS):
328
+ print(f"[InfWorld] Generating chunk {chunk_idx + 1}/{NUM_CHUNKS}")
329
+
330
+ with torch.no_grad():
331
+ current_cond = video_buffer.to(local_rank)
332
+ current_latent = vae.encode(current_cond)
333
+
334
+ # Get action slice for current chunk
335
+ curr_start = video_buffer.shape[2] - 1
336
+ curr_end = curr_start + args.validation_data.num_frames
337
+
338
+ move = torch.tensor(move_indices[curr_start:curr_end], dtype=torch.long, device=local_rank)
339
+ view = torch.tensor(view_indices[curr_start:curr_end], dtype=torch.long, device=local_rank)
340
+
341
+ # Pad if needed
342
+ num_frames = args.validation_data.num_frames
343
+ if move.shape[0] < num_frames:
344
+ pad_len = num_frames - move.shape[0]
345
+ move = torch.cat([move, torch.zeros(pad_len, dtype=torch.long, device=local_rank)])
346
+ view = torch.cat([view, torch.zeros(pad_len, dtype=torch.long, device=local_rank)])
347
+
348
+ additional_args = {
349
+ "image_cond": current_latent,
350
+ "move": move.unsqueeze(0),
351
+ "view": view.unsqueeze(0),
352
+ }
353
+
354
+ torch_gc()
355
+
356
+ with torch.no_grad():
357
+ samples = scheduler.sample(
358
+ model=dit,
359
+ text_encoder=text_encoder,
360
+ null_embedder=dit.y_embedder,
361
+ z_size=latent_size,
362
+ prompts=[prompt],
363
+ guidance_scale=TEXT_CFG_SCALE,
364
+ negative_prompts=[NEGATIVE_PROMPT],
365
+ device=torch.device(local_rank),
366
+ additional_args=additional_args,
367
+ )
368
+
369
+ decoded_chunk = vae.decode(samples).cpu()
370
+ video_buffer = torch.cat([video_buffer, decoded_chunk[:, :, 1:]], dim=2)
371
+
372
+ print(f"[InfWorld] Chunk {chunk_idx + 1} done. Total frames: {video_buffer.shape[2]}")
373
+ torch_gc()
374
+
375
+ # Save final video
376
+ video_name = f"{task_idx:04d}_{prompt[:30].replace(' ', '_')}"
377
+ save_path = os.path.join(output_dir, video_name)
378
+
379
+ quality = 10 if HIGH_QUALITY_SAVE else 5
380
+ save_silent_video(video_buffer.to(local_rank), save_path, fps=30, quality=quality)
381
+ print(f"[InfWorld] Saved: {save_path}.mp4")
382
+
383
+ if __name__ == "__main__":
384
+ main()
scripts/upload_to_hf.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Upload Infinite-World repo to Hugging Face Hub (including checkpoints).
4
+
5
+ Prerequisites:
6
+ 1. pip install huggingface_hub
7
+ 2. huggingface-cli login # or: from huggingface_hub import login; login()
8
+
9
+ Usage:
10
+ cd infinite-world
11
+ python scripts/upload_to_hf.py [REPO_ID]
12
+
13
+ Examples:
14
+ python scripts/upload_to_hf.py
15
+ python scripts/upload_to_hf.py your-username/infinite-world
16
+ """
17
+
18
+ import os
19
+ import sys
20
+
21
+ # Project root = parent of scripts/
22
+ PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
23
+
24
+
25
+ def main():
26
+ try:
27
+ from huggingface_hub import HfApi, create_repo, whoami
28
+ except ImportError:
29
+ print("Install: pip install huggingface_hub")
30
+ sys.exit(1)
31
+
32
+ repo_id = (
33
+ (sys.argv[1] if len(sys.argv) > 1 else None)
34
+ or os.environ.get("HF_REPO_ID")
35
+ or "MeiGen-AI/Infinite-World"
36
+ )
37
+
38
+ # Check login first (avoid 401 later)
39
+ try:
40
+ info = whoami()
41
+ print(f"[HF] Logged in as: {info.get('name', info.get('type', '?'))}")
42
+ except Exception as e:
43
+ print("[HF] Not logged in or token invalid (401).")
44
+ print(" Run: huggingface-cli login")
45
+ print(" Get a token with WRITE at: https://huggingface.co/settings/tokens")
46
+ print(" For org repo MeiGen-AI/Infinite-World, your account must have write access to the MeiGen-AI org.")
47
+ sys.exit(1)
48
+
49
+ api = HfApi()
50
+ repo_type = "model"
51
+
52
+ # Create repo if it doesn't exist (skip if 401; repo may already exist)
53
+ try:
54
+ create_repo(repo_id, repo_type=repo_type, exist_ok=True)
55
+ print(f"[HF] Repo ready: https://huggingface.co/{repo_id}")
56
+ except Exception as e:
57
+ err = str(e).lower()
58
+ if "401" in err or "unauthorized" in err:
59
+ print("[HF] No write permission for this repo. Fix: use a token with write access; for MeiGen-AI/Infinite-World, be a member of MeiGen-AI org or use the org token.")
60
+ sys.exit(1)
61
+ print(f"[HF] Create repo: {e}")
62
+ # Continue; repo might already exist
63
+
64
+ # Exclude cache/outputs, keep checkpoints and code
65
+ ignore_patterns = [
66
+ "__pycache__",
67
+ "*.pyc",
68
+ ".git",
69
+ "outputs",
70
+ ".cursor",
71
+ "*.egg-info",
72
+ ".eggs",
73
+ ]
74
+
75
+ print(f"[HF] Uploading from {PROJECT_ROOT} to {repo_id} ...")
76
+ api.upload_folder(
77
+ folder_path=PROJECT_ROOT,
78
+ repo_id=repo_id,
79
+ repo_type=repo_type,
80
+ ignore_patterns=ignore_patterns,
81
+ )
82
+ print(f"[HF] Done: https://huggingface.co/{repo_id}")
83
+
84
+
85
+ if __name__ == "__main__":
86
+ main()
setup_project.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Setup script to copy and adapt source files from hg-research-hub to infinite-world.
3
+ This creates a standalone project without external dependencies.
4
+ """
5
+
6
+ import os
7
+ import re
8
+ import shutil
9
+
10
+ # Source and target directories
11
+ SRC_BASE = '/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/wuruiqi/hg-research-hub/source'
12
+ DST_BASE = '/mnt/dolphinfs/ssd_pool/docker/user/hadoop-videogen-hl/hadoop-camera3d/wuruiqi/infinite-world/infworld'
13
+
14
+ # Files to copy and their destination subdirectories
15
+ FILES_TO_COPY = {
16
+ # Models
17
+ 'meigen/model_wanx_multi_action_v2v_convenc_locmem_slidewindow_temp_sample_mask_attn_real_checkpointing.py': 'models/dit_model.py',
18
+ 'meigen/rectified_flow_wanx_t2v_action.py': 'models/scheduler.py',
19
+ 'meigen/checkpoint.py': 'models/checkpoint.py',
20
+ 'meigen/umt5.py': 'models/umt5.py',
21
+ 'meigen/t5.py': 'models/t5.py',
22
+
23
+ # VAE
24
+ 'vae/__init__.py': 'vae/__init__.py',
25
+ 'vae/wan/vae.py': 'vae/vae.py',
26
+
27
+ # CLIP
28
+ 'clip/clip.py': 'clip/clip.py',
29
+ 'clip/tokenizers.py': 'clip/tokenizers.py',
30
+ 'clip/xlm_roberta.py': 'clip/xlm_roberta.py',
31
+
32
+ # Context Parallel
33
+ 'context_parallel/context_parallel_util.py': 'context_parallel/context_parallel_util.py',
34
+
35
+ # Utils
36
+ 'dataset/utils.py': 'utils/data_utils.py',
37
+ 'dataset/prepare_dataloader.py': 'utils/prepare_dataloader.py',
38
+
39
+ # OpenSora (for registry and dataset utils)
40
+ 'opensora/utils/dataset_utils.py': 'utils/dataset_utils.py',
41
+ 'opensora/registry.py': 'utils/registry.py',
42
+ }
43
+
44
+ # Import replacements (old pattern -> new pattern)
45
+ IMPORT_REPLACEMENTS = [
46
+ # Models
47
+ (r'from source\.meigen\.checkpoint', 'from infworld.models.checkpoint'),
48
+ (r'from source\.meigen\.model_wanx_multi_action', 'from infworld.models.dit_model'),
49
+ (r'from source\.meigen\.rectified_flow_wanx_t2v_action', 'from infworld.models.scheduler'),
50
+ (r'from source\.meigen\.umt5', 'from infworld.models.umt5'),
51
+ (r'from source\.meigen\.t5', 'from infworld.models.t5'),
52
+ (r'from source\.meigen', 'from infworld.models'),
53
+
54
+ # Context Parallel
55
+ (r'from source\.context_parallel\.context_parallel_util', 'from infworld.context_parallel.context_parallel_util'),
56
+ (r'from source\.context_parallel import context_parallel_util', 'from infworld.context_parallel import context_parallel_util'),
57
+
58
+ # VAE
59
+ (r'from source\.vae\.wan\.vae', 'from infworld.vae.vae'),
60
+ (r'from source\.vae\.cogvideo\.autoencoder_kl_cogvideox', 'from infworld.vae.vae'),
61
+ (r'from source\.vae', 'from infworld.vae'),
62
+ (r'from source\.opensora\.registry import MODELS', '# Registry disabled for standalone'),
63
+
64
+ # CLIP
65
+ (r'from source\.clip\.clip', 'from infworld.clip.clip'),
66
+ (r'from source\.clip\.tokenizers', 'from infworld.clip.tokenizers'),
67
+ (r'from source\.clip\.xlm_roberta', 'from infworld.clip.xlm_roberta'),
68
+ (r'from source\.clip', 'from infworld.clip'),
69
+
70
+ # Dataset utils
71
+ (r'from source\.dataset\.utils', 'from infworld.utils.data_utils'),
72
+ (r'from source\.dataset\.prepare_dataloader', 'from infworld.utils.prepare_dataloader'),
73
+ (r'from source\.opensora\.utils\.dataset_utils', 'from infworld.utils.dataset_utils'),
74
+ (r'from source\.opensora\.registry', 'from infworld.utils.registry'),
75
+ ]
76
+
77
+
78
+ def ensure_dir(path):
79
+ """Create directory if it doesn't exist."""
80
+ os.makedirs(os.path.dirname(path), exist_ok=True)
81
+
82
+
83
+ def copy_and_transform(src_path, dst_path):
84
+ """Copy file and transform imports."""
85
+ print(f"Copying: {src_path} -> {dst_path}")
86
+
87
+ ensure_dir(dst_path)
88
+
89
+ with open(src_path, 'r', encoding='utf-8') as f:
90
+ content = f.read()
91
+
92
+ # Apply import replacements
93
+ for old_pattern, new_pattern in IMPORT_REPLACEMENTS:
94
+ content = re.sub(old_pattern, new_pattern, content)
95
+
96
+ with open(dst_path, 'w', encoding='utf-8') as f:
97
+ f.write(content)
98
+
99
+
100
+ def create_init_files():
101
+ """Create __init__.py files for all packages."""
102
+ packages = ['infworld', 'infworld/models', 'infworld/vae', 'infworld/clip',
103
+ 'infworld/context_parallel', 'infworld/utils', 'infworld/configs']
104
+
105
+ for pkg in packages:
106
+ init_path = os.path.join(DST_BASE, '..', pkg, '__init__.py')
107
+ init_path = os.path.normpath(init_path)
108
+ ensure_dir(init_path)
109
+
110
+ if not os.path.exists(init_path):
111
+ with open(init_path, 'w') as f:
112
+ f.write(f'# {pkg} package\n')
113
+ print(f"Created: {init_path}")
114
+
115
+
116
+ def main():
117
+ print("=" * 60)
118
+ print("Setting up Infinite World standalone project")
119
+ print("=" * 60)
120
+
121
+ # Create package directories
122
+ create_init_files()
123
+
124
+ # Copy and transform files
125
+ for src_rel, dst_rel in FILES_TO_COPY.items():
126
+ src_path = os.path.join(SRC_BASE, src_rel)
127
+ dst_path = os.path.join(DST_BASE, dst_rel)
128
+
129
+ if os.path.exists(src_path):
130
+ copy_and_transform(src_path, dst_path)
131
+ else:
132
+ print(f"WARNING: Source file not found: {src_path}")
133
+
134
+ print("\n" + "=" * 60)
135
+ print("Setup complete!")
136
+ print("=" * 60)
137
+
138
+
139
+ if __name__ == '__main__':
140
+ main()