Spaces:
Running
on
Zero
Running
on
Zero
demo
Browse files
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 387 |
-
out_video
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|