Upload full text Space sources
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +51 -0
- .gitignore +418 -0
- LICENSE +21 -0
- NOTICE +22 -0
- THIRD_PARTY_LICENSES/LICENSE_DUO.txt +201 -0
- app.py +53 -0
- app_dvd_image.py +1160 -0
- app_dvd_image_gpu_lazy.py +283 -0
- app_dvd_image_trellis_style.py +202 -0
- app_dvd_image_wrapper.py +187 -0
- app_image_min.py +116 -0
- app_smoke.py +29 -0
- app_space_image.py +188 -0
- assets/example_image/T.png +3 -0
- assets/example_image/typical_building_building.png +3 -0
- assets/example_image/typical_building_castle.png +3 -0
- assets/example_image/typical_building_colorful_cottage.png +3 -0
- assets/example_image/typical_building_maya_pyramid.png +3 -0
- assets/example_image/typical_building_mushroom.png +3 -0
- assets/example_image/typical_building_space_station.png +3 -0
- assets/example_image/typical_creature_dragon.png +3 -0
- assets/example_image/typical_creature_elephant.png +3 -0
- assets/example_image/typical_creature_furry.png +3 -0
- assets/example_image/typical_creature_quadruped.png +3 -0
- assets/example_image/typical_creature_robot_crab.png +3 -0
- assets/example_image/typical_creature_robot_dinosour.png +3 -0
- assets/example_image/typical_creature_rock_monster.png +3 -0
- assets/example_image/typical_humanoid_block_robot.png +3 -0
- assets/example_image/typical_humanoid_dragonborn.png +3 -0
- assets/example_image/typical_humanoid_dwarf.png +3 -0
- assets/example_image/typical_humanoid_goblin.png +3 -0
- assets/example_image/typical_humanoid_mech.png +3 -0
- assets/example_image/typical_misc_crate.png +3 -0
- assets/example_image/typical_misc_fireplace.png +3 -0
- assets/example_image/typical_misc_gate.png +3 -0
- assets/example_image/typical_misc_lantern.png +3 -0
- assets/example_image/typical_misc_magicbook.png +3 -0
- assets/example_image/typical_misc_mailbox.png +3 -0
- assets/example_image/typical_misc_monster_chest.png +3 -0
- assets/example_image/typical_misc_paper_machine.png +3 -0
- assets/example_image/typical_misc_phonograph.png +3 -0
- assets/example_image/typical_misc_portal2.png +3 -0
- assets/example_image/typical_misc_storage_chest.png +3 -0
- assets/example_image/typical_misc_telephone.png +3 -0
- assets/example_image/typical_misc_television.png +3 -0
- assets/example_image/typical_misc_workbench.png +3 -0
- assets/example_image/typical_vehicle_biplane.png +3 -0
- assets/example_image/typical_vehicle_bulldozer.png +3 -0
- assets/example_image/typical_vehicle_cart.png +3 -0
- 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
|
assets/example_image/typical_building_building.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_building_castle.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_building_colorful_cottage.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_building_maya_pyramid.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_building_mushroom.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_building_space_station.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_creature_dragon.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_creature_elephant.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_creature_furry.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_creature_quadruped.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_creature_robot_crab.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_creature_robot_dinosour.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_creature_rock_monster.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_humanoid_block_robot.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_humanoid_dragonborn.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_humanoid_dwarf.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_humanoid_goblin.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_humanoid_mech.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_crate.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_fireplace.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_gate.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_lantern.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_magicbook.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_mailbox.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_monster_chest.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_paper_machine.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_phonograph.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_portal2.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_storage_chest.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_telephone.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_television.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_misc_workbench.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_vehicle_biplane.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_vehicle_bulldozer.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_vehicle_cart.png
ADDED
|
Git LFS Details
|
assets/example_image/typical_vehicle_excavator.png
ADDED
|
Git LFS Details
|