Spaces:
Paused
Paused
Download app.py from NiranjanWaghmare/modelGen: direct link, hf CLI and curl.
- Browser
- Download file 6.94 kB
-
https://huggingface.co/spaces/NiranjanWaghmare/modelGen/resolve/main/app.py
- Command line
-
hf download hf://spaces/NiranjanWaghmare/modelGen/app.py
-
curl -L -o app.py https://huggingface.co/spaces/NiranjanWaghmare/modelGen/resolve/main/app.py
6.94 kB
| """Model Gen — photos of one adult → 3D identity mesh + orbit video.""" | |
| from __future__ import annotations | |
| import os | |
| from pathlib import Path | |
| from typing import Any | |
| from PIL import Image | |
| from modelgen.config import APP_NAME, ASSETS, DEFAULT_PORT, REJECTED_ASSETS, running_on_spaces | |
| from modelgen.pipeline import build_model_and_video | |
| try: | |
| import spaces | |
| except ImportError: # local / CI | |
| class spaces: # type: ignore[no-redef] | |
| def GPU(*_args: Any, **_kwargs: Any): | |
| def decorator(fn): | |
| return fn | |
| return decorator | |
| def _patch_gradio_json_schema() -> None: | |
| """gradio_client 1.3 crashes when additionalProperties is a JSON-schema bool.""" | |
| import gradio_client.utils as gcu | |
| _get_type = gcu.get_type | |
| _convert = gcu._json_schema_to_python_type | |
| def get_type(schema): # noqa: ANN001 | |
| if not isinstance(schema, dict): | |
| return "any" | |
| return _get_type(schema) | |
| def convert(schema, defs): # noqa: ANN001 | |
| if isinstance(schema, bool): | |
| return "Any" | |
| return _convert(schema, defs) | |
| gcu.get_type = get_type # type: ignore[method-assign] | |
| gcu._json_schema_to_python_type = convert # type: ignore[method-assign] | |
| _patch_gradio_json_schema() | |
| def _as_images(files: Any) -> tuple[list[Image.Image], list[str]]: | |
| if not files: | |
| return [], [] | |
| if not isinstance(files, list): | |
| files = [files] | |
| images: list[Image.Image] = [] | |
| names: list[str] = [] | |
| for item in files: | |
| path = item.name if hasattr(item, "name") else item | |
| if not path: | |
| continue | |
| img = Image.open(path).convert("RGB") | |
| images.append(img) | |
| names.append(Path(str(path)).name) | |
| return images, names | |
| def _status_md(engine: str, warnings: list[str], n_photos: int) -> str: | |
| lines = [ | |
| f"**3D engine:** {engine}", | |
| f"**Photos used:** {n_photos}", | |
| "**Outputs:** `identity.glb` (3D model) + orbit video of that mesh", | |
| ] | |
| if warnings: | |
| lines.append("") | |
| lines.append("**Notes**") | |
| lines.extend(f"- {w}" for w in warnings) | |
| return "\n".join(lines) | |
| EMPTY_STATUS = ( | |
| "Upload 2–8 photos of **one consenting adult** (front plus other angles). " | |
| "Generate builds a **3D identity mesh** and an **orbit video of that mesh** — not a collage of stills. " | |
| "This path runs on CPU so it still works when ZeroGPU quota is exhausted." | |
| ) | |
| def run_generate(files, adult_ok, progress=None): | |
| import gradio as gr | |
| if not adult_ok: | |
| raise gr.Error("Confirm that every photo is of a consenting adult (18+).") | |
| images, names = _as_images(files) | |
| if not images: | |
| raise gr.Error("Upload at least one photo — a clear front view plus extra angles works best.") | |
| if len(images) > 8: | |
| raise gr.Error("Use at most 8 photos.") | |
| if progress is not None: | |
| progress(0.1, desc="Building 3D identity mesh…") | |
| try: | |
| bundle = build_model_and_video(images, names) | |
| except gr.Error: | |
| raise | |
| except Exception as exc: # noqa: BLE001 | |
| raise gr.Error(f"Generation failed: {exc}") from exc | |
| if progress is not None: | |
| progress(1.0, desc="Done") | |
| status = _status_md(bundle.engine, bundle.warnings, len(images)) | |
| return str(bundle.glb_path), str(bundle.video_path), status | |
| def _license_markdown() -> str: | |
| rows = [ | |
| "| Role | Source | License | Gated |", | |
| "| --- | --- | --- | --- |", | |
| ] | |
| for asset in ASSETS: | |
| rows.append( | |
| f"| {asset.role} | [`{asset.repo_id}`]({asset.source}) | {asset.license} | " | |
| f"{'yes' if asset.gated else 'no'} |" | |
| ) | |
| rejected = "\n".join(f"- **{a.repo_id}** ({a.license}): {a.notes}" for a in REJECTED_ASSETS) | |
| return ( | |
| "### Models and assets (free / OSS)\n\n" | |
| + "\n".join(rows) | |
| + "\n\nNot used (license, gating, or not 3D):\n\n" | |
| + rejected | |
| ) | |
| def _patch_gradio_localhost_probe() -> None: | |
| import gradio.networking as networking | |
| networking.url_ok = lambda _url: True # type: ignore[method-assign] | |
| def _launch_demo(demo) -> None: | |
| demo.queue(max_size=8, api_open=False) | |
| _patch_gradio_localhost_probe() | |
| if running_on_spaces(): | |
| server_name = os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0") | |
| server_port = int(os.environ.get("GRADIO_SERVER_PORT") or os.environ.get("PORT") or "7860") | |
| else: | |
| server_name = "0.0.0.0" | |
| server_port = int(os.environ.get("PORT", DEFAULT_PORT)) | |
| demo.launch( | |
| server_name=server_name, | |
| server_port=server_port, | |
| share=False, | |
| show_error=True, | |
| show_api=False, | |
| ) | |
| def build_demo(): | |
| import gradio as gr | |
| css = """ | |
| .gradio-container {max-width: 1180px !important;} | |
| footer {opacity: 0.7;} | |
| """ | |
| with gr.Blocks(title=APP_NAME, css=css, theme=gr.themes.Soft()) as demo: | |
| gr.Markdown( | |
| f""" | |
| # {APP_NAME} | |
| Photos of **one adult** → a **3D identity model** (GLB) → an **orbit video of that model**. | |
| This is not a 2D collage. Extra angles improve the bust; the video camera flies around the mesh. | |
| Geometry uses **Depth-Anything V2 Small** (Apache-2.0, ungated) when weights load, otherwise a CPU shape-from-shading relief. No InsightFace, no gated FLUX.1-dev, no ZeroGPU required for this slice. | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| files = gr.File( | |
| label="Photos of one person (front + other angles / lighting)", | |
| file_count="multiple", | |
| file_types=["image"], | |
| ) | |
| adult_ok = gr.Checkbox( | |
| label="I confirm every photo is of a consenting adult (18+).", | |
| value=False, | |
| ) | |
| generate_btn = gr.Button("Build 3D model + orbit video", variant="primary") | |
| with gr.Column(scale=1): | |
| model3d = gr.Model3D(label="3D identity (GLB)") | |
| video = gr.Video(label="Orbit video of the 3D model") | |
| status = gr.Markdown(EMPTY_STATUS) | |
| generate_btn.click( | |
| fn=run_generate, | |
| inputs=[files, adult_ok], | |
| outputs=[model3d, video, status], | |
| ) | |
| gr.Markdown(_license_markdown()) | |
| gr.Markdown( | |
| """ | |
| ### Video pipeline | |
| 1. **Now:** orbit render of the generated GLB (same asset, moving camera). | |
| 2. **Next:** import `identity.glb` into Blender / Unreal, or use orbit frames as control for **CogVideoX-2b** (Apache-2.0). | |
| Neural image-to-3D (TripoSR MIT, InstantMesh Apache) is stronger mesh quality but TripoSR is Hub-gated and InstantMesh is too heavy for a 60s ZeroGPU slot — documented, not loaded. | |
| """ | |
| ) | |
| return demo | |
| def main() -> None: | |
| _launch_demo(build_demo()) | |
| if __name__ == "__main__": | |
| main() | |