Georg Claude Sonnet 4.5 commited on
Commit
10b80bb
·
1 Parent(s): d5c35b5

Convert to pure Gradio app for ZeroGPU compatibility

Browse files

- Remove FastAPI integration (not compatible with ZeroGPU)
- Use Gradio-only approach with @spaces.GPU decorators
- Simplify to standard Gradio Space architecture
- Remove Dockerfile (Spaces use Python + requirements.txt)
- Update README.md sdk_version to match requirements
- Keep camera intrinsics inputs with defaults

ZeroGPU Spaces work best with pure Gradio applications.
API access still available via gradio_client library.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

Files changed (2) hide show
  1. README.md +2 -3
  2. app.py +32 -125
README.md CHANGED
@@ -4,16 +4,15 @@ emoji: 🎯
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.4.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
11
  tags:
12
  - computer-vision
13
  - 6D-pose
14
  - object-detection
15
  - robotics
16
- - zero-gpu
17
  ---
18
 
19
  # FoundationPose Inference Server
 
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 4.50.0
 
8
  app_file: app.py
9
  pinned: false
10
+ hf_oauth: false
11
  tags:
12
  - computer-vision
13
  - 6D-pose
14
  - object-detection
15
  - robotics
 
16
  ---
17
 
18
  # FoundationPose Inference Server
app.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
- Simple FoundationPose API server using FastAPI + Gradio
3
 
4
- This version uses FastAPI for clean REST API endpoints alongside Gradio UI.
5
  """
6
 
7
  import base64
@@ -15,8 +15,6 @@ import gradio as gr
15
  import numpy as np
16
  import spaces
17
  import torch
18
- from fastapi import FastAPI, HTTPException
19
- from pydantic import BaseModel
20
 
21
  logging.basicConfig(
22
  level=logging.INFO,
@@ -38,7 +36,6 @@ class FoundationPoseInference:
38
  self.tracked_objects = {}
39
  self.use_real_model = USE_REAL_MODEL
40
 
41
- @spaces.GPU(duration=120)
42
  def initialize_model(self):
43
  """Initialize the FoundationPose model on GPU."""
44
  if self.initialized:
@@ -167,112 +164,8 @@ class FoundationPoseInference:
167
  pose_estimator = FoundationPoseInference()
168
 
169
 
170
- # Pydantic models for API
171
- class InitializeRequest(BaseModel):
172
- object_id: str
173
- reference_images_b64: List[str]
174
- camera_intrinsics: str = None
175
- mesh_path: str = None
176
-
177
-
178
- class EstimateRequest(BaseModel):
179
- object_id: str
180
- query_image_b64: str
181
- camera_intrinsics: str = None
182
- depth_image_b64: str = None
183
- mask_b64: str = None
184
-
185
-
186
- # Create FastAPI app
187
- app = FastAPI()
188
-
189
-
190
- @app.post("/api/initialize")
191
- async def api_initialize(request: InitializeRequest):
192
- """Initialize object tracking with reference images."""
193
- try:
194
- # Decode reference images
195
- reference_images = []
196
- for img_b64 in request.reference_images_b64:
197
- img_bytes = base64.b64decode(img_b64)
198
- img_array = np.frombuffer(img_bytes, dtype=np.uint8)
199
- img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
200
- img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
201
- reference_images.append(img)
202
-
203
- # Parse camera intrinsics
204
- intrinsics = json.loads(request.camera_intrinsics) if request.camera_intrinsics else None
205
-
206
- # Register object
207
- success = pose_estimator.register_object(
208
- object_id=request.object_id,
209
- reference_images=reference_images,
210
- camera_intrinsics=intrinsics,
211
- mesh_path=request.mesh_path
212
- )
213
-
214
- return {
215
- "success": success,
216
- "message": f"Object '{request.object_id}' registered with {len(reference_images)} reference images"
217
- }
218
-
219
- except Exception as e:
220
- logger.error(f"Initialization error: {e}", exc_info=True)
221
- raise HTTPException(status_code=500, detail=str(e))
222
-
223
-
224
- @app.post("/api/estimate")
225
- async def api_estimate(request: EstimateRequest):
226
- """Estimate 6D pose from query image."""
227
- try:
228
- # Decode query image
229
- img_bytes = base64.b64decode(request.query_image_b64)
230
- img_array = np.frombuffer(img_bytes, dtype=np.uint8)
231
- img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
232
- img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
233
-
234
- # Decode optional depth image
235
- depth = None
236
- if request.depth_image_b64:
237
- depth_bytes = base64.b64decode(request.depth_image_b64)
238
- depth = np.frombuffer(depth_bytes, dtype=np.float32)
239
-
240
- # Decode optional mask
241
- mask = None
242
- if request.mask_b64:
243
- mask_bytes = base64.b64decode(request.mask_b64)
244
- mask_array = np.frombuffer(mask_bytes, dtype=np.uint8)
245
- mask = cv2.imdecode(mask_array, cv2.IMREAD_GRAYSCALE)
246
-
247
- # Parse camera intrinsics
248
- intrinsics = json.loads(request.camera_intrinsics) if request.camera_intrinsics else None
249
-
250
- # Estimate pose
251
- result = pose_estimator.estimate_pose(
252
- object_id=request.object_id,
253
- query_image=img,
254
- camera_intrinsics=intrinsics,
255
- depth_image=depth,
256
- mask=mask
257
- )
258
-
259
- return result
260
-
261
- except Exception as e:
262
- logger.error(f"Estimation error: {e}", exc_info=True)
263
- raise HTTPException(status_code=500, detail=str(e))
264
-
265
-
266
- # Warmup function to ensure ZeroGPU detects GPU usage
267
- @spaces.GPU(duration=10)
268
- def warmup():
269
- """Warmup function to initialize GPU context for ZeroGPU."""
270
- logger.info("Warming up GPU for ZeroGPU...")
271
- pose_estimator.initialize_model()
272
- return "✓ GPU initialized"
273
-
274
-
275
- # Gradio wrapper functions
276
  def gradio_initialize(object_id: str, reference_files: List, fx: float, fy: float, cx: float, cy: float):
277
  """Gradio wrapper for object initialization."""
278
  try:
@@ -316,6 +209,7 @@ def gradio_initialize(object_id: str, reference_files: List, fx: float, fy: floa
316
  return f"Error: {str(e)}"
317
 
318
 
 
319
  def gradio_estimate(object_id: str, query_image: np.ndarray, fx: float, fy: float, cx: float, cy: float):
320
  """Gradio wrapper for pose estimation."""
321
  try:
@@ -383,8 +277,8 @@ def gradio_estimate(object_id: str, query_image: np.ndarray, fx: float, fy: floa
383
  return f"Error: {str(e)}", None
384
 
385
 
386
- # Gradio UI with proper @spaces.GPU function calls
387
- with gr.Blocks(title="FoundationPose Inference", theme=gr.themes.Soft()) as gradio_app:
388
  gr.Markdown("# 🎯 FoundationPose 6D Object Pose Estimation")
389
 
390
  mode_indicator = gr.Markdown(
@@ -483,22 +377,35 @@ with gr.Blocks(title="FoundationPose Inference", theme=gr.themes.Soft()) as grad
483
 
484
  gr.Markdown("""
485
  ---
486
- ## REST API Endpoints
487
 
488
- This Space also provides REST API endpoints for programmatic access:
489
- - POST `/api/initialize` - Register object with reference images
490
- - POST `/api/estimate` - Estimate 6D pose from query image
491
 
492
- See the [API documentation](https://huggingface.co/spaces/gpue/foundationpose) for details.
493
- """)
494
 
495
- # Warmup on load to ensure ZeroGPU detects GPU usage
496
- gradio_app.load(warmup, outputs=None)
497
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
 
499
- # Mount Gradio to FastAPI
500
- app = gr.mount_gradio_app(app, gradio_app, path="/")
501
 
502
  if __name__ == "__main__":
503
- import uvicorn
504
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  """
2
+ FoundationPose inference server for Hugging Face Spaces with ZeroGPU.
3
 
4
+ This version uses pure Gradio for ZeroGPU compatibility.
5
  """
6
 
7
  import base64
 
15
  import numpy as np
16
  import spaces
17
  import torch
 
 
18
 
19
  logging.basicConfig(
20
  level=logging.INFO,
 
36
  self.tracked_objects = {}
37
  self.use_real_model = USE_REAL_MODEL
38
 
 
39
  def initialize_model(self):
40
  """Initialize the FoundationPose model on GPU."""
41
  if self.initialized:
 
164
  pose_estimator = FoundationPoseInference()
165
 
166
 
167
+ # Gradio wrapper functions with @spaces.GPU decorators
168
+ @spaces.GPU(duration=120)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  def gradio_initialize(object_id: str, reference_files: List, fx: float, fy: float, cx: float, cy: float):
170
  """Gradio wrapper for object initialization."""
171
  try:
 
209
  return f"Error: {str(e)}"
210
 
211
 
212
+ @spaces.GPU(duration=30)
213
  def gradio_estimate(object_id: str, query_image: np.ndarray, fx: float, fy: float, cx: float, cy: float):
214
  """Gradio wrapper for pose estimation."""
215
  try:
 
277
  return f"Error: {str(e)}", None
278
 
279
 
280
+ # Gradio UI
281
+ with gr.Blocks(title="FoundationPose Inference", theme=gr.themes.Soft()) as demo:
282
  gr.Markdown("# 🎯 FoundationPose 6D Object Pose Estimation")
283
 
284
  mode_indicator = gr.Markdown(
 
377
 
378
  gr.Markdown("""
379
  ---
380
+ ## API Documentation
381
 
382
+ This Space uses Gradio's built-in API. For programmatic access, use the `gradio_client` library:
 
 
383
 
384
+ ```python
385
+ from gradio_client import Client
386
 
387
+ client = Client("https://gpue-foundationpose.hf.space")
 
388
 
389
+ # Initialize object
390
+ result = client.predict(
391
+ object_id="target_cube",
392
+ reference_files=[file1, file2, ...],
393
+ fx=500.0, fy=500.0, cx=320.0, cy=240.0,
394
+ api_name="/gradio_initialize"
395
+ )
396
+
397
+ # Estimate pose
398
+ result = client.predict(
399
+ object_id="target_cube",
400
+ query_image=image,
401
+ fx=500.0, fy=500.0, cx=320.0, cy=240.0,
402
+ api_name="/gradio_estimate"
403
+ )
404
+ ```
405
+
406
+ See [client.py](https://huggingface.co/spaces/gpue/foundationpose/blob/main/client.py) for a complete example.
407
+ """)
408
 
 
 
409
 
410
  if __name__ == "__main__":
411
+ demo.launch()