weijielyu commited on
Commit
e799c2d
·
1 Parent(s): e8c4ba0
Files changed (1) hide show
  1. app.py +79 -14
app.py CHANGED
@@ -46,10 +46,44 @@ import spaces
46
  import subprocess
47
  import sys
48
 
49
- # Outputs directory for generated files
50
  OUTPUTS_DIR = Path.cwd() / "outputs"
51
  OUTPUTS_DIR.mkdir(exist_ok=True)
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  # -----------------------------
54
  # Ensure diff-gaussian-rasterization builds for current GPU
55
  # -----------------------------
@@ -135,7 +169,7 @@ class FaceLiftPipeline:
135
  self.camera_indices = [2, 1, 0, 5, 4, 3]
136
 
137
  # Load models (keep on CPU for ZeroGPU compatibility)
138
- print("Loading models...")
139
  try:
140
  self.mvdiffusion_pipeline = StableUnCLIPImg2ImgPipeline.from_pretrained(
141
  str(workspace_dir / "checkpoints/mvdiffusion/pipeckpts"),
@@ -326,10 +360,19 @@ class FaceLiftPipeline:
326
  turntable_path = output_dir / "turntable.mp4"
327
  imageseq2video(turntable_frames, str(turntable_path), fps=30)
328
 
 
 
 
329
  # Final CUDA cache clear
330
  torch.cuda.empty_cache()
331
 
332
- return str(output_path), str(turntable_path), str(ply_path)
 
 
 
 
 
 
333
 
334
  except Exception as e:
335
  import traceback
@@ -349,13 +392,6 @@ def main():
349
 
350
  with gr.Blocks(title="FaceLift: Single Image 3D Face Reconstruction") as demo:
351
 
352
- # Wrapper to return outputs for display
353
- def _generate_and_filter_outputs(image_path, auto_crop, guidance_scale, random_seed, num_steps):
354
- output_path, turntable_path, ply_path = \
355
- pipeline.generate_3d_head(image_path, auto_crop, guidance_scale, random_seed, num_steps)
356
-
357
- return output_path, turntable_path, str(ply_path), ply_path
358
-
359
  gr.Markdown("## FaceLift: Single Image 3D Face Reconstruction.")
360
 
361
  gr.Markdown("""
@@ -382,12 +418,33 @@ def main():
382
  examples_per_page=10,
383
  )
384
 
 
 
 
385
  with gr.Column(scale=1):
386
- out_recon = gr.Image(label="3D Reconstruction Views")
387
- out_video = gr.PlayableVideo(label="Turntable Animation (360° View)", height=600)
388
- # Interactive 3D viewer for the generated Gaussian PLY (uses three.js under the hood)
389
  out_viewer = gr.Model3D(label="Interactive 3D Viewer (.ply)", height=600)
390
- out_ply = gr.File(label="Download 3D Model (.ply)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
 
392
  # Run generation and display all outputs
393
  run_btn.click(
@@ -396,6 +453,14 @@ def main():
396
  outputs=[out_recon, out_video, out_viewer, out_ply],
397
  )
398
 
 
 
 
 
 
 
 
 
399
  demo.queue(max_size=10)
400
  demo.launch(share=True, server_name="0.0.0.0", server_port=7860, show_error=True)
401
 
 
46
  import subprocess
47
  import sys
48
 
49
+ # Outputs directory for generated files (ensures folder exists even if CWD differs)
50
  OUTPUTS_DIR = Path.cwd() / "outputs"
51
  OUTPUTS_DIR.mkdir(exist_ok=True)
52
 
53
+ def _as_str_path(p):
54
+ """Coerce any Path-like to a plain string for Gradio components."""
55
+ from pathlib import Path as _Path
56
+ return str(p) if isinstance(p, (str, _Path)) else ""
57
+
58
+ def _log_viewer_file(ply_path: Path):
59
+ """Print a concise JSON line about the viewer file so users can debug from Space logs."""
60
+ info = {
61
+ "ply_path": str(Path(ply_path).absolute()),
62
+ "exists": Path(ply_path).exists(),
63
+ "size_bytes": (Path(ply_path).stat().st_size if Path(ply_path).exists() else None)
64
+ }
65
+ print("[VIEWER-RETURN]", json.dumps(info))
66
+
67
+ def _write_sample_cube_ply(dst):
68
+ """Write a tiny ASCII PLY point-cloud cube; handy to sanity-check the Model3D wiring."""
69
+ import random
70
+ dst = Path(dst)
71
+ dst.parent.mkdir(parents=True, exist_ok=True)
72
+ pts = []
73
+ for _ in range(2000):
74
+ x, y, z = (random.uniform(-0.5, 0.5) for _ in range(3))
75
+ r, g, b = (random.randint(80, 255) for _ in range(3))
76
+ pts.append((x, y, z, r, g, b))
77
+ with open(dst, "w") as f:
78
+ f.write("ply\nformat ascii 1.0\n")
79
+ f.write(f"element vertex {len(pts)}\n")
80
+ f.write("property float x\nproperty float y\nproperty float z\n")
81
+ f.write("property uchar red\nproperty uchar green\nproperty uchar blue\n")
82
+ f.write("end_header\n")
83
+ for x, y, z, r, g, b in pts:
84
+ f.write(f"{x} {y} {z} {r} {g} {b}\n")
85
+ return str(dst)
86
+
87
  # -----------------------------
88
  # Ensure diff-gaussian-rasterization builds for current GPU
89
  # -----------------------------
 
169
  self.camera_indices = [2, 1, 0, 5, 4, 3]
170
 
171
  # Load models (keep on CPU for ZeroGPU compatibility)
172
+ print("Loading models... (gradio", getattr(gr, "__version__", "unknown"), ")")
173
  try:
174
  self.mvdiffusion_pipeline = StableUnCLIPImg2ImgPipeline.from_pretrained(
175
  str(workspace_dir / "checkpoints/mvdiffusion/pipeckpts"),
 
360
  turntable_path = output_dir / "turntable.mp4"
361
  imageseq2video(turntable_frames, str(turntable_path), fps=30)
362
 
363
+ # Log the viewer file for quick debugging
364
+ _log_viewer_file(ply_path)
365
+
366
  # Final CUDA cache clear
367
  torch.cuda.empty_cache()
368
 
369
+ # Return a Model3D.update for the viewer and a string path for the File component
370
+ return (
371
+ str(output_path), # Reconstruction grid
372
+ str(turntable_path), # Turntable video
373
+ gr.Model3D.update(value=_as_str_path(ply_path)), # Viewer
374
+ _as_str_path(ply_path), # Download file
375
+ )
376
 
377
  except Exception as e:
378
  import traceback
 
392
 
393
  with gr.Blocks(title="FaceLift: Single Image 3D Face Reconstruction") as demo:
394
 
 
 
 
 
 
 
 
395
  gr.Markdown("## FaceLift: Single Image 3D Face Reconstruction.")
396
 
397
  gr.Markdown("""
 
418
  examples_per_page=10,
419
  )
420
 
421
+ # Optional: a quick sanity button to verify the viewer path wiring
422
+ sample_btn = gr.Button("🔧 Load Sample Cube (sanity test)")
423
+
424
  with gr.Column(scale=1):
425
+ out_recon = gr.Image(label="3D Reconstruction Views")
426
+ out_video = gr.PlayableVideo(label="Turntable Animation (360° View)", height=600)
 
427
  out_viewer = gr.Model3D(label="Interactive 3D Viewer (.ply)", height=600)
428
+ out_ply = gr.File(label="Download 3D Model (.ply)")
429
+
430
+ # Wrapper: call the pipeline and forward outputs in the exact order expected
431
+ @spaces.GPU(duration=1) # trivial; only formats outputs
432
+ def _generate_and_filter_outputs(image_path, auto_crop, guidance_scale, random_seed, num_steps):
433
+ (
434
+ output_path,
435
+ turntable_path,
436
+ viewer_update,
437
+ ply_dl_path,
438
+ ) = pipeline.generate_3d_head(image_path, auto_crop, guidance_scale, random_seed, num_steps)
439
+ # Ensure correct types for each component
440
+ if not isinstance(viewer_update, gr.components.Model3D.Update):
441
+ viewer_update = gr.Model3D.update(value=_as_str_path(viewer_update))
442
+ return (
443
+ _as_str_path(output_path),
444
+ _as_str_path(turntable_path),
445
+ viewer_update,
446
+ _as_str_path(ply_dl_path),
447
+ )
448
 
449
  # Run generation and display all outputs
450
  run_btn.click(
 
453
  outputs=[out_recon, out_video, out_viewer, out_ply],
454
  )
455
 
456
+ # Wire the sample cube sanity test
457
+ def _load_sample_viewer():
458
+ sample_path = _write_sample_cube_ply(Path("outputs") / "sample_cube.ply")
459
+ print("[VIEWER-SAMPLE]", os.path.abspath(sample_path))
460
+ return gr.Model3D.update(value=_as_str_path(sample_path))
461
+
462
+ sample_btn.click(_load_sample_viewer, inputs=[], outputs=[out_viewer])
463
+
464
  demo.queue(max_size=10)
465
  demo.launch(share=True, server_name="0.0.0.0", server_port=7860, show_error=True)
466