multimodalart HF Staff commited on
Commit
000083b
·
verified ·
1 Parent(s): 9550667

Right-size ZeroGPU duration from mesh header, fix Gradio 6 theme/css, add second example scan

Browse files
Files changed (3) hide show
  1. .gitattributes +1 -0
  2. app.py +53 -3
  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
- @spaces.GPU(duration=110)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(theme=gr.themes.Citrus(), css=CSS, title="AQ3D 3D Instance Segmentation") as demo:
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
- demo.queue().launch(mcp_server=True)
 
 
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