Zhengrui commited on
Commit
4314c2a
·
verified ·
1 Parent(s): 37bbc9e

Upload full text Space sources

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +51 -0
  2. .gitignore +418 -0
  3. LICENSE +21 -0
  4. NOTICE +22 -0
  5. THIRD_PARTY_LICENSES/LICENSE_DUO.txt +201 -0
  6. app.py +53 -0
  7. app_dvd_image.py +1160 -0
  8. app_dvd_image_gpu_lazy.py +283 -0
  9. app_dvd_image_trellis_style.py +202 -0
  10. app_dvd_image_wrapper.py +187 -0
  11. app_image_min.py +116 -0
  12. app_smoke.py +29 -0
  13. app_space_image.py +188 -0
  14. assets/example_image/T.png +3 -0
  15. assets/example_image/typical_building_building.png +3 -0
  16. assets/example_image/typical_building_castle.png +3 -0
  17. assets/example_image/typical_building_colorful_cottage.png +3 -0
  18. assets/example_image/typical_building_maya_pyramid.png +3 -0
  19. assets/example_image/typical_building_mushroom.png +3 -0
  20. assets/example_image/typical_building_space_station.png +3 -0
  21. assets/example_image/typical_creature_dragon.png +3 -0
  22. assets/example_image/typical_creature_elephant.png +3 -0
  23. assets/example_image/typical_creature_furry.png +3 -0
  24. assets/example_image/typical_creature_quadruped.png +3 -0
  25. assets/example_image/typical_creature_robot_crab.png +3 -0
  26. assets/example_image/typical_creature_robot_dinosour.png +3 -0
  27. assets/example_image/typical_creature_rock_monster.png +3 -0
  28. assets/example_image/typical_humanoid_block_robot.png +3 -0
  29. assets/example_image/typical_humanoid_dragonborn.png +3 -0
  30. assets/example_image/typical_humanoid_dwarf.png +3 -0
  31. assets/example_image/typical_humanoid_goblin.png +3 -0
  32. assets/example_image/typical_humanoid_mech.png +3 -0
  33. assets/example_image/typical_misc_crate.png +3 -0
  34. assets/example_image/typical_misc_fireplace.png +3 -0
  35. assets/example_image/typical_misc_gate.png +3 -0
  36. assets/example_image/typical_misc_lantern.png +3 -0
  37. assets/example_image/typical_misc_magicbook.png +3 -0
  38. assets/example_image/typical_misc_mailbox.png +3 -0
  39. assets/example_image/typical_misc_monster_chest.png +3 -0
  40. assets/example_image/typical_misc_paper_machine.png +3 -0
  41. assets/example_image/typical_misc_phonograph.png +3 -0
  42. assets/example_image/typical_misc_portal2.png +3 -0
  43. assets/example_image/typical_misc_storage_chest.png +3 -0
  44. assets/example_image/typical_misc_telephone.png +3 -0
  45. assets/example_image/typical_misc_television.png +3 -0
  46. assets/example_image/typical_misc_workbench.png +3 -0
  47. assets/example_image/typical_vehicle_biplane.png +3 -0
  48. assets/example_image/typical_vehicle_bulldozer.png +3 -0
  49. assets/example_image/typical_vehicle_cart.png +3 -0
  50. assets/example_image/typical_vehicle_excavator.png +3 -0
.gitattributes CHANGED
@@ -33,3 +33,54 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/example_image/T.png filter=lfs diff=lfs merge=lfs -text
37
+ assets/example_image/typical_building_building.png filter=lfs diff=lfs merge=lfs -text
38
+ assets/example_image/typical_building_castle.png filter=lfs diff=lfs merge=lfs -text
39
+ assets/example_image/typical_building_colorful_cottage.png filter=lfs diff=lfs merge=lfs -text
40
+ assets/example_image/typical_building_maya_pyramid.png filter=lfs diff=lfs merge=lfs -text
41
+ assets/example_image/typical_building_mushroom.png filter=lfs diff=lfs merge=lfs -text
42
+ assets/example_image/typical_building_space_station.png filter=lfs diff=lfs merge=lfs -text
43
+ assets/example_image/typical_creature_dragon.png filter=lfs diff=lfs merge=lfs -text
44
+ assets/example_image/typical_creature_elephant.png filter=lfs diff=lfs merge=lfs -text
45
+ assets/example_image/typical_creature_furry.png filter=lfs diff=lfs merge=lfs -text
46
+ assets/example_image/typical_creature_quadruped.png filter=lfs diff=lfs merge=lfs -text
47
+ assets/example_image/typical_creature_robot_crab.png filter=lfs diff=lfs merge=lfs -text
48
+ assets/example_image/typical_creature_robot_dinosour.png filter=lfs diff=lfs merge=lfs -text
49
+ assets/example_image/typical_creature_rock_monster.png filter=lfs diff=lfs merge=lfs -text
50
+ assets/example_image/typical_humanoid_block_robot.png filter=lfs diff=lfs merge=lfs -text
51
+ assets/example_image/typical_humanoid_dragonborn.png filter=lfs diff=lfs merge=lfs -text
52
+ assets/example_image/typical_humanoid_dwarf.png filter=lfs diff=lfs merge=lfs -text
53
+ assets/example_image/typical_humanoid_goblin.png filter=lfs diff=lfs merge=lfs -text
54
+ assets/example_image/typical_humanoid_mech.png filter=lfs diff=lfs merge=lfs -text
55
+ assets/example_image/typical_misc_crate.png filter=lfs diff=lfs merge=lfs -text
56
+ assets/example_image/typical_misc_fireplace.png filter=lfs diff=lfs merge=lfs -text
57
+ assets/example_image/typical_misc_gate.png filter=lfs diff=lfs merge=lfs -text
58
+ assets/example_image/typical_misc_lantern.png filter=lfs diff=lfs merge=lfs -text
59
+ assets/example_image/typical_misc_magicbook.png filter=lfs diff=lfs merge=lfs -text
60
+ assets/example_image/typical_misc_mailbox.png filter=lfs diff=lfs merge=lfs -text
61
+ assets/example_image/typical_misc_monster_chest.png filter=lfs diff=lfs merge=lfs -text
62
+ assets/example_image/typical_misc_paper_machine.png filter=lfs diff=lfs merge=lfs -text
63
+ assets/example_image/typical_misc_phonograph.png filter=lfs diff=lfs merge=lfs -text
64
+ assets/example_image/typical_misc_portal2.png filter=lfs diff=lfs merge=lfs -text
65
+ assets/example_image/typical_misc_storage_chest.png filter=lfs diff=lfs merge=lfs -text
66
+ assets/example_image/typical_misc_telephone.png filter=lfs diff=lfs merge=lfs -text
67
+ assets/example_image/typical_misc_television.png filter=lfs diff=lfs merge=lfs -text
68
+ assets/example_image/typical_misc_workbench.png filter=lfs diff=lfs merge=lfs -text
69
+ assets/example_image/typical_vehicle_biplane.png filter=lfs diff=lfs merge=lfs -text
70
+ assets/example_image/typical_vehicle_bulldozer.png filter=lfs diff=lfs merge=lfs -text
71
+ assets/example_image/typical_vehicle_cart.png filter=lfs diff=lfs merge=lfs -text
72
+ assets/example_image/typical_vehicle_excavator.png filter=lfs diff=lfs merge=lfs -text
73
+ assets/example_image/typical_vehicle_helicopter.png filter=lfs diff=lfs merge=lfs -text
74
+ assets/example_image/typical_vehicle_locomotive.png filter=lfs diff=lfs merge=lfs -text
75
+ assets/example_image/typical_vehicle_pirate_ship.png filter=lfs diff=lfs merge=lfs -text
76
+ assets/example_image/weatherworn_misc_paper_machine3.png filter=lfs diff=lfs merge=lfs -text
77
+ assets/example_image_edit/flower_rm.png filter=lfs diff=lfs merge=lfs -text
78
+ assets/example_image_edit/ice_cream_rm.png filter=lfs diff=lfs merge=lfs -text
79
+ assets/example_image_edit/pineapple_rm.png filter=lfs diff=lfs merge=lfs -text
80
+ assets/example_image_edit/typical_building_mushroom.png filter=lfs diff=lfs merge=lfs -text
81
+ assets/example_image_edit/wooden_rm.png filter=lfs diff=lfs merge=lfs -text
82
+ assets/teaser.png filter=lfs diff=lfs merge=lfs -text
83
+ assets/teaser2.png filter=lfs diff=lfs merge=lfs -text
84
+ example_results/T.glb filter=lfs diff=lfs merge=lfs -text
85
+ trellis/representations/mesh/flexicubes/images/block_init.png filter=lfs diff=lfs merge=lfs -text
86
+ trellis/representations/mesh/flexicubes/images/teaser_top.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Ignore Visual Studio temporary files, build results, and
2
+ ## files generated by popular Visual Studio add-ons.
3
+ ##
4
+ ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5
+
6
+ # Python / Hugging Face Space artifacts
7
+ __pycache__/
8
+ *.py[cod]
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .mypy_cache/
12
+ .cache/
13
+ tmp/
14
+ ckpts/
15
+ *.safetensors
16
+ *.ckpt
17
+ *.pt
18
+ *.pth
19
+
20
+ # User-specific files
21
+ *.rsuser
22
+ *.suo
23
+ *.user
24
+ *.userosscache
25
+ *.sln.docstates
26
+
27
+ # User-specific files (MonoDevelop/Xamarin Studio)
28
+ *.userprefs
29
+
30
+ # Mono auto generated files
31
+ mono_crash.*
32
+
33
+ # Build results
34
+ [Dd]ebug/
35
+ [Dd]ebugPublic/
36
+ [Rr]elease/
37
+ [Rr]eleases/
38
+ x64/
39
+ x86/
40
+ [Ww][Ii][Nn]32/
41
+ [Aa][Rr][Mm]/
42
+ [Aa][Rr][Mm]64/
43
+ bld/
44
+ [Bb]in/
45
+ [Oo]bj/
46
+ [Ll]og/
47
+ [Ll]ogs/
48
+
49
+ # Visual Studio 2015/2017 cache/options directory
50
+ .vs/
51
+ # Uncomment if you have tasks that create the project's static files in wwwroot
52
+ #wwwroot/
53
+
54
+ # Visual Studio 2017 auto generated files
55
+ Generated\ Files/
56
+
57
+ # MSTest test Results
58
+ [Tt]est[Rr]esult*/
59
+ [Bb]uild[Ll]og.*
60
+
61
+ # NUnit
62
+ *.VisualState.xml
63
+ TestResult.xml
64
+ nunit-*.xml
65
+
66
+ # Build Results of an ATL Project
67
+ [Dd]ebugPS/
68
+ [Rr]eleasePS/
69
+ dlldata.c
70
+
71
+ # Benchmark Results
72
+ BenchmarkDotNet.Artifacts/
73
+
74
+ # .NET Core
75
+ project.lock.json
76
+ project.fragment.lock.json
77
+ artifacts/
78
+
79
+ # ASP.NET Scaffolding
80
+ ScaffoldingReadMe.txt
81
+
82
+ # StyleCop
83
+ StyleCopReport.xml
84
+
85
+ # Files built by Visual Studio
86
+ *_i.c
87
+ *_p.c
88
+ *_h.h
89
+ *.ilk
90
+ *.meta
91
+ *.obj
92
+ *.iobj
93
+ *.pch
94
+ *.pdb
95
+ *.ipdb
96
+ *.pgc
97
+ *.pgd
98
+ *.rsp
99
+ *.sbr
100
+ *.tlb
101
+ *.tli
102
+ *.tlh
103
+ *.tmp
104
+ *.tmp_proj
105
+ *_wpftmp.csproj
106
+ *.log
107
+ *.tlog
108
+ *.vspscc
109
+ *.vssscc
110
+ .builds
111
+ *.pidb
112
+ *.svclog
113
+ *.scc
114
+
115
+ # Chutzpah Test files
116
+ _Chutzpah*
117
+
118
+ # Visual C++ cache files
119
+ ipch/
120
+ *.aps
121
+ *.ncb
122
+ *.opendb
123
+ *.opensdf
124
+ *.sdf
125
+ *.cachefile
126
+ *.VC.db
127
+ *.VC.VC.opendb
128
+
129
+ # Visual Studio profiler
130
+ *.psess
131
+ *.vsp
132
+ *.vspx
133
+ *.sap
134
+
135
+ # Visual Studio Trace Files
136
+ *.e2e
137
+
138
+ # TFS 2012 Local Workspace
139
+ $tf/
140
+
141
+ # Guidance Automation Toolkit
142
+ *.gpState
143
+
144
+ # ReSharper is a .NET coding add-in
145
+ _ReSharper*/
146
+ *.[Rr]e[Ss]harper
147
+ *.DotSettings.user
148
+
149
+ # TeamCity is a build add-in
150
+ _TeamCity*
151
+
152
+ # DotCover is a Code Coverage Tool
153
+ *.dotCover
154
+
155
+ # AxoCover is a Code Coverage Tool
156
+ .axoCover/*
157
+ !.axoCover/settings.json
158
+
159
+ # Coverlet is a free, cross platform Code Coverage Tool
160
+ coverage*.json
161
+ coverage*.xml
162
+ coverage*.info
163
+
164
+ # Visual Studio code coverage results
165
+ *.coverage
166
+ *.coveragexml
167
+
168
+ # NCrunch
169
+ _NCrunch_*
170
+ .*crunch*.local.xml
171
+ nCrunchTemp_*
172
+
173
+ # MightyMoose
174
+ *.mm.*
175
+ AutoTest.Net/
176
+
177
+ # Web workbench (sass)
178
+ .sass-cache/
179
+
180
+ # Installshield output folder
181
+ [Ee]xpress/
182
+
183
+ # DocProject is a documentation generator add-in
184
+ DocProject/buildhelp/
185
+ DocProject/Help/*.HxT
186
+ DocProject/Help/*.HxC
187
+ DocProject/Help/*.hhc
188
+ DocProject/Help/*.hhk
189
+ DocProject/Help/*.hhp
190
+ DocProject/Help/Html2
191
+ DocProject/Help/html
192
+
193
+ # Click-Once directory
194
+ publish/
195
+
196
+ # Publish Web Output
197
+ *.[Pp]ublish.xml
198
+ *.azurePubxml
199
+ # Note: Comment the next line if you want to checkin your web deploy settings,
200
+ # but database connection strings (with potential passwords) will be unencrypted
201
+ *.pubxml
202
+ *.publishproj
203
+
204
+ # Microsoft Azure Web App publish settings. Comment the next line if you want to
205
+ # checkin your Azure Web App publish settings, but sensitive information contained
206
+ # in these scripts will be unencrypted
207
+ PublishScripts/
208
+
209
+ # NuGet Packages
210
+ *.nupkg
211
+ # NuGet Symbol Packages
212
+ *.snupkg
213
+ # The packages folder can be ignored because of Package Restore
214
+ **/[Pp]ackages/*
215
+ # except build/, which is used as an MSBuild target.
216
+ !**/[Pp]ackages/build/
217
+ # Uncomment if necessary however generally it will be regenerated when needed
218
+ #!**/[Pp]ackages/repositories.config
219
+ # NuGet v3's project.json files produces more ignorable files
220
+ *.nuget.props
221
+ *.nuget.targets
222
+
223
+ # Microsoft Azure Build Output
224
+ csx/
225
+ *.build.csdef
226
+
227
+ # Microsoft Azure Emulator
228
+ ecf/
229
+ rcf/
230
+
231
+ # Windows Store app package directories and files
232
+ AppPackages/
233
+ BundleArtifacts/
234
+ Package.StoreAssociation.xml
235
+ _pkginfo.txt
236
+ *.appx
237
+ *.appxbundle
238
+ *.appxupload
239
+
240
+ # Visual Studio cache files
241
+ # files ending in .cache can be ignored
242
+ *.[Cc]ache
243
+ # but keep track of directories ending in .cache
244
+ !?*.[Cc]ache/
245
+
246
+ # Others
247
+ ClientBin/
248
+ ~$*
249
+ *~
250
+ *.dbmdl
251
+ *.dbproj.schemaview
252
+ *.jfm
253
+ *.pfx
254
+ *.publishsettings
255
+ orleans.codegen.cs
256
+
257
+ # Including strong name files can present a security risk
258
+ # (https://github.com/github/gitignore/pull/2483#issue-259490424)
259
+ #*.snk
260
+
261
+ # Since there are multiple workflows, uncomment next line to ignore bower_components
262
+ # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
263
+ #bower_components/
264
+
265
+ # RIA/Silverlight projects
266
+ Generated_Code/
267
+
268
+ # Backup & report files from converting an old project file
269
+ # to a newer Visual Studio version. Backup files are not needed,
270
+ # because we have git ;-)
271
+ _UpgradeReport_Files/
272
+ Backup*/
273
+ UpgradeLog*.XML
274
+ UpgradeLog*.htm
275
+ ServiceFabricBackup/
276
+ *.rptproj.bak
277
+
278
+ # SQL Server files
279
+ *.mdf
280
+ *.ldf
281
+ *.ndf
282
+
283
+ # Business Intelligence projects
284
+ *.rdl.data
285
+ *.bim.layout
286
+ *.bim_*.settings
287
+ *.rptproj.rsuser
288
+ *- [Bb]ackup.rdl
289
+ *- [Bb]ackup ([0-9]).rdl
290
+ *- [Bb]ackup ([0-9][0-9]).rdl
291
+
292
+ # Microsoft Fakes
293
+ FakesAssemblies/
294
+
295
+ # GhostDoc plugin setting file
296
+ *.GhostDoc.xml
297
+
298
+ # Node.js Tools for Visual Studio
299
+ .ntvs_analysis.dat
300
+ node_modules/
301
+
302
+ # Visual Studio 6 build log
303
+ *.plg
304
+
305
+ # Visual Studio 6 workspace options file
306
+ *.opt
307
+
308
+ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
309
+ *.vbw
310
+
311
+ # Visual Studio 6 auto-generated project file (contains which files were open etc.)
312
+ *.vbp
313
+
314
+ # Visual Studio 6 workspace and project file (working project files containing files to include in project)
315
+ *.dsw
316
+ *.dsp
317
+
318
+ # Visual Studio 6 technical files
319
+ *.ncb
320
+ *.aps
321
+
322
+ # Visual Studio LightSwitch build output
323
+ **/*.HTMLClient/GeneratedArtifacts
324
+ **/*.DesktopClient/GeneratedArtifacts
325
+ **/*.DesktopClient/ModelManifest.xml
326
+ **/*.Server/GeneratedArtifacts
327
+ **/*.Server/ModelManifest.xml
328
+ _Pvt_Extensions
329
+
330
+ # Paket dependency manager
331
+ .paket/paket.exe
332
+ paket-files/
333
+
334
+ # FAKE - F# Make
335
+ .fake/
336
+
337
+ # CodeRush personal settings
338
+ .cr/personal
339
+
340
+ # Python Tools for Visual Studio (PTVS)
341
+ __pycache__/
342
+ *.pyc
343
+
344
+ # Cake - Uncomment if you are using it
345
+ # tools/**
346
+ # !tools/packages.config
347
+
348
+ # Tabs Studio
349
+ *.tss
350
+
351
+ # Telerik's JustMock configuration file
352
+ *.jmconfig
353
+
354
+ # BizTalk build output
355
+ *.btp.cs
356
+ *.btm.cs
357
+ *.odx.cs
358
+ *.xsd.cs
359
+
360
+ # OpenCover UI analysis results
361
+ OpenCover/
362
+
363
+ # Azure Stream Analytics local run output
364
+ ASALocalRun/
365
+
366
+ # MSBuild Binary and Structured Log
367
+ *.binlog
368
+
369
+ # NVidia Nsight GPU debugger configuration file
370
+ *.nvuser
371
+
372
+ # MFractors (Xamarin productivity tool) working folder
373
+ .mfractor/
374
+
375
+ # Local History for Visual Studio
376
+ .localhistory/
377
+
378
+ # Visual Studio History (VSHistory) files
379
+ .vshistory/
380
+
381
+ # BeatPulse healthcheck temp database
382
+ healthchecksdb
383
+
384
+ # Backup folder for Package Reference Convert tool in Visual Studio 2017
385
+ MigrationBackup/
386
+
387
+ # Ionide (cross platform F# VS Code tools) working folder
388
+ .ionide/
389
+
390
+ # Fody - auto-generated XML schema
391
+ FodyWeavers.xsd
392
+
393
+ # VS Code files for those working on multiple tools
394
+ .vscode/*
395
+ !.vscode/settings.json
396
+ !.vscode/tasks.json
397
+ !.vscode/launch.json
398
+ !.vscode/extensions.json
399
+ *.code-workspace
400
+
401
+ # Local History for Visual Studio Code
402
+ .history/
403
+
404
+ # Windows Installer files from build outputs
405
+ *.cab
406
+ *.msi
407
+ *.msix
408
+ *.msm
409
+ *.msp
410
+
411
+ # JetBrains Rider
412
+ *.sln.iml
413
+
414
+ # Mac
415
+ .DS_Store
416
+ .vscode/
417
+ ckpts/
418
+ tmp/
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) Microsoft Corporation.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE
NOTICE ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DVD: Discrete Voxel Diffusion for 3D Generation and Editing
2
+ Copyright 2026 Zhengrui Xiang
3
+
4
+ This project includes code and/or components adapted from the following projects:
5
+
6
+ TRELLIS
7
+ Copyright Microsoft Corporation and contributors.
8
+ Licensed under the MIT License.
9
+ Source: https://github.com/microsoft/TRELLIS
10
+
11
+ DUO: The Diffusion Duality
12
+ Subham Sekhar Sahoo, Justin Deschenaux, Aaron Gokaslan, Guanghan Wang, Justin Chiu, Volodymyr Kuleshov and contributors.
13
+
14
+ Licensed under the Apache License, Version 2.0.
15
+ Source: https://github.com/s-sahoo/duo
16
+
17
+ Portions adapted from DUO were modified for discrete voxel diffusion training,
18
+ sampling, evaluation, and related experiments.
19
+
20
+ This project may also depend on third-party packages with separate license terms,
21
+ including nvdiffrast and nvdiffrec. Please refer to the corresponding upstream repositories
22
+ and license files for details.
THIRD_PARTY_LICENSES/LICENSE_DUO.txt ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2025 Subham Sekhar Sahoo
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
app.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+
4
+ os.environ.setdefault("DVD_MODEL_REPO", "Zhengrui/dvd")
5
+ os.environ.setdefault("SPCONV_ALGO", "native")
6
+ os.environ.setdefault("ATTN_BACKEND", "flash_attn")
7
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
8
+
9
+ try:
10
+ import spaces
11
+ except ImportError:
12
+ spaces = None
13
+
14
+ import gradio as gr
15
+
16
+
17
+ # Do not request a ZeroGPU worker during app startup. CUDA render extensions
18
+ # are installed from prebuilt wheels in requirements.txt.
19
+ APP_MODE = os.environ.get("DVD_SPACE_APP", "image").strip().lower()
20
+
21
+
22
+ def _maybe_preload(module, label: str):
23
+ if os.environ.get("DVD_PRELOAD_DVD_GEN", "0").lower() in {"0", "false", "no"}:
24
+ return
25
+ module.log_event(f"preloading {label} DVD generation pipeline on cpu")
26
+ module.ensure_dvd_gen_pipeline("cpu")
27
+ module.log_event(f"{label} DVD generation pipeline preloaded on cpu")
28
+
29
+
30
+ if APP_MODE in {"image", "img"}:
31
+ import app_dvd_image as app_module
32
+ _maybe_preload(app_module, "image")
33
+ demo = app_module.demo
34
+ elif APP_MODE in {"text", "txt"}:
35
+ import app_dvd_text as app_module
36
+ _maybe_preload(app_module, "text")
37
+ demo = app_module.demo
38
+ elif APP_MODE in {"both", "all", "full"}:
39
+ import app_dvd_image
40
+ import app_dvd_text
41
+ _maybe_preload(app_dvd_image, "image")
42
+ _maybe_preload(app_dvd_text, "text")
43
+ demo = gr.TabbedInterface(
44
+ [app_dvd_image.demo, app_dvd_text.demo],
45
+ ["Image", "Text"],
46
+ title="DVD + TRELLIS Voxel Generation and Editing",
47
+ )
48
+ else:
49
+ raise ValueError("DVD_SPACE_APP must be 'image', 'text', or 'both'.")
50
+
51
+
52
+ if __name__ == "__main__":
53
+ demo.queue().launch(show_api=False, show_error=True, ssr_mode=False)
app_dvd_image.py ADDED
@@ -0,0 +1,1160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ os.environ.setdefault("SPCONV_ALGO", "native")
4
+ os.environ.setdefault("ATTN_BACKEND", "flash_attn")
5
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
6
+ os.environ.setdefault("DVD_MODEL_REPO", "Zhengrui/dvd")
7
+
8
+ try:
9
+ import huggingface_hub
10
+
11
+ if not hasattr(huggingface_hub, "HfFolder"):
12
+ class HfFolder:
13
+ @staticmethod
14
+ def get_token():
15
+ return huggingface_hub.get_token()
16
+
17
+ @staticmethod
18
+ def save_token(token):
19
+ return huggingface_hub.login(token=token, add_to_git_credential=False)
20
+
21
+ huggingface_hub.HfFolder = HfFolder
22
+ except Exception:
23
+ pass
24
+
25
+ import gradio as gr
26
+ import torch
27
+
28
+ try:
29
+ import spaces
30
+ except ImportError:
31
+ class _SpacesFallback:
32
+ @staticmethod
33
+ def GPU(duration=180):
34
+ return lambda fn: fn
35
+
36
+ spaces = _SpacesFallback()
37
+
38
+ import argparse
39
+ import gc
40
+ import shutil
41
+ import uuid
42
+ from pathlib import Path
43
+
44
+ import gradio_client.utils as gradio_client_utils
45
+ import numpy as np
46
+ from PIL import Image
47
+ from starlette.templating import Jinja2Templates
48
+
49
+ MAX_SEED = np.iinfo(np.int32).max
50
+ RESOLUTION = 64
51
+ ROOT_DIR = Path(__file__).resolve().parent
52
+ TMP_DIR = ROOT_DIR / "tmp" / "dvd_app"
53
+ TMP_DIR.mkdir(parents=True, exist_ok=True)
54
+
55
+ GEN_DVD_CONFIG = os.environ.get("DVD_GEN_CONFIG", "ckpts/dvd_img.json")
56
+ GEN_DVD_CKPT = os.environ.get("DVD_GEN_CKPT", "ckpts/dvd_img.safetensors")
57
+ EDIT_DVD_CONFIG = os.environ.get("DVD_EDIT_CONFIG", "ckpts/dvd_img_BSP_ft.json")
58
+ EDIT_DVD_CKPT = os.environ.get("DVD_EDIT_CKPT", "ckpts/dvd_img_BSP_ft.safetensors")
59
+ TRELLIS_IMAGE_MODEL = os.environ.get("TRELLIS_IMAGE_MODEL", "microsoft/TRELLIS-image-large")
60
+ DVD_MODEL_REPO = os.environ.get("DVD_MODEL_REPO")
61
+ DVD_MODEL_SUBFOLDER = os.environ.get("DVD_MODEL_SUBFOLDER") or None
62
+ DVD_MODEL_REVISION = os.environ.get("DVD_MODEL_REVISION") or None
63
+ DVD_MODEL_TOKEN = os.environ.get("DVD_MODEL_TOKEN") or os.environ.get("HF_TOKEN") or None
64
+ IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"}
65
+
66
+
67
+ def parse_camera_position(value: str) -> tuple[float, float, float]:
68
+ parts = [float(part.strip()) for part in value.split(",")]
69
+ if len(parts) != 3:
70
+ raise ValueError("DVD_VOXEL_CAMERA_POSITION must be three comma-separated numbers, e.g. -180,90,3")
71
+ return tuple(parts)
72
+
73
+
74
+ VOXEL_CAMERA_POSITION = parse_camera_position(os.environ.get("DVD_VOXEL_CAMERA_POSITION", "-180,90,3"))
75
+
76
+
77
+ dvd_gen_pipeline = None
78
+ dvd_edit_pipeline = None
79
+ trellis_pipeline = None
80
+
81
+ DVDImageToVoxelPipeline = None
82
+ TrellisImageTo3DPipeline = None
83
+ as_voxel_output = None
84
+ export_cubified_voxels = None
85
+ run_image_stage2_from_dvd_voxels = None
86
+
87
+ _postprocessing_utils = None
88
+
89
+
90
+ def log_event(message: str):
91
+ print(f"[DVD Space] {message}", flush=True)
92
+
93
+
94
+ def _download_file(url: str, dest: Path):
95
+ import urllib.request
96
+
97
+ dest.parent.mkdir(parents=True, exist_ok=True)
98
+ if dest.exists() and dest.stat().st_size > 0:
99
+ log_event(f"using cached {dest.name}")
100
+ return
101
+ tmp = dest.with_name(dest.name + ".tmp")
102
+ if tmp.exists():
103
+ tmp.unlink()
104
+ log_event(f"downloading {url} -> {dest}")
105
+ urllib.request.urlretrieve(url, tmp)
106
+ tmp.replace(dest)
107
+
108
+
109
+ def prefetch_dvd_weights():
110
+ from huggingface_hub import hf_hub_download, snapshot_download
111
+
112
+ if DVD_MODEL_REPO:
113
+ filenames = (
114
+ "dvd_img.json",
115
+ "dvd_img.safetensors",
116
+ "dvd_img_BSP_ft.json",
117
+ "dvd_img_BSP_ft.safetensors",
118
+ )
119
+ for filename in filenames:
120
+ log_event(f"prefetching {DVD_MODEL_REPO}/{filename}")
121
+ hf_hub_download(
122
+ repo_id=DVD_MODEL_REPO,
123
+ filename=filename,
124
+ subfolder=DVD_MODEL_SUBFOLDER,
125
+ revision=DVD_MODEL_REVISION,
126
+ token=DVD_MODEL_TOKEN,
127
+ )
128
+
129
+ if os.environ.get("DVD_PREFETCH_TRELLIS", "0").lower() not in {"0", "false", "no", "off"}:
130
+ log_event(f"prefetching TRELLIS stage2 model {TRELLIS_IMAGE_MODEL}")
131
+ snapshot_download(repo_id=TRELLIS_IMAGE_MODEL)
132
+
133
+
134
+ def prefetch_dinov2():
135
+ import shutil
136
+ import zipfile
137
+
138
+ hub_dir = Path(torch.hub.get_dir())
139
+ hub_dir.mkdir(parents=True, exist_ok=True)
140
+ repo_dir = hub_dir / "facebookresearch_dinov2_main"
141
+ if repo_dir.exists():
142
+ log_event(f"using cached DINOv2 repo {repo_dir}")
143
+ else:
144
+ zip_path = hub_dir / "main.zip"
145
+ _download_file("https://github.com/facebookresearch/dinov2/zipball/main", zip_path)
146
+ extract_tmp = hub_dir / "_dvd_dinov2_extract"
147
+ if extract_tmp.exists():
148
+ shutil.rmtree(extract_tmp)
149
+ extract_tmp.mkdir(parents=True, exist_ok=True)
150
+ log_event(f"extracting DINOv2 repo to {repo_dir}")
151
+ with zipfile.ZipFile(zip_path) as zf:
152
+ zf.extractall(extract_tmp)
153
+ top_level = zf.namelist()[0].split("/", 1)[0]
154
+ shutil.move(str(extract_tmp / top_level), str(repo_dir))
155
+ shutil.rmtree(extract_tmp, ignore_errors=True)
156
+
157
+ ckpt_dir = hub_dir / "checkpoints"
158
+ _download_file(
159
+ "https://dl.fbaipublicfiles.com/dinov2/dinov2_vitl14/dinov2_vitl14_reg4_pretrain.pth",
160
+ ckpt_dir / "dinov2_vitl14_reg4_pretrain.pth",
161
+ )
162
+
163
+
164
+ def prefetch_assets():
165
+ if os.environ.get("DVD_PREFETCH_ASSETS", "1").lower() in {"0", "false", "no", "off"}:
166
+ log_event("asset prefetch disabled")
167
+ return
168
+ try:
169
+ log_event("asset prefetch start")
170
+ prefetch_dvd_weights()
171
+ prefetch_dinov2()
172
+ log_event("asset prefetch done")
173
+ except Exception as exc:
174
+ log_event(f"asset prefetch failed; continuing without prefetch: {exc}")
175
+
176
+
177
+ def ensure_dvd_imports():
178
+ global DVDImageToVoxelPipeline
179
+ global TrellisImageTo3DPipeline
180
+ global as_voxel_output
181
+ global export_cubified_voxels
182
+ global run_image_stage2_from_dvd_voxels
183
+ if DVDImageToVoxelPipeline is not None:
184
+ return
185
+ log_event("importing DVD image/TRELLIS modules")
186
+ from dvd import (
187
+ DVDImageToVoxelPipeline as _DVDImageToVoxelPipeline,
188
+ TrellisImageTo3DPipeline as _TrellisImageTo3DPipeline,
189
+ as_voxel_output as _as_voxel_output,
190
+ export_cubified_voxels as _export_cubified_voxels,
191
+ run_image_stage2_from_dvd_voxels as _run_image_stage2_from_dvd_voxels,
192
+ )
193
+ DVDImageToVoxelPipeline = _DVDImageToVoxelPipeline
194
+ TrellisImageTo3DPipeline = _TrellisImageTo3DPipeline
195
+ as_voxel_output = _as_voxel_output
196
+ export_cubified_voxels = _export_cubified_voxels
197
+ run_image_stage2_from_dvd_voxels = _run_image_stage2_from_dvd_voxels
198
+
199
+
200
+ def ensure_pipeline_device(pipeline, device: str, label: str):
201
+ target = torch.device(device)
202
+ current = pipeline.device
203
+ if current != target:
204
+ log_event(f"moving {label} pipeline from {current} to {target}")
205
+ pipeline.to(target)
206
+ log_event(f"{label} pipeline is on {target}")
207
+ return pipeline
208
+
209
+
210
+
211
+
212
+ def ensure_zero_gpu_extensions():
213
+ """Verify TRELLIS CUDA render extensions installed by Space requirements."""
214
+ import importlib
215
+
216
+ missing = []
217
+ for module_name in ("nvdiffrast.torch", "diff_gaussian_rasterization"):
218
+ try:
219
+ importlib.import_module(module_name)
220
+ except ImportError as exc:
221
+ missing.append(f"{module_name}: {exc}")
222
+ if missing:
223
+ raise RuntimeError(
224
+ "Missing prebuilt CUDA render extensions. Rebuild/redeploy the Space wheels for: "
225
+ + "; ".join(missing)
226
+ )
227
+ log_event("CUDA render extensions ready")
228
+
229
+ def get_postprocessing_utils():
230
+ global _postprocessing_utils
231
+ if _postprocessing_utils is not None:
232
+ return _postprocessing_utils
233
+ ensure_zero_gpu_extensions()
234
+ from trellis.utils import postprocessing_utils
235
+ _postprocessing_utils = postprocessing_utils
236
+ return _postprocessing_utils
237
+
238
+
239
+ _original_json_schema_to_python_type = gradio_client_utils._json_schema_to_python_type
240
+
241
+
242
+ def _safe_json_schema_to_python_type(schema, defs):
243
+ if isinstance(schema, bool):
244
+ return "Any"
245
+ if isinstance(schema, dict) and isinstance(schema.get("additionalProperties"), bool):
246
+ schema = dict(schema)
247
+ if schema["additionalProperties"]:
248
+ schema["additionalProperties"] = {}
249
+ else:
250
+ schema.pop("additionalProperties")
251
+ return _original_json_schema_to_python_type(schema, defs)
252
+
253
+
254
+ gradio_client_utils._json_schema_to_python_type = _safe_json_schema_to_python_type
255
+
256
+
257
+ _original_template_response = Jinja2Templates.TemplateResponse
258
+
259
+
260
+ def _template_response_compat(self, *args, **kwargs):
261
+ if args and isinstance(args[0], str):
262
+ name = args[0]
263
+ context = args[1] if len(args) > 1 else kwargs.pop("context", None)
264
+ if isinstance(context, dict) and "request" in context:
265
+ return _original_template_response(self, context["request"], name, context, *args[2:], **kwargs)
266
+ return _original_template_response(self, *args, **kwargs)
267
+
268
+
269
+ Jinja2Templates.TemplateResponse = _template_response_compat
270
+
271
+
272
+ def list_asset_files(directory: str, suffixes: set[str]) -> list[Path]:
273
+ path = ROOT_DIR / directory
274
+ if not path.exists():
275
+ return []
276
+ return sorted(p for p in path.iterdir() if p.is_file() and p.suffix.lower() in suffixes)
277
+
278
+
279
+ def asset_label(path: Path) -> str:
280
+ return path.stem.replace("_", " ")
281
+
282
+
283
+ GENERATION_IMAGE_EXAMPLES = [
284
+ (asset_label(path), str(path)) for path in list_asset_files("assets/example_image", IMAGE_EXTENSIONS)
285
+ ]
286
+ EDIT_IMAGE_EXAMPLES = [
287
+ (asset_label(path), str(path)) for path in list_asset_files("assets/example_image_edit", IMAGE_EXTENSIONS)
288
+ ]
289
+ EDIT_VOXEL_EXAMPLES = [
290
+ (asset_label(path), str(path)) for path in list_asset_files("assets/example_voxel_edit", {".npy", ".npz", ".pt", ".pth"})
291
+ ]
292
+
293
+
294
+ def voxel_viewer(label: str, exposure: float = 5.0, height: int = 300):
295
+ return gr.Model3D(
296
+ label=label,
297
+ height=height,
298
+ camera_position=VOXEL_CAMERA_POSITION,
299
+ )
300
+
301
+
302
+ def get_device(device_arg: str) -> str:
303
+ if device_arg == "auto":
304
+ return "cuda" if torch.cuda.is_available() else "cpu"
305
+ return device_arg
306
+
307
+
308
+ def default_server_port():
309
+ port = os.environ.get("GRADIO_SERVER_PORT")
310
+ return int(port) if port else None
311
+
312
+
313
+ def load_dvd_pipeline_variant(device: str, variant: str):
314
+ ensure_dvd_imports()
315
+ if DVD_MODEL_REPO:
316
+ common_kwargs = {
317
+ "device": device,
318
+ "subfolder": DVD_MODEL_SUBFOLDER,
319
+ "revision": DVD_MODEL_REVISION,
320
+ "token": DVD_MODEL_TOKEN,
321
+ }
322
+ return DVDImageToVoxelPipeline.from_pretrained(DVD_MODEL_REPO, variant=variant, **common_kwargs)
323
+ if variant == "base":
324
+ return DVDImageToVoxelPipeline.from_files(GEN_DVD_CONFIG, GEN_DVD_CKPT, device=device)
325
+ if variant == "bsp":
326
+ return DVDImageToVoxelPipeline.from_files(EDIT_DVD_CONFIG, EDIT_DVD_CKPT, device=device)
327
+ raise ValueError(f"Unsupported DVD pipeline variant: {variant}")
328
+
329
+
330
+ def load_dvd_pipelines(device: str):
331
+ return (
332
+ load_dvd_pipeline_variant(device, "base"),
333
+ load_dvd_pipeline_variant(device, "bsp"),
334
+ )
335
+
336
+
337
+ def release_pipeline(name: str, label: str):
338
+ global dvd_gen_pipeline, dvd_edit_pipeline, trellis_pipeline
339
+ pipelines = {
340
+ "dvd_gen": dvd_gen_pipeline,
341
+ "dvd_edit": dvd_edit_pipeline,
342
+ "trellis": trellis_pipeline,
343
+ }
344
+ pipeline = pipelines[name]
345
+ if pipeline is None:
346
+ return
347
+ log_event(f"releasing {label} pipeline")
348
+ try:
349
+ pipeline.to("cpu")
350
+ except Exception as exc:
351
+ log_event(f"{label} pipeline CPU release warning: {exc}")
352
+ if name == "dvd_gen":
353
+ dvd_gen_pipeline = None
354
+ elif name == "dvd_edit":
355
+ dvd_edit_pipeline = None
356
+ elif name == "trellis":
357
+ trellis_pipeline = None
358
+ gc.collect()
359
+ if torch.cuda.is_available():
360
+ torch.cuda.empty_cache()
361
+ log_event(f"released {label} pipeline")
362
+
363
+
364
+ def release_dvd_pipelines():
365
+ release_pipeline("dvd_gen", "DVD generation")
366
+ release_pipeline("dvd_edit", "DVD editing")
367
+
368
+
369
+ def release_trellis_pipeline():
370
+ release_pipeline("trellis", "TRELLIS stage2")
371
+
372
+
373
+ def ensure_dvd_gen_pipeline(device: str | None = None):
374
+ global dvd_gen_pipeline
375
+ device = device or os.environ.get("DVD_SPACE_DEVICE", "cuda")
376
+ release_trellis_pipeline()
377
+ if dvd_gen_pipeline is None:
378
+ log_event(f"loading DVD generation pipeline on {device}")
379
+ dvd_gen_pipeline = load_dvd_pipeline_variant(device, "base")
380
+ log_event("DVD generation pipeline ready")
381
+ else:
382
+ ensure_pipeline_device(dvd_gen_pipeline, device, "DVD generation")
383
+ return dvd_gen_pipeline
384
+
385
+
386
+ def ensure_dvd_edit_pipeline(device: str | None = None):
387
+ global dvd_edit_pipeline
388
+ device = device or os.environ.get("DVD_SPACE_DEVICE", "cuda")
389
+ release_pipeline("dvd_gen", "DVD generation")
390
+ release_trellis_pipeline()
391
+ if dvd_edit_pipeline is None:
392
+ log_event(f"loading DVD editing pipeline on {device}")
393
+ dvd_edit_pipeline = load_dvd_pipeline_variant(device, "bsp")
394
+ log_event("DVD editing pipeline ready")
395
+ else:
396
+ ensure_pipeline_device(dvd_edit_pipeline, device, "DVD editing")
397
+ return dvd_edit_pipeline
398
+
399
+
400
+ def ensure_trellis_pipeline(device: str | None = None):
401
+ ensure_dvd_imports()
402
+ global trellis_pipeline
403
+ device = device or os.environ.get("DVD_SPACE_DEVICE", "cuda")
404
+ release_dvd_pipelines()
405
+ if trellis_pipeline is None:
406
+ log_event(f"loading TRELLIS stage2 pipeline on {device}")
407
+ trellis_pipeline = TrellisImageTo3DPipeline.from_pretrained(TRELLIS_IMAGE_MODEL)
408
+ trellis_pipeline.to(device)
409
+ log_event("TRELLIS stage2 pipeline ready")
410
+ else:
411
+ ensure_pipeline_device(trellis_pipeline, device, "TRELLIS stage2")
412
+ return trellis_pipeline
413
+
414
+
415
+ def preload_zero_gpu_models(device: str | None = None, preload: str | None = None):
416
+ device = device or os.environ.get("DVD_SPACE_DEVICE", "cuda")
417
+ preload = preload or os.environ.get("DVD_STARTUP_PRELOAD", "none")
418
+ requested = {
419
+ item.strip().lower()
420
+ for item in preload.replace(";", ",").split(",")
421
+ if item.strip()
422
+ }
423
+ if requested & {"0", "false", "no", "none", "off"}:
424
+ log_event("startup model preload disabled")
425
+ return
426
+
427
+ ensure_dvd_imports()
428
+ if requested & {"all", "gen", "generation", "dvd_gen", "image"}:
429
+ ensure_dvd_gen_pipeline(device)
430
+ if requested & {"all", "edit", "editing", "dvd_edit"}:
431
+ ensure_dvd_edit_pipeline(device)
432
+ if requested & {"all", "stage2", "trellis"}:
433
+ ensure_zero_gpu_extensions()
434
+ ensure_trellis_pipeline(device)
435
+
436
+
437
+ def start_session(req: gr.Request):
438
+ user_dir = TMP_DIR / str(req.session_hash)
439
+ user_dir.mkdir(parents=True, exist_ok=True)
440
+
441
+
442
+ def end_session(req: gr.Request):
443
+ user_dir = TMP_DIR / str(req.session_hash)
444
+ if user_dir.exists():
445
+ shutil.rmtree(user_dir)
446
+
447
+
448
+ def session_path(req: gr.Request, name: str) -> str:
449
+ user_dir = TMP_DIR / str(req.session_hash)
450
+ user_dir.mkdir(parents=True, exist_ok=True)
451
+ return str(user_dir / name)
452
+
453
+
454
+ def worker_path(name: str) -> str:
455
+ user_dir = TMP_DIR / f"worker-{uuid.uuid4().hex}"
456
+ user_dir.mkdir(parents=True, exist_ok=True)
457
+ return str(user_dir / name)
458
+
459
+
460
+ def get_seed(randomize_seed: bool, seed: int) -> int:
461
+ return int(np.random.randint(0, MAX_SEED)) if randomize_seed else int(seed)
462
+
463
+
464
+ def dvd_cfg_schedule(mode: str, constant: float, early: float, late: float, split: float):
465
+ if mode == "Default schedule":
466
+ return None
467
+ if mode == "Constant":
468
+ return float(constant)
469
+ split = float(split)
470
+ return lambda t: float(early) if t < split else float(late)
471
+
472
+
473
+ def dvd_sampler_kwargs(
474
+ steps: int,
475
+ cfg_mode: str,
476
+ cfg_constant: float,
477
+ cfg_early: float,
478
+ cfg_late: float,
479
+ cfg_split: float,
480
+ ) -> dict:
481
+ kwargs = {"steps": int(steps)}
482
+ cfg_strength = dvd_cfg_schedule(cfg_mode, cfg_constant, cfg_early, cfg_late, cfg_split)
483
+ if cfg_strength is not None:
484
+ kwargs["cfg_strength"] = cfg_strength
485
+ return kwargs
486
+
487
+
488
+ def slat_sampler_params(steps: int, cfg_strength: float) -> dict:
489
+ return {
490
+ "steps": int(steps),
491
+ "cfg_strength": float(cfg_strength),
492
+ }
493
+
494
+
495
+ def voxel_output(voxels, resolution: int = RESOLUTION):
496
+ ensure_dvd_imports()
497
+ if isinstance(voxels, np.lib.npyio.NpzFile):
498
+ key = next((k for k in ("coords", "voxels", "samples") if k in voxels.files), voxels.files[0])
499
+ voxels = voxels[key]
500
+ if isinstance(voxels, np.ndarray):
501
+ voxels = torch.as_tensor(voxels)
502
+ elif isinstance(voxels, dict):
503
+ for key in ("coords", "voxels", "samples"):
504
+ if key in voxels:
505
+ voxels = voxels[key]
506
+ break
507
+ else:
508
+ raise gr.Error("Voxel dict must contain one of: coords, voxels, samples.")
509
+ return voxel_output(voxels, resolution=resolution)
510
+ elif isinstance(voxels, (list, tuple)):
511
+ voxels = torch.as_tensor(voxels)
512
+ output = as_voxel_output(voxels, resolution=resolution)
513
+ return as_voxel_output(output.samples.detach().cpu().long(), resolution=resolution)
514
+
515
+
516
+ def voxel_state(voxels, resolution: int = RESOLUTION):
517
+ output = voxel_output(voxels, resolution=resolution)
518
+ return output.coords_without_batch.detach().cpu().numpy().astype(np.int32)
519
+
520
+
521
+ def load_voxel_file(file, resolution: int = RESOLUTION):
522
+ if file is None:
523
+ raise gr.Error("Please upload a voxel coordinate file.")
524
+
525
+ path = None
526
+ if isinstance(file, (str, Path)):
527
+ path = str(file)
528
+ elif isinstance(file, dict):
529
+ path = file.get("path") or file.get("name") or file.get("orig_name")
530
+ if path is None:
531
+ return voxel_output(file, resolution=resolution)
532
+ elif hasattr(file, "path"):
533
+ path = file.path
534
+ elif hasattr(file, "name"):
535
+ path = file.name
536
+ else:
537
+ return voxel_output(file, resolution=resolution)
538
+
539
+ suffix = Path(path).suffix.lower()
540
+ if suffix == ".npy":
541
+ data = np.load(path, allow_pickle=False)
542
+ elif suffix == ".npz":
543
+ data = np.load(path, allow_pickle=False)
544
+ elif suffix in {".pt", ".pth"}:
545
+ data = torch.load(path, map_location="cpu")
546
+ else:
547
+ raise gr.Error(f"Unsupported voxel file type: {suffix}. Use .npy, .npz, .pt, or .pth.")
548
+ return voxel_output(data, resolution=resolution)
549
+
550
+ def load_image_from_path(path: str):
551
+ if not path:
552
+ raise gr.Error("Please select an example image.")
553
+ return Image.open(path).convert("RGBA")
554
+
555
+
556
+ def load_generation_example(image_path: str):
557
+ return load_image_from_path(image_path)
558
+
559
+
560
+ def load_edit_image_example(image_path: str):
561
+ return load_image_from_path(image_path)
562
+
563
+
564
+ @spaces.GPU(duration=60)
565
+ def load_edit_voxel_example(voxel_path: str, req: gr.Request):
566
+ voxels = load_voxel_file(voxel_path)
567
+ mesh_path = voxel_to_mesh(voxels, session_path(req, "example_edit_voxels.glb"))
568
+ return voxel_state(voxels), mesh_path
569
+
570
+
571
+ def save_voxel_coords(voxels, path: str) -> str:
572
+ ensure_dvd_imports()
573
+ output = as_voxel_output(voxels, resolution=RESOLUTION)
574
+ np.save(path, output.coords_without_batch.numpy())
575
+ return path
576
+
577
+
578
+ def voxel_to_mesh(voxels, path: str) -> str:
579
+ ensure_dvd_imports()
580
+ return export_cubified_voxels(voxels, path, resolution=RESOLUTION)
581
+
582
+
583
+ def rotate_voxels(voxels, axis: str, req: gr.Request):
584
+ if voxels is None:
585
+ raise gr.Error("No editing voxels available. Upload voxels or transfer generated voxels first.")
586
+
587
+ output = load_voxel_file(voxels)
588
+ samples = output.samples.clone()
589
+ axis_to_dims = {
590
+ "x": (2, 3),
591
+ "y": (1, 3),
592
+ "z": (1, 2),
593
+ }
594
+ samples = torch.rot90(samples, k=1, dims=axis_to_dims[axis])
595
+ rotated = as_voxel_output(samples, resolution=RESOLUTION)
596
+ mesh_path = voxel_to_mesh(rotated, session_path(req, f"edit_voxels_rot_{axis}.glb"))
597
+ return voxel_state(rotated), mesh_path
598
+
599
+
600
+ @spaces.GPU(duration=60)
601
+ def rotate_x(voxels, req: gr.Request):
602
+ return rotate_voxels(voxels, "x", req)
603
+
604
+
605
+ @spaces.GPU(duration=60)
606
+ def rotate_y(voxels, req: gr.Request):
607
+ return rotate_voxels(voxels, "y", req)
608
+
609
+
610
+ @spaces.GPU(duration=60)
611
+ def rotate_z(voxels, req: gr.Request):
612
+ return rotate_voxels(voxels, "z", req)
613
+
614
+
615
+ def build_edit_mask(
616
+ use_1, x0_1, x1_1, y0_1, y1_1, z0_1, z1_1,
617
+ use_2, x0_2, x1_2, y0_2, y1_2, z0_2, z1_2,
618
+ use_3, x0_3, x1_3, y0_3, y1_3, z0_3, z1_3,
619
+ batch_size: int = 1,
620
+ ):
621
+ boxes = [
622
+ (use_1, x0_1, x1_1, y0_1, y1_1, z0_1, z1_1),
623
+ (use_2, x0_2, x1_2, y0_2, y1_2, z0_2, z1_2),
624
+ (use_3, x0_3, x1_3, y0_3, y1_3, z0_3, z1_3),
625
+ ]
626
+ edit_mask = torch.zeros((batch_size, RESOLUTION, RESOLUTION, RESOLUTION), dtype=torch.bool)
627
+ any_box = False
628
+ for use, x0, x1, y0, y1, z0, z1 in boxes:
629
+ if not use:
630
+ continue
631
+ ranges = [int(x0), int(x1), int(y0), int(y1), int(z0), int(z1)]
632
+ x0, x1, y0, y1, z0, z1 = [max(0, min(RESOLUTION, v)) for v in ranges]
633
+ if x0 >= x1 or y0 >= y1 or z0 >= z1:
634
+ continue
635
+ edit_mask[:, x0:x1, y0:y1, z0:z1] = True
636
+ any_box = True
637
+ if not any_box:
638
+ raise gr.Error("Enable at least one valid edit-mask box.")
639
+ return edit_mask
640
+
641
+
642
+ def mask_inputs():
643
+ return [
644
+ box1_use, box1_x0, box1_x1, box1_y0, box1_y1, box1_z0, box1_z1,
645
+ box2_use, box2_x0, box2_x1, box2_y0, box2_y1, box2_z0, box2_z1,
646
+ box3_use, box3_x0, box3_x1, box3_y0, box3_y1, box3_z0, box3_z1,
647
+ ]
648
+
649
+
650
+ @spaces.GPU(duration=600)
651
+ def generate_voxels(
652
+ image: Image.Image,
653
+ seed: int,
654
+ randomize_seed: bool,
655
+ preprocess_image: bool,
656
+ dvd_steps: int,
657
+ dvd_cfg_mode: str,
658
+ dvd_cfg_constant: float,
659
+ dvd_cfg_early: float,
660
+ dvd_cfg_late: float,
661
+ dvd_cfg_split: float,
662
+ progress=gr.Progress(track_tqdm=True),
663
+ ):
664
+ progress(0.02, desc="Starting ZeroGPU callback")
665
+ log_event(f"generate_voxels start seed={seed} randomize={randomize_seed} steps={dvd_steps}")
666
+ if image is None:
667
+ log_event("generate_voxels missing image")
668
+ raise gr.Error("Please provide a generation image.")
669
+ seed = get_seed(randomize_seed, seed)
670
+ progress(0.08, desc="Loading DVD generation model")
671
+ pipeline = ensure_dvd_gen_pipeline("cuda")
672
+ progress(0.18, desc="Sampling voxels")
673
+ log_event(f"generate_voxels sampling seed={seed}")
674
+ voxels = pipeline.sample_voxels(
675
+ image,
676
+ seed=seed,
677
+ preprocess_image=preprocess_image,
678
+ **dvd_sampler_kwargs(dvd_steps, dvd_cfg_mode, dvd_cfg_constant, dvd_cfg_early, dvd_cfg_late, dvd_cfg_split),
679
+ )
680
+ progress(0.88, desc="Exporting voxel preview")
681
+ log_event("generate_voxels sampled; exporting voxel mesh")
682
+ mesh_path = voxel_to_mesh(voxels, worker_path("generated_voxels.glb"))
683
+ npy_path = save_voxel_coords(voxels, worker_path("generated_voxel64_coords.npy"))
684
+ torch.cuda.empty_cache()
685
+ log_event(f"generate_voxels done seed={seed} npy={npy_path}")
686
+ return voxel_state(voxels), mesh_path, npy_path, seed
687
+
688
+
689
+ @spaces.GPU(duration=600)
690
+ def generation_stage2(
691
+ image: Image.Image,
692
+ voxels,
693
+ seed: int,
694
+ randomize_seed: bool,
695
+ preprocess_image: bool,
696
+ slat_steps: int,
697
+ slat_cfg_strength: float,
698
+ progress=gr.Progress(track_tqdm=True),
699
+ ):
700
+ progress(0.02, desc="Starting ZeroGPU callback")
701
+ log_event(f"generation_stage2 start seed={seed} randomize={randomize_seed} steps={slat_steps}")
702
+ if image is None:
703
+ log_event("generation_stage2 missing image")
704
+ raise gr.Error("Please provide the same generation image for TRELLIS stage 2.")
705
+ if voxels is None:
706
+ raise gr.Error("Generate voxels before running TRELLIS stage 2.")
707
+ seed = get_seed(randomize_seed, seed)
708
+ log_event(f"generation_stage2 loading voxel state type={type(voxels).__name__}")
709
+ voxels = load_voxel_file(voxels)
710
+ log_event(f"generation_stage2 voxel state ready shape={tuple(voxels.samples.shape)}")
711
+ progress(0.08, desc="Checking CUDA render extensions")
712
+ ensure_dvd_imports()
713
+ ensure_zero_gpu_extensions()
714
+ log_event("generation_stage2 CUDA render extensions ready")
715
+ progress(0.16, desc="Loading TRELLIS stage 2")
716
+ log_event("generation_stage2 running TRELLIS")
717
+ pipeline = ensure_trellis_pipeline()
718
+ log_event("generation_stage2 TRELLIS pipeline ready")
719
+ outputs = run_image_stage2_from_dvd_voxels(
720
+ pipeline,
721
+ image,
722
+ voxels,
723
+ seed=seed,
724
+ formats=["gaussian", "mesh"],
725
+ preprocess_image=preprocess_image,
726
+ slat_sampler_params=slat_sampler_params(slat_steps, slat_cfg_strength),
727
+ )
728
+ glb = get_postprocessing_utils().to_glb(outputs["gaussian"][0], outputs["mesh"][0])
729
+ glb_path = worker_path("generated_stage2.glb")
730
+ glb.export(glb_path)
731
+ torch.cuda.empty_cache()
732
+ log_event(f"generation_stage2 done seed={seed} glb={glb_path}")
733
+ return glb_path, glb_path, seed
734
+
735
+
736
+ def transfer_generation_to_editing(voxels, mesh_path):
737
+ if voxels is None:
738
+ raise gr.Error("No generated voxels to transfer.")
739
+ return voxels, mesh_path
740
+
741
+
742
+ @spaces.GPU(duration=60)
743
+ def load_edit_voxels(file, voxel_path: str, req: gr.Request):
744
+ voxel_source = file if file is not None else voxel_path
745
+ if voxel_source in (None, ""):
746
+ raise gr.Error("Upload a voxel file or select a preloaded edit voxel.")
747
+ voxels = load_voxel_file(voxel_source)
748
+ mesh_path = voxel_to_mesh(voxels, session_path(req, "loaded_edit_voxels.glb"))
749
+ return voxel_state(voxels), mesh_path
750
+
751
+
752
+ @spaces.GPU(duration=60)
753
+ def visualize_edit_mask(
754
+ voxels,
755
+ use_1, x0_1, x1_1, y0_1, y1_1, z0_1, z1_1,
756
+ use_2, x0_2, x1_2, y0_2, y1_2, z0_2, z1_2,
757
+ use_3, x0_3, x1_3, y0_3, y1_3, z0_3, z1_3,
758
+ req: gr.Request,
759
+ ):
760
+ if voxels is None:
761
+ raise gr.Error("No editing voxels available.")
762
+ output = load_voxel_file(voxels)
763
+ edit_mask = build_edit_mask(
764
+ use_1, x0_1, x1_1, y0_1, y1_1, z0_1, z1_1,
765
+ use_2, x0_2, x1_2, y0_2, y1_2, z0_2, z1_2,
766
+ use_3, x0_3, x1_3, y0_3, y1_3, z0_3, z1_3,
767
+ batch_size=output.samples.shape[0],
768
+ )
769
+ perturbed = output.samples.clone()
770
+ perturbed[edit_mask] = torch.randint(0, 2, perturbed[edit_mask].shape, dtype=perturbed.dtype)
771
+ mask_preview = as_voxel_output(perturbed, resolution=RESOLUTION)
772
+ mesh_path = voxel_to_mesh(mask_preview, session_path(req, "edit_mask_preview.glb"))
773
+ return mesh_path
774
+
775
+
776
+ @spaces.GPU(duration=600)
777
+ def run_editing(
778
+ target_image: Image.Image,
779
+ voxels,
780
+ seed: int,
781
+ randomize_seed: bool,
782
+ preprocess_image: bool,
783
+ dvd_steps: int,
784
+ dvd_cfg_mode: str,
785
+ dvd_cfg_constant: float,
786
+ dvd_cfg_early: float,
787
+ dvd_cfg_late: float,
788
+ dvd_cfg_split: float,
789
+ use_1, x0_1, x1_1, y0_1, y1_1, z0_1, z1_1,
790
+ use_2, x0_2, x1_2, y0_2, y1_2, z0_2, z1_2,
791
+ use_3, x0_3, x1_3, y0_3, y1_3, z0_3, z1_3,
792
+ progress=gr.Progress(track_tqdm=True),
793
+ ):
794
+ progress(0.02, desc="Starting ZeroGPU callback")
795
+ log_event(f"run_editing start seed={seed} randomize={randomize_seed} steps={dvd_steps}")
796
+ if target_image is None:
797
+ log_event("run_editing missing target image")
798
+ raise gr.Error("Please provide a target image for editing.")
799
+ if voxels is None:
800
+ raise gr.Error("Upload voxels or transfer generated voxels first.")
801
+
802
+ seed = get_seed(randomize_seed, seed)
803
+ output = load_voxel_file(voxels)
804
+ edit_mask = build_edit_mask(
805
+ use_1, x0_1, x1_1, y0_1, y1_1, z0_1, z1_1,
806
+ use_2, x0_2, x1_2, y0_2, y1_2, z0_2, z1_2,
807
+ use_3, x0_3, x1_3, y0_3, y1_3, z0_3, z1_3,
808
+ batch_size=output.samples.shape[0],
809
+ )
810
+ keep_mask = ~edit_mask
811
+ progress(0.08, desc="Loading DVD editing model")
812
+ pipeline = ensure_dvd_edit_pipeline("cuda")
813
+ progress(0.18, desc="Sampling edited voxels")
814
+ log_event("run_editing sampling")
815
+ edited = pipeline.edit_voxels(
816
+ target_image,
817
+ output,
818
+ keep_mask=keep_mask,
819
+ seed=seed,
820
+ preprocess_image=preprocess_image,
821
+ **dvd_sampler_kwargs(dvd_steps, dvd_cfg_mode, dvd_cfg_constant, dvd_cfg_early, dvd_cfg_late, dvd_cfg_split),
822
+ )
823
+ mesh_path = voxel_to_mesh(edited, worker_path("edited_voxels.glb"))
824
+ npy_path = save_voxel_coords(edited, worker_path("edited_voxel64_coords.npy"))
825
+ torch.cuda.empty_cache()
826
+ log_event(f"run_editing done seed={seed} npy={npy_path}")
827
+ return voxel_state(edited), mesh_path, npy_path, seed
828
+
829
+
830
+ @spaces.GPU(duration=600)
831
+ def editing_stage2(
832
+ target_image: Image.Image,
833
+ edited_voxels,
834
+ seed: int,
835
+ randomize_seed: bool,
836
+ preprocess_image: bool,
837
+ slat_steps: int,
838
+ slat_cfg_strength: float,
839
+ progress=gr.Progress(track_tqdm=True),
840
+ ):
841
+ progress(0.02, desc="Starting ZeroGPU callback")
842
+ log_event(f"editing_stage2 start seed={seed} randomize={randomize_seed} steps={slat_steps}")
843
+ if target_image is None:
844
+ log_event("editing_stage2 missing target image")
845
+ raise gr.Error("Please provide the target image for TRELLIS stage 2.")
846
+ if edited_voxels is None:
847
+ raise gr.Error("Run editing before TRELLIS stage 2.")
848
+ seed = get_seed(randomize_seed, seed)
849
+ log_event(f"editing_stage2 loading voxel state type={type(edited_voxels).__name__}")
850
+ edited_voxels = load_voxel_file(edited_voxels)
851
+ log_event(f"editing_stage2 voxel state ready shape={tuple(edited_voxels.samples.shape)}")
852
+ progress(0.08, desc="Checking CUDA render extensions")
853
+ ensure_dvd_imports()
854
+ ensure_zero_gpu_extensions()
855
+ log_event("editing_stage2 CUDA render extensions ready")
856
+ progress(0.16, desc="Loading TRELLIS stage 2")
857
+ pipeline = ensure_trellis_pipeline()
858
+ log_event("editing_stage2 TRELLIS pipeline ready")
859
+ outputs = run_image_stage2_from_dvd_voxels(
860
+ pipeline,
861
+ target_image,
862
+ edited_voxels,
863
+ seed=seed,
864
+ formats=["gaussian", "mesh"],
865
+ preprocess_image=preprocess_image,
866
+ slat_sampler_params=slat_sampler_params(slat_steps, slat_cfg_strength),
867
+ )
868
+ glb = get_postprocessing_utils().to_glb(outputs["gaussian"][0], outputs["mesh"][0])
869
+ glb_path = worker_path("edited_stage2.glb")
870
+ glb.export(glb_path)
871
+ torch.cuda.empty_cache()
872
+ return glb_path, glb_path, seed
873
+
874
+
875
+ APP_CSS = """
876
+ #editing-three-col {
877
+ align-items: flex-start;
878
+ }
879
+ #editing-three-col > div {
880
+ min-width: 220px !important;
881
+ }
882
+ """
883
+
884
+
885
+ with gr.Blocks(
886
+ delete_cache=(600, 600),
887
+ title="DVD + TRELLIS Voxel Generation and Editing",
888
+ css=APP_CSS,
889
+ fill_width=True,
890
+ ) as demo:
891
+ gr.Markdown(
892
+ """
893
+ ## DVD Voxel Generation and Editing
894
+ DVD generates or edits a 64^3 voxel structure first. TRELLIS stage 2 is run only when you click the stage-2 button.
895
+ """
896
+ )
897
+
898
+ generated_voxels_state = gr.State()
899
+ edit_voxels_state = gr.State()
900
+ edited_voxels_state = gr.State()
901
+
902
+ with gr.Tab("Generation"):
903
+ with gr.Row():
904
+ with gr.Column():
905
+ gen_example = gr.Dropdown(
906
+ choices=GENERATION_IMAGE_EXAMPLES,
907
+ label="Preloaded Generation Images",
908
+ value=None,
909
+ interactive=True,
910
+ )
911
+ gen_image = gr.Image(label="Condition Image", format="png", image_mode="RGBA", type="pil", height=300)
912
+ with gr.Accordion("Generation Settings", open=False):
913
+ gen_seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
914
+ gen_randomize = gr.Checkbox(value=True, label="Randomize seed")
915
+ gen_preprocess = gr.Checkbox(value=False, label="DVD preprocess image")
916
+ gen_dvd_steps = gr.Slider(1, 512, value=256, step=1, label="DVD voxel steps")
917
+ gen_dvd_cfg_mode = gr.Radio(
918
+ ["Default schedule", "Constant", "Two-stage"],
919
+ value="Default schedule",
920
+ label="DVD voxel CFG mode",
921
+ )
922
+ gen_dvd_cfg_constant = gr.Slider(
923
+ 0.0,
924
+ 5.0,
925
+ value=0.7,
926
+ step=0.05,
927
+ label="DVD voxel constant CFG",
928
+ )
929
+ with gr.Row():
930
+ gen_dvd_cfg_early = gr.Slider(0.0, 5.0, value=0.4, step=0.05, label="DVD CFG early t<0.5")
931
+ gen_dvd_cfg_late = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="DVD CFG late")
932
+ gen_dvd_cfg_split = gr.Slider(0.0, 1.0, value=0.5, step=0.05, label="DVD CFG switch time")
933
+ gen_stage2_preprocess = gr.Checkbox(value=True, label="TRELLIS preprocess image for stage 2")
934
+ gen_slat_steps = gr.Slider(1, 50, value=25, step=1, label="TRELLIS stage-2 steps")
935
+ gen_slat_cfg = gr.Slider(0.0, 10.0, value=5.0, step=0.1, label="TRELLIS stage-2 CFG")
936
+ gen_btn = gr.Button("1. Generate DVD Voxels")
937
+ gen_stage2_btn = gr.Button("2. Run TRELLIS Stage 2", interactive=True)
938
+ transfer_btn = gr.Button("Move Generated Voxels To Editing")
939
+ with gr.Column():
940
+ gen_voxel_view = voxel_viewer("Generated / Cubified Voxels", exposure=5.0, height=320)
941
+ gen_npy_download = gr.DownloadButton(label="Download Voxel Coords (.npy)", interactive=False)
942
+ gen_stage2_view = gr.Model3D(label="TRELLIS Stage 2 GLB", height=320)
943
+ gen_glb_download = gr.DownloadButton(label="Download Stage 2 GLB", interactive=False)
944
+
945
+ with gr.Tab("Editing"):
946
+ with gr.Row(equal_height=False, elem_id="editing-three-col"):
947
+ with gr.Column(scale=1, min_width=220):
948
+ gr.Markdown("### 1. Source Voxels")
949
+ edit_image_example = gr.Dropdown(
950
+ choices=EDIT_IMAGE_EXAMPLES,
951
+ label="Preloaded Edit Target Images",
952
+ value=None,
953
+ interactive=True,
954
+ )
955
+ edit_target_image = gr.Image(label="Target Image", format="png", image_mode="RGBA", type="pil", height=260)
956
+ edit_voxel_example = gr.Dropdown(
957
+ choices=EDIT_VOXEL_EXAMPLES,
958
+ label="Preloaded Edit Voxels",
959
+ value=None,
960
+ interactive=True,
961
+ )
962
+ edit_file = gr.File(label="Upload Voxel Coords (.npy, .npz, .pt, .pth)")
963
+ load_edit_btn = gr.Button("Load Selected / Uploaded Voxels")
964
+ with gr.Row():
965
+ rot_x_btn = gr.Button("Rotate X 90")
966
+ rot_y_btn = gr.Button("Rotate Y 90")
967
+ rot_z_btn = gr.Button("Rotate Z 90")
968
+ edit_voxel_view = voxel_viewer("Current Editing Voxels", exposure=10.0, height=300)
969
+
970
+ with gr.Column(scale=1, min_width=220):
971
+ gr.Markdown("### Edit Mask Boxes\nEach enabled box is an edit region. The union of enabled boxes is regenerated.")
972
+ with gr.Accordion("Box 1", open=True):
973
+ box1_use = gr.Checkbox(value=True, label="Use box 1")
974
+ with gr.Row():
975
+ box1_x0 = gr.Slider(0, RESOLUTION, value=0, step=1, label="x0")
976
+ box1_x1 = gr.Slider(0, RESOLUTION, value=RESOLUTION, step=1, label="x1")
977
+ with gr.Row():
978
+ box1_y0 = gr.Slider(0, RESOLUTION, value=0, step=1, label="y0")
979
+ box1_y1 = gr.Slider(0, RESOLUTION, value=RESOLUTION, step=1, label="y1")
980
+ with gr.Row():
981
+ box1_z0 = gr.Slider(0, RESOLUTION, value=32, step=1, label="z0")
982
+ box1_z1 = gr.Slider(0, RESOLUTION, value=RESOLUTION, step=1, label="z1")
983
+ with gr.Accordion("Box 2", open=False):
984
+ box2_use = gr.Checkbox(value=False, label="Use box 2")
985
+ with gr.Row():
986
+ box2_x0 = gr.Slider(0, RESOLUTION, value=0, step=1, label="x0")
987
+ box2_x1 = gr.Slider(0, RESOLUTION, value=RESOLUTION, step=1, label="x1")
988
+ with gr.Row():
989
+ box2_y0 = gr.Slider(0, RESOLUTION, value=0, step=1, label="y0")
990
+ box2_y1 = gr.Slider(0, RESOLUTION, value=RESOLUTION, step=1, label="y1")
991
+ with gr.Row():
992
+ box2_z0 = gr.Slider(0, RESOLUTION, value=0, step=1, label="z0")
993
+ box2_z1 = gr.Slider(0, RESOLUTION, value=16, step=1, label="z1")
994
+ with gr.Accordion("Box 3", open=False):
995
+ box3_use = gr.Checkbox(value=False, label="Use box 3")
996
+ with gr.Row():
997
+ box3_x0 = gr.Slider(0, RESOLUTION, value=0, step=1, label="x0")
998
+ box3_x1 = gr.Slider(0, RESOLUTION, value=RESOLUTION, step=1, label="x1")
999
+ with gr.Row():
1000
+ box3_y0 = gr.Slider(0, RESOLUTION, value=0, step=1, label="y0")
1001
+ box3_y1 = gr.Slider(0, RESOLUTION, value=RESOLUTION, step=1, label="y1")
1002
+ with gr.Row():
1003
+ box3_z0 = gr.Slider(0, RESOLUTION, value=16, step=1, label="z0")
1004
+ box3_z1 = gr.Slider(0, RESOLUTION, value=32, step=1, label="z1")
1005
+ preview_mask_btn = gr.Button("Preview Edit Region By Perturbing")
1006
+ edit_mask_view = voxel_viewer("Edit Region Preview", exposure=5.0, height=300)
1007
+
1008
+ with gr.Column(scale=1, min_width=220):
1009
+ gr.Markdown("### 3. Edited Result")
1010
+ with gr.Accordion("Editing Settings", open=False):
1011
+ edit_seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
1012
+ edit_randomize = gr.Checkbox(value=True, label="Randomize seed")
1013
+ edit_preprocess = gr.Checkbox(value=True, label="DVD preprocess target image")
1014
+ edit_dvd_steps = gr.Slider(1, 512, value=128, step=1, label="DVD edit steps")
1015
+ edit_dvd_cfg_mode = gr.Radio(
1016
+ ["Default schedule", "Constant", "Two-stage"],
1017
+ value="Default schedule",
1018
+ label="DVD edit CFG mode",
1019
+ )
1020
+ edit_dvd_cfg_constant = gr.Slider(
1021
+ 0.0,
1022
+ 5.0,
1023
+ value=0.45,
1024
+ step=0.05,
1025
+ label="DVD edit constant CFG",
1026
+ )
1027
+ with gr.Row():
1028
+ edit_dvd_cfg_early = gr.Slider(0.0, 5.0, value=0.45, step=0.05, label="DVD CFG early t<0.5")
1029
+ edit_dvd_cfg_late = gr.Slider(0.0, 5.0, value=0.45, step=0.05, label="DVD CFG late")
1030
+ edit_dvd_cfg_split = gr.Slider(0.0, 1.0, value=0.5, step=0.05, label="DVD CFG switch time")
1031
+ edit_stage2_preprocess = gr.Checkbox(value=True, label="TRELLIS preprocess target image for stage 2")
1032
+ edit_slat_steps = gr.Slider(1, 50, value=25, step=1, label="TRELLIS stage-2 steps")
1033
+ edit_slat_cfg = gr.Slider(0.0, 10.0, value=5.0, step=0.1, label="TRELLIS stage-2 CFG")
1034
+ edit_btn = gr.Button("Run DVD Editing")
1035
+ edit_stage2_btn = gr.Button("Run TRELLIS Stage 2")
1036
+ edited_voxel_view = voxel_viewer("Edited / Cubified Voxels", exposure=5.0, height=300)
1037
+ edited_npy_download = gr.DownloadButton(label="Download Edited Voxel Coords (.npy)", interactive=False)
1038
+ edit_stage2_view = gr.Model3D(label="Edited TRELLIS Stage 2 GLB", height=300)
1039
+ edited_glb_download = gr.DownloadButton(label="Download Edited Stage 2 GLB", interactive=False)
1040
+
1041
+ demo.load(start_session)
1042
+ demo.unload(end_session)
1043
+
1044
+
1045
+ gen_example.change(load_generation_example, inputs=[gen_example], outputs=[gen_image])
1046
+ edit_image_example.change(load_edit_image_example, inputs=[edit_image_example], outputs=[edit_target_image])
1047
+ edit_voxel_example.change(
1048
+ load_edit_voxel_example,
1049
+ inputs=[edit_voxel_example],
1050
+ outputs=[edit_voxels_state, edit_voxel_view],
1051
+ )
1052
+
1053
+ gen_btn.click(
1054
+ generate_voxels,
1055
+ inputs=[
1056
+ gen_image,
1057
+ gen_seed,
1058
+ gen_randomize,
1059
+ gen_preprocess,
1060
+ gen_dvd_steps,
1061
+ gen_dvd_cfg_mode,
1062
+ gen_dvd_cfg_constant,
1063
+ gen_dvd_cfg_early,
1064
+ gen_dvd_cfg_late,
1065
+ gen_dvd_cfg_split,
1066
+ ],
1067
+ outputs=[generated_voxels_state, gen_voxel_view, gen_npy_download, gen_seed],
1068
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[gen_npy_download])
1069
+
1070
+ gen_stage2_btn.click(
1071
+ generation_stage2,
1072
+ inputs=[
1073
+ gen_image,
1074
+ generated_voxels_state,
1075
+ gen_seed,
1076
+ gen_randomize,
1077
+ gen_stage2_preprocess,
1078
+ gen_slat_steps,
1079
+ gen_slat_cfg,
1080
+ ],
1081
+ outputs=[gen_stage2_view, gen_glb_download, gen_seed],
1082
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[gen_glb_download])
1083
+
1084
+ transfer_btn.click(
1085
+ transfer_generation_to_editing,
1086
+ inputs=[generated_voxels_state, gen_voxel_view],
1087
+ outputs=[edit_voxels_state, edit_voxel_view],
1088
+ )
1089
+
1090
+ load_edit_btn.click(
1091
+ load_edit_voxels,
1092
+ inputs=[edit_file, edit_voxel_example],
1093
+ outputs=[edit_voxels_state, edit_voxel_view],
1094
+ )
1095
+ rot_x_btn.click(rotate_x, inputs=[edit_voxels_state], outputs=[edit_voxels_state, edit_voxel_view])
1096
+ rot_y_btn.click(rotate_y, inputs=[edit_voxels_state], outputs=[edit_voxels_state, edit_voxel_view])
1097
+ rot_z_btn.click(rotate_z, inputs=[edit_voxels_state], outputs=[edit_voxels_state, edit_voxel_view])
1098
+
1099
+ preview_mask_btn.click(
1100
+ visualize_edit_mask,
1101
+ inputs=[edit_voxels_state] + mask_inputs(),
1102
+ outputs=[edit_mask_view],
1103
+ )
1104
+
1105
+ edit_btn.click(
1106
+ run_editing,
1107
+ inputs=[
1108
+ edit_target_image,
1109
+ edit_voxels_state,
1110
+ edit_seed,
1111
+ edit_randomize,
1112
+ edit_preprocess,
1113
+ edit_dvd_steps,
1114
+ edit_dvd_cfg_mode,
1115
+ edit_dvd_cfg_constant,
1116
+ edit_dvd_cfg_early,
1117
+ edit_dvd_cfg_late,
1118
+ edit_dvd_cfg_split,
1119
+ ] + mask_inputs(),
1120
+ outputs=[edited_voxels_state, edited_voxel_view, edited_npy_download, edit_seed],
1121
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[edited_npy_download])
1122
+
1123
+ edit_stage2_btn.click(
1124
+ editing_stage2,
1125
+ inputs=[
1126
+ edit_target_image,
1127
+ edited_voxels_state,
1128
+ edit_seed,
1129
+ edit_randomize,
1130
+ edit_stage2_preprocess,
1131
+ edit_slat_steps,
1132
+ edit_slat_cfg,
1133
+ ],
1134
+ outputs=[edit_stage2_view, edited_glb_download, edit_seed],
1135
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[edited_glb_download])
1136
+
1137
+
1138
+ def parse_args():
1139
+ parser = argparse.ArgumentParser()
1140
+ parser.add_argument("--device", default="auto", help="cuda, cpu, or auto")
1141
+ parser.add_argument("--share", action="store_true")
1142
+ parser.add_argument("--server-name", default=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"))
1143
+ parser.add_argument("--server-port", type=int, default=default_server_port())
1144
+ return parser.parse_args()
1145
+
1146
+
1147
+ if __name__ == "__main__":
1148
+ args = parse_args()
1149
+
1150
+ # Keep startup free of DVD/TRELLIS imports; only prefetch raw assets.
1151
+ prefetch_assets()
1152
+ preload_zero_gpu_models(os.environ.get("DVD_SPACE_DEVICE", "cuda"))
1153
+ demo.queue().launch(
1154
+ share=args.share,
1155
+ server_name=args.server_name,
1156
+ server_port=args.server_port,
1157
+ show_api=False,
1158
+ show_error=True,
1159
+ ssr_mode=False,
1160
+ )
app_dvd_image_gpu_lazy.py ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import uuid
4
+ from pathlib import Path
5
+
6
+ os.environ.setdefault("SPCONV_ALGO", "native")
7
+ os.environ.setdefault("ATTN_BACKEND", "flash_attn")
8
+ os.environ.setdefault("SPARSE_ATTN_BACKEND", "flash_attn")
9
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
10
+ os.environ.setdefault("DVD_MODEL_REPO", "Zhengrui/dvd")
11
+
12
+ import gradio as gr
13
+ import numpy as np
14
+ import spaces
15
+ import torch
16
+
17
+
18
+ MAX_SEED = 2**31 - 1
19
+ ROOT_DIR = Path(__file__).resolve().parent
20
+ TMP_DIR = ROOT_DIR / "tmp" / "dvd_image_gpu_lazy"
21
+ TMP_DIR.mkdir(parents=True, exist_ok=True)
22
+ IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"}
23
+ EXAMPLE_DIR = ROOT_DIR / "assets" / "example_image"
24
+ EXAMPLES = [
25
+ str(path)
26
+ for path in sorted(EXAMPLE_DIR.iterdir())
27
+ if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
28
+ ] if EXAMPLE_DIR.exists() else []
29
+
30
+ _dvd_pipe = None
31
+ _export_cubified_voxels = None
32
+
33
+
34
+ def log_event(message: str):
35
+ print(f"[DVD GPULazy] {message}", flush=True)
36
+
37
+
38
+ def _download_file(url: str, dest: Path):
39
+ import urllib.request
40
+
41
+ dest.parent.mkdir(parents=True, exist_ok=True)
42
+ if dest.exists() and dest.stat().st_size > 0:
43
+ log_event(f"using cached {dest.name}")
44
+ return
45
+ tmp = dest.with_name(dest.name + ".tmp")
46
+ if tmp.exists():
47
+ tmp.unlink()
48
+ log_event(f"downloading {url} -> {dest}")
49
+ urllib.request.urlretrieve(url, tmp)
50
+ tmp.replace(dest)
51
+
52
+
53
+ def prefetch_dvd_weights():
54
+ from huggingface_hub import hf_hub_download
55
+
56
+ repo = os.environ.get("DVD_MODEL_REPO", "Zhengrui/dvd")
57
+ subfolder = os.environ.get("DVD_MODEL_SUBFOLDER") or None
58
+ revision = os.environ.get("DVD_MODEL_REVISION") or None
59
+ token = os.environ.get("DVD_MODEL_TOKEN") or os.environ.get("HF_TOKEN") or None
60
+ for filename in ("dvd_img.json", "dvd_img.safetensors"):
61
+ log_event(f"prefetching {repo}/{filename}")
62
+ hf_hub_download(
63
+ repo_id=repo,
64
+ filename=filename,
65
+ subfolder=subfolder,
66
+ revision=revision,
67
+ token=token,
68
+ )
69
+
70
+
71
+ def prefetch_dinov2():
72
+ import shutil
73
+ import zipfile
74
+
75
+ hub_dir = Path(torch.hub.get_dir())
76
+ hub_dir.mkdir(parents=True, exist_ok=True)
77
+ repo_dir = hub_dir / "facebookresearch_dinov2_main"
78
+ if repo_dir.exists():
79
+ log_event(f"using cached DINOv2 repo {repo_dir}")
80
+ else:
81
+ zip_path = hub_dir / "main.zip"
82
+ _download_file("https://github.com/facebookresearch/dinov2/zipball/main", zip_path)
83
+ extract_tmp = hub_dir / "_dvd_dinov2_extract"
84
+ if extract_tmp.exists():
85
+ shutil.rmtree(extract_tmp)
86
+ extract_tmp.mkdir(parents=True, exist_ok=True)
87
+ log_event(f"extracting DINOv2 repo to {repo_dir}")
88
+ with zipfile.ZipFile(zip_path) as zf:
89
+ zf.extractall(extract_tmp)
90
+ top_level = zf.namelist()[0].split("/", 1)[0]
91
+ shutil.move(str(extract_tmp / top_level), str(repo_dir))
92
+ shutil.rmtree(extract_tmp, ignore_errors=True)
93
+
94
+ ckpt_dir = hub_dir / "checkpoints"
95
+ _download_file(
96
+ "https://dl.fbaipublicfiles.com/dinov2/dinov2_vitl14/dinov2_vitl14_reg4_pretrain.pth",
97
+ ckpt_dir / "dinov2_vitl14_reg4_pretrain.pth",
98
+ )
99
+
100
+
101
+ def prefetch_assets():
102
+ if os.environ.get("DVD_PREFETCH_ASSETS", "1").lower() in {"0", "false", "no", "off"}:
103
+ log_event("asset prefetch disabled")
104
+ return
105
+ try:
106
+ log_event("asset prefetch start")
107
+ prefetch_dvd_weights()
108
+ prefetch_dinov2()
109
+ log_event("asset prefetch done")
110
+ except Exception as exc:
111
+ log_event(f"asset prefetch failed; continuing without prefetch: {exc}")
112
+
113
+
114
+ prefetch_assets()
115
+
116
+
117
+ def worker_path(name: str) -> str:
118
+ path = TMP_DIR / f"worker-{uuid.uuid4().hex}"
119
+ path.mkdir(parents=True, exist_ok=True)
120
+ return str(path / name)
121
+
122
+
123
+ def cfg_schedule(mode: str, constant: float, early: float, late: float, split: float):
124
+ if mode == "Constant":
125
+ return float(constant)
126
+ if mode == "Two-stage":
127
+ split = float(split)
128
+ early = float(early)
129
+ late = float(late)
130
+ return lambda t: early if t < split else late
131
+ return None
132
+
133
+
134
+ def ensure_dvd_pipe():
135
+ global _dvd_pipe, _export_cubified_voxels
136
+ if _dvd_pipe is not None:
137
+ log_event("reusing cached DVD image pipeline")
138
+ return _dvd_pipe, _export_cubified_voxels
139
+
140
+ # Intentionally import DVD/TRELLIS only after @spaces.GPU has acquired a worker.
141
+ log_event("importing DVD inside GPU callback")
142
+ from dvd import DVDImageToVoxelPipeline, export_cubified_voxels
143
+
144
+ repo = os.environ.get("DVD_MODEL_REPO", "Zhengrui/dvd")
145
+ subfolder = os.environ.get("DVD_MODEL_SUBFOLDER") or None
146
+ revision = os.environ.get("DVD_MODEL_REVISION") or None
147
+ token = os.environ.get("DVD_MODEL_TOKEN") or os.environ.get("HF_TOKEN") or None
148
+
149
+ log_event(f"loading DVD image pipeline from {repo} on cuda")
150
+ _dvd_pipe = DVDImageToVoxelPipeline.from_pretrained(
151
+ repo,
152
+ variant="base",
153
+ device="cuda",
154
+ subfolder=subfolder,
155
+ revision=revision,
156
+ token=token,
157
+ )
158
+ _export_cubified_voxels = export_cubified_voxels
159
+ log_event("DVD image pipeline ready on cuda")
160
+ return _dvd_pipe, _export_cubified_voxels
161
+
162
+
163
+ @spaces.GPU(duration=60)
164
+ def zero_gpu_smoke_test():
165
+ log_event("zero_gpu_smoke_test start")
166
+ if not torch.cuda.is_available():
167
+ log_event("zero_gpu_smoke_test no cuda")
168
+ return "CUDA unavailable inside ZeroGPU worker"
169
+ value = torch.ones((1,), device="cuda").sum().item()
170
+ name = torch.cuda.get_device_name(0)
171
+ log_event(f"zero_gpu_smoke_test done device={name} value={value}")
172
+ return f"OK: {name}, value={value}"
173
+
174
+
175
+ @spaces.GPU(duration=600)
176
+ def generate_voxels(
177
+ image,
178
+ seed: int,
179
+ randomize_seed: bool,
180
+ preprocess_image: bool,
181
+ dvd_steps: int,
182
+ dvd_cfg_mode: str,
183
+ dvd_cfg_constant: float,
184
+ dvd_cfg_early: float,
185
+ dvd_cfg_late: float,
186
+ dvd_cfg_split: float,
187
+ progress=gr.Progress(track_tqdm=True),
188
+ ):
189
+ progress(0.01, desc="Starting ZeroGPU callback")
190
+ log_event(f"generate_voxels start seed={seed} randomize={randomize_seed} steps={dvd_steps}")
191
+ if image is None:
192
+ raise gr.Error("Please provide an image.")
193
+
194
+ progress(0.04, desc="Loading DVD pipeline")
195
+ pipeline, export_cubified_voxels = ensure_dvd_pipe()
196
+
197
+ seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
198
+ sampler_kwargs = {"steps": int(dvd_steps)}
199
+ schedule = cfg_schedule(
200
+ dvd_cfg_mode,
201
+ dvd_cfg_constant,
202
+ dvd_cfg_early,
203
+ dvd_cfg_late,
204
+ dvd_cfg_split,
205
+ )
206
+ if schedule is not None:
207
+ sampler_kwargs["cfg_strength"] = schedule
208
+
209
+ progress(0.18, desc="Sampling DVD voxels")
210
+ log_event(f"sampling seed={seed} steps={dvd_steps}")
211
+ voxels = pipeline.sample_voxels(
212
+ image,
213
+ seed=seed,
214
+ preprocess_image=preprocess_image,
215
+ **sampler_kwargs,
216
+ )
217
+
218
+ progress(0.88, desc="Exporting voxel preview")
219
+ mesh_path = worker_path("generated_voxels.glb")
220
+ npy_path = worker_path("generated_voxel64_coords.npy")
221
+ export_cubified_voxels(voxels, mesh_path)
222
+ np.save(npy_path, voxels.coords_without_batch.detach().cpu().numpy().astype(np.int32))
223
+ torch.cuda.empty_cache()
224
+ log_event(f"generate_voxels done seed={seed} mesh={mesh_path} npy={npy_path}")
225
+ return mesh_path, npy_path, int(seed), f"Done. seed={seed}"
226
+
227
+
228
+ with gr.Blocks(title="DVD Image", fill_width=True) as demo:
229
+ gr.Markdown("## DVD Image Voxel Generation")
230
+ with gr.Row():
231
+ smoke_btn = gr.Button("ZeroGPU Smoke Test")
232
+ smoke_out = gr.Textbox(label="ZeroGPU Status", interactive=False)
233
+ smoke_btn.click(zero_gpu_smoke_test, outputs=smoke_out)
234
+
235
+ with gr.Row(equal_height=False):
236
+ with gr.Column():
237
+ image = gr.Image(label="Input Image", format="png", image_mode="RGBA", type="pil", height=320)
238
+ if EXAMPLES:
239
+ gr.Examples(examples=EXAMPLES[:12], inputs=image, examples_per_page=6)
240
+ with gr.Accordion("DVD Settings", open=False):
241
+ seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
242
+ randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
243
+ preprocess_image = gr.Checkbox(value=True, label="DVD preprocess image")
244
+ dvd_steps = gr.Slider(1, 512, value=256, step=1, label="DVD steps")
245
+ dvd_cfg_mode = gr.Radio(
246
+ ["Default schedule", "Constant", "Two-stage"],
247
+ value="Default schedule",
248
+ label="DVD CFG mode",
249
+ )
250
+ dvd_cfg_constant = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="Constant CFG")
251
+ dvd_cfg_early = gr.Slider(0.0, 5.0, value=0.4, step=0.05, label="Early CFG")
252
+ dvd_cfg_late = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="Late CFG")
253
+ dvd_cfg_split = gr.Slider(0.0, 1.0, value=0.5, step=0.05, label="CFG switch time")
254
+ gen_btn = gr.Button("Generate DVD Voxels", variant="primary")
255
+ with gr.Column():
256
+ voxel_view = gr.Model3D(
257
+ label="Generated / Cubified Voxels",
258
+ height=360,
259
+ camera_position=(-180, 90, 3),
260
+ )
261
+ npy_download = gr.DownloadButton(label="Download Voxel Coords (.npy)", interactive=False)
262
+ status = gr.Textbox(label="Status", interactive=False)
263
+
264
+ gen_btn.click(
265
+ generate_voxels,
266
+ inputs=[
267
+ image,
268
+ seed,
269
+ randomize_seed,
270
+ preprocess_image,
271
+ dvd_steps,
272
+ dvd_cfg_mode,
273
+ dvd_cfg_constant,
274
+ dvd_cfg_early,
275
+ dvd_cfg_late,
276
+ dvd_cfg_split,
277
+ ],
278
+ outputs=[voxel_view, npy_download, seed, status],
279
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[npy_download])
280
+
281
+
282
+ if __name__ == "__main__":
283
+ demo.queue().launch(show_api=False, show_error=True, ssr_mode=False)
app_dvd_image_trellis_style.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import uuid
4
+ from pathlib import Path
5
+
6
+ os.environ.setdefault("SPCONV_ALGO", "native")
7
+ os.environ.setdefault("ATTN_BACKEND", "flash_attn")
8
+ os.environ.setdefault("SPARSE_ATTN_BACKEND", "flash_attn")
9
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
10
+ os.environ.setdefault("DVD_MODEL_REPO", "Zhengrui/dvd")
11
+
12
+ import spaces
13
+ import gradio as gr
14
+ import numpy as np
15
+ import torch
16
+
17
+
18
+ MAX_SEED = 2**31 - 1
19
+ ROOT_DIR = Path(__file__).resolve().parent
20
+ TMP_DIR = ROOT_DIR / "tmp" / "dvd_image_trellis_style"
21
+ TMP_DIR.mkdir(parents=True, exist_ok=True)
22
+ IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"}
23
+ EXAMPLE_DIR = ROOT_DIR / "assets" / "example_image"
24
+ EXAMPLES = [
25
+ str(path)
26
+ for path in sorted(EXAMPLE_DIR.iterdir())
27
+ if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
28
+ ] if EXAMPLE_DIR.exists() else []
29
+
30
+
31
+ def log_event(message: str):
32
+ print(f"[DVD TrellisStyle] {message}", flush=True)
33
+
34
+
35
+ @spaces.GPU(duration=60)
36
+ def first_gpu_setup():
37
+ log_event("first_gpu_setup start")
38
+ if not torch.cuda.is_available():
39
+ raise RuntimeError("CUDA unavailable during first_gpu_setup")
40
+ value = torch.ones((1,), device="cuda").sum().item()
41
+ name = torch.cuda.get_device_name(0)
42
+ log_event(f"first_gpu_setup ok device={name} value={value}")
43
+
44
+
45
+ # Match trellis-community: acquire a ZeroGPU worker once before importing TRELLIS.
46
+ first_gpu_setup()
47
+
48
+ from dvd import DVDImageToVoxelPipeline, export_cubified_voxels
49
+
50
+
51
+ def worker_path(name: str) -> str:
52
+ path = TMP_DIR / f"worker-{uuid.uuid4().hex}"
53
+ path.mkdir(parents=True, exist_ok=True)
54
+ return str(path / name)
55
+
56
+
57
+ def cfg_schedule(mode: str, constant: float, early: float, late: float, split: float):
58
+ if mode == "Constant":
59
+ return float(constant)
60
+ if mode == "Two-stage":
61
+ split = float(split)
62
+ early = float(early)
63
+ late = float(late)
64
+ return lambda t: early if t < split else late
65
+ return None
66
+
67
+
68
+ repo = os.environ.get("DVD_MODEL_REPO", "Zhengrui/dvd")
69
+ subfolder = os.environ.get("DVD_MODEL_SUBFOLDER") or None
70
+ revision = os.environ.get("DVD_MODEL_REVISION") or None
71
+ token = os.environ.get("DVD_MODEL_TOKEN") or os.environ.get("HF_TOKEN") or None
72
+
73
+ log_event(f"loading DVD image pipeline from {repo}")
74
+ dvd_pipe = DVDImageToVoxelPipeline.from_pretrained(
75
+ repo,
76
+ variant="base",
77
+ subfolder=subfolder,
78
+ revision=revision,
79
+ token=token,
80
+ )
81
+ log_event("moving DVD image pipeline to cuda")
82
+ dvd_pipe.to("cuda")
83
+ log_event("DVD image pipeline ready on cuda")
84
+
85
+
86
+ @spaces.GPU(duration=30)
87
+ def zero_gpu_smoke_test():
88
+ log_event("zero_gpu_smoke_test start")
89
+ if not torch.cuda.is_available():
90
+ log_event("zero_gpu_smoke_test no cuda")
91
+ return "CUDA unavailable inside ZeroGPU worker"
92
+ value = torch.ones((1,), device="cuda").sum().item()
93
+ name = torch.cuda.get_device_name(0)
94
+ log_event(f"zero_gpu_smoke_test done device={name} value={value}")
95
+ return f"OK: {name}, value={value}"
96
+
97
+
98
+ @spaces.GPU(duration=240)
99
+ def generate_voxels(
100
+ image,
101
+ seed: int,
102
+ randomize_seed: bool,
103
+ preprocess_image: bool,
104
+ dvd_steps: int,
105
+ dvd_cfg_mode: str,
106
+ dvd_cfg_constant: float,
107
+ dvd_cfg_early: float,
108
+ dvd_cfg_late: float,
109
+ dvd_cfg_split: float,
110
+ progress=gr.Progress(track_tqdm=True),
111
+ ):
112
+ progress(0.01, desc="Starting ZeroGPU callback")
113
+ log_event(f"generate_voxels start seed={seed} randomize={randomize_seed} steps={dvd_steps}")
114
+ if image is None:
115
+ raise gr.Error("Please provide an image.")
116
+
117
+ seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
118
+ sampler_kwargs = {"steps": int(dvd_steps)}
119
+ schedule = cfg_schedule(
120
+ dvd_cfg_mode,
121
+ dvd_cfg_constant,
122
+ dvd_cfg_early,
123
+ dvd_cfg_late,
124
+ dvd_cfg_split,
125
+ )
126
+ if schedule is not None:
127
+ sampler_kwargs["cfg_strength"] = schedule
128
+
129
+ progress(0.08, desc="Sampling DVD voxels")
130
+ voxels = dvd_pipe.sample_voxels(
131
+ image,
132
+ seed=seed,
133
+ preprocess_image=preprocess_image,
134
+ **sampler_kwargs,
135
+ )
136
+
137
+ progress(0.88, desc="Exporting voxel preview")
138
+ mesh_path = worker_path("generated_voxels.glb")
139
+ npy_path = worker_path("generated_voxel64_coords.npy")
140
+ export_cubified_voxels(voxels, mesh_path)
141
+ np.save(npy_path, voxels.coords_without_batch.detach().cpu().numpy().astype(np.int32))
142
+ torch.cuda.empty_cache()
143
+ log_event(f"generate_voxels done seed={seed} mesh={mesh_path} npy={npy_path}")
144
+ return mesh_path, npy_path, int(seed), f"Done. seed={seed}"
145
+
146
+
147
+ with gr.Blocks(title="DVD Image", fill_width=True) as demo:
148
+ gr.Markdown("## DVD Image Voxel Generation")
149
+ with gr.Row():
150
+ smoke_btn = gr.Button("ZeroGPU Smoke Test")
151
+ smoke_out = gr.Textbox(label="ZeroGPU Status", interactive=False)
152
+ smoke_btn.click(zero_gpu_smoke_test, outputs=smoke_out)
153
+
154
+ with gr.Row(equal_height=False):
155
+ with gr.Column():
156
+ image = gr.Image(label="Input Image", format="png", image_mode="RGBA", type="pil", height=320)
157
+ if EXAMPLES:
158
+ gr.Examples(examples=EXAMPLES[:12], inputs=image, examples_per_page=6)
159
+ with gr.Accordion("DVD Settings", open=False):
160
+ seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
161
+ randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
162
+ preprocess_image = gr.Checkbox(value=True, label="DVD preprocess image")
163
+ dvd_steps = gr.Slider(1, 512, value=256, step=1, label="DVD steps")
164
+ dvd_cfg_mode = gr.Radio(
165
+ ["Default schedule", "Constant", "Two-stage"],
166
+ value="Default schedule",
167
+ label="DVD CFG mode",
168
+ )
169
+ dvd_cfg_constant = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="Constant CFG")
170
+ dvd_cfg_early = gr.Slider(0.0, 5.0, value=0.4, step=0.05, label="Early CFG")
171
+ dvd_cfg_late = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="Late CFG")
172
+ dvd_cfg_split = gr.Slider(0.0, 1.0, value=0.5, step=0.05, label="CFG switch time")
173
+ gen_btn = gr.Button("Generate DVD Voxels", variant="primary")
174
+ with gr.Column():
175
+ voxel_view = gr.Model3D(
176
+ label="Generated / Cubified Voxels",
177
+ height=360,
178
+ camera_position=(-180, 90, 3),
179
+ )
180
+ npy_download = gr.DownloadButton(label="Download Voxel Coords (.npy)", interactive=False)
181
+ status = gr.Textbox(label="Status", interactive=False)
182
+
183
+ gen_btn.click(
184
+ generate_voxels,
185
+ inputs=[
186
+ image,
187
+ seed,
188
+ randomize_seed,
189
+ preprocess_image,
190
+ dvd_steps,
191
+ dvd_cfg_mode,
192
+ dvd_cfg_constant,
193
+ dvd_cfg_early,
194
+ dvd_cfg_late,
195
+ dvd_cfg_split,
196
+ ],
197
+ outputs=[voxel_view, npy_download, seed, status],
198
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[npy_download])
199
+
200
+
201
+ if __name__ == "__main__":
202
+ demo.launch(show_api=False, show_error=True, ssr_mode=False)
app_dvd_image_wrapper.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import uuid
4
+ from pathlib import Path
5
+
6
+ os.environ.setdefault("SPCONV_ALGO", "native")
7
+ os.environ.setdefault("ATTN_BACKEND", "flash_attn")
8
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
9
+ os.environ.setdefault("DVD_MODEL_REPO", "Zhengrui/dvd")
10
+
11
+ import spaces
12
+ import gradio as gr
13
+ import numpy as np
14
+ import torch
15
+
16
+ from dvd import DVDImageToVoxelPipeline, export_cubified_voxels
17
+
18
+
19
+ MAX_SEED = 2**31 - 1
20
+ ROOT_DIR = Path(__file__).resolve().parent
21
+ TMP_DIR = ROOT_DIR / "tmp" / "dvd_image_wrapper"
22
+ TMP_DIR.mkdir(parents=True, exist_ok=True)
23
+ IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"}
24
+ EXAMPLE_DIR = ROOT_DIR / "assets" / "example_image"
25
+ EXAMPLES = [
26
+ str(path)
27
+ for path in sorted(EXAMPLE_DIR.iterdir())
28
+ if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
29
+ ] if EXAMPLE_DIR.exists() else []
30
+
31
+
32
+ def log_event(message: str):
33
+ print(f"[DVD Wrapper] {message}", flush=True)
34
+
35
+
36
+ def worker_path(name: str) -> str:
37
+ path = TMP_DIR / f"worker-{uuid.uuid4().hex}"
38
+ path.mkdir(parents=True, exist_ok=True)
39
+ return str(path / name)
40
+
41
+
42
+ def cfg_schedule(mode: str, constant: float, early: float, late: float, split: float):
43
+ if mode == "Constant":
44
+ return float(constant)
45
+ if mode == "Two-stage":
46
+ split = float(split)
47
+ early = float(early)
48
+ late = float(late)
49
+ return lambda t: early if t < split else late
50
+ return None
51
+
52
+
53
+ repo = os.environ.get("DVD_MODEL_REPO", "Zhengrui/dvd")
54
+ subfolder = os.environ.get("DVD_MODEL_SUBFOLDER") or None
55
+ revision = os.environ.get("DVD_MODEL_REVISION") or None
56
+ token = os.environ.get("DVD_MODEL_TOKEN") or os.environ.get("HF_TOKEN") or None
57
+
58
+ log_event(f"loading DVD image pipeline from {repo}")
59
+ dvd_pipe = DVDImageToVoxelPipeline.from_pretrained(
60
+ repo,
61
+ variant="base",
62
+ subfolder=subfolder,
63
+ revision=revision,
64
+ token=token,
65
+ )
66
+ log_event("moving DVD image pipeline to cuda")
67
+ dvd_pipe.to("cuda")
68
+ log_event("DVD image pipeline ready on cuda")
69
+
70
+
71
+ @spaces.GPU(duration=30)
72
+ def zero_gpu_smoke_test():
73
+ log_event("zero_gpu_smoke_test start")
74
+ if not torch.cuda.is_available():
75
+ log_event("zero_gpu_smoke_test no cuda")
76
+ return "CUDA unavailable inside ZeroGPU worker"
77
+ value = torch.ones((1,), device="cuda").sum().item()
78
+ name = torch.cuda.get_device_name(0)
79
+ log_event(f"zero_gpu_smoke_test done device={name} value={value}")
80
+ return f"OK: {name}, value={value}"
81
+
82
+
83
+ @spaces.GPU(duration=180)
84
+ def generate_voxels(
85
+ image,
86
+ seed: int,
87
+ randomize_seed: bool,
88
+ preprocess_image: bool,
89
+ dvd_steps: int,
90
+ dvd_cfg_mode: str,
91
+ dvd_cfg_constant: float,
92
+ dvd_cfg_early: float,
93
+ dvd_cfg_late: float,
94
+ dvd_cfg_split: float,
95
+ progress=gr.Progress(track_tqdm=True),
96
+ ):
97
+ progress(0.01, desc="Starting ZeroGPU callback")
98
+ log_event(f"generate_voxels start seed={seed} randomize={randomize_seed} steps={dvd_steps}")
99
+ if image is None:
100
+ raise gr.Error("Please provide an image.")
101
+
102
+ seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
103
+ sampler_kwargs = {"steps": int(dvd_steps)}
104
+ schedule = cfg_schedule(
105
+ dvd_cfg_mode,
106
+ dvd_cfg_constant,
107
+ dvd_cfg_early,
108
+ dvd_cfg_late,
109
+ dvd_cfg_split,
110
+ )
111
+ if schedule is not None:
112
+ sampler_kwargs["cfg_strength"] = schedule
113
+
114
+ progress(0.08, desc="Sampling DVD voxels")
115
+ voxels = dvd_pipe.sample_voxels(
116
+ image,
117
+ seed=seed,
118
+ preprocess_image=preprocess_image,
119
+ **sampler_kwargs,
120
+ )
121
+
122
+ progress(0.88, desc="Exporting voxel preview")
123
+ mesh_path = worker_path("generated_voxels.glb")
124
+ npy_path = worker_path("generated_voxel64_coords.npy")
125
+ export_cubified_voxels(voxels, mesh_path)
126
+ np.save(npy_path, voxels.coords_without_batch.detach().cpu().numpy().astype(np.int32))
127
+ torch.cuda.empty_cache()
128
+ log_event(f"generate_voxels done seed={seed} mesh={mesh_path} npy={npy_path}")
129
+ return mesh_path, npy_path, int(seed), f"Done. seed={seed}"
130
+
131
+
132
+ with gr.Blocks(title="DVD Image", fill_width=True) as demo:
133
+ gr.Markdown("## DVD Image Voxel Generation")
134
+ with gr.Row():
135
+ smoke_btn = gr.Button("ZeroGPU Smoke Test")
136
+ smoke_out = gr.Textbox(label="ZeroGPU Status", interactive=False)
137
+ smoke_btn.click(zero_gpu_smoke_test, outputs=smoke_out)
138
+
139
+ with gr.Row(equal_height=False):
140
+ with gr.Column():
141
+ image = gr.Image(label="Input Image", format="png", image_mode="RGBA", type="pil", height=320)
142
+ if EXAMPLES:
143
+ gr.Examples(examples=EXAMPLES[:12], inputs=image, examples_per_page=6)
144
+ with gr.Accordion("DVD Settings", open=False):
145
+ seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
146
+ randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
147
+ preprocess_image = gr.Checkbox(value=True, label="DVD preprocess image")
148
+ dvd_steps = gr.Slider(1, 512, value=256, step=1, label="DVD steps")
149
+ dvd_cfg_mode = gr.Radio(
150
+ ["Default schedule", "Constant", "Two-stage"],
151
+ value="Default schedule",
152
+ label="DVD CFG mode",
153
+ )
154
+ dvd_cfg_constant = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="Constant CFG")
155
+ dvd_cfg_early = gr.Slider(0.0, 5.0, value=0.4, step=0.05, label="Early CFG")
156
+ dvd_cfg_late = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="Late CFG")
157
+ dvd_cfg_split = gr.Slider(0.0, 1.0, value=0.5, step=0.05, label="CFG switch time")
158
+ gen_btn = gr.Button("Generate DVD Voxels", variant="primary")
159
+ with gr.Column():
160
+ voxel_view = gr.Model3D(
161
+ label="Generated / Cubified Voxels",
162
+ height=360,
163
+ camera_position=(-180, 90, 3),
164
+ )
165
+ npy_download = gr.DownloadButton(label="Download Voxel Coords (.npy)", interactive=False)
166
+ status = gr.Textbox(label="Status", interactive=False)
167
+
168
+ gen_btn.click(
169
+ generate_voxels,
170
+ inputs=[
171
+ image,
172
+ seed,
173
+ randomize_seed,
174
+ preprocess_image,
175
+ dvd_steps,
176
+ dvd_cfg_mode,
177
+ dvd_cfg_constant,
178
+ dvd_cfg_early,
179
+ dvd_cfg_late,
180
+ dvd_cfg_split,
181
+ ],
182
+ outputs=[voxel_view, npy_download, seed, status],
183
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[npy_download])
184
+
185
+
186
+ if __name__ == "__main__":
187
+ demo.launch(show_api=False, show_error=True, ssr_mode=False)
app_image_min.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import spaces
4
+
5
+ import json
6
+ import os
7
+ import random
8
+ import subprocess
9
+ import sys
10
+ import uuid
11
+ from pathlib import Path
12
+
13
+ os.environ.setdefault("SPCONV_ALGO", "native")
14
+ os.environ.setdefault("ATTN_BACKEND", "flash_attn")
15
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
16
+ os.environ.setdefault("DVD_MODEL_REPO", "Zhengrui/dvd")
17
+
18
+ MAX_SEED = 2**31 - 1
19
+ TMP_DIR = Path(__file__).resolve().parent / "tmp" / "dvd_image_min"
20
+ TMP_DIR.mkdir(parents=True, exist_ok=True)
21
+
22
+
23
+ def log_event(message: str):
24
+ print(f"[DVD Image Min] {message}", flush=True)
25
+
26
+
27
+ def worker_path(name: str) -> str:
28
+ path = TMP_DIR / f"worker-{uuid.uuid4().hex}"
29
+ path.mkdir(parents=True, exist_ok=True)
30
+ return str(path / name)
31
+
32
+
33
+ @spaces.GPU(duration=30)
34
+ def zero_gpu_smoke_test():
35
+ log_event("zero_gpu_smoke_test start")
36
+ if not torch.cuda.is_available():
37
+ log_event("cuda unavailable")
38
+ return "CUDA unavailable"
39
+ value = torch.ones((1,), device="cuda").sum().item()
40
+ name = torch.cuda.get_device_name(0)
41
+ log_event(f"zero_gpu_smoke_test done device={name} value={value}")
42
+ return f"OK: {name}, value={value}"
43
+
44
+
45
+ @spaces.GPU(duration=120)
46
+ def generate_voxels(image, seed, randomize_seed, preprocess_image, dvd_steps, progress=gr.Progress(track_tqdm=True)):
47
+ progress(0.01, desc="Starting ZeroGPU callback")
48
+ log_event(f"generate_voxels start seed={seed} randomize={randomize_seed} steps={dvd_steps}")
49
+ if image is None:
50
+ raise gr.Error("Please provide an image.")
51
+ seed = random.randint(0, MAX_SEED) if randomize_seed else int(seed)
52
+ image_path = worker_path("input.png")
53
+ mesh_path = worker_path("generated_voxels.glb")
54
+ npy_path = worker_path("generated_voxel64_coords.npy")
55
+ config_path = worker_path("generate_config.json")
56
+ image.save(image_path)
57
+ with open(config_path, "w") as f:
58
+ json.dump(
59
+ {
60
+ "image_path": image_path,
61
+ "mesh_path": mesh_path,
62
+ "npy_path": npy_path,
63
+ "seed": int(seed),
64
+ "preprocess_image": bool(preprocess_image),
65
+ "dvd_steps": int(dvd_steps),
66
+ "dvd_cfg_mode": "Default schedule",
67
+ "dvd_cfg_constant": 0.7,
68
+ "dvd_cfg_early": 0.4,
69
+ "dvd_cfg_late": 0.7,
70
+ "dvd_cfg_split": 0.5,
71
+ },
72
+ f,
73
+ )
74
+ progress(0.08, desc="Running isolated DVD worker")
75
+ env = os.environ.copy()
76
+ env.setdefault("PYTHONUNBUFFERED", "1")
77
+ cmd = [sys.executable, str(Path(__file__).resolve().parent / "space_image_worker.py"), "generate", config_path]
78
+ proc = subprocess.run(cmd, env=env, text=True, capture_output=True)
79
+ if proc.stdout:
80
+ print(proc.stdout, flush=True)
81
+ if proc.stderr:
82
+ print(proc.stderr, flush=True)
83
+ if proc.returncode != 0:
84
+ raise gr.Error(f"DVD worker failed with exit code {proc.returncode}. Check Space logs.")
85
+ log_event(f"generate_voxels done seed={seed}")
86
+ return mesh_path, npy_path, int(seed), f"Done. seed={seed}"
87
+
88
+
89
+ with gr.Blocks(title="DVD Image", fill_width=True) as demo:
90
+ with gr.Row():
91
+ smoke_btn = gr.Button("ZeroGPU Smoke Test")
92
+ smoke_out = gr.Textbox(label="ZeroGPU Status", interactive=False)
93
+ smoke_btn.click(zero_gpu_smoke_test, outputs=smoke_out)
94
+
95
+ with gr.Row(equal_height=False):
96
+ with gr.Column():
97
+ image = gr.Image(label="Input Image", format="png", image_mode="RGBA", type="pil", height=320)
98
+ seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
99
+ randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
100
+ preprocess_image = gr.Checkbox(value=True, label="Preprocess image")
101
+ dvd_steps = gr.Slider(1, 512, value=256, step=1, label="DVD steps")
102
+ gen_btn = gr.Button("Generate DVD Voxels", variant="primary")
103
+ with gr.Column():
104
+ voxel_view = gr.Model3D(label="Generated / Cubified Voxels", height=360, camera_position=(-180, 90, 3))
105
+ npy_download = gr.DownloadButton(label="Download Voxel Coords (.npy)", interactive=False)
106
+ status = gr.Textbox(label="Status", interactive=False)
107
+
108
+ gen_btn.click(
109
+ generate_voxels,
110
+ inputs=[image, seed, randomize_seed, preprocess_image, dvd_steps],
111
+ outputs=[voxel_view, npy_download, seed, status],
112
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[npy_download])
113
+
114
+
115
+ if __name__ == "__main__":
116
+ demo.queue().launch(show_api=False, show_error=True, ssr_mode=False)
app_smoke.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import spaces
4
+
5
+
6
+ def log_event(message: str):
7
+ print(f"[DVD Smoke] {message}", flush=True)
8
+
9
+
10
+ @spaces.GPU(duration=30)
11
+ def zero_gpu_smoke_test():
12
+ log_event("zero_gpu_smoke_test start")
13
+ if not torch.cuda.is_available():
14
+ log_event("cuda unavailable")
15
+ return "CUDA unavailable"
16
+ value = torch.ones((1,), device="cuda").sum().item()
17
+ name = torch.cuda.get_device_name(0)
18
+ log_event(f"zero_gpu_smoke_test done device={name} value={value}")
19
+ return f"OK: {name}, value={value}"
20
+
21
+
22
+ with gr.Blocks(title="ZeroGPU Smoke Test") as demo:
23
+ out = gr.Textbox(label="Status")
24
+ btn = gr.Button("ZeroGPU Smoke Test")
25
+ btn.click(zero_gpu_smoke_test, outputs=out)
26
+
27
+
28
+ if __name__ == "__main__":
29
+ demo.queue().launch(show_api=False, show_error=True, ssr_mode=False)
app_space_image.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import spaces
4
+
5
+ import json
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ import uuid
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+
14
+ os.environ.setdefault("SPCONV_ALGO", "native")
15
+ os.environ.setdefault("ATTN_BACKEND", "flash_attn")
16
+ os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
17
+ os.environ.setdefault("DVD_MODEL_REPO", "Zhengrui/dvd")
18
+
19
+
20
+ MAX_SEED = np.iinfo(np.int32).max
21
+ TMP_DIR = Path(__file__).resolve().parent / "tmp" / "dvd_space_image"
22
+ TMP_DIR.mkdir(parents=True, exist_ok=True)
23
+ IMAGE_EXTENSIONS = {".png", ".jpg", ".jpeg", ".webp"}
24
+ EXAMPLE_DIR = Path(__file__).resolve().parent / "assets" / "example_image"
25
+ GENERATION_IMAGE_EXAMPLES = [
26
+ str(path) for path in sorted(EXAMPLE_DIR.iterdir())
27
+ if path.is_file() and path.suffix.lower() in IMAGE_EXTENSIONS
28
+ ]
29
+
30
+
31
+
32
+ def log_event(message: str):
33
+ print(f"[DVD Space Lean] {message}", flush=True)
34
+
35
+
36
+ def worker_path(name: str) -> str:
37
+ path = TMP_DIR / f"worker-{uuid.uuid4().hex}"
38
+ path.mkdir(parents=True, exist_ok=True)
39
+ return str(path / name)
40
+
41
+
42
+ def cfg_schedule(mode: str, constant: float, early: float, late: float, split: float):
43
+ if mode == "Constant":
44
+ return float(constant)
45
+ if mode == "Two-stage":
46
+ split = float(split)
47
+ early = float(early)
48
+ late = float(late)
49
+ return lambda t: early if t < split else late
50
+ return None
51
+
52
+
53
+ def save_voxel_coords(voxels, path: str) -> str:
54
+ coords = voxels.coords.detach().cpu().numpy().astype(np.int32)
55
+ np.save(path, coords)
56
+ return path
57
+
58
+
59
+
60
+ def get_seed(randomize_seed: bool, seed: int) -> int:
61
+ return int(np.random.randint(0, MAX_SEED)) if randomize_seed else int(seed)
62
+
63
+
64
+
65
+ @spaces.GPU(duration=30)
66
+ def zero_gpu_smoke_test():
67
+ log_event("zero_gpu_smoke_test start")
68
+ if not torch.cuda.is_available():
69
+ log_event("zero_gpu_smoke_test no cuda")
70
+ return "CUDA unavailable inside ZeroGPU worker"
71
+ value = torch.ones((1,), device="cuda").sum().item()
72
+ name = torch.cuda.get_device_name(0)
73
+ log_event(f"zero_gpu_smoke_test done device={name} value={value}")
74
+ return f"OK: {name}, value={value}"
75
+
76
+
77
+ @spaces.GPU(duration=300)
78
+ def generate_voxels(
79
+ image,
80
+ seed: int,
81
+ randomize_seed: bool,
82
+ preprocess_image: bool,
83
+ dvd_steps: int,
84
+ dvd_cfg_mode: str,
85
+ dvd_cfg_constant: float,
86
+ dvd_cfg_early: float,
87
+ dvd_cfg_late: float,
88
+ dvd_cfg_split: float,
89
+ progress=gr.Progress(track_tqdm=True),
90
+ ):
91
+ progress(0.01, desc="Starting ZeroGPU callback")
92
+ log_event(f"generate_voxels start seed={seed} randomize={randomize_seed} steps={dvd_steps}")
93
+ if image is None:
94
+ raise gr.Error("Please provide an image.")
95
+
96
+ seed = get_seed(randomize_seed, seed)
97
+ image_path = worker_path("input.png")
98
+ mesh_path = worker_path("generated_voxels.glb")
99
+ npy_path = worker_path("generated_voxel64_coords.npy")
100
+ config_path = worker_path("generate_config.json")
101
+ image.save(image_path)
102
+ with open(config_path, "w") as f:
103
+ json.dump(
104
+ {
105
+ "image_path": image_path,
106
+ "mesh_path": mesh_path,
107
+ "npy_path": npy_path,
108
+ "seed": int(seed),
109
+ "preprocess_image": bool(preprocess_image),
110
+ "dvd_steps": int(dvd_steps),
111
+ "dvd_cfg_mode": dvd_cfg_mode,
112
+ "dvd_cfg_constant": float(dvd_cfg_constant),
113
+ "dvd_cfg_early": float(dvd_cfg_early),
114
+ "dvd_cfg_late": float(dvd_cfg_late),
115
+ "dvd_cfg_split": float(dvd_cfg_split),
116
+ },
117
+ f,
118
+ )
119
+
120
+ progress(0.08, desc="Running isolated DVD worker")
121
+ env = os.environ.copy()
122
+ env.setdefault("PYTHONUNBUFFERED", "1")
123
+ cmd = [sys.executable, str(Path(__file__).resolve().parent / "space_image_worker.py"), "generate", config_path]
124
+ log_event("starting isolated DVD worker")
125
+ proc = subprocess.run(cmd, env=env, text=True, capture_output=True)
126
+ if proc.stdout:
127
+ print(proc.stdout, flush=True)
128
+ if proc.stderr:
129
+ print(proc.stderr, flush=True)
130
+ if proc.returncode != 0:
131
+ raise gr.Error(f"DVD worker failed with exit code {proc.returncode}. Check Space logs.")
132
+
133
+ progress(0.98, desc="Done")
134
+ log_event(f"generate_voxels done seed={seed} npy={npy_path}")
135
+ return mesh_path, npy_path, seed, f"Done. seed={seed}"
136
+
137
+
138
+ with gr.Blocks(title="DVD Image Generation", fill_width=True) as demo:
139
+ gr.Markdown("## DVD Image Voxel Generation")
140
+ with gr.Row():
141
+ smoke_btn = gr.Button("ZeroGPU Smoke Test")
142
+ smoke_out = gr.Textbox(label="ZeroGPU Status", interactive=False)
143
+ smoke_btn.click(zero_gpu_smoke_test, outputs=smoke_out)
144
+
145
+ with gr.Row(equal_height=False):
146
+ with gr.Column(scale=1):
147
+ image = gr.Image(label="Input Image", format="png", image_mode="RGBA", type="pil", height=320)
148
+ gr.Examples(
149
+ examples=GENERATION_IMAGE_EXAMPLES[:12],
150
+ inputs=image,
151
+ examples_per_page=6,
152
+ )
153
+ with gr.Accordion("DVD Settings", open=False):
154
+ seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
155
+ randomize_seed = gr.Checkbox(value=True, label="Randomize seed")
156
+ preprocess_image = gr.Checkbox(value=True, label="DVD preprocess image")
157
+ dvd_steps = gr.Slider(1, 512, value=128, step=1, label="DVD steps")
158
+ dvd_cfg_mode = gr.Radio(["Default schedule", "Constant", "Two-stage"], value="Default schedule", label="DVD CFG mode")
159
+ dvd_cfg_constant = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="Constant CFG")
160
+ dvd_cfg_early = gr.Slider(0.0, 5.0, value=0.4, step=0.05, label="Early CFG")
161
+ dvd_cfg_late = gr.Slider(0.0, 5.0, value=0.7, step=0.05, label="Late CFG")
162
+ dvd_cfg_split = gr.Slider(0.0, 1.0, value=0.5, step=0.05, label="CFG switch time")
163
+ gen_btn = gr.Button("Generate DVD Voxels", variant="primary")
164
+ with gr.Column(scale=1):
165
+ voxel_view = gr.Model3D(label="Generated / Cubified Voxels", height=360, camera_position=(-180, 90, 3))
166
+ npy_download = gr.DownloadButton(label="Download Voxel Coords (.npy)", interactive=False)
167
+ status = gr.Textbox(label="Status", interactive=False)
168
+
169
+ gen_btn.click(
170
+ generate_voxels,
171
+ inputs=[
172
+ image,
173
+ seed,
174
+ randomize_seed,
175
+ preprocess_image,
176
+ dvd_steps,
177
+ dvd_cfg_mode,
178
+ dvd_cfg_constant,
179
+ dvd_cfg_early,
180
+ dvd_cfg_late,
181
+ dvd_cfg_split,
182
+ ],
183
+ outputs=[voxel_view, npy_download, seed, status],
184
+ ).then(lambda: gr.DownloadButton(interactive=True), outputs=[npy_download])
185
+
186
+
187
+ if __name__ == "__main__":
188
+ demo.queue().launch(show_api=False, show_error=True, ssr_mode=False)
assets/example_image/T.png ADDED

Git LFS Details

  • SHA256: e29ddc83a5bd3a05fe9b34732169bc4ea7131f7c36527fdc5f626a90a73076d2
  • Pointer size: 131 Bytes
  • Size of remote file: 955 kB
assets/example_image/typical_building_building.png ADDED

Git LFS Details

  • SHA256: 8faa11d557be95c000c475247e61a773d511114c7d1e517c04f8d3d88a6049ec
  • Pointer size: 131 Bytes
  • Size of remote file: 547 kB
assets/example_image/typical_building_castle.png ADDED

Git LFS Details

  • SHA256: 076f0554b087b921863643d2b1ab3e0572a13a347fd66bc29cd9d194034affae
  • Pointer size: 131 Bytes
  • Size of remote file: 426 kB
assets/example_image/typical_building_colorful_cottage.png ADDED

Git LFS Details

  • SHA256: 687305b4e35da759692be0de614d728583a2a9cd2fd3a55593fa753e567d0d47
  • Pointer size: 131 Bytes
  • Size of remote file: 609 kB
assets/example_image/typical_building_maya_pyramid.png ADDED

Git LFS Details

  • SHA256: 4d514f7f4db244ee184af4ddfbc5948d417b4e5bf1c6ee5f5a592679561690df
  • Pointer size: 131 Bytes
  • Size of remote file: 232 kB
assets/example_image/typical_building_mushroom.png ADDED

Git LFS Details

  • SHA256: de9b72d3e13e967e70844ddc54643832a84a1b35ca043a11e7c774371d0ccdab
  • Pointer size: 131 Bytes
  • Size of remote file: 488 kB
assets/example_image/typical_building_space_station.png ADDED

Git LFS Details

  • SHA256: 212c7b4c27ba1e01a7908dbc7f245e7115850eadbc9974aa726327cf35062846
  • Pointer size: 131 Bytes
  • Size of remote file: 620 kB
assets/example_image/typical_creature_dragon.png ADDED

Git LFS Details

  • SHA256: 0e8d6720dfa1e7b332b76e897e617b7f0863187f30879451b4724f482c84185a
  • Pointer size: 131 Bytes
  • Size of remote file: 564 kB
assets/example_image/typical_creature_elephant.png ADDED

Git LFS Details

  • SHA256: 86a171e37a3d781e7215977f565cd63e813341c1f89e2c586fa61937e4ed6916
  • Pointer size: 131 Bytes
  • Size of remote file: 482 kB
assets/example_image/typical_creature_furry.png ADDED

Git LFS Details

  • SHA256: 5b5445b8f1996cf6d72497b2d7564c656f4048e6c1fa626fd7bb3ee582fee671
  • Pointer size: 131 Bytes
  • Size of remote file: 648 kB
assets/example_image/typical_creature_quadruped.png ADDED

Git LFS Details

  • SHA256: 7469f43f58389adec101e9685f60188bd4e7fbede77eef975102f6a8865bc786
  • Pointer size: 131 Bytes
  • Size of remote file: 685 kB
assets/example_image/typical_creature_robot_crab.png ADDED

Git LFS Details

  • SHA256: d7e716abe8f8895080f562d1dc26b14fa0e20a05aa5beb2770c6fb3b87b3476a
  • Pointer size: 131 Bytes
  • Size of remote file: 594 kB
assets/example_image/typical_creature_robot_dinosour.png ADDED

Git LFS Details

  • SHA256: d0986f29557a6fddf9b52b5251a6b6103728c61e201b1cfad1e709b090b72f56
  • Pointer size: 131 Bytes
  • Size of remote file: 632 kB
assets/example_image/typical_creature_rock_monster.png ADDED

Git LFS Details

  • SHA256: e29458a6110bee8374c0d4d12471e7167a6c1c98c18f6e2d7ff4f5f0ca3fa01b
  • Pointer size: 131 Bytes
  • Size of remote file: 648 kB
assets/example_image/typical_humanoid_block_robot.png ADDED

Git LFS Details

  • SHA256: 3a0acbb532668e1bf35f3eef5bcbfdd094c22219ef2d837fa01ccf51cce75ca3
  • Pointer size: 131 Bytes
  • Size of remote file: 441 kB
assets/example_image/typical_humanoid_dragonborn.png ADDED

Git LFS Details

  • SHA256: 5d7c547909a6c12da55dbab1c1c98181ff09e58c9ba943682ca105e71be9548e
  • Pointer size: 131 Bytes
  • Size of remote file: 481 kB
assets/example_image/typical_humanoid_dwarf.png ADDED

Git LFS Details

  • SHA256: a4a7c157d5d8071128c27594e45a7a03e5113b3333b7f1c5ff1379481e3e0264
  • Pointer size: 131 Bytes
  • Size of remote file: 498 kB
assets/example_image/typical_humanoid_goblin.png ADDED

Git LFS Details

  • SHA256: 2b0e9a04ae3e7bef44b7180a70306f95374b60727ffa0f6f01fd6c746595cd77
  • Pointer size: 131 Bytes
  • Size of remote file: 496 kB
assets/example_image/typical_humanoid_mech.png ADDED

Git LFS Details

  • SHA256: a244ec54b7984e646e54d433de6897657081dd5b9cd5ccd3d865328d813beb49
  • Pointer size: 131 Bytes
  • Size of remote file: 850 kB
assets/example_image/typical_misc_crate.png ADDED

Git LFS Details

  • SHA256: 59fd9884301faca93265166d90078e8c31e76c7f93524b1db31975df4b450748
  • Pointer size: 131 Bytes
  • Size of remote file: 642 kB
assets/example_image/typical_misc_fireplace.png ADDED

Git LFS Details

  • SHA256: 2288c034603e289192d63cbc73565107caefd99e81c4b7afa2983c8b13e34440
  • Pointer size: 131 Bytes
  • Size of remote file: 558 kB
assets/example_image/typical_misc_gate.png ADDED

Git LFS Details

  • SHA256: ec8db5389b74fe56b826e3c6d860234541033387350e09268591c46d411cc8e9
  • Pointer size: 131 Bytes
  • Size of remote file: 572 kB
assets/example_image/typical_misc_lantern.png ADDED

Git LFS Details

  • SHA256: e17bd83adf433ebfca17abd220097b2b7f08affc649518bd7822e03797e83d41
  • Pointer size: 131 Bytes
  • Size of remote file: 300 kB
assets/example_image/typical_misc_magicbook.png ADDED

Git LFS Details

  • SHA256: aff9c14589c340e31b61bf82e4506d77d72c511e741260fa1e600cefa4e103e6
  • Pointer size: 131 Bytes
  • Size of remote file: 496 kB
assets/example_image/typical_misc_mailbox.png ADDED

Git LFS Details

  • SHA256: 01e86a5d68edafb7e11d7a86f7e8081f5ed1b02578198a3271554c5fb8fb9fcf
  • Pointer size: 131 Bytes
  • Size of remote file: 631 kB
assets/example_image/typical_misc_monster_chest.png ADDED

Git LFS Details

  • SHA256: c57a598e842225a31b9770bf3bbb9ae86197ec57d0c2883caf8cb5eed4908fbc
  • Pointer size: 131 Bytes
  • Size of remote file: 690 kB
assets/example_image/typical_misc_paper_machine.png ADDED

Git LFS Details

  • SHA256: 2d55400ae5d4df2377258400d800ece75766d5274e80ce07c3b29a4d1fd1fa36
  • Pointer size: 131 Bytes
  • Size of remote file: 614 kB
assets/example_image/typical_misc_phonograph.png ADDED

Git LFS Details

  • SHA256: 14fff9a27ea769d3ca711e9ff55ab3d9385486a5e8b99117f506df326a0a357e
  • Pointer size: 131 Bytes
  • Size of remote file: 517 kB
assets/example_image/typical_misc_portal2.png ADDED

Git LFS Details

  • SHA256: 57aab2bba56bc946523a3fca77ca70651a4ad8c6fbf1b91a1a824418df48faae
  • Pointer size: 131 Bytes
  • Size of remote file: 386 kB
assets/example_image/typical_misc_storage_chest.png ADDED

Git LFS Details

  • SHA256: 0e4ac1c67fdda902ecb709447b8defd949c738954c844c1b8364b8e3f7d9e55a
  • Pointer size: 131 Bytes
  • Size of remote file: 632 kB
assets/example_image/typical_misc_telephone.png ADDED

Git LFS Details

  • SHA256: 00048be46234a2709c12614b04cbad61c6e3c7e63c2a4ef33d999185f5393e36
  • Pointer size: 131 Bytes
  • Size of remote file: 648 kB
assets/example_image/typical_misc_television.png ADDED

Git LFS Details

  • SHA256: 6a1947b737398bf535ec212668a4d78cd38fe84cf9da1ccd6c0c0d838337755e
  • Pointer size: 131 Bytes
  • Size of remote file: 627 kB
assets/example_image/typical_misc_workbench.png ADDED

Git LFS Details

  • SHA256: a6d9ed4d005a5253b8571fd976b0d102e293512d7b5a8ed5e3f7f17c5f4e19da
  • Pointer size: 131 Bytes
  • Size of remote file: 463 kB
assets/example_image/typical_vehicle_biplane.png ADDED

Git LFS Details

  • SHA256: c73e98112eb603b4ba635b8965cad7807d0588f083811bc2faa0c7ab9668a65a
  • Pointer size: 131 Bytes
  • Size of remote file: 574 kB
assets/example_image/typical_vehicle_bulldozer.png ADDED

Git LFS Details

  • SHA256: 23d821b4daea61cbea28cc6ddd3ae46712514dfcdff995c2664f5a70d21f4ef3
  • Pointer size: 131 Bytes
  • Size of remote file: 693 kB
assets/example_image/typical_vehicle_cart.png ADDED

Git LFS Details

  • SHA256: b72c04a2aa5cf57717c05151a2982d6dc31afde130d5e830adf37a84a70616cb
  • Pointer size: 131 Bytes
  • Size of remote file: 693 kB
assets/example_image/typical_vehicle_excavator.png ADDED

Git LFS Details

  • SHA256: 27a418853eefa197f1e10ed944a7bb071413fd2bc1681804ee773a6ce3799c52
  • Pointer size: 131 Bytes
  • Size of remote file: 712 kB