| import os |
| import io |
| import zipfile |
| import base64 |
| import tempfile |
| from pathlib import Path |
|
|
| import requests |
| import gradio as gr |
|
|
| |
| |
| |
| 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 |
| |
| if isinstance(img, str): |
| path = img |
| files.append(("files", (Path(path).name, open(path, "rb"), "image/*"))) |
| continue |
| |
| 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"] |
|
|
| |
| 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__": |
| |
| demo.launch(server_name="0.0.0.0", server_port=7860) |
|
|