Sempy32 commited on
Commit
619a09a
·
verified ·
1 Parent(s): a0ec681

Fix ZeroGPU SAM3 runtime and request diagnostics

Browse files
Files changed (2) hide show
  1. README.md +4 -3
  2. app.py +42 -18
README.md CHANGED
@@ -7,6 +7,7 @@ pinned: false
7
  short_description: SAM3 open-vocabulary panoptic concept segmentation API
8
  ---
9
 
10
- `api_panoptic(image, concepts, confidence, mask_threshold)` returns JSON
11
- detections with base64-encoded PNG masks. The Space requires an `HF_TOKEN`
12
- secret with access to `facebook/sam3`.
 
 
7
  short_description: SAM3 open-vocabulary panoptic concept segmentation API
8
  ---
9
 
10
+ `api_panoptic(image, concepts, confidence, mask_threshold)` returns a compressed
11
+ `.json.gz` file containing detections with base64-encoded PNG masks. File
12
+ transport avoids blocking Gradio's event channel with large inline mask JSON.
13
+ The Space requires an `HF_TOKEN` secret with access to `facebook/sam3`.
app.py CHANGED
@@ -3,8 +3,11 @@
3
  from __future__ import annotations
4
 
5
  import base64
 
6
  import io
 
7
  import os
 
8
  import traceback
9
 
10
  import gradio as gr
@@ -33,6 +36,14 @@ def _encode_mask(mask_bool: np.ndarray) -> str:
33
  return base64.b64encode(buffer.getvalue()).decode("ascii")
34
 
35
 
 
 
 
 
 
 
 
 
36
  def _gpu_duration(image, concepts, conf, mask_threshold=0.5) -> int:
37
  del image, conf, mask_threshold
38
  concept_count = len(
@@ -44,20 +55,24 @@ def _gpu_duration(image, concepts, conf, mask_threshold=0.5) -> int:
44
  @spaces.GPU(duration=_gpu_duration)
45
  def api_panoptic(image, concepts, conf, mask_threshold=0.5):
46
  if image is None:
47
- return {"error": "no image provided", "detections": []}
 
 
48
  image = image.convert("RGB")
49
  width, height = image.size
50
  concept_list = [
51
  value.strip() for value in str(concepts or "").split(",") if value.strip()
52
  ]
53
  if not concept_list:
54
- return {
55
- "version": "4",
56
- "model": MODEL_ID,
57
- "width": width,
58
- "height": height,
59
- "detections": [],
60
- }
 
 
61
 
62
  print(
63
  f"SAM3 request: size={width}x{height} concepts={len(concept_list)}",
@@ -114,21 +129,30 @@ def api_panoptic(image, concepts, conf, mask_threshold=0.5):
114
  traceback.print_exc()
115
  raise
116
 
117
- print(f"SAM3 response: detections={len(detections)}", flush=True)
118
- return {
119
- "version": "4",
120
- "model": MODEL_ID,
121
- "width": width,
122
- "height": height,
123
- "detections": detections,
124
- }
 
 
 
 
 
 
 
125
 
126
 
127
  with gr.Blocks(title="SAM3 Panoptic") as demo:
128
  gr.Markdown("# SAM3 Panoptic API")
129
  with gr.Row():
130
  input_image = gr.Image(type="pil", label="Image")
131
- output_json = gr.JSON(label="Detections")
 
 
132
  concept_text = gr.Textbox(
133
  label="Concepts",
134
  value="person, car, road, building, tree",
@@ -142,7 +166,7 @@ with gr.Blocks(title="SAM3 Panoptic") as demo:
142
  gr.Button("Segment").click(
143
  api_panoptic,
144
  [input_image, concept_text, confidence, mask_threshold],
145
- output_json,
146
  api_name="api_panoptic",
147
  )
148
 
 
3
  from __future__ import annotations
4
 
5
  import base64
6
+ import gzip
7
  import io
8
+ import json
9
  import os
10
+ import tempfile
11
  import traceback
12
 
13
  import gradio as gr
 
36
  return base64.b64encode(buffer.getvalue()).decode("ascii")
37
 
38
 
39
+ def _write_response_file(payload: dict) -> str:
40
+ descriptor, path = tempfile.mkstemp(prefix="sam3_", suffix=".json.gz")
41
+ os.close(descriptor)
42
+ with gzip.open(path, "wt", encoding="utf-8", compresslevel=6) as handle:
43
+ json.dump(payload, handle, separators=(",", ":"))
44
+ return path
45
+
46
+
47
  def _gpu_duration(image, concepts, conf, mask_threshold=0.5) -> int:
48
  del image, conf, mask_threshold
49
  concept_count = len(
 
55
  @spaces.GPU(duration=_gpu_duration)
56
  def api_panoptic(image, concepts, conf, mask_threshold=0.5):
57
  if image is None:
58
+ return _write_response_file(
59
+ {"error": "no image provided", "detections": []}
60
+ )
61
  image = image.convert("RGB")
62
  width, height = image.size
63
  concept_list = [
64
  value.strip() for value in str(concepts or "").split(",") if value.strip()
65
  ]
66
  if not concept_list:
67
+ return _write_response_file(
68
+ {
69
+ "version": "5",
70
+ "model": MODEL_ID,
71
+ "width": width,
72
+ "height": height,
73
+ "detections": [],
74
+ }
75
+ )
76
 
77
  print(
78
  f"SAM3 request: size={width}x{height} concepts={len(concept_list)}",
 
129
  traceback.print_exc()
130
  raise
131
 
132
+ response_path = _write_response_file(
133
+ {
134
+ "version": "5",
135
+ "model": MODEL_ID,
136
+ "width": width,
137
+ "height": height,
138
+ "detections": detections,
139
+ }
140
+ )
141
+ print(
142
+ f"SAM3 response: detections={len(detections)} "
143
+ f"transport_bytes={os.path.getsize(response_path)}",
144
+ flush=True,
145
+ )
146
+ return response_path
147
 
148
 
149
  with gr.Blocks(title="SAM3 Panoptic") as demo:
150
  gr.Markdown("# SAM3 Panoptic API")
151
  with gr.Row():
152
  input_image = gr.Image(type="pil", label="Image")
153
+ output_file = gr.File(
154
+ type="filepath", label="Compressed detections (.json.gz)"
155
+ )
156
  concept_text = gr.Textbox(
157
  label="Concepts",
158
  value="person, car, road, building, tree",
 
166
  gr.Button("Segment").click(
167
  api_panoptic,
168
  [input_image, concept_text, confidence, mask_threshold],
169
+ output_file,
170
  api_name="api_panoptic",
171
  )
172