Spaces:
Running
on
Zero
Running
on
Zero
Justin Means
commited on
Commit
Β·
0d62c57
1
Parent(s):
cfa6140
Add PLY download and interactive viewer for 3DGS
Browse filesMinimal additions to working Depth Anything 3 space:
- Add PLY file download button for Gaussian splats
- Add viewer section with SuperSplat link for WebXR
- Keep all existing functionality (video rendering, examples, etc.)
- WebXR ready for Quest and Vision Pro
README.md
CHANGED
|
@@ -1,17 +1,25 @@
|
|
| 1 |
---
|
| 2 |
-
title: Depth Anything 3
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.49.1
|
| 8 |
app_file: app.py
|
| 9 |
python_version: 3.11
|
| 10 |
-
pinned:
|
| 11 |
-
license:
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
## Citation
|
| 17 |
|
|
@@ -20,7 +28,7 @@ If you find Depth Anything 3 useful in your research or projects, please cite:
|
|
| 20 |
```bibtex
|
| 21 |
@article{depthanything3,
|
| 22 |
title={Depth Anything 3: Recovering the visual space from any views},
|
| 23 |
-
author={Haotong Lin and Sili Chen and Jun Hao Liew and Donny Y. Chen and Zhenyu Li and Guang Shi and Jiashi Feng and Bingyi Kang},
|
| 24 |
journal={arXiv preprint arXiv:2511.10647},
|
| 25 |
year={2025}
|
| 26 |
```
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Depth Anything 3 + Interactive 3DGS Viewer
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 5.49.1
|
| 8 |
app_file: app.py
|
| 9 |
python_version: 3.11
|
| 10 |
+
pinned: true
|
| 11 |
+
license: apache-2.0
|
| 12 |
+
short_description: Interactive 3D Gaussian Splats with WebXR
|
| 13 |
---
|
| 14 |
|
| 15 |
+
# Depth Anything 3 + Interactive 3DGS Viewer
|
| 16 |
+
|
| 17 |
+
Generate depth maps and **interactive 3D Gaussian Splats** from images/video. Features PLY download and **WebXR support** for Vision Pro and Quest via SuperSplat.
|
| 18 |
+
|
| 19 |
+
## Features
|
| 20 |
+
- Full Depth Anything 3 reconstruction
|
| 21 |
+
- Download PLY files for SuperSplat, Blender, etc.
|
| 22 |
+
- WebXR ready for VR/AR viewing
|
| 23 |
|
| 24 |
## Citation
|
| 25 |
|
|
|
|
| 28 |
```bibtex
|
| 29 |
@article{depthanything3,
|
| 30 |
title={Depth Anything 3: Recovering the visual space from any views},
|
| 31 |
+
author={Haotong Lin and Sili Chen and Jun Hao Liew and Donny Y. Chen and Zhenyu Li and Guang Shi and Jiashi Feng and Bingyi Kang},
|
| 32 |
journal={arXiv preprint arXiv:2511.10647},
|
| 33 |
year={2025}
|
| 34 |
```
|
depth_anything_3/app/gradio_app.py
CHANGED
|
@@ -223,7 +223,7 @@ class DepthAnything3App:
|
|
| 223 |
) = self.ui_components.create_measure_section()
|
| 224 |
|
| 225 |
with gr.Tab("3DGS Rendered Novel Views"):
|
| 226 |
-
gs_video, gs_info = self.ui_components.create_nvs_video()
|
| 227 |
|
| 228 |
# Inference control section (before inference)
|
| 229 |
(process_res_method_dropdown, infer_gs) = (
|
|
@@ -250,9 +250,11 @@ class DepthAnything3App:
|
|
| 250 |
gr.update(visible=checked),
|
| 251 |
gr.update(visible=checked),
|
| 252 |
gr.update(visible=(not checked)),
|
|
|
|
|
|
|
| 253 |
),
|
| 254 |
inputs=infer_gs,
|
| 255 |
-
outputs=[gs_trj_mode, gs_video_quality, gs_video, gs_info],
|
| 256 |
)
|
| 257 |
|
| 258 |
# Example scenes section
|
|
@@ -296,6 +298,8 @@ class DepthAnything3App:
|
|
| 296 |
scene_components,
|
| 297 |
gs_video,
|
| 298 |
gs_info,
|
|
|
|
|
|
|
| 299 |
gs_trj_mode,
|
| 300 |
gs_video_quality,
|
| 301 |
)
|
|
@@ -340,6 +344,8 @@ class DepthAnything3App:
|
|
| 340 |
scene_components: List[gr.Image],
|
| 341 |
gs_video: gr.Video,
|
| 342 |
gs_info: gr.Markdown,
|
|
|
|
|
|
|
| 343 |
gs_trj_mode: gr.Dropdown,
|
| 344 |
gs_video_quality: gr.Dropdown,
|
| 345 |
) -> None:
|
|
@@ -393,6 +399,8 @@ class DepthAnything3App:
|
|
| 393 |
gs_video,
|
| 394 |
gs_video, # gs_video visibility
|
| 395 |
gs_info, # gs_info visibility
|
|
|
|
|
|
|
| 396 |
],
|
| 397 |
).then(
|
| 398 |
fn=lambda: "False",
|
|
@@ -476,6 +484,8 @@ class DepthAnything3App:
|
|
| 476 |
measure_depth_image,
|
| 477 |
gs_video,
|
| 478 |
gs_info,
|
|
|
|
|
|
|
| 479 |
)
|
| 480 |
|
| 481 |
def _setup_visualization_handlers(
|
|
@@ -573,6 +583,8 @@ class DepthAnything3App:
|
|
| 573 |
measure_depth_image: gr.Image,
|
| 574 |
gs_video: gr.Video,
|
| 575 |
gs_info: gr.Markdown,
|
|
|
|
|
|
|
| 576 |
) -> None:
|
| 577 |
"""Set up example scene handlers."""
|
| 578 |
|
|
@@ -624,6 +636,8 @@ class DepthAnything3App:
|
|
| 624 |
gs_video,
|
| 625 |
gs_video, # gs_video_visibility
|
| 626 |
gs_info, # gs_info_visibility
|
|
|
|
|
|
|
| 627 |
is_example,
|
| 628 |
measure_image,
|
| 629 |
measure_depth_image,
|
|
|
|
| 223 |
) = self.ui_components.create_measure_section()
|
| 224 |
|
| 225 |
with gr.Tab("3DGS Rendered Novel Views"):
|
| 226 |
+
gs_video, gs_info, gs_viewer, gs_ply_download = self.ui_components.create_nvs_video()
|
| 227 |
|
| 228 |
# Inference control section (before inference)
|
| 229 |
(process_res_method_dropdown, infer_gs) = (
|
|
|
|
| 250 |
gr.update(visible=checked),
|
| 251 |
gr.update(visible=checked),
|
| 252 |
gr.update(visible=(not checked)),
|
| 253 |
+
gr.update(visible=False), # gs_viewer hidden initially
|
| 254 |
+
gr.update(visible=False), # gs_ply_download hidden initially
|
| 255 |
),
|
| 256 |
inputs=infer_gs,
|
| 257 |
+
outputs=[gs_trj_mode, gs_video_quality, gs_video, gs_info, gs_viewer, gs_ply_download],
|
| 258 |
)
|
| 259 |
|
| 260 |
# Example scenes section
|
|
|
|
| 298 |
scene_components,
|
| 299 |
gs_video,
|
| 300 |
gs_info,
|
| 301 |
+
gs_viewer,
|
| 302 |
+
gs_ply_download,
|
| 303 |
gs_trj_mode,
|
| 304 |
gs_video_quality,
|
| 305 |
)
|
|
|
|
| 344 |
scene_components: List[gr.Image],
|
| 345 |
gs_video: gr.Video,
|
| 346 |
gs_info: gr.Markdown,
|
| 347 |
+
gs_viewer: gr.HTML,
|
| 348 |
+
gs_ply_download: gr.File,
|
| 349 |
gs_trj_mode: gr.Dropdown,
|
| 350 |
gs_video_quality: gr.Dropdown,
|
| 351 |
) -> None:
|
|
|
|
| 399 |
gs_video,
|
| 400 |
gs_video, # gs_video visibility
|
| 401 |
gs_info, # gs_info visibility
|
| 402 |
+
gs_viewer, # interactive viewer HTML
|
| 403 |
+
gs_ply_download, # PLY download file
|
| 404 |
],
|
| 405 |
).then(
|
| 406 |
fn=lambda: "False",
|
|
|
|
| 484 |
measure_depth_image,
|
| 485 |
gs_video,
|
| 486 |
gs_info,
|
| 487 |
+
gs_viewer,
|
| 488 |
+
gs_ply_download,
|
| 489 |
)
|
| 490 |
|
| 491 |
def _setup_visualization_handlers(
|
|
|
|
| 583 |
measure_depth_image: gr.Image,
|
| 584 |
gs_video: gr.Video,
|
| 585 |
gs_info: gr.Markdown,
|
| 586 |
+
gs_viewer: gr.HTML,
|
| 587 |
+
gs_ply_download: gr.File,
|
| 588 |
) -> None:
|
| 589 |
"""Set up example scene handlers."""
|
| 590 |
|
|
|
|
| 636 |
gs_video,
|
| 637 |
gs_video, # gs_video_visibility
|
| 638 |
gs_info, # gs_info_visibility
|
| 639 |
+
gs_viewer, # gs_viewer HTML
|
| 640 |
+
gs_ply_download, # gs_ply file
|
| 641 |
is_example,
|
| 642 |
measure_image,
|
| 643 |
measure_depth_image,
|
depth_anything_3/app/modules/event_handlers.py
CHANGED
|
@@ -156,6 +156,8 @@ class EventHandlers:
|
|
| 156 |
Optional[str], # gs video path
|
| 157 |
gr.update, # gs video visibility update
|
| 158 |
gr.update, # gs info visibility update
|
|
|
|
|
|
|
| 159 |
]:
|
| 160 |
"""
|
| 161 |
Perform reconstruction using the already-created target_dir/images.
|
|
@@ -184,6 +186,8 @@ class EventHandlers:
|
|
| 184 |
None,
|
| 185 |
gr.update(visible=False), # gs_video
|
| 186 |
gr.update(visible=True), # gs_info
|
|
|
|
|
|
|
| 187 |
)
|
| 188 |
|
| 189 |
start_time = time.time()
|
|
@@ -226,10 +230,14 @@ class EventHandlers:
|
|
| 226 |
# The GLB file is already generated by the API
|
| 227 |
glbfile = os.path.join(target_dir, "scene.glb")
|
| 228 |
|
| 229 |
-
# Handle 3DGS video based on infer_gs flag
|
| 230 |
gsvideo_path = None
|
| 231 |
gs_video_visible = False
|
| 232 |
gs_info_visible = True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
if infer_gs:
|
| 235 |
try:
|
|
@@ -240,6 +248,26 @@ class EventHandlers:
|
|
| 240 |
gsvideo_path = None
|
| 241 |
print("3DGS video not found, but infer_gs was enabled")
|
| 242 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
# Cleanup
|
| 244 |
cleanup_memory()
|
| 245 |
|
|
@@ -268,6 +296,8 @@ class EventHandlers:
|
|
| 268 |
gsvideo_path,
|
| 269 |
gr.update(visible=gs_video_visible), # gs_video visibility
|
| 270 |
gr.update(visible=gs_info_visible), # gs_info visibility
|
|
|
|
|
|
|
| 271 |
)
|
| 272 |
|
| 273 |
def update_visualization(
|
|
@@ -367,6 +397,8 @@ class EventHandlers:
|
|
| 367 |
Optional[str],
|
| 368 |
gr.update,
|
| 369 |
gr.update,
|
|
|
|
|
|
|
| 370 |
]:
|
| 371 |
"""
|
| 372 |
Load a scene from examples directory.
|
|
@@ -376,7 +408,7 @@ class EventHandlers:
|
|
| 376 |
examples_dir: Path to examples directory (if None, uses workspace_dir/examples)
|
| 377 |
|
| 378 |
Returns:
|
| 379 |
-
Tuple of (reconstruction_output, target_dir, image_paths, log_message, processed_data, measure_view_selector, gs_video, gs_video_vis, gs_info_vis) # noqa: E501
|
| 380 |
"""
|
| 381 |
if examples_dir is None:
|
| 382 |
# Get workspace directory from environment variable
|
|
@@ -393,6 +425,10 @@ class EventHandlers:
|
|
| 393 |
gs_video_path = None
|
| 394 |
gs_video_visible = False
|
| 395 |
gs_info_visible = True
|
|
|
|
|
|
|
|
|
|
|
|
|
| 396 |
|
| 397 |
if target_dir and target_dir != "None":
|
| 398 |
predictions_path = os.path.join(target_dir, "predictions.npz")
|
|
@@ -444,6 +480,32 @@ class EventHandlers:
|
|
| 444 |
except Exception as e:
|
| 445 |
print(f"Error loading cached 3DGS video: {e}")
|
| 446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
return (
|
| 448 |
reconstruction_output,
|
| 449 |
target_dir,
|
|
@@ -454,6 +516,8 @@ class EventHandlers:
|
|
| 454 |
gs_video_path,
|
| 455 |
gr.update(visible=gs_video_visible),
|
| 456 |
gr.update(visible=gs_info_visible),
|
|
|
|
|
|
|
| 457 |
)
|
| 458 |
|
| 459 |
def navigate_depth_view(
|
|
|
|
| 156 |
Optional[str], # gs video path
|
| 157 |
gr.update, # gs video visibility update
|
| 158 |
gr.update, # gs info visibility update
|
| 159 |
+
gr.update, # gs_viewer HTML update
|
| 160 |
+
gr.update, # gs_ply_download file update
|
| 161 |
]:
|
| 162 |
"""
|
| 163 |
Perform reconstruction using the already-created target_dir/images.
|
|
|
|
| 186 |
None,
|
| 187 |
gr.update(visible=False), # gs_video
|
| 188 |
gr.update(visible=True), # gs_info
|
| 189 |
+
gr.update(visible=False), # gs_viewer
|
| 190 |
+
gr.update(visible=False), # gs_ply_download
|
| 191 |
)
|
| 192 |
|
| 193 |
start_time = time.time()
|
|
|
|
| 230 |
# The GLB file is already generated by the API
|
| 231 |
glbfile = os.path.join(target_dir, "scene.glb")
|
| 232 |
|
| 233 |
+
# Handle 3DGS video and PLY based on infer_gs flag
|
| 234 |
gsvideo_path = None
|
| 235 |
gs_video_visible = False
|
| 236 |
gs_info_visible = True
|
| 237 |
+
gs_ply_path = None
|
| 238 |
+
gs_ply_visible = False
|
| 239 |
+
gs_viewer_visible = False
|
| 240 |
+
gs_viewer_html = ""
|
| 241 |
|
| 242 |
if infer_gs:
|
| 243 |
try:
|
|
|
|
| 248 |
gsvideo_path = None
|
| 249 |
print("3DGS video not found, but infer_gs was enabled")
|
| 250 |
|
| 251 |
+
# Check for PLY file
|
| 252 |
+
try:
|
| 253 |
+
gs_ply_path = sorted(glob(os.path.join(target_dir, "gs_ply", "*.ply")))[-1]
|
| 254 |
+
gs_ply_visible = True
|
| 255 |
+
gs_viewer_visible = True
|
| 256 |
+
gs_viewer_html = """
|
| 257 |
+
<div style="width:100%; padding:20px; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
| 258 |
+
border-radius:12px; margin-top:10px; text-align:center;">
|
| 259 |
+
<h3 style="color:#fff; margin-bottom:15px;">π₯½ Interactive 3D Gaussian Splat Viewer</h3>
|
| 260 |
+
<p style="color:#aaa; margin-bottom:15px;">
|
| 261 |
+
Download the PLY file below and open it in
|
| 262 |
+
<a href="https://playcanvas.com/supersplat/editor" target="_blank" style="color:#4da6ff;">SuperSplat Editor</a>
|
| 263 |
+
for an interactive WebXR experience on Quest or Vision Pro!
|
| 264 |
+
</p>
|
| 265 |
+
</div>
|
| 266 |
+
"""
|
| 267 |
+
except IndexError:
|
| 268 |
+
gs_ply_path = None
|
| 269 |
+
print("3DGS PLY file not found")
|
| 270 |
+
|
| 271 |
# Cleanup
|
| 272 |
cleanup_memory()
|
| 273 |
|
|
|
|
| 296 |
gsvideo_path,
|
| 297 |
gr.update(visible=gs_video_visible), # gs_video visibility
|
| 298 |
gr.update(visible=gs_info_visible), # gs_info visibility
|
| 299 |
+
gr.update(value=gs_viewer_html, visible=gs_viewer_visible), # gs_viewer
|
| 300 |
+
gr.update(value=gs_ply_path, visible=gs_ply_visible), # gs_ply_download
|
| 301 |
)
|
| 302 |
|
| 303 |
def update_visualization(
|
|
|
|
| 397 |
Optional[str],
|
| 398 |
gr.update,
|
| 399 |
gr.update,
|
| 400 |
+
gr.update, # gs_viewer HTML
|
| 401 |
+
gr.update, # gs_ply_download file
|
| 402 |
]:
|
| 403 |
"""
|
| 404 |
Load a scene from examples directory.
|
|
|
|
| 408 |
examples_dir: Path to examples directory (if None, uses workspace_dir/examples)
|
| 409 |
|
| 410 |
Returns:
|
| 411 |
+
Tuple of (reconstruction_output, target_dir, image_paths, log_message, processed_data, measure_view_selector, gs_video, gs_video_vis, gs_info_vis, gs_viewer, gs_ply) # noqa: E501
|
| 412 |
"""
|
| 413 |
if examples_dir is None:
|
| 414 |
# Get workspace directory from environment variable
|
|
|
|
| 425 |
gs_video_path = None
|
| 426 |
gs_video_visible = False
|
| 427 |
gs_info_visible = True
|
| 428 |
+
gs_ply_path = None
|
| 429 |
+
gs_ply_visible = False
|
| 430 |
+
gs_viewer_visible = False
|
| 431 |
+
gs_viewer_html = ""
|
| 432 |
|
| 433 |
if target_dir and target_dir != "None":
|
| 434 |
predictions_path = os.path.join(target_dir, "predictions.npz")
|
|
|
|
| 480 |
except Exception as e:
|
| 481 |
print(f"Error loading cached 3DGS video: {e}")
|
| 482 |
|
| 483 |
+
# Check for cached 3DGS PLY file
|
| 484 |
+
gs_ply_dir = os.path.join(target_dir, "gs_ply")
|
| 485 |
+
if os.path.exists(gs_ply_dir):
|
| 486 |
+
try:
|
| 487 |
+
from glob import glob
|
| 488 |
+
|
| 489 |
+
gs_plys = sorted(glob(os.path.join(gs_ply_dir, "*.ply")))
|
| 490 |
+
if gs_plys:
|
| 491 |
+
gs_ply_path = gs_plys[-1]
|
| 492 |
+
gs_ply_visible = True
|
| 493 |
+
gs_viewer_visible = True
|
| 494 |
+
gs_viewer_html = """
|
| 495 |
+
<div style="width:100%; padding:20px; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
| 496 |
+
border-radius:12px; margin-top:10px; text-align:center;">
|
| 497 |
+
<h3 style="color:#fff; margin-bottom:15px;">π₯½ Interactive 3D Gaussian Splat Viewer</h3>
|
| 498 |
+
<p style="color:#aaa; margin-bottom:15px;">
|
| 499 |
+
Download the PLY file below and open it in
|
| 500 |
+
<a href="https://playcanvas.com/supersplat/editor" target="_blank" style="color:#4da6ff;">SuperSplat Editor</a>
|
| 501 |
+
for an interactive WebXR experience on Quest or Vision Pro!
|
| 502 |
+
</p>
|
| 503 |
+
</div>
|
| 504 |
+
"""
|
| 505 |
+
print(f"Loaded cached 3DGS PLY: {gs_ply_path}")
|
| 506 |
+
except Exception as e:
|
| 507 |
+
print(f"Error loading cached 3DGS PLY: {e}")
|
| 508 |
+
|
| 509 |
return (
|
| 510 |
reconstruction_output,
|
| 511 |
target_dir,
|
|
|
|
| 516 |
gs_video_path,
|
| 517 |
gr.update(visible=gs_video_visible),
|
| 518 |
gr.update(visible=gs_info_visible),
|
| 519 |
+
gr.update(value=gs_viewer_html, visible=gs_viewer_visible),
|
| 520 |
+
gr.update(value=gs_ply_path, visible=gs_ply_visible),
|
| 521 |
)
|
| 522 |
|
| 523 |
def navigate_depth_view(
|
depth_anything_3/app/modules/ui_components.py
CHANGED
|
@@ -83,12 +83,12 @@ class UIComponents:
|
|
| 83 |
elem_id="reconstruction_3d_viewer",
|
| 84 |
)
|
| 85 |
|
| 86 |
-
def create_nvs_video(self) -> Tuple[gr.Video, gr.Markdown]:
|
| 87 |
"""
|
| 88 |
-
Create the 3DGS rendered video display component and
|
| 89 |
|
| 90 |
Returns:
|
| 91 |
-
Tuple of (video component, info message
|
| 92 |
"""
|
| 93 |
with gr.Column():
|
| 94 |
gs_info = gr.Markdown(
|
|
@@ -99,7 +99,8 @@ class UIComponents:
|
|
| 99 |
"Next, in **Visualization Options**, "
|
| 100 |
"*optionally* configure the **rendering trajectory** (default: smooth) "
|
| 101 |
"and **video quality** (default: low), "
|
| 102 |
-
"then click **Reconstruct
|
|
|
|
| 103 |
),
|
| 104 |
visible=True,
|
| 105 |
height=520,
|
|
@@ -110,7 +111,40 @@ class UIComponents:
|
|
| 110 |
interactive=False,
|
| 111 |
visible=False,
|
| 112 |
)
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
|
| 115 |
def create_depth_section(self) -> Tuple[gr.Button, gr.Dropdown, gr.Button, gr.Image]:
|
| 116 |
"""
|
|
|
|
| 83 |
elem_id="reconstruction_3d_viewer",
|
| 84 |
)
|
| 85 |
|
| 86 |
+
def create_nvs_video(self) -> Tuple[gr.Video, gr.Markdown, gr.HTML, gr.File]:
|
| 87 |
"""
|
| 88 |
+
Create the 3DGS rendered video display component, info message, interactive viewer, and PLY download.
|
| 89 |
|
| 90 |
Returns:
|
| 91 |
+
Tuple of (video component, info message, interactive viewer HTML, PLY download)
|
| 92 |
"""
|
| 93 |
with gr.Column():
|
| 94 |
gs_info = gr.Markdown(
|
|
|
|
| 99 |
"Next, in **Visualization Options**, "
|
| 100 |
"*optionally* configure the **rendering trajectory** (default: smooth) "
|
| 101 |
"and **video quality** (default: low), "
|
| 102 |
+
"then click **Reconstruct**.<br><br>"
|
| 103 |
+
"π₯½ **WebXR Ready** - After reconstruction, use the interactive viewer below for VR on Quest or Vision Pro!"
|
| 104 |
),
|
| 105 |
visible=True,
|
| 106 |
height=520,
|
|
|
|
| 111 |
interactive=False,
|
| 112 |
visible=False,
|
| 113 |
)
|
| 114 |
+
gs_viewer = gr.HTML(
|
| 115 |
+
value=self._get_empty_viewer_html(),
|
| 116 |
+
visible=False,
|
| 117 |
+
label="Interactive 3DGS Viewer",
|
| 118 |
+
)
|
| 119 |
+
gs_ply_download = gr.File(
|
| 120 |
+
label="Download Gaussian Splat (.ply) - Use in SuperSplat, Blender, etc.",
|
| 121 |
+
visible=False,
|
| 122 |
+
interactive=False,
|
| 123 |
+
)
|
| 124 |
+
return gs_video, gs_info, gs_viewer, gs_ply_download
|
| 125 |
+
|
| 126 |
+
def _get_empty_viewer_html(self) -> str:
|
| 127 |
+
"""Return placeholder HTML for empty viewer state."""
|
| 128 |
+
return """
|
| 129 |
+
<div style="width:100%; height:400px; display:flex; align-items:center; justify-content:center;
|
| 130 |
+
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); border-radius:12px; margin-top:10px;">
|
| 131 |
+
<p style="color:#888; font-size:1.1em;">Interactive 3DGS Viewer will appear here after reconstruction</p>
|
| 132 |
+
</div>
|
| 133 |
+
"""
|
| 134 |
+
|
| 135 |
+
def get_viewer_html_with_instructions(self) -> str:
|
| 136 |
+
"""Return HTML with instructions for using SuperSplat viewer."""
|
| 137 |
+
return """
|
| 138 |
+
<div style="width:100%; padding:20px; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
| 139 |
+
border-radius:12px; margin-top:10px; text-align:center;">
|
| 140 |
+
<h3 style="color:#fff; margin-bottom:15px;">π₯½ Interactive 3D Gaussian Splat Viewer</h3>
|
| 141 |
+
<p style="color:#aaa; margin-bottom:15px;">
|
| 142 |
+
Download the PLY file below and open it in
|
| 143 |
+
<a href="https://playcanvas.com/supersplat/editor" target="_blank" style="color:#4da6ff;">SuperSplat Editor</a>
|
| 144 |
+
for an interactive WebXR experience on Quest or Vision Pro!
|
| 145 |
+
</p>
|
| 146 |
+
</div>
|
| 147 |
+
"""
|
| 148 |
|
| 149 |
def create_depth_section(self) -> Tuple[gr.Button, gr.Dropdown, gr.Button, gr.Image]:
|
| 150 |
"""
|