lidavidsh's picture
add remote server ip
75e3709
import os
import io
import zipfile
import base64
import tempfile
from pathlib import Path
import requests
import gradio as gr
# Front-end Gradio app that calls the backend FastAPI service hosted on GPU cloud.
# Configure the backend base URL through environment variable on Hugging Face Spaces.
# Example: API_BASE_URL = "https://your-api.example.com"
API_BASE_URL = os.getenv("API_BASE_URL")
MISSING_BACKEND_MSG = (
"Backend API is not configured. Set API_BASE_URL in Spaces Secrets "
"(e.g., http://134.199.133.78:80)"
)
def _files_payload(images):
"""Prepare multipart/form-data payload for requests.post(files=...)."""
files = []
for img in images:
if img is None:
continue
# gr.Image(type="filepath") returns a string path
if isinstance(img, str):
path = img
files.append(("files", (Path(path).name, open(path, "rb"), "image/*")))
continue
# gr.File returns objects with a .name attribute (path), or dict-like in some cases
path = getattr(img, "name", None)
if path is None and isinstance(img, dict) and "name" in img:
path = img["name"]
if path:
files.append(("files", (Path(path).name, open(path, "rb"), "image/*")))
return files
def predict_single(image):
"""Call /predict on backend for a single image and return one PLY file to download."""
if not image:
return None, "No image provided."
files = _files_payload([image])
if not files:
return None, "Invalid image input."
if not API_BASE_URL:
return None, MISSING_BACKEND_MSG
try:
resp = requests.post(f"{API_BASE_URL}/predict", files=files, timeout=120)
resp.raise_for_status()
data = resp.json()
except Exception as e:
return None, f"Backend error: {e}"
results = data.get("results", [])
if not results:
return None, "No result."
item = results[0]
if "error" in item:
return None, item["error"]
# Decode base64 PLY to a temporary file
ply_bytes = base64.b64decode(item["ply_data"])
with tempfile.NamedTemporaryFile(suffix=".ply", delete=False) as tmpf:
tmpf.write(ply_bytes)
ply_path = tmpf.name
meta = f"{item['ply_filename']} ({item['width']}x{item['height']}), f={item['focal_length']:.2f}"
return ply_path, meta
def predict_batch(images):
"""Call /predict on backend for multiple images and return a ZIP of PLY files."""
if not images:
return None, "No images provided."
files = _files_payload(images)
if not files:
return None, "Invalid inputs."
if not API_BASE_URL:
return None, MISSING_BACKEND_MSG
try:
resp = requests.post(f"{API_BASE_URL}/predict", files=files, timeout=300)
resp.raise_for_status()
data = resp.json()
except Exception as e:
return None, f"Backend error: {e}"
results = data.get("results", [])
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
metas = []
for item in results:
if "error" in item:
metas.append(f"{item.get('filename', '?')}: ERROR {item['error']}")
continue
ply_bytes = base64.b64decode(item["ply_data"])
zf.writestr(item["ply_filename"], ply_bytes)
metas.append(
f"{item['filename']} -> {item['ply_filename']} "
f"({item['width']}x{item['height']}, f={item['focal_length']:.2f})"
)
buf.seek(0)
return buf, "\n".join(metas)
with gr.Blocks(title="SHARP View Synthesis") as demo:
gr.Markdown(
"# SHARP View Synthesis\nUpload image(s) to generate 3D Gaussian PLY files via the backend API."
)
with gr.Tab("Single Image"):
in_img = gr.Image(type="filepath", label="Input Image")
out_file = gr.File(label="Generated PLY")
out_info = gr.Textbox(label="Info")
btn = gr.Button("Predict")
btn.click(predict_single, inputs=[in_img], outputs=[out_file, out_info])
with gr.Tab("Batch"):
in_imgs = gr.File(
file_count="multiple", file_types=["image"], label="Input Images"
)
out_zip = gr.File(label="PLY ZIP")
out_info2 = gr.Textbox(label="Info")
btn2 = gr.Button("Predict Batch")
btn2.click(predict_batch, inputs=[in_imgs], outputs=[out_zip, out_info2])
if __name__ == "__main__":
# On Hugging Face Spaces, API_BASE_URL must point to your GPU cloud FastAPI server
demo.launch(server_name="0.0.0.0", server_port=7860)