bartduis claude opus 4.8 (1m context) commited on
Commit
e298226
·
0 Parent(s):

Initial public release

Browse files

Co-authored-by: claude opus 4.8 (1m context) <claude opus 4.8 (1m context)@users.noreply.huggingface.co>

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +86 -0
  2. .gitignore +6 -0
  3. .gradio/cached_examples/31/Depth (disparity, magma)/00514d1d228e1970f9a5/image.png +3 -0
  4. .gradio/cached_examples/31/Depth (disparity, magma)/21da509194782c87b704/image.png +0 -0
  5. .gradio/cached_examples/31/Depth (disparity, magma)/282b8de47381433b20ba/image.png +3 -0
  6. .gradio/cached_examples/31/Depth (disparity, magma)/72f252326e89ba602db3/image.png +0 -0
  7. .gradio/cached_examples/31/Depth (disparity, magma)/77eb75a4ae32a2779670/image.png +3 -0
  8. .gradio/cached_examples/31/Depth (disparity, magma)/b3a0c160552d198bcca2/image.png +3 -0
  9. .gradio/cached_examples/31/Interactive 3D point cloud/6351a7c3b27ea01e3670/cloud_45fd4adef349.glb +3 -0
  10. .gradio/cached_examples/31/Interactive 3D point cloud/a42239d35b0256820e21/cloud_82bf2e710be0.glb +3 -0
  11. .gradio/cached_examples/31/Interactive 3D point cloud/a600814ba3ef00eacfeb/cloud_9a8d39949ba7.glb +3 -0
  12. .gradio/cached_examples/31/Interactive 3D point cloud/a6b517c351861e68aa8f/cloud_6f14c0ff2da6.glb +3 -0
  13. .gradio/cached_examples/31/Interactive 3D point cloud/a93a97926162980d549a/cloud_53c7df1dbc77.glb +3 -0
  14. .gradio/cached_examples/31/Interactive 3D point cloud/e208d09a8d563ea27323/cloud_a7b919815dba.glb +3 -0
  15. .gradio/cached_examples/31/RGB image/4763ceeb1dd670d3f927/image.png +3 -0
  16. .gradio/cached_examples/31/RGB image/530eb79823e527aed6bf/image.png +3 -0
  17. .gradio/cached_examples/31/RGB image/65c25b8debef84e0027b/image.png +3 -0
  18. .gradio/cached_examples/31/RGB image/6d51368904340f3a7f23/image.png +3 -0
  19. .gradio/cached_examples/31/RGB image/91174cfa8f5a5909a013/image.png +3 -0
  20. .gradio/cached_examples/31/RGB image/d68b76efe33346efd7e3/image.png +3 -0
  21. .gradio/cached_examples/31/indices.csv +6 -0
  22. .gradio/cached_examples/31/log.csv +7 -0
  23. .gradio/cached_examples/32/Depth (disparity, magma)/0891e8543770d0db2151/image.png +0 -0
  24. .gradio/cached_examples/32/Depth (disparity, magma)/38d56c7397d95d524055/image.png +3 -0
  25. .gradio/cached_examples/32/Depth (disparity, magma)/9a36d2e382db4e333742/image.png +3 -0
  26. .gradio/cached_examples/32/Interactive 3D point cloud/674110f2aecaaf5effa8/cloud_f9d62ed7d997.glb +3 -0
  27. .gradio/cached_examples/32/Interactive 3D point cloud/85ee5283a423852aa42d/cloud_8a6b0d1109ae.glb +3 -0
  28. .gradio/cached_examples/32/Interactive 3D point cloud/e7f9024213e4656b8beb/cloud_a6225bea9d7e.glb +3 -0
  29. .gradio/cached_examples/32/RGB image/3c2c4fa7881bb773646d/image.png +3 -0
  30. .gradio/cached_examples/32/RGB image/87aaba9c524a915404cd/image.png +3 -0
  31. .gradio/cached_examples/32/RGB image/e64a0aff7688d0635057/image.png +3 -0
  32. .gradio/cached_examples/32/indices.csv +3 -0
  33. .gradio/cached_examples/32/log.csv +4 -0
  34. .ruff_cache/.gitignore +2 -0
  35. .ruff_cache/0.5.5/12279076348749595670 +0 -0
  36. .ruff_cache/CACHEDIR.TAG +1 -0
  37. LICENSE +202 -0
  38. LICENSE-WEIGHTS +14 -0
  39. README.md +99 -0
  40. app.py +642 -0
  41. assets/examples/alley.png +3 -0
  42. assets/examples/castle.png +3 -0
  43. assets/examples/yosemite.png +3 -0
  44. flux_rgbd/__init__.py +13 -0
  45. flux_rgbd/_flux2/__init__.py +11 -0
  46. flux_rgbd/_flux2/autoencoder.py +499 -0
  47. flux_rgbd/_flux2/constants.py +207 -0
  48. flux_rgbd/_flux2/model.py +674 -0
  49. flux_rgbd/depth/__init__.py +8 -0
  50. flux_rgbd/depth/preprocess.py +123 -0
.gitattributes ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz 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
+ .gradio/cached_examples/30/Depth[[:space:]](disparity,[[:space:]]magma)/5e96afc67581e463f100/image.png filter=lfs diff=lfs merge=lfs -text
37
+ .gradio/cached_examples/30/Depth[[:space:]](disparity,[[:space:]]magma)/9220cd48d295ca353a6b/image.png filter=lfs diff=lfs merge=lfs -text
38
+ .gradio/cached_examples/30/Depth[[:space:]](disparity,[[:space:]]magma)/c8838412e5dee9697701/image.png filter=lfs diff=lfs merge=lfs -text
39
+ .gradio/cached_examples/30/Depth[[:space:]](disparity,[[:space:]]magma)/f7095c71dfb49f7e3a2a/image.png filter=lfs diff=lfs merge=lfs -text
40
+ .gradio/cached_examples/30/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/a2b2ed4415d6c0310a36/cloud_05cfcce5d5e6.glb filter=lfs diff=lfs merge=lfs -text
41
+ .gradio/cached_examples/30/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/b72cecf121cf47496c82/cloud_394dafbecc42.glb filter=lfs diff=lfs merge=lfs -text
42
+ .gradio/cached_examples/30/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/b8c85f926a8d6d09ee32/cloud_dbf7146aa8d6.glb filter=lfs diff=lfs merge=lfs -text
43
+ .gradio/cached_examples/30/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/bb6dcdbd8cf58c73cd2c/cloud_a05855c7716b.glb filter=lfs diff=lfs merge=lfs -text
44
+ .gradio/cached_examples/30/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/c3390d29364b0fab5237/cloud_14b821bf5517.glb filter=lfs diff=lfs merge=lfs -text
45
+ .gradio/cached_examples/30/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/f90705e1df43a796cd63/cloud_41032084d765.glb filter=lfs diff=lfs merge=lfs -text
46
+ .gradio/cached_examples/30/RGB[[:space:]]image/02ab28426d5807f6943a/image.png filter=lfs diff=lfs merge=lfs -text
47
+ .gradio/cached_examples/30/RGB[[:space:]]image/179b01cce83461a9a19a/image.png filter=lfs diff=lfs merge=lfs -text
48
+ .gradio/cached_examples/30/RGB[[:space:]]image/1965504ccdd23a0da632/image.png filter=lfs diff=lfs merge=lfs -text
49
+ .gradio/cached_examples/30/RGB[[:space:]]image/3f5824c31471ef256471/image.png filter=lfs diff=lfs merge=lfs -text
50
+ .gradio/cached_examples/30/RGB[[:space:]]image/7ebf7b4b6db056927956/image.png filter=lfs diff=lfs merge=lfs -text
51
+ .gradio/cached_examples/30/RGB[[:space:]]image/dba880c3bec4afd1a9ff/image.png filter=lfs diff=lfs merge=lfs -text
52
+ .gradio/cached_examples/31/Depth[[:space:]](disparity,[[:space:]]magma)/b8e54b0d5e39f055f164/image.png filter=lfs diff=lfs merge=lfs -text
53
+ .gradio/cached_examples/31/Depth[[:space:]](disparity,[[:space:]]magma)/e3b3eed2144fe2f212bb/image.png filter=lfs diff=lfs merge=lfs -text
54
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/7b2796d4a9ff918e3ff0/cloud_16bed495fda3.glb filter=lfs diff=lfs merge=lfs -text
55
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/d21fe0e76f4b13cc03c4/cloud_b91d63f1da9e.glb filter=lfs diff=lfs merge=lfs -text
56
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/ff979f9418cc0388e99b/cloud_006af054b659.glb filter=lfs diff=lfs merge=lfs -text
57
+ .gradio/cached_examples/31/RGB[[:space:]]image/1a6ed5112df1d233314c/image.png filter=lfs diff=lfs merge=lfs -text
58
+ .gradio/cached_examples/31/RGB[[:space:]]image/c14e4a0757ca88e19135/image.png filter=lfs diff=lfs merge=lfs -text
59
+ .gradio/cached_examples/31/RGB[[:space:]]image/e858eac28e2a39b56e43/image.png filter=lfs diff=lfs merge=lfs -text
60
+ assets/examples/alley.png filter=lfs diff=lfs merge=lfs -text
61
+ assets/examples/castle.png filter=lfs diff=lfs merge=lfs -text
62
+ assets/examples/yosemite.png filter=lfs diff=lfs merge=lfs -text
63
+ .gradio/cached_examples/31/Depth[[:space:]](disparity,[[:space:]]magma)/00514d1d228e1970f9a5/image.png filter=lfs diff=lfs merge=lfs -text
64
+ .gradio/cached_examples/31/Depth[[:space:]](disparity,[[:space:]]magma)/282b8de47381433b20ba/image.png filter=lfs diff=lfs merge=lfs -text
65
+ .gradio/cached_examples/31/Depth[[:space:]](disparity,[[:space:]]magma)/77eb75a4ae32a2779670/image.png filter=lfs diff=lfs merge=lfs -text
66
+ .gradio/cached_examples/31/Depth[[:space:]](disparity,[[:space:]]magma)/b3a0c160552d198bcca2/image.png filter=lfs diff=lfs merge=lfs -text
67
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/6351a7c3b27ea01e3670/cloud_45fd4adef349.glb filter=lfs diff=lfs merge=lfs -text
68
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/a42239d35b0256820e21/cloud_82bf2e710be0.glb filter=lfs diff=lfs merge=lfs -text
69
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/a600814ba3ef00eacfeb/cloud_9a8d39949ba7.glb filter=lfs diff=lfs merge=lfs -text
70
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/a6b517c351861e68aa8f/cloud_6f14c0ff2da6.glb filter=lfs diff=lfs merge=lfs -text
71
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/a93a97926162980d549a/cloud_53c7df1dbc77.glb filter=lfs diff=lfs merge=lfs -text
72
+ .gradio/cached_examples/31/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/e208d09a8d563ea27323/cloud_a7b919815dba.glb filter=lfs diff=lfs merge=lfs -text
73
+ .gradio/cached_examples/31/RGB[[:space:]]image/4763ceeb1dd670d3f927/image.png filter=lfs diff=lfs merge=lfs -text
74
+ .gradio/cached_examples/31/RGB[[:space:]]image/530eb79823e527aed6bf/image.png filter=lfs diff=lfs merge=lfs -text
75
+ .gradio/cached_examples/31/RGB[[:space:]]image/65c25b8debef84e0027b/image.png filter=lfs diff=lfs merge=lfs -text
76
+ .gradio/cached_examples/31/RGB[[:space:]]image/6d51368904340f3a7f23/image.png filter=lfs diff=lfs merge=lfs -text
77
+ .gradio/cached_examples/31/RGB[[:space:]]image/91174cfa8f5a5909a013/image.png filter=lfs diff=lfs merge=lfs -text
78
+ .gradio/cached_examples/31/RGB[[:space:]]image/d68b76efe33346efd7e3/image.png filter=lfs diff=lfs merge=lfs -text
79
+ .gradio/cached_examples/32/Depth[[:space:]](disparity,[[:space:]]magma)/38d56c7397d95d524055/image.png filter=lfs diff=lfs merge=lfs -text
80
+ .gradio/cached_examples/32/Depth[[:space:]](disparity,[[:space:]]magma)/9a36d2e382db4e333742/image.png filter=lfs diff=lfs merge=lfs -text
81
+ .gradio/cached_examples/32/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/674110f2aecaaf5effa8/cloud_f9d62ed7d997.glb filter=lfs diff=lfs merge=lfs -text
82
+ .gradio/cached_examples/32/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/85ee5283a423852aa42d/cloud_8a6b0d1109ae.glb filter=lfs diff=lfs merge=lfs -text
83
+ .gradio/cached_examples/32/Interactive[[:space:]]3D[[:space:]]point[[:space:]]cloud/e7f9024213e4656b8beb/cloud_a6225bea9d7e.glb filter=lfs diff=lfs merge=lfs -text
84
+ .gradio/cached_examples/32/RGB[[:space:]]image/3c2c4fa7881bb773646d/image.png filter=lfs diff=lfs merge=lfs -text
85
+ .gradio/cached_examples/32/RGB[[:space:]]image/87aaba9c524a915404cd/image.png filter=lfs diff=lfs merge=lfs -text
86
+ .gradio/cached_examples/32/RGB[[:space:]]image/e64a0aff7688d0635057/image.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ flux_rgbd_outputs/
5
+ *.npy
6
+ artifacts/
.gradio/cached_examples/31/Depth (disparity, magma)/00514d1d228e1970f9a5/image.png ADDED

Git LFS Details

  • SHA256: 10879b58e6c03fde97b669315c00bf71bdeafb5d918ff9f25ac544564475aeef
  • Pointer size: 131 Bytes
  • Size of remote file: 364 kB
.gradio/cached_examples/31/Depth (disparity, magma)/21da509194782c87b704/image.png ADDED
.gradio/cached_examples/31/Depth (disparity, magma)/282b8de47381433b20ba/image.png ADDED

Git LFS Details

  • SHA256: 9e6f89e921019d1bf9caa044e77785469ba45b2ed21d080f39be97f06cd701dd
  • Pointer size: 131 Bytes
  • Size of remote file: 157 kB
.gradio/cached_examples/31/Depth (disparity, magma)/72f252326e89ba602db3/image.png ADDED
.gradio/cached_examples/31/Depth (disparity, magma)/77eb75a4ae32a2779670/image.png ADDED

Git LFS Details

  • SHA256: 4c8916e160ea6d6c6b792075a1f924eacf09840e44baab2df857b3bb04b26463
  • Pointer size: 131 Bytes
  • Size of remote file: 103 kB
.gradio/cached_examples/31/Depth (disparity, magma)/b3a0c160552d198bcca2/image.png ADDED

Git LFS Details

  • SHA256: 6e6b73e6d887e1d46d16499daa723b390882e9bf839a5bc46177a202b2722687
  • Pointer size: 131 Bytes
  • Size of remote file: 144 kB
.gradio/cached_examples/31/Interactive 3D point cloud/6351a7c3b27ea01e3670/cloud_45fd4adef349.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:318e55582668f25686e92a4db1b6c5d3f75434d047ffd58c1de526abd12473c4
3
+ size 3068480
.gradio/cached_examples/31/Interactive 3D point cloud/a42239d35b0256820e21/cloud_82bf2e710be0.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:da22a39f84033e0f93e395d90715aedd4c15de191ac5740704e8036a3c2238f2
3
+ size 3839248
.gradio/cached_examples/31/Interactive 3D point cloud/a600814ba3ef00eacfeb/cloud_9a8d39949ba7.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c9c9c01e77f9425692ccb77dcacfd10690f42aa2f59f5086df11e313f98bca4a
3
+ size 1557100
.gradio/cached_examples/31/Interactive 3D point cloud/a6b517c351861e68aa8f/cloud_6f14c0ff2da6.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e8143c842b204228b93431b2c937d2e531d477854a1177c4a23076d027ae63e7
3
+ size 4057104
.gradio/cached_examples/31/Interactive 3D point cloud/a93a97926162980d549a/cloud_53c7df1dbc77.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6f7e2b84bf61c50398f8fdb3b0f84ae218d5d3991c268f96f3b6184bf02a021d
3
+ size 4050768
.gradio/cached_examples/31/Interactive 3D point cloud/e208d09a8d563ea27323/cloud_a7b919815dba.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a2e5928c97bf15bc99c66232dbcd8fd09b9dcb5401ad945409c5e08694314b5d
3
+ size 3992720
.gradio/cached_examples/31/RGB image/4763ceeb1dd670d3f927/image.png ADDED

Git LFS Details

  • SHA256: f216cef36f008e9aebbd1a42fd3e8a2ea7f750120dd7da53c73e29603095cbbb
  • Pointer size: 131 Bytes
  • Size of remote file: 431 kB
.gradio/cached_examples/31/RGB image/530eb79823e527aed6bf/image.png ADDED

Git LFS Details

  • SHA256: d3703a4031f55b4fccaa71f38375896c2f165c8932ac37064bd8ac81ce46ce76
  • Pointer size: 131 Bytes
  • Size of remote file: 467 kB
.gradio/cached_examples/31/RGB image/65c25b8debef84e0027b/image.png ADDED

Git LFS Details

  • SHA256: 24647b9483e60b0f5f38fc1bf2d47e8504ab9d03484817a58a2ee869d4559c59
  • Pointer size: 131 Bytes
  • Size of remote file: 374 kB
.gradio/cached_examples/31/RGB image/6d51368904340f3a7f23/image.png ADDED

Git LFS Details

  • SHA256: 4fc5d7fd792da9837b7e716cd6b35e01c628db0056eeb5fe807f1c93c87e8a51
  • Pointer size: 131 Bytes
  • Size of remote file: 472 kB
.gradio/cached_examples/31/RGB image/91174cfa8f5a5909a013/image.png ADDED

Git LFS Details

  • SHA256: 6ce3a9428a12cf898421b3ab2f1cbed13e7cb87874ebf238845111ca32293fdc
  • Pointer size: 131 Bytes
  • Size of remote file: 332 kB
.gradio/cached_examples/31/RGB image/d68b76efe33346efd7e3/image.png ADDED

Git LFS Details

  • SHA256: 7f5bd27a66d93922348c7782de1dc9c0a1b700591e30e8549a0c683bc435b5a1
  • Pointer size: 131 Bytes
  • Size of remote file: 410 kB
.gradio/cached_examples/31/indices.csv ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ 0
2
+ 1
3
+ 2
4
+ 3
5
+ 4
6
+ 5
.gradio/cached_examples/31/log.csv ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ RGB image,"Depth (disparity, magma)",Interactive 3D point cloud,Status,timestamp
2
+ "{""path"": "".gradio/cached_examples/31/RGB image/91174cfa8f5a5909a013/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/dfd5ecf1e795fbfd7f794edd94c8f06e7dc0f0008a15814286a7a11b0e3bda0a/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Depth (disparity, magma)/72f252326e89ba602db3/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/7111df1c084d7d80e87ed0ba942ef806ebb82b0ea3358242a9471e87ed025aca/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Interactive 3D point cloud/e208d09a8d563ea27323/cloud_a7b919815dba.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/b7842ce128885af0de98f62956ea71483eb3ca75a5af89df81202f0c19de2502/cloud_a7b919815dba.glb"", ""size"": null, ""orig_name"": ""cloud_a7b919815dba.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","joint · 17.0 s · depth median=1.06 p5=0.55 p95=1.39 · 249,485 points",2026-06-12 07:14:17.591889
3
+ "{""path"": "".gradio/cached_examples/31/RGB image/d68b76efe33346efd7e3/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/db51824edbde88fd4de0749ffb077c98b27d9ee96017d42db904e2c763579606/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Depth (disparity, magma)/21da509194782c87b704/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/e3e01501ae08e1438d11238cf43d427406c012cf0b287f1820975b5cc461f224/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Interactive 3D point cloud/a93a97926162980d549a/cloud_53c7df1dbc77.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/96342e925c3ba767021690b60f3715015db850198dccd2fd3e5245468dbcafee/cloud_53c7df1dbc77.glb"", ""size"": null, ""orig_name"": ""cloud_53c7df1dbc77.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","joint · 14.8 s · depth median=1.04 p5=0.57 p95=1.37 · 253,113 points",2026-06-12 07:14:32.666816
4
+ "{""path"": "".gradio/cached_examples/31/RGB image/6d51368904340f3a7f23/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/9919ff6eef6ba9aa87da068492e8a80dd0e5f62f7f27dcc20cb3de5e975ef2b0/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Depth (disparity, magma)/00514d1d228e1970f9a5/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/0ffd5b599e783b31637214af5bfb99c6fea27cce8df77021893d3f63e9e1f5aa/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Interactive 3D point cloud/a600814ba3ef00eacfeb/cloud_9a8d39949ba7.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/3e1f8dedd7baf5f8977d2f5e7488413ca6499045d5939ee6371c86fb2b039e54/cloud_9a8d39949ba7.glb"", ""size"": null, ""orig_name"": ""cloud_9a8d39949ba7.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","joint · 14.9 s · depth median=0.84 p5=0.37 p95=2.21 · 97,259 points",2026-06-12 07:14:47.729293
5
+ "{""path"": "".gradio/cached_examples/31/RGB image/530eb79823e527aed6bf/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/0bbd8ccaaeccd0e353685be2f1146bbbdd4e2a42d5350fe13bd182a27f505a61/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Depth (disparity, magma)/b3a0c160552d198bcca2/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/91abce7d476b85c56d457cb3526e279dbf126ff0d36d266cbde7b0ba97d0714f/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Interactive 3D point cloud/a42239d35b0256820e21/cloud_82bf2e710be0.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/fff39c18ed5428d50126dee01f175fe9339a93dedb01c226549e19234c3a18d3/cloud_82bf2e710be0.glb"", ""size"": null, ""orig_name"": ""cloud_82bf2e710be0.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","joint · 14.9 s · depth median=0.98 p5=0.65 p95=1.38 · 239,893 points",2026-06-12 07:15:02.835368
6
+ "{""path"": "".gradio/cached_examples/31/RGB image/65c25b8debef84e0027b/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/eed4a59e42c6c0c9e9cd120b9f5900522c0f4006c54aec5e3466300cce5f5c3d/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Depth (disparity, magma)/77eb75a4ae32a2779670/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/c9434cd1a2b855cdf7615290ea2d29c8c5418e0793a3cd859e55150891bc495a/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Interactive 3D point cloud/a6b517c351861e68aa8f/cloud_6f14c0ff2da6.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/3fe22788a5f8d0a6d274d5b367522db4306e5dca95dbfe1bd1cfb1c2f1c4c6f0/cloud_6f14c0ff2da6.glb"", ""size"": null, ""orig_name"": ""cloud_6f14c0ff2da6.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","joint · 14.8 s · depth median=0.90 p5=0.63 p95=1.65 · 253,509 points",2026-06-12 07:15:17.944777
7
+ "{""path"": "".gradio/cached_examples/31/RGB image/4763ceeb1dd670d3f927/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/71b22a185a03aa169cdca016381da0b4179ed403b377b7fe4cb005c97797b8ee/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Depth (disparity, magma)/282b8de47381433b20ba/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/9ef7335cc9a72e4364a920bf4431a69841eff7326a683f601376302cc1ddaa24/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/31/Interactive 3D point cloud/6351a7c3b27ea01e3670/cloud_45fd4adef349.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/92aa7dea6a27cd503ba5c2235856b6e28f283700cbfc51764f794167491464e4/cloud_45fd4adef349.glb"", ""size"": null, ""orig_name"": ""cloud_45fd4adef349.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","joint · 14.8 s · depth median=0.82 p5=0.07 p95=2.22 · 191,720 points",2026-06-12 07:15:32.973544
.gradio/cached_examples/32/Depth (disparity, magma)/0891e8543770d0db2151/image.png ADDED
.gradio/cached_examples/32/Depth (disparity, magma)/38d56c7397d95d524055/image.png ADDED

Git LFS Details

  • SHA256: 01e1fef5b2755c3e03dddc46962f991d6afe86ba360bbac4f5cf6938a3a1b0f1
  • Pointer size: 131 Bytes
  • Size of remote file: 108 kB
.gradio/cached_examples/32/Depth (disparity, magma)/9a36d2e382db4e333742/image.png ADDED

Git LFS Details

  • SHA256: 5f4d00494d4feaa228b35900730ea1dfbd407f72bdfe494c606c42145935557f
  • Pointer size: 131 Bytes
  • Size of remote file: 181 kB
.gradio/cached_examples/32/Interactive 3D point cloud/674110f2aecaaf5effa8/cloud_f9d62ed7d997.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c5d3183aec16883d35c85a8d6826d8c437cb1630c9d10ff08a5f67116dd3fd65
3
+ size 3730060
.gradio/cached_examples/32/Interactive 3D point cloud/85ee5283a423852aa42d/cloud_8a6b0d1109ae.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:98a8873b3e5893e5a7dc8afdaca3c8f92484cc6981f493bb834bfed6ca019caf
3
+ size 1768556
.gradio/cached_examples/32/Interactive 3D point cloud/e7f9024213e4656b8beb/cloud_a6225bea9d7e.glb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4ca71c04f12756b0bfb26002b18e7221bf22ea36a5e23824a0a01cf753806f9e
3
+ size 3742080
.gradio/cached_examples/32/RGB image/3c2c4fa7881bb773646d/image.png ADDED

Git LFS Details

  • SHA256: a125ca90ef87e753901fde94cd0befefb05c4cff1c4c856ade115aa91327b8f1
  • Pointer size: 131 Bytes
  • Size of remote file: 434 kB
.gradio/cached_examples/32/RGB image/87aaba9c524a915404cd/image.png ADDED

Git LFS Details

  • SHA256: f66d7a4e1eaed6bfa4746a058decca0362b186f6fa43ecd6e4b2940640a1d227
  • Pointer size: 131 Bytes
  • Size of remote file: 469 kB
.gradio/cached_examples/32/RGB image/e64a0aff7688d0635057/image.png ADDED

Git LFS Details

  • SHA256: f6f5edfd3b450ddb42a83bccbb9ecbecb9c6125f34e8863f89066b2f0a5b52b8
  • Pointer size: 131 Bytes
  • Size of remote file: 372 kB
.gradio/cached_examples/32/indices.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ 0
2
+ 1
3
+ 2
.gradio/cached_examples/32/log.csv ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ RGB image,"Depth (disparity, magma)",Interactive 3D point cloud,Status,timestamp
2
+ "{""path"": "".gradio/cached_examples/32/RGB image/3c2c4fa7881bb773646d/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/1ed5fba88325554dd8fde55f43d90e4b52d96bd770ae9abb55558a721d2ad4dc/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/32/Depth (disparity, magma)/38d56c7397d95d524055/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/675d608d8081b140ee360ac99a11615574a95a1f575dd55ceb8941f405daac0f/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/32/Interactive 3D point cloud/674110f2aecaaf5effa8/cloud_f9d62ed7d997.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/6396a2958022a63007f08ff0882cd34405d96b6cee56b1a69444e1ef08649811/cloud_f9d62ed7d997.glb"", ""size"": null, ""orig_name"": ""cloud_f9d62ed7d997.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","i2d · 5.4 s · depth median=0.65 p5=0.37 p95=3.31 · 233,069 points",2026-06-12 07:15:38.629118
3
+ "{""path"": "".gradio/cached_examples/32/RGB image/e64a0aff7688d0635057/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/3a7962dcf7f6b1d7db2e1a0e7a625daecd56aebbac87a934bd805b05c026ee6c/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/32/Depth (disparity, magma)/9a36d2e382db4e333742/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/647b0d2a6553dedf70711720e7a0dd7c2d0c1778bbbc29c066ebbdb74137ba43/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/32/Interactive 3D point cloud/85ee5283a423852aa42d/cloud_8a6b0d1109ae.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/9b0b10012948a25b48290aefa846f9ddb57792cae0bccf98ead298a0a18baf6e/cloud_8a6b0d1109ae.glb"", ""size"": null, ""orig_name"": ""cloud_8a6b0d1109ae.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","i2d · 5.0 s · depth median=1.18 p5=0.36 p95=2.33 · 110,475 points",2026-06-12 07:15:43.778321
4
+ "{""path"": "".gradio/cached_examples/32/RGB image/87aaba9c524a915404cd/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/782693cd71c502c69e559adc17a82d06dc75ece451c4745b286f74f8be98284d/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/32/Depth (disparity, magma)/0891e8543770d0db2151/image.png"", ""url"": ""/gradio_api/file=/tmp/gradio/112070edef52867273e2552aa71313e0447940a6eda5b9215a5202cc82c501a2/image.png"", ""size"": null, ""orig_name"": ""image.png"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","{""path"": "".gradio/cached_examples/32/Interactive 3D point cloud/e7f9024213e4656b8beb/cloud_a6225bea9d7e.glb"", ""url"": ""/gradio_api/file=/tmp/gradio/bd563503321e9ee362fcadf84021c707d2ddfb1ae60357b595f562a830465900/cloud_a6225bea9d7e.glb"", ""size"": null, ""orig_name"": ""cloud_a6225bea9d7e.glb"", ""mime_type"": null, ""is_stream"": false, ""meta"": {""_type"": ""gradio.FileData""}}","i2d · 5.0 s · depth median=0.72 p5=0.08 p95=2.41 · 233,820 points",2026-06-12 07:15:49.021708
.ruff_cache/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Automatically created by ruff.
2
+ *
.ruff_cache/0.5.5/12279076348749595670 ADDED
Binary file (13.5 kB). View file
 
.ruff_cache/CACHEDIR.TAG ADDED
@@ -0,0 +1 @@
 
 
1
+ Signature: 8a477f597d28d172789f06886806bc55
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 World Labs
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
LICENSE-WEIGHTS ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Creative Commons Attribution-NonCommercial 4.0 International (CC BY-NC 4.0)
2
+
3
+ Copyright (c) 2026 World Labs.
4
+
5
+ The MODEL WEIGHTS distributed for this project (e.g. via the Hugging Face
6
+ repository bartduis/modality_forcing) are licensed under the Creative Commons
7
+ Attribution-NonCommercial 4.0 International License. You are free to share and
8
+ adapt the weights for non-commercial purposes, provided you give appropriate
9
+ credit. THE WEIGHTS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
10
+
11
+ Full license text: https://creativecommons.org/licenses/by-nc/4.0/legalcode
12
+
13
+ The CODE in this repository is licensed separately under Apache-2.0 (see
14
+ LICENSE).
README.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Modality Forcing
3
+ emoji: 🏢
4
+ colorFrom: blue
5
+ colorTo: gray
6
+ sdk: gradio
7
+ sdk_version: 6.16.0
8
+ python_version: '3.12'
9
+ app_file: app.py
10
+ hardware: zero-h200
11
+ pinned: false
12
+ license: apache-2.0
13
+ short_description: Text → RGB + depth + 3D point cloud.
14
+ ---
15
+
16
+ <div align="center">
17
+
18
+ <h1>Modality Forcing for Scalable<br>Spatial Generation</h1>
19
+
20
+ [![Project Page](https://img.shields.io/badge/Project_Page-Modality_Forcing-blue?logo=googlechrome&logoColor=white)](https://modality-forcing.github.io/)
21
+ [![Code](https://img.shields.io/badge/Code-GitHub-181717?logo=github)](https://github.com/Duisterhof/modality-forcing)
22
+ [![Model](https://img.shields.io/badge/Model-modality__forcing-yellow?logo=huggingface)](https://huggingface.co/bartduis/modality_forcing)
23
+ [![arXiv](https://img.shields.io/badge/arXiv-2606.13676-b31b1b?logo=arxiv&logoColor=white)](https://arxiv.org/abs/2606.13676)
24
+
25
+ **[Bardienus Pieter Duisterhof](https://bart-ai.com)<sup>1,2</sup> · [Deva Ramanan](https://www.cs.cmu.edu/~deva/)<sup>1</sup> · [Jeffrey Ichnowski](https://ichnow.ski)<sup>1</sup> · [Justin Johnson](https://web.eecs.umich.edu/~justincj/)<sup>2</sup> · [Keunhong Park](https://keunhong.com)<sup>2</sup>**
26
+
27
+ **<sup>1</sup> Carnegie Mellon University &nbsp;&nbsp; <sup>2</sup> World Labs**
28
+
29
+ *Preprint, 2026*
30
+
31
+ <em>Modality Forcing turns a pretrained text-to-image model into a joint image–depth generator with a simple post-training recipe.</em>
32
+
33
+ </div>
34
+
35
+ ## Overview
36
+
37
+ This Space hosts the interactive demo of **Modality Forcing**: joint text → RGB + depth diffusion built on FLUX.2. A single DiT supports every permutation of conditional and joint generation by assigning a separate noise level to each modality:
38
+
39
+ | Mode | Input | Output |
40
+ |------|-------|--------|
41
+ | **Joint** | text prompt | RGB + depth + 3D point cloud |
42
+ | **Image → depth** | text + image | depth + 3D point cloud (any aspect ratio, letterbox resize) |
43
+
44
+ ## Abstract
45
+
46
+ Text-to-image (T2I) models contain rich spatial priors.
47
+ Synthesizing photorealistic, cluttered scenes requires an understanding of geometry, including perspective and relative scale.
48
+ Prior works adapt T2I models to leverage this prior for depth prediction, but they require dense depth data and involve complex recipes.
49
+ We propose **Modality Forcing**, a simple, scalable post-training recipe for joint image-depth generation using a single DiT trained on sparse depth data.
50
+ Modality Forcing enables conditional and joint generation of image and depth in any permutation by assigning separate noise levels per modality.
51
+ Per-modality decoders let us train on sparse, real-world depth and achieve strong, generalizable depth prediction.
52
+ We further show that Modality Forcing inherits the scalability of T2I pre-training: by training a set of T2I models from scratch (300M to 3B parameters), we find that larger models trained on more image data produce more accurate depth.
53
+ Our strongest model is competitive with state-of-the-art monocular depth estimators and reduces AbsRel by **57%** relative to existing joint image-depth generative models.
54
+ These results provide strong evidence that image generation is a scalable pre-training objective for spatial perception.
55
+
56
+ ## Space configuration
57
+
58
+ ### Weights
59
+
60
+ All weights are pulled from public repos — no token required:
61
+ [`bartduis/modality_forcing`](https://huggingface.co/bartduis/modality_forcing)
62
+ (DiT + FLUX.2 autoencoder, CC BY-NC 4.0) and
63
+ [`Qwen/Qwen3-8B`](https://huggingface.co/Qwen/Qwen3-8B) (text encoder).
64
+ Override the model repo via the `WEIGHTS_REPO` Space variable.
65
+
66
+ ### Hardware
67
+
68
+ Pinned to `zero-h200`. The runner builds the DiT on the `meta` device and
69
+ assigns checkpoint weights straight onto the GPU (`runner.from_pretrained`),
70
+ so the BF16 model (~24 GB) loads in ~8 s on the first invocation — skipping
71
+ ~45 s of throwaway random initialization — and stays resident for subsequent
72
+ calls within the same Space instance.
73
+
74
+ ### Avoiding the cold re-download
75
+
76
+ A Space instance that has gone to sleep starts a fresh container, which
77
+ re-downloads the weights (~24 GB DiT + ~16 GB Qwen3-8B + the FLUX.2 VAE)
78
+ before the load above can run. To skip that, attach **persistent storage**
79
+ (Settings → Storage, or mount an HF Bucket) and point the HF cache at it via
80
+ the `HF_HOME` Space variable so the weights survive restarts.
81
+
82
+ ## License
83
+
84
+ Code: Apache-2.0 (files derived from the FLUX.2 reference implementation —
85
+ `flux_rgbd/_flux2/`, `flux_rgbd/dit.py` — are Apache-2.0, Copyright Black
86
+ Forest Labs, with World Labs modifications). Model weights: CC BY-NC 4.0.
87
+
88
+ ## Citation
89
+
90
+ If you find Modality Forcing useful, please consider citing:
91
+
92
+ ```bibtex
93
+ @article{duisterhof2026mofo,
94
+ title = {Modality Forcing for Scalable Spatial Generation},
95
+ author = {Duisterhof, Bardienus Pieter and Ramanan, Deva and Ichnowski, Jeffrey and Johnson, Justin and Park, Keunhong},
96
+ journal = {arXiv preprint arXiv:2606.13676},
97
+ year = {2026}
98
+ }
99
+ ```
app.py ADDED
@@ -0,0 +1,642 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 World Labs.
3
+ """HuggingFace ZeroGPU Space for flux_rgbd.
4
+
5
+ Wraps the same generate-and-render path used by the local demo with
6
+ ``@spaces.GPU`` so HF ZeroGPU can attach a GPU per call. The model
7
+ loads at module-level (CPU), then moves to CUDA inside the first
8
+ decorated call.
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ import time
14
+ import uuid
15
+ from pathlib import Path
16
+
17
+ # The vendored flux_rgbd package lives next to this file.
18
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
19
+
20
+ import gradio as gr
21
+ import numpy as np
22
+ import spaces
23
+ import torch
24
+
25
+ from flux_rgbd import FluxRGBDRunner
26
+ from flux_rgbd.pointcloud import depth_edge_mask, statistical_outlier_mask
27
+
28
+ # BF16 checkpoint, downloaded from the Hub on first use. Override with the
29
+ # WEIGHTS_REPO env var (repo id or local path).
30
+ WEIGHTS_REPO = os.environ.get("WEIGHTS_REPO", "bartduis/modality_forcing")
31
+
32
+ DEFAULT_PROMPT = (
33
+ "A warm, inviting kitchen with a rustic-modern feel, where soft morning "
34
+ "light filters through a small window above the sink."
35
+ )
36
+
37
+ # Lazy-loaded runner. On ZeroGPU the model is loaded inside the first
38
+ # @spaces.GPU call so the import path costs nothing.
39
+ _runner: FluxRGBDRunner | None = None
40
+
41
+
42
+ def _ensure_runner() -> FluxRGBDRunner:
43
+ global _runner
44
+ if _runner is None:
45
+ # Use BF16 Qwen3-8B instead of Qwen3-8B-FP8 to avoid the
46
+ # finegrained-fp8 / deep-gemm kernel dependency, which currently
47
+ # hits a metadata.json parse bug on HF's kernels-community.
48
+ text_encoder = os.environ.get("TEXT_ENCODER_REPO", "Qwen/Qwen3-8B")
49
+ # Generation resolution must match the checkpoint's training resolution
50
+ # (512 for the default model, 1024 for the 1024 checkpoint). Set
51
+ # IMG_RESOLUTION=1024 alongside WEIGHTS_REPO when using the 1024 ckpt.
52
+ res = int(os.environ.get("IMG_RESOLUTION", "512"))
53
+ print(f"[boot] loading {WEIGHTS_REPO} @ {res}px (text encoder: {text_encoder})…",
54
+ flush=True)
55
+ _runner = FluxRGBDRunner.from_pretrained(
56
+ WEIGHTS_REPO, device="cuda",
57
+ dtype=torch.bfloat16, head_dtype=torch.float32,
58
+ text_encoder=text_encoder, img_hw=(res, res),
59
+ )
60
+ print("[boot] runner ready.", flush=True)
61
+ return _runner
62
+
63
+
64
+ # --- helpers (kept inline so the Space repo doesn't depend on demo/app_lib) ---
65
+
66
+ _SH_C0 = 0.28209479177387814
67
+
68
+
69
+ def _letterbox(img: np.ndarray, target: int = 512):
70
+ """Resize so long side = target, then zero-pad to (target, target)."""
71
+ import cv2
72
+ h_in, w_in = img.shape[:2]
73
+ if h_in >= w_in:
74
+ h_out, w_out = target, max(1, int(round(w_in * target / h_in)))
75
+ else:
76
+ w_out, h_out = target, max(1, int(round(h_in * target / w_in)))
77
+ resized = cv2.resize(img, (w_out, h_out), interpolation=cv2.INTER_AREA)
78
+ canvas = np.zeros((target, target, img.shape[2] if img.ndim == 3 else 1),
79
+ dtype=img.dtype)
80
+ if img.ndim == 2:
81
+ canvas = canvas[..., 0]
82
+ top = (target - h_out) // 2
83
+ left = (target - w_out) // 2
84
+ canvas[top:top + h_out, left:left + w_out] = resized
85
+ return canvas, (top, left, h_out, w_out)
86
+
87
+
88
+ def _depth_to_pointcloud(rgb_u8, depth, *, fov_deg=65.0, max_points=1_200_000,
89
+ edge_rtol=0.04, sor=False):
90
+ h, w = depth.shape
91
+ fx = w / (2.0 * np.tan(np.deg2rad(fov_deg) / 2.0))
92
+ cx, cy = w * 0.5, h * 0.5
93
+ # Keep every valid pixel — no percentile clip. The earlier [1, 99] clip
94
+ # discarded the nearest 1% of points, carving a hole in the closest
95
+ # surface (e.g. the front edge of a table) and also dropping the far
96
+ # background. The i2d depth is clean enough that this clipping isn't
97
+ # needed and it was cutting off the geometry users care most about.
98
+ valid = (depth > 0) & np.isfinite(depth)
99
+ # Depth-edge mask: drop occlusion-boundary "veil" pixels (MoGe-style).
100
+ if edge_rtol and edge_rtol > 0:
101
+ valid &= ~depth_edge_mask(depth, rtol=float(edge_rtol))
102
+ v_idx, u_idx = np.where(valid)
103
+ z = depth[v_idx, u_idx]
104
+ x = (u_idx + 0.5 - cx) * z / fx
105
+ y = (v_idx + 0.5 - cy) * z / fx
106
+ # glTF / Three.js: +Y up, camera looks down -Z. Flip image-y
107
+ # (which points down) and depth (which points into the scene).
108
+ pts = np.stack([x, -y, -z], axis=-1).astype(np.float32)
109
+ cols = rgb_u8[v_idx, u_idx]
110
+ if sor:
111
+ # Statistical outlier rejection: drops isolated floaters, but can
112
+ # over-trim fine structures — opt-in (the edge mask above is the
113
+ # default cleanup).
114
+ inliers = statistical_outlier_mask(pts)
115
+ pts, cols = pts[inliers], cols[inliers]
116
+ if pts.shape[0] > max_points:
117
+ idx = np.random.default_rng(0).choice(pts.shape[0], max_points, replace=False)
118
+ pts, cols = pts[idx], cols[idx]
119
+ if pts.shape[0]:
120
+ pts -= np.median(pts, axis=0, keepdims=True)
121
+ return pts, cols
122
+
123
+
124
+ def _save_glb(path, points, colors):
125
+ """Colored point cloud → binary glTF, the format gr.Model3D handles cleanly."""
126
+ import trimesh
127
+ cloud = trimesh.PointCloud(vertices=points, colors=colors)
128
+ scene = trimesh.Scene()
129
+ scene.add_geometry(cloud)
130
+ scene.export(str(path))
131
+
132
+
133
+ def _depth_to_magma(depth: np.ndarray) -> np.ndarray:
134
+ """Depth → magma-colormapped disparity image (uint8 RGB).
135
+
136
+ Visualizes 1/depth (so near = bright) robustly normalized to the 5–95th
137
+ percentile, matching the depth panel in the reference notebook.
138
+ """
139
+ from matplotlib import cm
140
+ valid = (depth > 0) & np.isfinite(depth)
141
+ disparity = np.zeros_like(depth, dtype=np.float32)
142
+ if valid.any():
143
+ disparity[valid] = 1.0 / np.maximum(depth[valid], 1e-8)
144
+ lo, hi = np.percentile(disparity[valid], [5, 95])
145
+ disparity = np.clip((disparity - lo) / max(hi - lo, 1e-8), 0, 1)
146
+ disparity[~valid] = 0.0
147
+ return (cm.magma(disparity)[..., :3] * 255).astype(np.uint8)
148
+
149
+
150
+ # /tmp is the writable mount on HF Spaces. We write the PLY here from the
151
+ # parent process (i.e. NOT inside the @spaces.GPU subprocess) so Gradio's
152
+ # file route can read it. Unique filename per call so Gradio's content-
153
+ # hashed cache always serves fresh bytes.
154
+ _ARTIFACT_DIR = Path("/tmp/flux_rgbd_artifacts")
155
+
156
+ # 2h is comfortably longer than any viewer session; keeps a busy Space's
157
+ # /tmp bounded since nothing else ever deletes these.
158
+ _ARTIFACT_TTL_S = 2 * 3600.0
159
+
160
+
161
+ def _prune_old_artifacts() -> None:
162
+ now = time.time()
163
+ for f in _ARTIFACT_DIR.glob("cloud_*.glb"):
164
+ try:
165
+ if now - f.stat().st_mtime > _ARTIFACT_TTL_S:
166
+ f.unlink()
167
+ except OSError:
168
+ pass # concurrent delete / fs hiccup — never fail a generation
169
+
170
+
171
+ _ARTIFACT_DIR.mkdir(parents=True, exist_ok=True)
172
+
173
+
174
+ @spaces.GPU(duration=120)
175
+ def _sample_on_gpu(prompt: str, input_image,
176
+ num_steps: int, cfg_scale: float, seed: int,
177
+ refine_depth: bool = True, log2_alpha: float = 5.0):
178
+ """GPU-only step: text encode + diffusion sample + VAE decode.
179
+
180
+ Returns plain numpy arrays so the parent process (which is what
181
+ serves Gradio files) can do the rest. Writing the PLY here would
182
+ leave it in the subprocess's filesystem view where the parent's
183
+ Gradio file route can't find it (returns 404).
184
+ """
185
+ import time
186
+ runner = _ensure_runner()
187
+ mode = "i2d" if input_image is not None else "joint"
188
+
189
+ target = runner.img_hw[0]
190
+ if mode == "i2d":
191
+ letterboxed, (top, left, vh, vw) = _letterbox(input_image, target)
192
+ model_input = letterboxed
193
+ else:
194
+ letterboxed = None
195
+ top = left = 0
196
+ vh = vw = target
197
+ model_input = None
198
+
199
+ t0 = time.time()
200
+ if mode == "i2d":
201
+ # Image given: single image→depth pass at CFG 1.0 (no guidance — the
202
+ # RGB is fixed, so there is nothing for CFG to steer).
203
+ result = runner.generate(
204
+ prompt.strip() if prompt else "",
205
+ mode="i2d",
206
+ num_steps=int(num_steps), cfg_scale=1.0, seed=int(seed),
207
+ clean_rgb_image=model_input,
208
+ )
209
+ else:
210
+ # Text→RGBD. Stage 1 joint at the requested CFG (default 4.0), rgb-first
211
+ # trajectory (log2_alpha=5) for cleaner depth. When `refine_depth` is on,
212
+ # a stage 2 re-derives depth via i2d on that RGB at CFG 1.0 for sharper,
213
+ # RGB-consistent geometry; otherwise the single joint pass is used.
214
+ result = runner.generate(
215
+ prompt.strip() if prompt else "",
216
+ mode="joint",
217
+ num_steps=int(num_steps), cfg_scale=float(cfg_scale), seed=int(seed),
218
+ log2_alpha=float(log2_alpha),
219
+ refine_depth_i2d=bool(refine_depth), i2d_cfg_scale=1.0,
220
+ )
221
+ elapsed = time.time() - t0
222
+
223
+ rgb_for_pc = (letterboxed[top:top + vh, left:left + vw] if mode == "i2d"
224
+ else result["rgb"])
225
+ depth = result["depth"]
226
+ if mode == "i2d":
227
+ depth = depth[top:top + vh, left:left + vw]
228
+ return rgb_for_pc, depth, mode, elapsed
229
+
230
+
231
+ def generate(prompt: str, input_image, num_steps: int, cfg_scale: float, seed: int,
232
+ refine_depth: bool = True, log2_alpha: float = 5.0,
233
+ edge_rtol: float = 0.04, sor: bool = False):
234
+ """Public Gradio handler. Runs the GPU step then does PLY writing
235
+ here in the parent process so the file persists for Gradio."""
236
+ rgb_for_pc, depth, mode, elapsed = _sample_on_gpu(
237
+ prompt, input_image, num_steps, cfg_scale, seed, refine_depth, log2_alpha,
238
+ )
239
+
240
+ pts, cols = _depth_to_pointcloud(rgb_for_pc, depth, edge_rtol=edge_rtol,
241
+ sor=bool(sor))
242
+ _prune_old_artifacts()
243
+ cloud_path = str(_ARTIFACT_DIR / f"cloud_{uuid.uuid4().hex[:12]}.glb")
244
+ _save_glb(cloud_path, pts, cols)
245
+
246
+ valid = (depth > 0) & np.isfinite(depth)
247
+ if valid.any():
248
+ d = depth[valid]
249
+ depth_summary = (
250
+ f"depth median={float(np.median(d)):.2f} "
251
+ f"p5={float(np.percentile(d, 5)):.2f} "
252
+ f"p95={float(np.percentile(d, 95)):.2f}"
253
+ )
254
+ else:
255
+ depth_summary = "depth has no valid pixels"
256
+ status = f"{mode} · {elapsed:.1f} s · {depth_summary} · {pts.shape[0]:,} points"
257
+ return rgb_for_pc, _depth_to_magma(depth), cloud_path, status
258
+
259
+
260
+ # --- Presentation layer ----------------------------------------------------
261
+ # Only the Gradio UI definition lives below. The generation/model code above
262
+ # is untouched.
263
+
264
+ WORLD_LABS_URL = "https://www.worldlabs.ai"
265
+ _PROJECT_URL = "https://modality-forcing.github.io/"
266
+ _ARXIV_URL = "https://arxiv.org/abs/2606.13676"
267
+ _CODE_URL = "https://github.com/Duisterhof/modality-forcing"
268
+
269
+ # Editorial monochrome: a fully neutral palette, Inter for body, JetBrains
270
+ # Mono for the small uppercase "eyebrow" labels. The serif display face for
271
+ # the title (Gilda Display) is pulled in via @import in the CSS below.
272
+ _THEME = gr.themes.Default(
273
+ # System fonts only — no Google-fetched web fonts for the body/mono, which
274
+ # were loading unreliably (falling back to Arial and looking cheap). The
275
+ # serif display title uses Gilda Display, pulled in via @import in the CSS.
276
+ font=(
277
+ "system-ui",
278
+ "-apple-system",
279
+ "Segoe UI",
280
+ "Helvetica Neue",
281
+ "Arial",
282
+ "sans-serif",
283
+ ),
284
+ font_mono=(
285
+ "ui-monospace",
286
+ "SFMono-Regular",
287
+ "Menlo",
288
+ "Consolas",
289
+ "monospace",
290
+ ),
291
+ primary_hue=gr.themes.colors.neutral,
292
+ secondary_hue=gr.themes.colors.neutral,
293
+ neutral_hue=gr.themes.colors.neutral,
294
+ ).set(
295
+ # Hairline, low-contrast borders; no heavy shadows or filled labels.
296
+ block_border_width="1px",
297
+ block_border_color="*neutral_200",
298
+ block_background_fill="white",
299
+ block_shadow="none",
300
+ block_label_background_fill="transparent",
301
+ block_label_border_width="0px",
302
+ block_label_text_weight="500",
303
+ input_border_width="1px",
304
+ input_border_color="*neutral_200",
305
+ input_shadow="none",
306
+ # Near-black, fully-rounded primary button (pill); white secondary.
307
+ button_large_radius="*radius_xxl",
308
+ button_small_radius="*radius_xxl",
309
+ button_primary_background_fill="#111111",
310
+ button_primary_background_fill_hover="#1f1f1f",
311
+ button_primary_text_color="white",
312
+ button_primary_border_color="#111111",
313
+ button_secondary_background_fill="white",
314
+ button_secondary_border_color="rgba(0,0,0,0.16)",
315
+ )
316
+
317
+ _CSS = """
318
+ @import url('https://fonts.googleapis.com/css2?family=Gilda+Display&display=swap');
319
+
320
+ /* Warm "paper" canvas everywhere (page + app + container) so the white
321
+ component cards lift off the background and the layout reads premium
322
+ rather than flat white-on-white. Body uses the system UI font. */
323
+ html, body, gradio-app, .gradio-container, .gradio-container .gap {
324
+ background: #f4f3ef !important;
325
+ }
326
+ .gradio-container {
327
+ color: #141414 !important;
328
+ max-width: 1120px !important;
329
+ margin: 0 auto !important;
330
+ padding: 28px 24px 12px !important;
331
+ font-family: system-ui, -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif !important;
332
+ }
333
+
334
+ /* Components become quiet white cards: hairline edge, soft round corners,
335
+ and a whisper of shadow for depth. (Also overrides this Gradio build's
336
+ hardcoded 3px black .block border.) */
337
+ .gradio-container .block {
338
+ border: 1px solid rgba(20,20,20,0.07) !important;
339
+ border-radius: 16px !important;
340
+ background: #ffffff !important;
341
+ box-shadow: 0 1px 2px rgba(20,20,20,0.04),
342
+ 0 12px 28px -18px rgba(20,20,20,0.18) !important;
343
+ }
344
+
345
+ /* Text/HTML blocks float on the page — no card border, fill, or shadow. */
346
+ .gradio-container .mf-bare {
347
+ border: 0 !important; background: transparent !important;
348
+ box-shadow: none !important; padding: 0 !important;
349
+ }
350
+
351
+ /* Hairline rule separating the masthead from the workspace. */
352
+ .mf-rule {
353
+ height: 1px; border: 0; background: rgba(20,20,20,0.08);
354
+ max-width: 1080px; margin: 0.75rem auto 1.5rem;
355
+ }
356
+
357
+ /* ---- Publication header (mirrors the project page) ---- */
358
+ /* Everything in the masthead is centered. Forced with !important because
359
+ Gradio's prose CSS otherwise left-aligns <p>/<div> inside gr.HTML. */
360
+ .mf-pub, .mf-pub *, .mf-intro, .mf-intro * { text-align: center !important; }
361
+ .mf-pub { margin: 0.25rem auto 0.25rem; }
362
+ .mf-pub-title {
363
+ font-weight: 600;
364
+ font-size: clamp(1.9rem, 4.2vw, 3rem);
365
+ line-height: 1.13; letter-spacing: -0.01em;
366
+ color: #363636; margin: 0 auto 0.7em; max-width: 900px;
367
+ }
368
+ .mf-authors {
369
+ font-size: clamp(1rem, 1.4vw, 1.25rem); line-height: 1.5;
370
+ color: #363636; margin: 0 auto;
371
+ }
372
+ .mf-authors a { color: #3273dc !important; text-decoration: none !important; }
373
+ .mf-authors a:hover { text-decoration: underline !important; }
374
+ .mf-authors .ab { white-space: nowrap; margin: 0 0.15em; }
375
+ .mf-affil {
376
+ font-size: clamp(0.95rem, 1.3vw, 1.2rem); color: #363636;
377
+ margin: 0.5em auto 0;
378
+ }
379
+ .mf-affil .ab { margin: 0 0.6em; }
380
+ .mf-logos {
381
+ display: flex; justify-content: center; align-items: center;
382
+ gap: 40px; flex-wrap: wrap; margin: 1.4em auto 0;
383
+ }
384
+ .mf-logos img { height: 78px; }
385
+ .mf-venue { font-weight: 700; color: #363636; margin: 1em auto 0; }
386
+ .mf-links {
387
+ display: flex; justify-content: center; gap: 12px;
388
+ flex-wrap: wrap; margin: 1.1em auto 0.25rem;
389
+ }
390
+ .mf-btn {
391
+ display: inline-flex; align-items: center; gap: 7px;
392
+ padding: 7px 18px; border-radius: 9999px;
393
+ background: #363636; color: #ffffff !important;
394
+ font-size: 14px; font-weight: 500; text-decoration: none !important;
395
+ transition: background .15s ease;
396
+ }
397
+ .mf-btn:hover { background: #4a4a4a; }
398
+ .mf-pub-sub {
399
+ font-size: clamp(1rem, 1.3vw, 1.2rem); line-height: 1.5;
400
+ color: #4a4a4a; max-width: 720px; margin: 1.2em auto 0; font-weight: 400;
401
+ }
402
+
403
+ /* ---- Quiet helper line + section eyebrows ---- */
404
+ .mf-intro {
405
+ text-align: center; max-width: 640px; margin: 0.25rem auto 1rem;
406
+ font-size: 14px; line-height: 1.6; color: #6b6b6b;
407
+ }
408
+ .mf-intro b { color: #111111; font-weight: 600; }
409
+ .mf-sec {
410
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
411
+ font-size: 11px; font-weight: 500; letter-spacing: 0.18em;
412
+ text-transform: uppercase; color: #9a9a9a; margin: 4px 2px 2px;
413
+ }
414
+
415
+ /* ---- Primary button: full pill, ink-black ---- */
416
+ .gradio-container button.primary,
417
+ .gradio-container button.lg.primary {
418
+ background: #111111 !important; color: #ffffff !important;
419
+ border: 1px solid #111111 !important; border-radius: 9999px !important;
420
+ font-weight: 500 !important; letter-spacing: -0.005em !important;
421
+ }
422
+ .gradio-container button.primary:hover { background: #1f1f1f !important; }
423
+
424
+ /* ---- Examples: borderless, quiet ---- */
425
+ .gradio-container .examples table,
426
+ .gradio-container .examples .tr-head { border: 0 !important; }
427
+ .gradio-container .examples td {
428
+ border-color: rgba(0,0,0,0.06) !important; font-size: 13px !important;
429
+ }
430
+
431
+ /* ---- Footer ---- */
432
+ .mf-footer {
433
+ text-align: center; margin-top: 2rem; padding-top: 1.1rem;
434
+ border-top: 1px solid rgba(0,0,0,0.08);
435
+ }
436
+ .mf-footer .mf-cta { font-size: 14px; color: #3d3d3d; }
437
+ .mf-footer .mf-cta a {
438
+ color: #111111 !important; text-decoration: none !important;
439
+ border-bottom: 1px solid rgba(0,0,0,0.25);
440
+ }
441
+ .mf-footer .mf-credit {
442
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
443
+ font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
444
+ color: #9a9a9a; margin-top: 6px;
445
+ }
446
+ """
447
+
448
+ _HEADER_HTML = (
449
+ '<div class="mf-pub">'
450
+ '<h1 class="mf-pub-title">Modality Forcing for Scalable Spatial Generation</h1>'
451
+ '<div class="mf-authors">'
452
+ '<span class="ab"><a href="https://bart-ai.com" target="_blank" rel="noreferrer">Bardienus Pieter Duisterhof</a><sup>1,2</sup>,</span>'
453
+ '<span class="ab"><a href="https://www.cs.cmu.edu/~deva/" target="_blank" rel="noreferrer">Deva Ramanan</a><sup>1</sup>,</span>'
454
+ '<span class="ab"><a href="https://ichnow.ski" target="_blank" rel="noreferrer">Jeffrey Ichnowski</a><sup>1</sup>,</span>'
455
+ '<span class="ab"><a href="https://web.eecs.umich.edu/~justincj/" target="_blank" rel="noreferrer">Justin Johnson</a><sup>2</sup>,</span>'
456
+ '<span class="ab"><a href="https://keunhong.com" target="_blank" rel="noreferrer">Keunhong Park</a><sup>2</sup></span>'
457
+ '</div>'
458
+ '<div class="mf-affil">'
459
+ '<span class="ab"><sup>1</sup>Carnegie Mellon University</span>'
460
+ '<span class="ab"><sup>2</sup>World Labs</span>'
461
+ '</div>'
462
+ '<div class="mf-logos">'
463
+ '<img alt="Carnegie Mellon University" src="https://modality-forcing.github.io/static/images/cmu_logo.png">'
464
+ '<img alt="World Labs" src="https://modality-forcing.github.io/static/images/world_labs_logo.jpg" style="border-radius:12px;">'
465
+ '</div>'
466
+ '<div class="mf-venue">Preprint, 2026</div>'
467
+ '<div class="mf-links">'
468
+ f'<a class="mf-btn" href="{_PROJECT_URL}" target="_blank" rel="noopener">📄 Project Page</a>'
469
+ f'<a class="mf-btn" href="{_ARXIV_URL}" target="_blank" rel="noopener">📚 arXiv</a>'
470
+ f'<a class="mf-btn" href="{_CODE_URL}" target="_blank" rel="noopener">⌨ Code</a>'
471
+ '</div>'
472
+ '<div class="mf-pub-sub" style="text-align:center !important;">Modality '
473
+ 'Forcing turns a pretrained text-to-image diffusion transformer into a '
474
+ 'joint image-depth generator with a simple post-training recipe.</div>'
475
+ '</div>'
476
+ )
477
+
478
+ _INTRO_HTML = (
479
+ '<div class="mf-intro">Type a scene and press <b>Generate</b>, or upload an '
480
+ 'image to run <b>image→depth</b> mode instead.</div>'
481
+ )
482
+
483
+ _EXAMPLE_IMAGES = [
484
+ ["assets/examples/alley.png"],
485
+ ["assets/examples/yosemite.png"],
486
+ ["assets/examples/castle.png"],
487
+ ]
488
+
489
+ _EXAMPLE_PROMPTS = [
490
+ [DEFAULT_PROMPT],
491
+ ["A sunlit Scandinavian living room with a linen sofa, a low oak coffee "
492
+ "table, and tall windows opening onto a snowy courtyard."],
493
+ ["A misty pine forest at dawn, shafts of golden light cutting between the "
494
+ "trunks and a narrow dirt trail winding into the distance."],
495
+ ["A cozy bookshop interior with floor-to-ceiling wooden shelves, a rolling "
496
+ "ladder, warm pendant lighting, and a worn leather reading chair."],
497
+ ["A still life on a marble countertop: a bowl of ripe lemons, a ceramic "
498
+ "pitcher, and a sprig of rosemary lit by soft side light."],
499
+ ["A coastal cliffside at golden hour overlooking a turquoise bay, with wild "
500
+ "grass in the foreground and distant sailboats on the water."],
501
+ ]
502
+
503
+ with gr.Blocks(title="Modality Forcing — World Labs") as demo:
504
+ gr.HTML(_HEADER_HTML, elem_classes="mf-bare")
505
+ gr.HTML(_INTRO_HTML, elem_classes="mf-bare")
506
+ gr.HTML('<hr class="mf-rule">', elem_classes="mf-bare")
507
+
508
+ with gr.Row(equal_height=False):
509
+ with gr.Column(scale=2, min_width=320) as left_col:
510
+ gr.HTML('<div class="mf-sec">Input</div>', elem_classes="mf-bare")
511
+ prompt = gr.Textbox(
512
+ value=DEFAULT_PROMPT,
513
+ lines=4,
514
+ label="Scene prompt",
515
+ placeholder="Describe a scene to generate…",
516
+ )
517
+ input_image = gr.Image(
518
+ label="Optional input image (switches to image→depth mode)",
519
+ type="numpy", height=200, sources=("upload", "clipboard"),
520
+ )
521
+ btn = gr.Button("Generate", variant="primary", size="lg")
522
+
523
+ with gr.Accordion("Advanced settings", open=False):
524
+ with gr.Row():
525
+ num_steps = gr.Slider(
526
+ 1, 80, value=50, step=1, label="Sampling steps")
527
+ cfg_scale = gr.Slider(
528
+ 1.0, 8.0, value=4.0, step=0.1,
529
+ label="Guidance (CFG) — text mode only")
530
+ log2_alpha = gr.Slider(
531
+ -5.0, 5.0, value=5.0, step=0.5,
532
+ label="log2(alpha) — depth trajectory (>0 rgb-first, "
533
+ "cleaner depth; 0 diagonal; <0 depth-first) — text mode")
534
+ edge_rtol = gr.Slider(
535
+ 0.0, 0.25, value=0.04, step=0.005,
536
+ label="Point-cloud depth-edge mask (rtol) — drop pixels at "
537
+ "depth jumps > this; lower = more aggressive, 0 = off")
538
+ sor_toggle = gr.Checkbox(
539
+ value=False,
540
+ label="Statistical outlier removal (point cloud) — drops "
541
+ "isolated floaters; can over-trim fine structures")
542
+ seed = gr.Number(value=0, precision=0, label="Seed")
543
+ refine_depth = gr.Checkbox(
544
+ value=True,
545
+ label="Refine depth: joint (CFG) → image→depth (CFG 1) "
546
+ "— text mode only",
547
+ )
548
+
549
+ status = gr.Textbox(label="Status", interactive=False)
550
+
551
+ with gr.Column(scale=3, min_width=380):
552
+ gr.HTML('<div class="mf-sec">Output</div>', elem_classes="mf-bare")
553
+ with gr.Row():
554
+ rgb_out = gr.Image(
555
+ label="RGB image", type="numpy", height=320, format="png")
556
+ depth_out = gr.Image(
557
+ label="Depth (disparity, magma)",
558
+ type="numpy", height=320, format="png")
559
+ with gr.Group():
560
+ cloud_out = gr.Model3D(
561
+ label="Interactive 3D point cloud",
562
+ clear_color=(0.12, 0.12, 0.14, 1.0),
563
+ zoom_speed=0.5, pan_speed=0.5,
564
+ height=520,
565
+ )
566
+
567
+ def _run_text_example(prompt_text):
568
+ # Cached example runs pin the UI defaults so the cache stays valid.
569
+ return generate(prompt_text, None, 50, 4.0, 0, True, 5.0, 0.04, False)
570
+
571
+ def _run_image_example(image):
572
+ return generate("", image, 50, 4.0, 0, True, 5.0, 0.04, False)
573
+
574
+ # Defined after the output components exist, rendered back into the left
575
+ # column. Cached: clicking an example serves precomputed results instead
576
+ # of spending GPU time ("lazy" = no cache rebuild at startup; the
577
+ # committed cache ships with the Space repo).
578
+ _CACHE_MODE = os.environ.get("EXAMPLES_CACHE_MODE", "lazy")
579
+ with left_col:
580
+ gr.Examples(
581
+ examples=_EXAMPLE_PROMPTS,
582
+ inputs=[prompt],
583
+ outputs=[rgb_out, depth_out, cloud_out, status],
584
+ fn=_run_text_example,
585
+ cache_examples=True,
586
+ cache_mode=_CACHE_MODE,
587
+ label="Example prompts",
588
+ )
589
+ gr.Examples(
590
+ examples=_EXAMPLE_IMAGES,
591
+ inputs=[input_image],
592
+ outputs=[rgb_out, depth_out, cloud_out, status],
593
+ fn=_run_image_example,
594
+ cache_examples=True,
595
+ cache_mode=_CACHE_MODE,
596
+ label="Example images (image → depth)",
597
+ )
598
+
599
+ gr.HTML(
600
+ '<div class="mf-footer">'
601
+ '<div class="mf-cta">Built by <a href="' + WORLD_LABS_URL + '" '
602
+ 'target="_blank" rel="noopener noreferrer">World Labs</a></div>'
603
+ '<div class="mf-credit">worldlabs.ai · Modality Forcing</div>'
604
+ '</div>',
605
+ elem_classes="mf-bare",
606
+ )
607
+
608
+ btn.click(
609
+ generate,
610
+ inputs=[prompt, input_image, num_steps, cfg_scale, seed, refine_depth,
611
+ log2_alpha, edge_rtol, sor_toggle],
612
+ outputs=[rgb_out, depth_out, cloud_out, status],
613
+ )
614
+
615
+ # Adding an image switches the demo into image→depth mode, where the prompt
616
+ # is optional — clear it so it defaults to empty. The user can retype one.
617
+ input_image.change(
618
+ lambda img: "" if img is not None else gr.update(),
619
+ inputs=[input_image],
620
+ outputs=[prompt],
621
+ )
622
+
623
+ # The UI is designed light-only (paper-white cards, explicit light CSS). A
624
+ # visitor whose OS is in dark mode otherwise gets gradio's dark text colors on
625
+ # our light backgrounds — an unreadable mix. Redirect to ?__theme=light in
626
+ # <head>, before gradio boots, so every visitor gets the light theme.
627
+ _FORCE_LIGHT_HEAD = """
628
+ <script>
629
+ (function () {
630
+ const url = new URL(window.location);
631
+ if (url.searchParams.get("__theme") !== "light") {
632
+ url.searchParams.set("__theme", "light");
633
+ window.location.replace(url);
634
+ }
635
+ })();
636
+ </script>
637
+ """
638
+
639
+ if __name__ == "__main__":
640
+ # Gradio 6 takes theme + css on launch() (not the Blocks constructor),
641
+ # so they must be passed here to actually apply on the Space.
642
+ demo.queue(max_size=4).launch(theme=_THEME, css=_CSS, head=_FORCE_LIGHT_HEAD)
assets/examples/alley.png ADDED

Git LFS Details

  • SHA256: 12d47280f10a0f7e07077ddb595d18d3636e6ef3cbe63ccf281f193aca7f55e3
  • Pointer size: 132 Bytes
  • Size of remote file: 1.03 MB
assets/examples/castle.png ADDED

Git LFS Details

  • SHA256: d94240486e851197441a2b63b02ee70eeab7d2e607d3568e34741c54ca55b94b
  • Pointer size: 132 Bytes
  • Size of remote file: 1.9 MB
assets/examples/yosemite.png ADDED

Git LFS Details

  • SHA256: edeb3066dd0ac94309cb8ab2372c4b2c3474847cacf2055821dcde036faeca45
  • Pointer size: 132 Bytes
  • Size of remote file: 1.66 MB
flux_rgbd/__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 World Labs.
3
+ """flux_rgbd — joint text → RGB + depth diffusion on top of FLUX.2."""
4
+
5
+ __version__ = "0.1.0"
6
+ __all__ = ["FluxRGBDRunner"]
7
+
8
+
9
+ def __getattr__(name):
10
+ if name == "FluxRGBDRunner":
11
+ from flux_rgbd.runner import FluxRGBDRunner
12
+ return FluxRGBDRunner
13
+ raise AttributeError(name)
flux_rgbd/_flux2/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2025 Black Forest Labs.
3
+ """Vendored FLUX.2 reference implementation.
4
+
5
+ This subpackage holds the unmodified FLUX.2 architecture (Apache-2.0,
6
+ Black Forest Labs upstream). Do not edit these files; they are
7
+ upstream-tracking. All modifications and extensions for RGB+D inference
8
+ live in the parent ``flux_rgbd`` package.
9
+
10
+ Upstream: https://github.com/black-forest-labs/flux2
11
+ """
flux_rgbd/_flux2/autoencoder.py ADDED
@@ -0,0 +1,499 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2025 Black Forest Labs.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """FLUX.2 autoencoder.
17
+
18
+ Adapted from the FLUX.2 codebase:
19
+ https://github.com/black-forest-labs/flux2
20
+ """
21
+
22
+ import math
23
+ from collections.abc import Sequence
24
+
25
+ import torch
26
+ from einops import rearrange
27
+ from jaxtyping import Float
28
+ from safetensors.torch import load_file as load_safetensors
29
+ from torch import Tensor, nn
30
+
31
+ __all__ = ["Flux2Encoder", "Flux2Decoder"]
32
+
33
+ # FLUX.2-dev autoencoder weights, pre-split into encoder + decoder
34
+ # safetensors. (BFL's canonical `ae.safetensors` is a combined file
35
+ # we'd have to filter by key prefix — easier to host the split files
36
+ # inside the model repo. Override via the FLUX_RGBD_LOCAL_AE_{...}
37
+ # env vars if you want to skip the Hub fetch entirely.)
38
+ DEFAULT_AE_REPO = "bartduis/modality_forcing"
39
+ DEFAULT_ENCODER_FILE = "ae_encoder.safetensors"
40
+ DEFAULT_DECODER_FILE = "ae_decoder.safetensors"
41
+
42
+ DEFAULT_RESOLUTION = 256
43
+ DEFAULT_IN_CHANNELS = 3
44
+ DEFAULT_CH = 128
45
+ DEFAULT_OUT_CHANNELS = 3
46
+ DEFAULT_CH_MULT = (1, 2, 4, 4)
47
+ DEFAULT_NUM_RES_BLOCKS = 2
48
+ DEFAULT_Z_CHANNELS = 32
49
+
50
+
51
+ def _hf_download(repo_id: str, filename: str) -> str:
52
+ """Resolve a HuggingFace Hub file path. Lazy-import hf_hub to keep the
53
+ base package importable in environments that pin huggingface_hub via
54
+ extras."""
55
+ from huggingface_hub import hf_hub_download
56
+
57
+ return hf_hub_download(repo_id=repo_id, filename=filename)
58
+
59
+
60
+ def _load_weights(repo_id: str, filename: str) -> dict[str, Tensor]:
61
+ # Allow callers to point at a local file via env var. Useful for tests
62
+ # and air-gapped environments.
63
+ import os
64
+ env_key = f"FLUX_RGBD_LOCAL_{filename.upper().replace('.', '_')}"
65
+ override = os.environ.get(env_key)
66
+ if override:
67
+ if not os.path.isfile(override):
68
+ raise FileNotFoundError(f"{env_key}={override} is set but not a file")
69
+ return load_safetensors(override)
70
+ path = _hf_download(repo_id, filename)
71
+ return load_safetensors(path)
72
+
73
+
74
+ def _swish(x: Tensor) -> Tensor:
75
+ return x * torch.sigmoid(x)
76
+
77
+
78
+ class AttnBlock(nn.Module):
79
+ """Attention block for FLUX.2 autoencoder."""
80
+
81
+ def __init__(self, in_channels: int):
82
+ super().__init__()
83
+ self.in_channels = in_channels
84
+
85
+ self.norm = nn.GroupNorm(
86
+ num_groups=32, num_channels=in_channels, eps=1e-6, affine=True
87
+ )
88
+
89
+ self.q = nn.Conv2d(in_channels, in_channels, kernel_size=1)
90
+ self.k = nn.Conv2d(in_channels, in_channels, kernel_size=1)
91
+ self.v = nn.Conv2d(in_channels, in_channels, kernel_size=1)
92
+ self.proj_out = nn.Conv2d(in_channels, in_channels, kernel_size=1)
93
+
94
+ def attention(self, h_: Tensor) -> Tensor:
95
+ h_ = self.norm(h_)
96
+ q = self.q(h_)
97
+ k = self.k(h_)
98
+ v = self.v(h_)
99
+
100
+ b, c, h, w = q.shape
101
+ q = rearrange(q, "b c h w -> b 1 (h w) c").contiguous()
102
+ k = rearrange(k, "b c h w -> b 1 (h w) c").contiguous()
103
+ v = rearrange(v, "b c h w -> b 1 (h w) c").contiguous()
104
+ h_ = nn.functional.scaled_dot_product_attention(q, k, v)
105
+
106
+ return rearrange(h_, "b 1 (h w) c -> b c h w", h=h, w=w, c=c, b=b)
107
+
108
+ def forward(self, x: Tensor) -> Tensor:
109
+ return x + self.proj_out(self.attention(x))
110
+
111
+
112
+ class ResnetBlock(nn.Module):
113
+ """Resnet block for FLUX.2 autoencoder."""
114
+
115
+ def __init__(self, in_channels: int, out_channels: int):
116
+ super().__init__()
117
+ self.in_channels = in_channels
118
+ out_channels = in_channels if out_channels is None else out_channels
119
+ self.out_channels = out_channels
120
+
121
+ self.norm1 = nn.GroupNorm(
122
+ num_groups=32, num_channels=in_channels, eps=1e-6, affine=True
123
+ )
124
+ self.conv1 = nn.Conv2d(
125
+ in_channels, out_channels, kernel_size=3, stride=1, padding=1
126
+ )
127
+ self.norm2 = nn.GroupNorm(
128
+ num_groups=32, num_channels=out_channels, eps=1e-6, affine=True
129
+ )
130
+ self.conv2 = nn.Conv2d(
131
+ out_channels, out_channels, kernel_size=3, stride=1, padding=1
132
+ )
133
+ if self.in_channels != self.out_channels:
134
+ self.nin_shortcut = nn.Conv2d(
135
+ in_channels, out_channels, kernel_size=1, stride=1, padding=0
136
+ )
137
+
138
+ def forward(self, x):
139
+ h = x
140
+ h = self.norm1(h)
141
+ h = _swish(h)
142
+ h = self.conv1(h)
143
+
144
+ h = self.norm2(h)
145
+ h = _swish(h)
146
+ h = self.conv2(h)
147
+
148
+ if self.in_channels != self.out_channels:
149
+ x = self.nin_shortcut(x)
150
+
151
+ return x + h
152
+
153
+
154
+ class Downsample(nn.Module):
155
+ """Downsample block for FLUX.2 autoencoder."""
156
+
157
+ def __init__(self, in_channels: int):
158
+ super().__init__()
159
+ # no asymmetric padding in torch conv, must do it ourselves
160
+ self.conv = nn.Conv2d(
161
+ in_channels, in_channels, kernel_size=3, stride=2, padding=0
162
+ )
163
+
164
+ def forward(self, x: Tensor):
165
+ pad = (0, 1, 0, 1)
166
+ x = nn.functional.pad(x, pad, mode="constant", value=0)
167
+ x = self.conv(x)
168
+ return x
169
+
170
+
171
+ class Upsample(nn.Module):
172
+ """Upsample block for FLUX.2 autoencoder."""
173
+
174
+ def __init__(self, in_channels: int):
175
+ super().__init__()
176
+ self.conv = nn.Conv2d(
177
+ in_channels, in_channels, kernel_size=3, stride=1, padding=1
178
+ )
179
+
180
+ def forward(self, x: Tensor):
181
+ x = nn.functional.interpolate(x, scale_factor=2.0, mode="nearest")
182
+ x = self.conv(x)
183
+ return x
184
+
185
+
186
+ class Flux2Encoder(nn.Module):
187
+ """Encoder for FLUX.2 autoencoder."""
188
+
189
+ def __init__(
190
+ self,
191
+ resolution: int = DEFAULT_RESOLUTION,
192
+ in_channels: int = DEFAULT_IN_CHANNELS,
193
+ ch: int = DEFAULT_CH,
194
+ ch_mult: Sequence[int] = DEFAULT_CH_MULT,
195
+ num_res_blocks: int = DEFAULT_NUM_RES_BLOCKS,
196
+ z_channels: int = DEFAULT_Z_CHANNELS,
197
+ repo_id: str = DEFAULT_AE_REPO,
198
+ filename: str = DEFAULT_ENCODER_FILE,
199
+ ):
200
+ """Initialize the FLUX.2 encoder.
201
+
202
+ Args:
203
+ resolution: The resolution of the input images.
204
+ in_channels: The number of channels in the input images.
205
+ ch: The number of channels in the encoder.
206
+ ch_mult: The number of channels in the encoder at each resolution.
207
+ num_res_blocks: The number of ResNet blocks in each downsampling block.
208
+ z_channels: The number of channels in the latent space.
209
+ repo_id: HuggingFace Hub repository id from which to load weights.
210
+ filename: Filename of the safetensors weights within `repo_id`.
211
+ """
212
+ super().__init__()
213
+ self._repo_id = repo_id
214
+ self._filename = filename
215
+
216
+ self.quant_conv = torch.nn.Conv2d(2 * z_channels, 2 * z_channels, 1)
217
+ self.ch = ch
218
+ self.num_resolutions = len(ch_mult)
219
+ self.num_res_blocks = num_res_blocks
220
+ self.resolution = resolution
221
+ self.in_channels = in_channels
222
+ # downsampling
223
+ self.conv_in = nn.Conv2d(
224
+ in_channels, self.ch, kernel_size=3, stride=1, padding=1
225
+ )
226
+
227
+ curr_res = resolution
228
+ in_ch_mult = (1,) + tuple(ch_mult)
229
+ self.in_ch_mult = in_ch_mult
230
+ self.down = nn.ModuleList()
231
+ block_in = self.ch
232
+ for i_level in range(self.num_resolutions):
233
+ block = nn.ModuleList()
234
+ attn = nn.ModuleList()
235
+ block_in = ch * in_ch_mult[i_level]
236
+ block_out = ch * ch_mult[i_level]
237
+ for _ in range(self.num_res_blocks):
238
+ block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
239
+ block_in = block_out
240
+ down = nn.Module()
241
+ down.block = block
242
+ down.attn = attn
243
+ if i_level != self.num_resolutions - 1:
244
+ down.downsample = Downsample(block_in)
245
+ curr_res = curr_res // 2
246
+ self.down.append(down)
247
+
248
+ # middle
249
+ self.mid = nn.Module()
250
+ self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
251
+ self.mid.attn_1 = AttnBlock(block_in)
252
+ self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
253
+
254
+ # end
255
+ self.norm_out = nn.GroupNorm(
256
+ num_groups=32, num_channels=block_in, eps=1e-6, affine=True
257
+ )
258
+ self.conv_out = nn.Conv2d(
259
+ block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1
260
+ )
261
+
262
+ self.bn_eps = 1e-4
263
+ self.bn_momentum = 0.1
264
+ self.ps = [2, 2]
265
+ self.bn = torch.nn.BatchNorm2d(
266
+ math.prod(self.ps) * z_channels,
267
+ eps=self.bn_eps,
268
+ momentum=self.bn_momentum,
269
+ affine=False,
270
+ track_running_stats=True,
271
+ )
272
+
273
+ def load_weights(self):
274
+ """Fetch and load weights from HuggingFace Hub."""
275
+ self.load_state_dict(_load_weights(self._repo_id, self._filename))
276
+
277
+ def _forward(self, x: Float[Tensor, "n cx hx wx"]) -> Float[Tensor, "n cz hz wz"]:
278
+ # downsampling
279
+ hs = [self.conv_in(x)]
280
+ for i_level in range(self.num_resolutions):
281
+ for i_block in range(self.num_res_blocks):
282
+ h = self.down[i_level].block[i_block](hs[-1])
283
+ if len(self.down[i_level].attn) > 0:
284
+ h = self.down[i_level].attn[i_block](h)
285
+ hs.append(h)
286
+ if i_level != self.num_resolutions - 1:
287
+ hs.append(self.down[i_level].downsample(hs[-1]))
288
+
289
+ # middle
290
+ h = hs[-1]
291
+ h = self.mid.block_1(h)
292
+ h = self.mid.attn_1(h)
293
+ h = self.mid.block_2(h)
294
+ # end
295
+ h = self.norm_out(h)
296
+ h = _swish(h)
297
+ h = self.conv_out(h)
298
+ h = self.quant_conv(h)
299
+ return h
300
+
301
+ def _normalize(self, z: Float[Tensor, "n cz hz wz"]) -> Float[Tensor, "n cz hz wz"]:
302
+ self.bn.eval()
303
+ return self.bn(z)
304
+
305
+ def forward(
306
+ self, x: Float[Tensor, "... hx wx cx"]
307
+ ) -> Float[Tensor, "... hz wz cz"]:
308
+ """Encode images to latents.
309
+
310
+ Args:
311
+ x: Images to encode.
312
+
313
+ Returns:
314
+ The encoded latents. The latents will be normalized using the running
315
+ statistics from training.
316
+ """
317
+ *batch_shape, _, _, _ = x.shape
318
+ # Convert to NCHW.
319
+ x = rearrange(x, "... h w c -> (...) c h w")
320
+
321
+ # Compute latents.
322
+ moments = self._forward(x)
323
+ mean = torch.chunk(moments, 2, dim=1)[0]
324
+ z = rearrange(
325
+ mean,
326
+ "... c (i pi) (j pj) -> ... (c pi pj) i j",
327
+ pi=self.ps[0],
328
+ pj=self.ps[1],
329
+ )
330
+ z = self._normalize(z)
331
+
332
+ # Convert back to NHWC and restore batch shape.
333
+ z = rearrange(z, "... c h w -> ... h w c")
334
+ return z.reshape(*batch_shape, *z.shape[-3:])
335
+
336
+
337
+ class Flux2Decoder(nn.Module):
338
+ """Decoder for FLUX.2 autoencoder."""
339
+
340
+ def __init__(
341
+ self,
342
+ ch: int = DEFAULT_CH,
343
+ out_ch: int = DEFAULT_OUT_CHANNELS,
344
+ ch_mult: Sequence[int] = DEFAULT_CH_MULT,
345
+ num_res_blocks: int = DEFAULT_NUM_RES_BLOCKS,
346
+ in_channels: int = DEFAULT_IN_CHANNELS,
347
+ resolution: int = DEFAULT_RESOLUTION,
348
+ z_channels: int = DEFAULT_Z_CHANNELS,
349
+ repo_id: str = DEFAULT_AE_REPO,
350
+ filename: str = DEFAULT_DECODER_FILE,
351
+ ):
352
+ """Initialize the FLUX.2 decoder.
353
+
354
+ Args:
355
+ ch: The number of channels in the decoder.
356
+ out_ch: The number of channels in the output.
357
+ ch_mult: The number of channels in the decoder at each resolution.
358
+ num_res_blocks: The number of ResNet blocks in each upsampling block.
359
+ in_channels: The number of channels in the input images.
360
+ resolution: The resolution of the input images.
361
+ z_channels: The number of channels in the latent space.
362
+ repo_id: HuggingFace Hub repository id from which to load weights.
363
+ filename: Filename of the safetensors weights within `repo_id`.
364
+ """
365
+ super().__init__()
366
+ self._repo_id = repo_id
367
+ self._filename = filename
368
+
369
+ self.post_quant_conv = torch.nn.Conv2d(z_channels, z_channels, 1)
370
+ self.ch = ch
371
+ self.num_resolutions = len(ch_mult)
372
+ self.num_res_blocks = num_res_blocks
373
+ self.resolution = resolution
374
+ self.in_channels = in_channels
375
+ self.ffactor = 2 ** (self.num_resolutions - 1)
376
+
377
+ # compute in_ch_mult, block_in and curr_res at lowest res
378
+ block_in = ch * ch_mult[self.num_resolutions - 1]
379
+ curr_res = resolution // 2 ** (self.num_resolutions - 1)
380
+ self.z_shape = (1, z_channels, curr_res, curr_res)
381
+
382
+ # z to block_in
383
+ self.conv_in = nn.Conv2d(
384
+ z_channels, block_in, kernel_size=3, stride=1, padding=1
385
+ )
386
+
387
+ # middle
388
+ self.mid = nn.Module()
389
+ self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in)
390
+ self.mid.attn_1 = AttnBlock(block_in)
391
+ self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in)
392
+
393
+ # upsampling
394
+ self.up = nn.ModuleList()
395
+ for i_level in reversed(range(self.num_resolutions)):
396
+ block = nn.ModuleList()
397
+ attn = nn.ModuleList()
398
+ block_out = ch * ch_mult[i_level]
399
+ for _ in range(self.num_res_blocks + 1):
400
+ block.append(ResnetBlock(in_channels=block_in, out_channels=block_out))
401
+ block_in = block_out
402
+ up = nn.Module()
403
+ up.block = block
404
+ up.attn = attn
405
+ if i_level != 0:
406
+ up.upsample = Upsample(block_in)
407
+ curr_res = curr_res * 2
408
+ self.up.insert(0, up) # prepend to get consistent order
409
+
410
+ # end
411
+ self.norm_out = nn.GroupNorm(
412
+ num_groups=32, num_channels=block_in, eps=1e-6, affine=True
413
+ )
414
+ self.conv_out = nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1)
415
+
416
+ self.bn_eps = 1e-4
417
+ self.bn_momentum = 0.1
418
+ self.ps = [2, 2]
419
+ self.bn = torch.nn.BatchNorm2d(
420
+ math.prod(self.ps) * z_channels,
421
+ eps=self.bn_eps,
422
+ momentum=self.bn_momentum,
423
+ affine=False,
424
+ track_running_stats=True,
425
+ )
426
+
427
+ def load_weights(self):
428
+ """Fetch and load weights from HuggingFace Hub."""
429
+ self.load_state_dict(_load_weights(self._repo_id, self._filename))
430
+
431
+ def _forward(self, z: Float[Tensor, "n cz hz wz"]) -> Float[Tensor, "n cx hx wx"]:
432
+ z = self.post_quant_conv(z)
433
+
434
+ # get dtype for proper tracing
435
+ upscale_dtype = next(self.up.parameters()).dtype
436
+
437
+ # z to block_in
438
+ h = self.conv_in(z)
439
+
440
+ # middle
441
+ h = self.mid.block_1(h)
442
+ h = self.mid.attn_1(h)
443
+ h = self.mid.block_2(h)
444
+
445
+ # cast to proper dtype
446
+ h = h.to(upscale_dtype)
447
+ # upsampling
448
+ for i_level in reversed(range(self.num_resolutions)):
449
+ for i_block in range(self.num_res_blocks + 1):
450
+ h = self.up[i_level].block[i_block](h)
451
+ if len(self.up[i_level].attn) > 0:
452
+ h = self.up[i_level].attn[i_block](h)
453
+ if i_level != 0:
454
+ h = self.up[i_level].upsample(h)
455
+
456
+ # end
457
+ h = self.norm_out(h)
458
+ h = _swish(h)
459
+ h = self.conv_out(h)
460
+ return h
461
+
462
+ def _inv_normalize(
463
+ self, z: Float[Tensor, "n cz hz wz"]
464
+ ) -> Float[Tensor, "n cz hz wz"]:
465
+ self.bn.eval()
466
+ s = torch.sqrt(self.bn.running_var.view(1, -1, 1, 1) + self.bn_eps)
467
+ m = self.bn.running_mean.view(1, -1, 1, 1)
468
+ return z * s + m
469
+
470
+ def forward(
471
+ self, z: Float[Tensor, "... cz hz wz"]
472
+ ) -> Float[Tensor, "... cx hx wx"]:
473
+ """Decode latents to images.
474
+
475
+ Args:
476
+ z: Image latents normalized to have zero mean and unit variance.
477
+
478
+ Returns:
479
+ Decoded images.
480
+ """
481
+ *batch_shape, _, _, _ = z.shape
482
+
483
+ # Convert to NCHW.
484
+ z = rearrange(z, "... h w c -> (...) c h w")
485
+
486
+ # Denormalize and evaluate decoder.
487
+ z = self._inv_normalize(z)
488
+ z = rearrange(
489
+ z,
490
+ "... (c pi pj) i j -> ... c (i pi) (j pj)",
491
+ pi=self.ps[0],
492
+ pj=self.ps[1],
493
+ )
494
+ dec = self._forward(z)
495
+
496
+ # Convert back to NHWC and restore batch shape.
497
+ dec = rearrange(dec, "... c h w -> ... h w c")
498
+ dec = dec.reshape(*batch_shape, *dec.shape[-3:])
499
+ return dec
flux_rgbd/_flux2/constants.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2025 Black Forest Labs.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Adapted from the FLUX.2 codebase:
11
+ # https://github.com/black-forest-labs/flux2
12
+ """
13
+ Architecture parameters and other constants useful for FLUX.2 inference.
14
+
15
+ This module centralizes all constants used across the FLUX.2 model implementation,
16
+ including model architecture configurations for all public FLUX.2 variants, their
17
+ text encoder parameters, and system prompts for prompt upsampling.
18
+ """
19
+
20
+ from types import MappingProxyType
21
+
22
+ __all__ = [
23
+ "FLUX2_DEV_PARAMS",
24
+ "FLUX2_KLEIN_4B_PARAMS",
25
+ "FLUX2_KLEIN_9B_PARAMS",
26
+ "OUTPUT_LAYERS_MISTRAL",
27
+ "OUTPUT_LAYERS_QWEN3",
28
+ "MAX_LENGTH",
29
+ "UPSAMPLING_MAX_IMAGE_SIZE",
30
+ "SYSTEM_MESSAGE",
31
+ "SYSTEM_MESSAGE_UPSAMPLING_T2I",
32
+ "SYSTEM_MESSAGE_UPSAMPLING_I2I",
33
+ ]
34
+
35
+ # =============================================================================
36
+ # Model Architecture Parameters
37
+ # =============================================================================
38
+
39
+ FLUX2_DEV_PARAMS = MappingProxyType(
40
+ {
41
+ "in_channels": 128,
42
+ "context_in_dim": 15360,
43
+ "hidden_size": 6144,
44
+ "num_heads": 48,
45
+ "depth": 8,
46
+ "depth_single_blocks": 48,
47
+ "axes_dim": (32, 32, 32, 32),
48
+ "theta": 2000,
49
+ "mlp_ratio": 3.0,
50
+ "use_guidance_embed": True,
51
+ }
52
+ )
53
+ """
54
+ Architecture config for FLUX.2 [dev] 32B parameter model (guidance distilled).
55
+ """
56
+
57
+ FLUX2_KLEIN_4B_PARAMS = MappingProxyType(
58
+ {
59
+ "in_channels": 128,
60
+ "context_in_dim": 7680,
61
+ "hidden_size": 3072,
62
+ "num_heads": 24,
63
+ "depth": 5,
64
+ "depth_single_blocks": 20,
65
+ "axes_dim": (32, 32, 32, 32),
66
+ "theta": 2000,
67
+ "mlp_ratio": 3.0,
68
+ "use_guidance_embed": False,
69
+ }
70
+ )
71
+ """
72
+ Architecture config for FLUX.2 [klein] 4B parameter model (size distilled).
73
+ """
74
+
75
+ FLUX2_KLEIN_9B_PARAMS = MappingProxyType(
76
+ {
77
+ "in_channels": 128,
78
+ "context_in_dim": 12288,
79
+ "hidden_size": 4096,
80
+ "num_heads": 32,
81
+ "depth": 8,
82
+ "depth_single_blocks": 24,
83
+ "axes_dim": (32, 32, 32, 32),
84
+ "theta": 2000,
85
+ "mlp_ratio": 3.0,
86
+ "use_guidance_embed": False,
87
+ }
88
+ )
89
+ """
90
+ Immutable architecture configuration for FLUX.2 [klein]-9B model.
91
+
92
+ This is a mid-size 9B parameter model that balances quality and speed. It uses
93
+ Qwen3-8B as text encoder and provides higher quality than the 4B variant while
94
+ maintaining fast 4-step inference. The model is guidance-distilled.
95
+
96
+ Key architectural features:
97
+ - 24 single-stream transformer blocks (between 4B and dev)
98
+ - 4096 hidden dimensions with 32 attention heads
99
+ - No guidance embeddings (guidance-distilled into weights)
100
+ - Context dimension 12288 matches concatenated Qwen3-8B outputs
101
+
102
+ Default generation parameters (distilled variants):
103
+ - guidance: 1.0 (fixed, baked into weights)
104
+ - num_steps: 4 (fixed)
105
+
106
+ Base variant generation parameters:
107
+ - guidance: 4.0 (adjustable)
108
+ - num_steps: 50 (adjustable)
109
+ """
110
+
111
+ # =============================================================================
112
+ # Text Encoder Configuration
113
+ # =============================================================================
114
+
115
+ OUTPUT_LAYERS_MISTRAL = (10, 20, 30)
116
+ """
117
+ Layer indices to extract hidden states from Mistral-Small-3.2-24B text encoder.
118
+
119
+ The FLUX.2 [dev] model uses Mistral-Small as its text encoder and concatenates
120
+ hidden states from these three intermediate layers to form rich text embeddings.
121
+ """
122
+
123
+ OUTPUT_LAYERS_QWEN3 = (9, 18, 27)
124
+ """
125
+ Layer indices to extract hidden states from Qwen3 text encoder variants.
126
+
127
+ The FLUX.2 [klein] models (4B and 9B) use Qwen3 text encoders and concatenate
128
+ hidden states from these three intermediate layers to form text embeddings.
129
+ """
130
+
131
+ MAX_LENGTH = 512
132
+ """
133
+ Maximum sequence length for text encoder tokenization.
134
+
135
+ Text prompts are truncated or padded to this length during encoding. This
136
+ applies to both Mistral-Small and Qwen3 text encoders.
137
+ """
138
+
139
+ UPSAMPLING_MAX_IMAGE_SIZE = 768**2
140
+ """
141
+ Maximum pixel area for images during prompt upsampling.
142
+
143
+ When images are provided as conditioning for prompt upsampling (image-to-image
144
+ mode), they are resized to have at most this many pixels while preserving
145
+ aspect ratio. This prevents out-of-memory errors when processing large images.
146
+ """
147
+
148
+ # =============================================================================
149
+ # System Messages for Prompt Upsampling
150
+ # =============================================================================
151
+
152
+ SYSTEM_MESSAGE = """You are an AI that reasons about image descriptions. You give \
153
+ structured responses focusing on object relationships, object attribution and \
154
+ actions without speculation."""
155
+ """
156
+ Default system message for text encoder when generating image descriptions.
157
+
158
+ This system message is used for general-purpose text encoding tasks where the
159
+ model needs to reason about images in a structured, factual manner.
160
+ """
161
+
162
+ SYSTEM_MESSAGE_UPSAMPLING_T2I = """You are an expert prompt engineer for FLUX.2 \
163
+ by Black Forest Labs. Rewrite user prompts to be more descriptive while \
164
+ strictly preserving their core subject and intent.
165
+
166
+ Guidelines:
167
+ 1. Structure: Keep structured inputs structured (enhance within fields). \
168
+ Convert natural language to detailed paragraphs.
169
+ 2. Details: Add concrete visual specifics - form, scale, textures, materials, \
170
+ lighting (quality, direction, color), shadows, spatial relationships, and \
171
+ environmental context.
172
+ 3. Text in Images: Put ALL text in quotation marks, matching the prompt's \
173
+ language. Always provide explicit quoted text for objects that would contain \
174
+ text in reality (signs, labels, screens, etc.) - without it, the model \
175
+ generates gibberish.
176
+
177
+ Output only the revised prompt and nothing else."""
178
+ """
179
+ System message for text-to-image prompt upsampling.
180
+
181
+ This prompt engineering template guides the text encoder to expand sparse user
182
+ prompts into detailed, visually-rich descriptions that improve image generation
183
+ quality. It emphasizes adding concrete visual details while preserving the
184
+ original intent.
185
+ """
186
+
187
+ SYSTEM_MESSAGE_UPSAMPLING_I2I = """You are FLUX.2 by Black Forest Labs, an \
188
+ image-editing expert. You convert editing requests into one concise \
189
+ instruction (50-80 words, ~30 for brief requests).
190
+
191
+ Rules:
192
+ - Single instruction only, no commentary
193
+ - Use clear, analytical language (avoid "whimsical," "cascading," etc.)
194
+ - Specify what changes AND what stays the same (face, lighting, composition)
195
+ - Reference actual image elements
196
+ - Turn negatives into positives ("don't change X" → "keep X")
197
+ - Make abstractions concrete ("futuristic" → "glowing cyan neon, metallic panels")
198
+ - Keep content PG-13
199
+
200
+ Output only the final instruction in plain text and nothing else."""
201
+ """
202
+ System message for image-to-image prompt upsampling.
203
+
204
+ This prompt engineering template guides the text encoder to convert free-form
205
+ image editing requests into concise, actionable instructions suitable for
206
+ image-to-image generation tasks.
207
+ """
flux_rgbd/_flux2/model.py ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright 2025 Black Forest Labs.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Adapted from the FLUX.2 codebase:
11
+ # https://github.com/black-forest-labs/flux2
12
+ """
13
+ FLUX.2 diffusion transformer architecture for image generation and editing.
14
+
15
+ This module implements the core transformer architecture for FLUX.2 models from
16
+ Black Forest Labs. The architecture uses dual-stream and single-stream transformer
17
+ blocks to process text and image latents for text-to-image and image-to-image tasks.
18
+ """
19
+
20
+ import math
21
+
22
+ import einops
23
+ import torch
24
+ from torch import Tensor, nn
25
+
26
+
27
+ class Flux2(nn.Module):
28
+ """
29
+ FLUX.2 diffusion transformer for image generation and editing.
30
+
31
+ This is a flow-matching diffusion model that uses a stack of dual-stream and
32
+ single-stream transformer blocks for text context and image latents. The
33
+ model supports text-to-image and image-to-image generation tasks.
34
+
35
+ Default parameter values match the FLUX.2 [klein] 4B architecture, which is
36
+ optimized for fast inference. For other model variants (klein-9B or dev),
37
+ use the parameters from :mod:`flux_rgbd._flux2.constants`.
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ in_channels: int = 128,
43
+ context_in_dim: int = 7680,
44
+ hidden_size: int = 3072,
45
+ num_heads: int = 24,
46
+ depth: int = 5,
47
+ depth_single_blocks: int = 20,
48
+ axes_dim: tuple[int, int, int, int] = (32, 32, 32, 32),
49
+ theta: int = 2000,
50
+ mlp_ratio: float = 3.0,
51
+ use_guidance_embed: bool = False,
52
+ ):
53
+ """
54
+ Args:
55
+ in_channels: Number of input channels for image latents. Matches the
56
+ output dimension of the autoencoder used to encode images.
57
+ context_in_dim: Dimension of text context embeddings from the text
58
+ encoder. This should match the concatenated output dimension of
59
+ the text encoder being used (7680 for Qwen3-4B, 12288 for Qwen3-8B,
60
+ 15360 for Mistral-Small).
61
+ hidden_size: Hidden dimension size for transformer blocks. All
62
+ attention and MLP operations use this dimension internally.
63
+ num_heads: Number of attention heads in multi-head attention layers.
64
+ Must evenly divide `hidden_size`.
65
+ depth: Number of dual-stream transformer blocks. These blocks process
66
+ text and image streams separately with cross-attention.
67
+ depth_single_blocks: Number of single-stream transformer blocks. These
68
+ blocks process the concatenated text+image sequence.
69
+ axes_dim: Tuple of 4 integers specifying the dimensionality for each
70
+ axis in rotary embeddings. Must sum to `hidden_size / num_heads`.
71
+ theta: Base frequency for rotary position embeddings (RoPE). Higher
72
+ values result in slower position encoding rotation.
73
+ mlp_ratio: Expansion ratio for MLP hidden dimension relative to
74
+ `hidden_size`. MLP hidden dim = `hidden_size * mlp_ratio`.
75
+ use_guidance_embed: Whether to include guidance scale embeddings for
76
+ classifier-free guidance. Set to False for distilled models where
77
+ guidance is baked into weights.
78
+ """
79
+ super().__init__()
80
+
81
+ self.in_channels = in_channels
82
+ self.out_channels = in_channels
83
+ if hidden_size % num_heads != 0:
84
+ raise ValueError(
85
+ f"Hidden size {hidden_size} must be divisible by "
86
+ f"num_heads {num_heads}"
87
+ )
88
+ pe_dim = hidden_size // num_heads
89
+ if sum(axes_dim) != pe_dim:
90
+ raise ValueError(f"Got {axes_dim} but expected positional dim {pe_dim}")
91
+ self.hidden_size = hidden_size
92
+ self.num_heads = num_heads
93
+ self.pe_embedder = EmbedND(dim=pe_dim, theta=theta, axes_dim=axes_dim)
94
+ self.img_in = nn.Linear(self.in_channels, self.hidden_size, bias=False)
95
+ self.time_in = MLPEmbedder(
96
+ in_dim=256, hidden_dim=self.hidden_size, disable_bias=True
97
+ )
98
+ self.txt_in = nn.Linear(context_in_dim, self.hidden_size, bias=False)
99
+
100
+ self.use_guidance_embed = use_guidance_embed
101
+ if self.use_guidance_embed:
102
+ self.guidance_in = MLPEmbedder(
103
+ in_dim=256, hidden_dim=self.hidden_size, disable_bias=True
104
+ )
105
+
106
+ double_blocks = [
107
+ DoubleStreamBlock(self.hidden_size, self.num_heads, mlp_ratio=mlp_ratio)
108
+ for _ in range(depth)
109
+ ]
110
+ self.double_blocks = nn.ModuleList(double_blocks)
111
+
112
+ single_blocks = [
113
+ SingleStreamBlock(self.hidden_size, self.num_heads, mlp_ratio=mlp_ratio)
114
+ for _ in range(depth_single_blocks)
115
+ ]
116
+ self.single_blocks = nn.ModuleList(single_blocks)
117
+
118
+ self.double_stream_modulation_img = Modulation(
119
+ self.hidden_size, double=True, disable_bias=True
120
+ )
121
+ self.double_stream_modulation_txt = Modulation(
122
+ self.hidden_size, double=True, disable_bias=True
123
+ )
124
+ self.single_stream_modulation = Modulation(
125
+ self.hidden_size, double=False, disable_bias=True
126
+ )
127
+
128
+ self.final_layer = LastLayer(self.hidden_size, self.out_channels)
129
+
130
+ def forward(
131
+ self,
132
+ x: Tensor,
133
+ x_ids: Tensor,
134
+ timesteps: Tensor,
135
+ ctx: Tensor,
136
+ ctx_ids: Tensor,
137
+ guidance: Tensor | None,
138
+ ):
139
+ num_txt_tokens = ctx.shape[1]
140
+
141
+ timestep_emb = timestep_embedding(timesteps, 256)
142
+ vec = self.time_in(timestep_emb)
143
+ if self.use_guidance_embed:
144
+ guidance_emb = timestep_embedding(guidance, 256)
145
+ vec = vec + self.guidance_in(guidance_emb)
146
+
147
+ double_block_mod_img = self.double_stream_modulation_img(vec)
148
+ double_block_mod_txt = self.double_stream_modulation_txt(vec)
149
+ single_block_mod, _ = self.single_stream_modulation(vec)
150
+
151
+ img = self.img_in(x)
152
+ txt = self.txt_in(ctx)
153
+
154
+ pe_x = self.pe_embedder(x_ids)
155
+ pe_ctx = self.pe_embedder(ctx_ids)
156
+
157
+ for block in self.double_blocks:
158
+ img, txt = block(
159
+ img,
160
+ txt,
161
+ pe_x,
162
+ pe_ctx,
163
+ double_block_mod_img,
164
+ double_block_mod_txt,
165
+ )
166
+
167
+ img = torch.cat((txt, img), dim=1)
168
+ pe = torch.cat((pe_ctx, pe_x), dim=2)
169
+
170
+ for block in self.single_blocks:
171
+ img = block(
172
+ img,
173
+ pe,
174
+ single_block_mod,
175
+ )
176
+
177
+ img = img[:, num_txt_tokens:, ...]
178
+
179
+ img = self.final_layer(img, vec)
180
+ return img
181
+
182
+
183
+ class SelfAttention(nn.Module):
184
+ """
185
+ Multi-head self-attention with QK normalization.
186
+
187
+ This module computes query, key, and value projections in a single linear layer,
188
+ applies RMS normalization to queries and keys, then performs attention and projects
189
+ the output back to the original dimension.
190
+ """
191
+
192
+ def __init__(self, dim: int, num_heads: int = 8):
193
+ """
194
+ Args:
195
+ dim: Hidden dimension size. Must be divisible by `num_heads`.
196
+ num_heads: Number of parallel attention heads.
197
+ """
198
+ super().__init__()
199
+ self.num_heads = num_heads
200
+ head_dim = dim // num_heads
201
+ self.qkv = nn.Linear(dim, dim * 3, bias=False)
202
+
203
+ self.norm = QKNorm(head_dim)
204
+ self.proj = nn.Linear(dim, dim, bias=False)
205
+
206
+
207
+ class SiLUActivation(nn.Module):
208
+ """
209
+ Gated activation using SiLU (Swish) function.
210
+
211
+ This module splits the input tensor along the last dimension, applies SiLU to one
212
+ half, and element-wise multiplies it with the other half. This is commonly known
213
+ as SwiGLU when used in MLP layers.
214
+ """
215
+
216
+ def __init__(self):
217
+ super().__init__()
218
+ self.gate_fn = nn.SiLU()
219
+
220
+ def forward(self, x: Tensor) -> Tensor:
221
+ """
222
+ Args:
223
+ x: Input tensor of shape `(..., 2 * dim)` where the last dimension will
224
+ be split into two equal parts for gating.
225
+
226
+ Returns:
227
+ Gated output of shape `(..., dim)`.
228
+ """
229
+ x1, x2 = x.chunk(2, dim=-1)
230
+ return self.gate_fn(x1) * x2
231
+
232
+
233
+ class Modulation(nn.Module):
234
+ """
235
+ Adaptive layer normalization (AdaLN) modulation layer.
236
+
237
+ This module generates scale, shift, and gate parameters for adaptive normalization
238
+ from timestep/guidance embeddings. For double-stream blocks, it produces two sets
239
+ of modulation parameters (one for each stream).
240
+ """
241
+
242
+ def __init__(self, dim: int, double: bool, disable_bias: bool = False):
243
+ """
244
+ Args:
245
+ dim: Hidden dimension size matching the transformer blocks.
246
+ double: If True, generates parameters for dual-stream blocks (6 params:
247
+ shift, scale, gate for each stream). If False, generates for
248
+ single-stream blocks (3 params: shift, scale, gate).
249
+ disable_bias: If True, the linear layer has no bias term.
250
+ """
251
+ super().__init__()
252
+ self.is_double = double
253
+ self.multiplier = 6 if double else 3
254
+ self.lin = nn.Linear(dim, self.multiplier * dim, bias=not disable_bias)
255
+
256
+ def forward(self, vec: Tensor):
257
+ """
258
+ Args:
259
+ vec: Timestep/guidance embedding of shape `(batch_size, dim)` or
260
+ `(batch_size, seq_len, dim)`.
261
+
262
+ Returns:
263
+ Tuple of modulation parameters. For single-stream: `(mod, None)` where
264
+ `mod` is a 3-tuple of (shift, scale, gate). For double-stream:
265
+ `(mod1, mod2)` where each is a 3-tuple for different streams.
266
+ """
267
+ out = self.lin(nn.functional.silu(vec))
268
+ if out.ndim == 2:
269
+ out = out[:, None, :]
270
+ out = out.chunk(self.multiplier, dim=-1)
271
+ return out[:3], out[3:] if self.is_double else None
272
+
273
+
274
+ class LastLayer(nn.Module):
275
+ """
276
+ Final output layer with adaptive layer normalization.
277
+
278
+ This module applies AdaLN-modulated normalization followed by a linear projection
279
+ to map transformer hidden states back to the output space (image latent channels).
280
+ """
281
+
282
+ def __init__(self, hidden_size: int, out_channels: int):
283
+ """
284
+ Args:
285
+ hidden_size: Hidden dimension of transformer blocks.
286
+ out_channels: Output dimension (number of latent channels).
287
+ """
288
+ super().__init__()
289
+ self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
290
+ self.linear = nn.Linear(hidden_size, out_channels, bias=False)
291
+ self.adaLN_modulation = nn.Sequential( # pylint: disable=invalid-name
292
+ nn.SiLU(), nn.Linear(hidden_size, 2 * hidden_size, bias=False)
293
+ )
294
+
295
+ def forward(self, x: Tensor, vec: Tensor) -> Tensor:
296
+ """
297
+ Args:
298
+ x: Hidden states from transformer as a tensor of shape
299
+ `(batch_size, seq_len, hidden_size)`.
300
+ vec: Timestep embedding of shape `(batch_size, hidden_size)`.
301
+
302
+ Returns:
303
+ Output tensor of shape `(batch_size, seq_len, out_channels)`.
304
+ """
305
+ mod = self.adaLN_modulation(vec)
306
+ shift, scale = mod.chunk(2, dim=-1)
307
+ if shift.ndim == 2:
308
+ shift = shift[:, None, :]
309
+ scale = scale[:, None, :]
310
+ x = (1 + scale) * self.norm_final(x) + shift
311
+ x = self.linear(x)
312
+ return x
313
+
314
+
315
+ class SingleStreamBlock(nn.Module):
316
+ """
317
+ Single-stream transformer block processing concatenated text+image tokens.
318
+
319
+ This block applies self-attention and MLP operations to a unified sequence of
320
+ text and image tokens. Both operations share pre-normalization and use adaptive
321
+ modulation from timestep embeddings.
322
+ """
323
+
324
+ def __init__(self, hidden_size: int, num_heads: int, mlp_ratio: float = 4.0):
325
+ """
326
+ Args:
327
+ hidden_size: Hidden dimension size for all linear projections.
328
+ num_heads: Number of attention heads. Must divide `hidden_size` evenly.
329
+ mlp_ratio: Ratio of MLP hidden dimension to `hidden_size`. The actual
330
+ MLP hidden dim is `int(hidden_size * mlp_ratio)`.
331
+ """
332
+ super().__init__()
333
+
334
+ self.hidden_dim = hidden_size
335
+ self.num_heads = num_heads
336
+ head_dim = hidden_size // num_heads
337
+ self.scale = head_dim**-0.5
338
+ self.mlp_hidden_dim = int(hidden_size * mlp_ratio)
339
+ self.mlp_mult_factor = 2
340
+
341
+ self.linear1 = nn.Linear(
342
+ hidden_size,
343
+ hidden_size * 3 + self.mlp_hidden_dim * self.mlp_mult_factor,
344
+ bias=False,
345
+ )
346
+ self.linear2 = nn.Linear(
347
+ hidden_size + self.mlp_hidden_dim, hidden_size, bias=False
348
+ )
349
+ self.norm = QKNorm(head_dim)
350
+
351
+ self.hidden_size = hidden_size
352
+ self.pre_norm = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
353
+
354
+ self.mlp_act = SiLUActivation()
355
+
356
+ def forward(self, x: Tensor, pe: Tensor, mod: tuple[Tensor, Tensor]) -> Tensor:
357
+ mod_shift, mod_scale, mod_gate = mod
358
+ x_mod = (1 + mod_scale) * self.pre_norm(x) + mod_shift
359
+
360
+ qkv, mlp = torch.split(
361
+ self.linear1(x_mod),
362
+ [3 * self.hidden_size, self.mlp_hidden_dim * self.mlp_mult_factor],
363
+ dim=-1,
364
+ )
365
+
366
+ q, k, v = einops.rearrange(
367
+ qkv, "B L (K H D) -> K B H L D", K=3, H=self.num_heads
368
+ )
369
+ q, k = self.norm(q, k, v)
370
+
371
+ attn = attention(q, k, v, pe)
372
+
373
+ # Compute activation in mlp stream, cat again and run second linear layer.
374
+ output = self.linear2(torch.cat((attn, self.mlp_act(mlp)), 2))
375
+ return x + mod_gate * output
376
+
377
+
378
+ class DoubleStreamBlock(nn.Module):
379
+ """
380
+ Dual-stream transformer block processing text and image tokens separately.
381
+
382
+ This block maintains separate streams for text and image tokens, each with their
383
+ own self-attention and MLP sublayers. Cross-stream information exchange happens
384
+ through joint attention where Q, K, V from both streams are concatenated.
385
+ """
386
+
387
+ def __init__(self, hidden_size: int, num_heads: int, mlp_ratio: float):
388
+ """
389
+ Args:
390
+ hidden_size: Hidden dimension size for all linear projections.
391
+ num_heads: Number of attention heads. Must divide `hidden_size` evenly.
392
+ mlp_ratio: Ratio of MLP hidden dimension to `hidden_size`.
393
+ """
394
+ super().__init__()
395
+ mlp_hidden_dim = int(hidden_size * mlp_ratio)
396
+ self.num_heads = num_heads
397
+ assert (
398
+ hidden_size % num_heads == 0
399
+ ), f"{hidden_size=} must be divisible by {num_heads=}"
400
+
401
+ self.hidden_size = hidden_size
402
+ self.img_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
403
+ self.mlp_mult_factor = 2
404
+
405
+ self.img_attn = SelfAttention(dim=hidden_size, num_heads=num_heads)
406
+
407
+ self.img_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
408
+ self.img_mlp = nn.Sequential(
409
+ nn.Linear(hidden_size, mlp_hidden_dim * self.mlp_mult_factor, bias=False),
410
+ SiLUActivation(),
411
+ nn.Linear(mlp_hidden_dim, hidden_size, bias=False),
412
+ )
413
+
414
+ self.txt_norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
415
+ self.txt_attn = SelfAttention(dim=hidden_size, num_heads=num_heads)
416
+
417
+ self.txt_norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
418
+ self.txt_mlp = nn.Sequential(
419
+ nn.Linear(
420
+ hidden_size,
421
+ mlp_hidden_dim * self.mlp_mult_factor,
422
+ bias=False,
423
+ ),
424
+ SiLUActivation(),
425
+ nn.Linear(mlp_hidden_dim, hidden_size, bias=False),
426
+ )
427
+
428
+ def forward(
429
+ self,
430
+ img: Tensor,
431
+ txt: Tensor,
432
+ pe: Tensor,
433
+ pe_ctx: Tensor,
434
+ mod_img: tuple[Tensor, Tensor],
435
+ mod_txt: tuple[Tensor, Tensor],
436
+ ) -> tuple[Tensor, Tensor]:
437
+ img_mod1, img_mod2 = mod_img
438
+ txt_mod1, txt_mod2 = mod_txt
439
+
440
+ img_mod1_shift, img_mod1_scale, img_mod1_gate = img_mod1
441
+ img_mod2_shift, img_mod2_scale, img_mod2_gate = img_mod2
442
+ txt_mod1_shift, txt_mod1_scale, txt_mod1_gate = txt_mod1
443
+ txt_mod2_shift, txt_mod2_scale, txt_mod2_gate = txt_mod2
444
+
445
+ # Prepare image for attention.
446
+ img_modulated = self.img_norm1(img)
447
+ img_modulated = (1 + img_mod1_scale) * img_modulated + img_mod1_shift
448
+
449
+ img_qkv = self.img_attn.qkv(img_modulated)
450
+ img_q, img_k, img_v = einops.rearrange(
451
+ img_qkv, "B L (K H D) -> K B H L D", K=3, H=self.num_heads
452
+ )
453
+ img_q, img_k = self.img_attn.norm(img_q, img_k, img_v)
454
+
455
+ # Prepare txt for attention.
456
+ txt_modulated = self.txt_norm1(txt)
457
+ txt_modulated = (1 + txt_mod1_scale) * txt_modulated + txt_mod1_shift
458
+
459
+ txt_qkv = self.txt_attn.qkv(txt_modulated)
460
+ txt_q, txt_k, txt_v = einops.rearrange(
461
+ txt_qkv, "B L (K H D) -> K B H L D", K=3, H=self.num_heads
462
+ )
463
+ txt_q, txt_k = self.txt_attn.norm(txt_q, txt_k, txt_v)
464
+
465
+ q = torch.cat((txt_q, img_q), dim=2)
466
+ k = torch.cat((txt_k, img_k), dim=2)
467
+ v = torch.cat((txt_v, img_v), dim=2)
468
+
469
+ pe = torch.cat((pe_ctx, pe), dim=2)
470
+ attn = attention(q, k, v, pe)
471
+ txt_attn, img_attn = attn[:, : txt_q.shape[2]], attn[:, txt_q.shape[2] :]
472
+
473
+ # Calculate the img blocks.
474
+ img = img + img_mod1_gate * self.img_attn.proj(img_attn)
475
+ img = img + img_mod2_gate * self.img_mlp(
476
+ (1 + img_mod2_scale) * (self.img_norm2(img)) + img_mod2_shift
477
+ )
478
+
479
+ # Calculate the txt blocks.
480
+ txt = txt + txt_mod1_gate * self.txt_attn.proj(txt_attn)
481
+ txt = txt + txt_mod2_gate * self.txt_mlp(
482
+ (1 + txt_mod2_scale) * (self.txt_norm2(txt)) + txt_mod2_shift
483
+ )
484
+ return img, txt
485
+
486
+
487
+ class MLPEmbedder(nn.Module):
488
+ """
489
+ Two-layer MLP for embedding timestep and guidance values.
490
+
491
+ This simple MLP transforms scalar timestep or guidance embeddings (after
492
+ sinusoidal encoding) into the transformer's hidden dimension space.
493
+ """
494
+
495
+ def __init__(self, in_dim: int, hidden_dim: int, disable_bias: bool = False):
496
+ """
497
+ Args:
498
+ in_dim: Input dimension (typically 256 for sinusoidal embeddings).
499
+ hidden_dim: Output hidden dimension matching transformer blocks.
500
+ disable_bias: If True, linear layers have no bias terms.
501
+ """
502
+ super().__init__()
503
+ self.in_layer = nn.Linear(in_dim, hidden_dim, bias=not disable_bias)
504
+ self.silu = nn.SiLU()
505
+ self.out_layer = nn.Linear(hidden_dim, hidden_dim, bias=not disable_bias)
506
+
507
+ def forward(self, x: Tensor) -> Tensor:
508
+ """
509
+ Args:
510
+ x: Input embeddings of shape `(batch_size, in_dim)`.
511
+
512
+ Returns:
513
+ Projected embeddings of shape `(batch_size, hidden_dim)`.
514
+ """
515
+ return self.out_layer(self.silu(self.in_layer(x)))
516
+
517
+
518
+ class EmbedND(nn.Module):
519
+ """
520
+ N-dimensional rotary position embeddings (RoPE) for spatial-temporal tokens.
521
+
522
+ This module creates rotary embeddings for multi-dimensional position indices
523
+ (e.g., time, height, width, sequence). Each dimension gets its own embedding
524
+ component with configurable dimensions.
525
+ """
526
+
527
+ def __init__(self, dim: int, theta: int, axes_dim: list[int]):
528
+ """
529
+ Args:
530
+ dim: Total position embedding dimension. Should equal `sum(axes_dim)`.
531
+ theta: Base frequency for RoPE. Higher values give slower rotation.
532
+ axes_dim: Dimension allocation for each position axis.
533
+ """
534
+ super().__init__()
535
+ self.dim = dim
536
+ self.theta = theta
537
+ self.axes_dim = axes_dim
538
+
539
+ def forward(self, ids: Tensor) -> Tensor:
540
+ """
541
+ Args:
542
+ ids: Position indices of shape `(..., num_axes)` where `num_axes`
543
+ matches `len(axes_dim)`.
544
+
545
+ Returns:
546
+ Rotary embeddings of shape `(..., 1, sum(axes_dim), 2, 2)` suitable
547
+ for applying rotation to query and key tensors.
548
+ """
549
+ emb = torch.cat(
550
+ [
551
+ rope(ids[..., i], self.axes_dim[i], self.theta)
552
+ for i in range(len(self.axes_dim))
553
+ ],
554
+ dim=-3,
555
+ )
556
+
557
+ return emb.unsqueeze(1)
558
+
559
+
560
+ def timestep_embedding(t: Tensor, dim, max_period=10000, time_factor: float = 1000.0):
561
+ """
562
+ Create sinusoidal timestep embeddings.
563
+ :param t: a 1-D Tensor of N indices, one per batch element.
564
+ These may be fractional.
565
+ :param dim: the dimension of the output.
566
+ :param max_period: controls the minimum frequency of the embeddings.
567
+ :return: an (N, D) Tensor of positional embeddings.
568
+ """
569
+ t = time_factor * t
570
+ half = dim // 2
571
+ freqs = torch.exp(
572
+ -math.log(max_period)
573
+ * torch.arange(start=0, end=half, device=t.device, dtype=torch.float32)
574
+ / half
575
+ )
576
+
577
+ args = t[:, None].float() * freqs[None]
578
+ embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
579
+ if dim % 2:
580
+ embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
581
+ if torch.is_floating_point(t):
582
+ embedding = embedding.to(t)
583
+ return embedding
584
+
585
+
586
+ class RMSNorm(torch.nn.Module):
587
+ """
588
+ Root Mean Square Layer Normalization.
589
+
590
+ RMSNorm normalizes using only the variance (RMS) without centering by mean,
591
+ providing a simpler and often equally effective alternative to LayerNorm.
592
+ """
593
+
594
+ def __init__(self, dim: int):
595
+ """
596
+ Args:
597
+ dim: Dimension to normalize over (last dimension of input).
598
+ """
599
+ super().__init__()
600
+ self.scale = nn.Parameter(torch.ones(dim))
601
+
602
+ def forward(self, x: Tensor):
603
+ """
604
+ Args:
605
+ x: Input tensor of shape `(..., dim)`.
606
+
607
+ Returns:
608
+ Normalized tensor of same shape as input.
609
+ """
610
+ x_dtype = x.dtype
611
+ x = x.float()
612
+ rrms = torch.rsqrt(torch.mean(x**2, dim=-1, keepdim=True) + 1e-6)
613
+ return (x * rrms).to(dtype=x_dtype) * self.scale
614
+
615
+
616
+ class QKNorm(torch.nn.Module):
617
+ """
618
+ Separate RMSNorm for query and key tensors in attention.
619
+
620
+ Normalizing queries and keys independently before attention computation improves
621
+ training stability and can lead to better performance.
622
+ """
623
+
624
+ def __init__(self, dim: int):
625
+ """
626
+ Args:
627
+ dim: Head dimension for queries and keys.
628
+ """
629
+ super().__init__()
630
+ self.query_norm = RMSNorm(dim)
631
+ self.key_norm = RMSNorm(dim)
632
+
633
+ def forward(self, q: Tensor, k: Tensor, v: Tensor) -> tuple[Tensor, Tensor]:
634
+ """
635
+ Args:
636
+ q: Query tensor of shape `(..., head_dim)`.
637
+ k: Key tensor of shape `(..., head_dim)`.
638
+ v: Value tensor (used only for dtype matching).
639
+
640
+ Returns:
641
+ Tuple of normalized `(query, key)` tensors with dtype matching `v`.
642
+ """
643
+ q = self.query_norm(q)
644
+ k = self.key_norm(k)
645
+ return q.to(v), k.to(v)
646
+
647
+
648
+ def attention(q: Tensor, k: Tensor, v: Tensor, pe: Tensor) -> Tensor:
649
+ q, k = apply_rope(q, k, pe)
650
+
651
+ x = torch.nn.functional.scaled_dot_product_attention(q, k, v)
652
+ x = einops.rearrange(x, "B H L D -> B L (H D)")
653
+
654
+ return x
655
+
656
+
657
+ def rope(pos: Tensor, dim: int, theta: int) -> Tensor:
658
+ assert dim % 2 == 0
659
+ scale = torch.arange(0, dim, 2, dtype=pos.dtype, device=pos.device) / dim
660
+ omega = 1.0 / (theta**scale)
661
+ out = torch.einsum("...n,d->...nd", pos, omega)
662
+ out = torch.stack(
663
+ [torch.cos(out), -torch.sin(out), torch.sin(out), torch.cos(out)], dim=-1
664
+ )
665
+ out = einops.rearrange(out, "b n d (i j) -> b n d i j", i=2, j=2)
666
+ return out.float()
667
+
668
+
669
+ def apply_rope(xq: Tensor, xk: Tensor, freqs_cis: Tensor) -> tuple[Tensor, Tensor]:
670
+ xq_ = xq.float().reshape(*xq.shape[:-1], -1, 1, 2)
671
+ xk_ = xk.float().reshape(*xk.shape[:-1], -1, 1, 2)
672
+ xq_out = freqs_cis[..., 0] * xq_[..., 0] + freqs_cis[..., 1] * xq_[..., 1]
673
+ xk_out = freqs_cis[..., 0] * xk_[..., 0] + freqs_cis[..., 1] * xk_[..., 1]
674
+ return xq_out.reshape(*xq.shape).type_as(xq), xk_out.reshape(*xk.shape).type_as(xk)
flux_rgbd/depth/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 World Labs.
3
+ """Depth-side preprocessing and schedule helpers for flux_rgbd."""
4
+
5
+ from flux_rgbd.depth.preprocess import DepthConfig, decode_depth
6
+ from flux_rgbd.depth.schedule import ScheduleConfig, rollout_timesteps
7
+
8
+ __all__ = ["DepthConfig", "decode_depth", "ScheduleConfig", "rollout_timesteps"]
flux_rgbd/depth/preprocess.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # Copyright (c) 2026 World Labs.
3
+ """Depth-side decoding: model tokens → depth pixel map.
4
+
5
+ Decoding steps:
6
+ 1. Unpatchify the (tok_h, tok_w, patch_h * patch_w) token grid.
7
+ 2. Divide out the post-normalization scalar `depth_value_scale`.
8
+ 3. Apply the inverse of every recorded normalization stage, in reverse.
9
+
10
+ Only `"contract"` (the mip-NeRF 360 radial squash, arXiv 2111.12077
11
+ Eq. 10) is genuinely invertible — the other stages discard per-sample
12
+ statistics during encoding and pass through unchanged on decode.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import dataclasses
18
+
19
+ import einops
20
+ import torch
21
+ from torch import Tensor
22
+
23
+ _KNOWN_STAGES = frozenset({"contract", "normal", "mean", "unit_mean", "none"})
24
+
25
+
26
+ @dataclasses.dataclass(frozen=True)
27
+ class DepthConfig:
28
+ """How depth was normalized at training time. Decoder reads these."""
29
+
30
+ depth_normalize_mode: tuple[str, ...] | str = ("unit_mean", "contract")
31
+ patch_size: int = 16
32
+ depth_value_scale: float = 1.0
33
+
34
+ def __post_init__(self) -> None:
35
+ stages = self.depth_normalize_mode
36
+ if isinstance(stages, str):
37
+ object.__setattr__(self, "depth_normalize_mode", (stages,))
38
+ unknown = set(self.depth_normalize_mode) - _KNOWN_STAGES
39
+ if unknown:
40
+ raise ValueError(f"unknown stages {sorted(unknown)}; expected {sorted(_KNOWN_STAGES)}")
41
+ if self.depth_value_scale <= 0:
42
+ raise ValueError(f"depth_value_scale must be > 0; got {self.depth_value_scale}")
43
+ if self.patch_size <= 0:
44
+ raise ValueError(f"patch_size must be > 0; got {self.patch_size}")
45
+
46
+
47
+ def _inv_contract(z: Tensor) -> Tensor:
48
+ """Inverse of the mip-NeRF 360 radial contract bijection (operates on last dim).
49
+
50
+ Contract maps R^n into the ball of radius 2; this undoes it. For |z| <= 1
51
+ the contract was identity, so this is also identity in that region.
52
+ """
53
+ mag_sq = torch.clamp(z.square().sum(dim=-1, keepdim=True), min=1.0)
54
+ return z / (2.0 * mag_sq.sqrt() - mag_sq)
55
+
56
+
57
+ def _undo_stage(depth: Tensor, stage: str) -> Tensor:
58
+ if stage != "contract": # "normal"/"mean"/"unit_mean"/"none" are pass-through.
59
+ return depth
60
+ if depth.shape[-1] == 1:
61
+ return _inv_contract(depth)
62
+ return _inv_contract(depth.unsqueeze(-1)).squeeze(-1)
63
+
64
+
65
+ def decode_depth(tokens: Tensor, config: DepthConfig) -> Tensor:
66
+ """Token grid -> depth pixel map.
67
+
68
+ Input shape: (..., tok_h, tok_w, patch_h * patch_w)
69
+ Output shape: (..., tok_h * patch_size, tok_w * patch_size, 1)
70
+ """
71
+ p = config.patch_size
72
+ depth = einops.rearrange(tokens, "... h w (p1 p2 c) -> ... (h p1) (w p2) c", p1=p, p2=p)
73
+ if config.depth_value_scale != 1.0:
74
+ depth = depth / config.depth_value_scale
75
+ for stage in reversed(config.depth_normalize_mode):
76
+ depth = _undo_stage(depth, stage)
77
+ return depth
78
+
79
+
80
+ def _contract(z: Tensor) -> Tensor:
81
+ """Forward mip-NeRF 360 radial contract (inverse of `_inv_contract`).
82
+
83
+ Identity for |z| <= 1; otherwise squashes radius r into (2 - 1/r).
84
+ """
85
+ mag = z.norm(dim=-1, keepdim=True)
86
+ safe = mag.clamp(min=1e-12)
87
+ scale = torch.where(mag <= 1.0, torch.ones_like(mag), (2.0 - 1.0 / safe) / safe)
88
+ return z * scale
89
+
90
+
91
+ def _apply_stage(depth: Tensor, stage: str) -> Tensor:
92
+ """Forward of `_undo_stage`. Only `contract` and `unit_mean` are active.
93
+
94
+ `unit_mean` divides by the per-map mean so the depth is scale-normalised
95
+ the way the model was trained; it is pass-through on decode (the absolute
96
+ scale is not recoverable), so encode/decode round-trips up to that scale.
97
+ """
98
+ if stage == "unit_mean":
99
+ return depth / depth.mean().clamp(min=1e-12)
100
+ if stage != "contract": # "normal"/"mean"/"none" are pass-through.
101
+ return depth
102
+ if depth.shape[-1] == 1:
103
+ return _contract(depth)
104
+ return _contract(depth.unsqueeze(-1)).squeeze(-1)
105
+
106
+
107
+ def encode_depth(depth_map: Tensor, config: DepthConfig) -> Tensor:
108
+ """Depth pixel map -> token grid (inverse of `decode_depth`).
109
+
110
+ Input shape: (..., H, W, 1)
111
+ Output shape: (..., H/patch_size, W/patch_size, patch_size**2)
112
+
113
+ Used for ``mode="d2i"`` to turn a depth map into the depth-stream tokens
114
+ the model conditions on. Because `unit_mean` is scale-normalising, only the
115
+ relative depth structure matters — the input need not be metric.
116
+ """
117
+ p = config.patch_size
118
+ depth = depth_map
119
+ for stage in config.depth_normalize_mode:
120
+ depth = _apply_stage(depth, stage)
121
+ if config.depth_value_scale != 1.0:
122
+ depth = depth * config.depth_value_scale
123
+ return einops.rearrange(depth, "... (h p1) (w p2) c -> ... h w (p1 p2 c)", p1=p, p2=p)