Spaces:
Running on Zero
Running on Zero
Right-size ZeroGPU duration from mesh header, fix Gradio 6 theme/css, add second example scan
Browse files- .gitattributes +1 -0
- app.py +53 -3
- examples/historic-interior.glb +3 -0
.gitattributes
CHANGED
|
@@ -34,3 +34,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
examples/attic.glb filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
examples/attic.glb filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
examples/historic-interior.glb filter=lfs diff=lfs merge=lfs -text
|
app.py
CHANGED
|
@@ -12,6 +12,7 @@ os.environ.setdefault("NUMBA_CACHE_DIR", "/tmp/numba-cache")
|
|
| 12 |
|
| 13 |
import spaces # noqa: E402 (before torch)
|
| 14 |
|
|
|
|
| 15 |
import tempfile # noqa: E402
|
| 16 |
import time # noqa: E402
|
| 17 |
from typing import List, Optional, Tuple # noqa: E402
|
|
@@ -46,7 +47,54 @@ def _empty(msg: str):
|
|
| 46 |
return None, [], msg
|
| 47 |
|
| 48 |
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
def segment(
|
| 51 |
mesh_file: Optional[str],
|
| 52 |
up_axis: str = "Auto",
|
|
@@ -116,7 +164,7 @@ CSS = """
|
|
| 116 |
#viewer { height: 520px; }
|
| 117 |
"""
|
| 118 |
|
| 119 |
-
with gr.Blocks(
|
| 120 |
gr.Markdown(
|
| 121 |
"""
|
| 122 |
# 🪑 AQ3D — 3D Instance Segmentation
|
|
@@ -161,6 +209,7 @@ with gr.Blocks(theme=gr.themes.Citrus(), css=CSS, title="AQ3D 3D Instance Segmen
|
|
| 161 |
gr.Examples(
|
| 162 |
examples=[
|
| 163 |
["examples/attic.glb"],
|
|
|
|
| 164 |
],
|
| 165 |
inputs=[mesh_in],
|
| 166 |
outputs=[mesh_out, table, status],
|
|
@@ -193,4 +242,5 @@ with gr.Blocks(theme=gr.themes.Citrus(), css=CSS, title="AQ3D 3D Instance Segmen
|
|
| 193 |
)
|
| 194 |
|
| 195 |
if __name__ == "__main__":
|
| 196 |
-
|
|
|
|
|
|
| 12 |
|
| 13 |
import spaces # noqa: E402 (before torch)
|
| 14 |
|
| 15 |
+
import json # noqa: E402
|
| 16 |
import tempfile # noqa: E402
|
| 17 |
import time # noqa: E402
|
| 18 |
from typing import List, Optional, Tuple # noqa: E402
|
|
|
|
| 47 |
return None, [], msg
|
| 48 |
|
| 49 |
|
| 50 |
+
# --------------------------------------------------------------------------- #
|
| 51 |
+
# GPU-duration estimate
|
| 52 |
+
#
|
| 53 |
+
# Runtime is dominated by the vertex count (superpoint graph segmentation, the
|
| 54 |
+
# 0.6 x |superpoints| adaptive queries and their NMS). Measured on this Space:
|
| 55 |
+
# 211k vertices -> 8.2 s, 526k vertices -> 19.2 s, i.e. ~0.035 s per 1k vertices.
|
| 56 |
+
# The vertex count is read straight out of the file header (cheap, no parsing of
|
| 57 |
+
# the geometry) so every visitor only reserves the quota their own scan needs.
|
| 58 |
+
# --------------------------------------------------------------------------- #
|
| 59 |
+
def _vertex_count(path: str) -> Optional[int]:
|
| 60 |
+
"""Vertex count from a glTF-binary / PLY header, without loading geometry."""
|
| 61 |
+
try:
|
| 62 |
+
with open(path, "rb") as fh:
|
| 63 |
+
head = fh.read(20)
|
| 64 |
+
if head[:4] == b"glTF":
|
| 65 |
+
chunk_len = int.from_bytes(head[12:16], "little")
|
| 66 |
+
if head[16:20] != b"JSON":
|
| 67 |
+
return None
|
| 68 |
+
gltf = json.loads(fh.read(chunk_len).decode("utf-8", "replace"))
|
| 69 |
+
accessors = gltf.get("accessors", [])
|
| 70 |
+
total = 0
|
| 71 |
+
for mesh in gltf.get("meshes", []):
|
| 72 |
+
for prim in mesh.get("primitives", []):
|
| 73 |
+
i = prim.get("attributes", {}).get("POSITION")
|
| 74 |
+
if isinstance(i, int) and 0 <= i < len(accessors):
|
| 75 |
+
total += int(accessors[i].get("count", 0))
|
| 76 |
+
return total or None
|
| 77 |
+
if head[:3] == b"ply":
|
| 78 |
+
fh.seek(0)
|
| 79 |
+
for line in fh.read(8192).split(b"\n"):
|
| 80 |
+
if line.startswith(b"element vertex"):
|
| 81 |
+
return int(line.split()[2])
|
| 82 |
+
except Exception:
|
| 83 |
+
pass
|
| 84 |
+
return None
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _gpu_duration(mesh_file: Optional[str], *args, **kwargs) -> int:
|
| 88 |
+
if not mesh_file or not os.path.exists(mesh_file):
|
| 89 |
+
return 25
|
| 90 |
+
verts = _vertex_count(mesh_file)
|
| 91 |
+
if verts is None: # OBJ / unknown container: size proxy
|
| 92 |
+
verts = os.path.getsize(mesh_file) / 60.0
|
| 93 |
+
seconds = (1.0 + 0.035 * verts / 1000.0) * 1.5 + 5.0 # fit + 50% + fork cost
|
| 94 |
+
return int(min(75, max(20, round(seconds))))
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
@spaces.GPU(duration=_gpu_duration)
|
| 98 |
def segment(
|
| 99 |
mesh_file: Optional[str],
|
| 100 |
up_axis: str = "Auto",
|
|
|
|
| 164 |
#viewer { height: 520px; }
|
| 165 |
"""
|
| 166 |
|
| 167 |
+
with gr.Blocks(title="AQ3D 3D Instance Segmentation") as demo:
|
| 168 |
gr.Markdown(
|
| 169 |
"""
|
| 170 |
# 🪑 AQ3D — 3D Instance Segmentation
|
|
|
|
| 209 |
gr.Examples(
|
| 210 |
examples=[
|
| 211 |
["examples/attic.glb"],
|
| 212 |
+
["examples/historic-interior.glb"],
|
| 213 |
],
|
| 214 |
inputs=[mesh_in],
|
| 215 |
outputs=[mesh_out, table, status],
|
|
|
|
| 242 |
)
|
| 243 |
|
| 244 |
if __name__ == "__main__":
|
| 245 |
+
# Gradio 6 moved `theme` / `css` from the Blocks constructor to launch().
|
| 246 |
+
demo.queue().launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)
|
examples/historic-interior.glb
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d3609e9d0de9c1e6b7d99a60fd47dff832a4e727038b4b63f1639150a35c749a
|
| 3 |
+
size 26084836
|