weijielyu commited on
Commit
9b72c9a
·
1 Parent(s): 343399b
Files changed (1) hide show
  1. app.py +3 -159
app.py CHANGED
@@ -46,12 +46,6 @@ import sys
46
  OUTPUTS_DIR = Path.cwd() / "outputs"
47
  OUTPUTS_DIR.mkdir(exist_ok=True)
48
 
49
- # Copy viewer.js to outputs so it can be served as a static file
50
- VIEWER_JS_SRC = Path(__file__).parent / "viewer.js"
51
- if VIEWER_JS_SRC.exists():
52
- shutil.copy2(VIEWER_JS_SRC, OUTPUTS_DIR / "viewer.js")
53
- print(f"✓ Copied viewer.js to {OUTPUTS_DIR / 'viewer.js'}")
54
-
55
  # -----------------------------
56
  # Ensure diff-gaussian-rasterization builds for current GPU
57
  # -----------------------------
@@ -340,136 +334,6 @@ class FaceLiftPipeline:
340
  print(f"Error details:\n{error_details}")
341
  raise gr.Error(f"Generation failed: {str(e)}")
342
 
343
- # Create viewer HTML file that can be served by Gradio
344
- def create_splat_viewer_html(ply_url: str, viewer_js_url: str, viewer_id: str) -> str:
345
- """Create a standalone HTML file with embedded viewer for the PLY file."""
346
-
347
- # Read viewer.js content
348
- viewer_js_path = Path(__file__).parent / "viewer.js"
349
- viewer_js_content = viewer_js_path.read_text() if viewer_js_path.exists() else "console.error('viewer.js not found');"
350
-
351
- # Create HTML file in outputs directory
352
- output_dir = Path(ply_url.replace("/file=", "").rsplit("/", 1)[0])
353
- viewer_html_path = output_dir / f"viewer_{viewer_id}.html"
354
-
355
- html_content = f"""<!DOCTYPE html>
356
- <html>
357
- <head>
358
- <meta charset="utf-8">
359
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
360
- <title>3D Gaussian Splat Viewer</title>
361
- <style>
362
- body {{ margin: 0; overflow: hidden; background: #000; }}
363
- #canvas {{ width: 100vw; height: 100vh; display: block; }}
364
- #spinner {{
365
- position: absolute; top: 50%; left: 50%;
366
- transform: translate(-50%, -50%);
367
- color: white; font-family: Arial; z-index: 10;
368
- text-align: center; background: rgba(0,0,0,0.8);
369
- padding: 20px; border-radius: 8px;
370
- }}
371
- #progress {{ background: #4CAF50; height: 4px; width: 0%; transition: width 0.3s; }}
372
- #message {{
373
- position: absolute; top: 50%; left: 50%;
374
- transform: translate(-50%, -50%);
375
- color: #ff4444; font-family: Arial; font-size: 14px;
376
- background: rgba(0,0,0,0.9); padding: 20px;
377
- border-radius: 8px; display: none; z-index: 11;
378
- }}
379
- #fps, #camid {{
380
- position: absolute; right: 10px;
381
- color: white; font-family: monospace; font-size: 11px;
382
- background: rgba(0,0,0,0.7); padding: 6px 10px;
383
- border-radius: 4px; display: none;
384
- }}
385
- #fps {{ top: 10px; }}
386
- #camid {{ top: 40px; }}
387
- #controls-info {{
388
- position: absolute; bottom: 10px; left: 10px;
389
- color: white; font-family: Arial; font-size: 11px;
390
- background: rgba(0,0,0,0.7); padding: 8px 12px;
391
- border-radius: 4px;
392
- }}
393
- </style>
394
- </head>
395
- <body>
396
- <canvas id="canvas"></canvas>
397
- <div id="spinner">
398
- <div style="font-size:14px; margin-bottom:10px;">Loading 3D Viewer...</div>
399
- <div style="background:#333; height:4px; width:200px; border-radius:2px; overflow:hidden;">
400
- <div id="progress"></div>
401
- </div>
402
- </div>
403
- <div id="message"></div>
404
- <div id="fps"></div>
405
- <div id="camid"></div>
406
- <div id="controls-info">
407
- <strong>Controls:</strong> Drag: Rotate | Scroll: Zoom | Right-drag: Pan
408
- </div>
409
-
410
- <script>
411
- {viewer_js_content}
412
- </script>
413
-
414
- <script>
415
- // Auto-load PLY after viewer initializes
416
- const plyUrl = "{ply_url}";
417
- console.log("=== Splat Viewer Init ===");
418
- console.log("PLY URL:", plyUrl);
419
-
420
- let attempts = 0;
421
- const checkAndLoad = setInterval(function() {{
422
- attempts++;
423
-
424
- if (window.worker) {{
425
- console.log("✓ Worker ready after", attempts * 100, "ms");
426
- clearInterval(checkAndLoad);
427
-
428
- fetch(plyUrl)
429
- .then(r => {{ if (!r.ok) throw new Error("HTTP " + r.status); return r.arrayBuffer(); }})
430
- .then(buffer => {{
431
- console.log("✓ PLY loaded:", buffer.byteLength, "bytes");
432
- const file = new File([buffer], "model.ply");
433
- const reader = new FileReader();
434
- reader.onload = () => {{
435
- window.worker.postMessage({{ ply: reader.result }});
436
- console.log("✓ Sent to worker");
437
- }};
438
- reader.readAsArrayBuffer(file);
439
- }})
440
- .catch(err => {{
441
- console.error("✗ Error:", err);
442
- document.getElementById("spinner").style.display = "none";
443
- const msg = document.getElementById("message");
444
- msg.textContent = "Error: " + err.message;
445
- msg.style.display = "block";
446
- }});
447
- }} else if (attempts >= 50) {{
448
- console.error("✗ Worker timeout");
449
- clearInterval(checkAndLoad);
450
- document.getElementById("spinner").style.display = "none";
451
- const msg = document.getElementById("message");
452
- msg.textContent = "Viewer failed to initialize.";
453
- msg.style.display = "block";
454
- }}
455
- }}, 100);
456
- </script>
457
- </body>
458
- </html>"""
459
-
460
- # Write HTML file
461
- viewer_html_path.write_text(html_content)
462
-
463
- # Return iframe that loads this HTML file
464
- viewer_html_url = f"/file={viewer_html_path}"
465
-
466
- return f"""
467
- <iframe id="viewer-frame-{viewer_id}" src="{viewer_html_url}" style="width:100%; height:600px; border:1px solid #333; border-radius:8px;" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
468
- <p style="font-size:11px; color:#666; margin-top:5px;">
469
- <a href="{viewer_html_url}" target="_blank" style="color:#4CAF50;">Open in new tab</a> for better performance
470
- </p>
471
- """
472
-
473
  def main():
474
  """Run the FaceLift application."""
475
  pipeline = FaceLiftPipeline()
@@ -487,20 +351,7 @@ def main():
487
  input_path, multiview_path, output_path, turntable_path, ply_path = \
488
  pipeline.generate_3d_head(image_path, auto_crop, guidance_scale, random_seed, num_steps)
489
 
490
- # Create Gradio-accessible URL for the PLY file
491
- ply_url = f"/file={ply_path}"
492
-
493
- # viewer.js is in the outputs directory
494
- viewer_js_url = f"/file={OUTPUTS_DIR}/viewer.js"
495
-
496
- # Generate unique viewer ID
497
- viewer_id = str(uuid.uuid4())[:8]
498
- viewer_html = create_splat_viewer_html(ply_url, viewer_js_url, viewer_id)
499
-
500
- # Debug info showing the paths
501
- debug_info = f"PLY Path: {ply_path}\nPLY URL: {ply_url}\nViewer JS URL: {viewer_js_url}\nFile exists: {Path(ply_path).exists()}\nViewer ID: {viewer_id}"
502
-
503
- return viewer_html, output_path, turntable_path, ply_path, debug_info
504
 
505
  gr.Markdown("## FaceLift: Single Image 3D Face Reconstruction.")
506
 
@@ -529,22 +380,15 @@ def main():
529
  )
530
 
531
  with gr.Column(scale=1):
532
- out_viewer = gr.HTML(label="🎮 Interactive 3D Viewer")
533
- out_debug = gr.Textbox(label="🔍 Debug Info", lines=3, visible=True)
534
  out_recon = gr.Image(label="3D Reconstruction Views")
535
- out_video = gr.PlayableVideo(label="Turntable Animation (360° View)", height=400)
536
  out_ply = gr.File(label="Download 3D Model (.ply)")
537
- gr.Markdown("""
538
- **💡 Controls:** Drag to rotate | Scroll to zoom | Right-drag to pan
539
-
540
- *Interactive viewer powered by [antimatter15/splat](https://github.com/antimatter15/splat)*
541
- """)
542
 
543
  # Run generation and display all outputs
544
  run_btn.click(
545
  fn=_generate_and_filter_outputs,
546
  inputs=[in_image, auto_crop, guidance, seed, steps],
547
- outputs=[out_viewer, out_recon, out_video, out_ply, out_debug],
548
  )
549
 
550
  demo.queue(max_size=10)
 
46
  OUTPUTS_DIR = Path.cwd() / "outputs"
47
  OUTPUTS_DIR.mkdir(exist_ok=True)
48
 
 
 
 
 
 
 
49
  # -----------------------------
50
  # Ensure diff-gaussian-rasterization builds for current GPU
51
  # -----------------------------
 
334
  print(f"Error details:\n{error_details}")
335
  raise gr.Error(f"Generation failed: {str(e)}")
336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  def main():
338
  """Run the FaceLift application."""
339
  pipeline = FaceLiftPipeline()
 
351
  input_path, multiview_path, output_path, turntable_path, ply_path = \
352
  pipeline.generate_3d_head(image_path, auto_crop, guidance_scale, random_seed, num_steps)
353
 
354
+ return output_path, turntable_path, ply_path
 
 
 
 
 
 
 
 
 
 
 
 
 
355
 
356
  gr.Markdown("## FaceLift: Single Image 3D Face Reconstruction.")
357
 
 
380
  )
381
 
382
  with gr.Column(scale=1):
 
 
383
  out_recon = gr.Image(label="3D Reconstruction Views")
384
+ out_video = gr.PlayableVideo(label="Turntable Animation (360° View)", height=600)
385
  out_ply = gr.File(label="Download 3D Model (.ply)")
 
 
 
 
 
386
 
387
  # Run generation and display all outputs
388
  run_btn.click(
389
  fn=_generate_and_filter_outputs,
390
  inputs=[in_image, auto_crop, guidance, seed, steps],
391
+ outputs=[out_recon, out_video, out_ply],
392
  )
393
 
394
  demo.queue(max_size=10)