Spaces:
Running
on
Zero
Running
on
Zero
Receive data in multipart/form-data instead of base64
Browse files- API_README.md +38 -38
- gradio_app.py +51 -24
API_README.md
CHANGED
@@ -34,20 +34,21 @@ Check if the service is running.
|
|
34 |
|
35 |
Start a 3D model generation job.
|
36 |
|
37 |
-
**Request
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
```json
|
39 |
{
|
40 |
-
"
|
41 |
-
|
42 |
-
|
43 |
-
"left": "base64_encoded_image", // Optional
|
44 |
-
"right": "base64_encoded_image" // Optional
|
45 |
-
},
|
46 |
-
"options": {
|
47 |
-
"enable_pbr": true,
|
48 |
-
"should_remesh": true,
|
49 |
-
"should_texture": true
|
50 |
-
}
|
51 |
}
|
52 |
```
|
53 |
|
@@ -60,10 +61,9 @@ Start a 3D model generation job.
|
|
60 |
```
|
61 |
|
62 |
**Notes:**
|
63 |
-
- At least one image is required
|
64 |
- The `front` image is mandatory
|
65 |
-
- Images should be
|
66 |
-
- The `options`
|
67 |
- **Texture generation is enabled by default for high-quality 3D models**
|
68 |
|
69 |
### 3. Check Job Status
|
@@ -104,22 +104,30 @@ Check the status of a generation job.
|
|
104 |
### Python Example
|
105 |
```python
|
106 |
import requests
|
107 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
|
109 |
-
|
110 |
-
|
111 |
-
|
|
|
|
|
112 |
|
113 |
# Start generation
|
114 |
-
response = requests.post(
|
115 |
-
"
|
116 |
-
|
117 |
-
}
|
118 |
-
|
119 |
-
"enable_pbr": True,
|
120 |
-
"should_texture": True # Critical for 3D model quality
|
121 |
-
}
|
122 |
-
})
|
123 |
|
124 |
job_id = response.json()["job_id"]
|
125 |
|
@@ -146,16 +154,8 @@ curl http://localhost:7860/api/health
|
|
146 |
|
147 |
# Generate model
|
148 |
curl -X POST http://localhost:7860/api/generate \
|
149 |
-
-
|
150 |
-
-
|
151 |
-
"images": {
|
152 |
-
"front": "base64_encoded_image_here"
|
153 |
-
},
|
154 |
-
"options": {
|
155 |
-
"enable_pbr": true,
|
156 |
-
"should_texture": true
|
157 |
-
}
|
158 |
-
}'
|
159 |
|
160 |
# Check status
|
161 |
curl "http://localhost:7860/api/status?job_id=your_job_id"
|
|
|
34 |
|
35 |
Start a 3D model generation job.
|
36 |
|
37 |
+
**Request Format:**
|
38 |
+
Multipart/form-data with the following fields:
|
39 |
+
|
40 |
+
- `front`: (Required) Front view image file
|
41 |
+
- `back`: (Optional) Back view image file
|
42 |
+
- `left`: (Optional) Left view image file
|
43 |
+
- `right`: (Optional) Right view image file
|
44 |
+
- `options`: (Optional) JSON string with generation options
|
45 |
+
|
46 |
+
**Options JSON Format:**
|
47 |
```json
|
48 |
{
|
49 |
+
"enable_pbr": true,
|
50 |
+
"should_remesh": true,
|
51 |
+
"should_texture": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
}
|
53 |
```
|
54 |
|
|
|
61 |
```
|
62 |
|
63 |
**Notes:**
|
|
|
64 |
- The `front` image is mandatory
|
65 |
+
- Images should be uploaded as files in multipart/form-data format
|
66 |
+
- The `options` field is optional and will use defaults if not provided
|
67 |
- **Texture generation is enabled by default for high-quality 3D models**
|
68 |
|
69 |
### 3. Check Job Status
|
|
|
104 |
### Python Example
|
105 |
```python
|
106 |
import requests
|
107 |
+
import json
|
108 |
+
import time
|
109 |
+
|
110 |
+
# Prepare files and options
|
111 |
+
files = {
|
112 |
+
'front': ('front.png', open('front.png', 'rb'), 'image/png'),
|
113 |
+
# Optional additional views
|
114 |
+
# 'back': ('back.png', open('back.png', 'rb'), 'image/png'),
|
115 |
+
# 'left': ('left.png', open('left.png', 'rb'), 'image/png'),
|
116 |
+
# 'right': ('right.png', open('right.png', 'rb'), 'image/png'),
|
117 |
+
}
|
118 |
|
119 |
+
options = {
|
120 |
+
"enable_pbr": True,
|
121 |
+
"should_texture": True, # Critical for 3D model quality
|
122 |
+
"should_remesh": True
|
123 |
+
}
|
124 |
|
125 |
# Start generation
|
126 |
+
response = requests.post(
|
127 |
+
"http://localhost:7860/api/generate",
|
128 |
+
files=files,
|
129 |
+
data={'options': json.dumps(options)}
|
130 |
+
)
|
|
|
|
|
|
|
|
|
131 |
|
132 |
job_id = response.json()["job_id"]
|
133 |
|
|
|
154 |
|
155 |
# Generate model
|
156 |
curl -X POST http://localhost:7860/api/generate \
|
157 |
+
-F "front=@front.png" \
|
158 |
+
-F 'options={"enable_pbr":true,"should_texture":true}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
|
160 |
# Check status
|
161 |
curl "http://localhost:7860/api/status?job_id=your_job_id"
|
gradio_app.py
CHANGED
@@ -65,7 +65,7 @@ import gradio as gr
|
|
65 |
import torch
|
66 |
import trimesh
|
67 |
import uvicorn
|
68 |
-
from fastapi import FastAPI, HTTPException, BackgroundTasks
|
69 |
from fastapi.staticfiles import StaticFiles
|
70 |
from fastapi.responses import JSONResponse
|
71 |
from pydantic import BaseModel
|
@@ -86,13 +86,10 @@ class JobStatus(Enum):
|
|
86 |
FAILED = "failed"
|
87 |
|
88 |
|
89 |
-
class
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
"should_remesh": True,
|
94 |
-
"should_texture": True # Critical for 3D model quality
|
95 |
-
}
|
96 |
|
97 |
|
98 |
class JobInfo:
|
@@ -168,17 +165,15 @@ def base64_to_pil_image(base64_string: str) -> Image.Image:
|
|
168 |
raise HTTPException(status_code=400, detail=f"Invalid image data: {str(e)}")
|
169 |
|
170 |
|
171 |
-
def process_generation_job(job_id: str, images: Dict[str,
|
172 |
"""Background task to process generation job."""
|
173 |
global face_reduce_worker, tex_pipeline, HAS_TEXTUREGEN, SAVE_DIR
|
174 |
|
175 |
try:
|
176 |
update_job_status(job_id, JobStatus.PROCESSING, progress=10, stage="initializing")
|
177 |
|
178 |
-
#
|
179 |
-
pil_images =
|
180 |
-
for view, base64_img in images.items():
|
181 |
-
pil_images[view] = base64_to_pil_image(base64_img)
|
182 |
|
183 |
# Extract options
|
184 |
enable_pbr = options.get("enable_pbr", True)
|
@@ -1324,29 +1319,61 @@ if __name__ == '__main__':
|
|
1324 |
|
1325 |
# API Endpoints
|
1326 |
@app.post("/api/generate")
|
1327 |
-
async def generate_3d_model(
|
1328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1329 |
try:
|
1330 |
-
#
|
1331 |
-
if
|
1332 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1333 |
|
1334 |
-
|
|
|
1335 |
raise HTTPException(status_code=400, detail="Front image is required")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1336 |
|
1337 |
# Create job
|
1338 |
job_id = create_job()
|
1339 |
|
1340 |
-
# Store job data
|
1341 |
-
jobs[job_id].images =
|
1342 |
-
jobs[job_id].options =
|
1343 |
|
1344 |
# Start background task
|
1345 |
background_tasks.add_task(
|
1346 |
process_generation_job,
|
1347 |
job_id,
|
1348 |
-
|
1349 |
-
|
1350 |
)
|
1351 |
|
1352 |
return JSONResponse({
|
|
|
65 |
import torch
|
66 |
import trimesh
|
67 |
import uvicorn
|
68 |
+
from fastapi import FastAPI, HTTPException, BackgroundTasks, File, Form, UploadFile
|
69 |
from fastapi.staticfiles import StaticFiles
|
70 |
from fastapi.responses import JSONResponse
|
71 |
from pydantic import BaseModel
|
|
|
86 |
FAILED = "failed"
|
87 |
|
88 |
|
89 |
+
class GenerateOptions(BaseModel):
|
90 |
+
enable_pbr: bool = True
|
91 |
+
should_remesh: bool = True
|
92 |
+
should_texture: bool = True # Critical for 3D model quality
|
|
|
|
|
|
|
93 |
|
94 |
|
95 |
class JobInfo:
|
|
|
165 |
raise HTTPException(status_code=400, detail=f"Invalid image data: {str(e)}")
|
166 |
|
167 |
|
168 |
+
def process_generation_job(job_id: str, images: Dict[str, Image.Image], options: Dict[str, Any]):
|
169 |
"""Background task to process generation job."""
|
170 |
global face_reduce_worker, tex_pipeline, HAS_TEXTUREGEN, SAVE_DIR
|
171 |
|
172 |
try:
|
173 |
update_job_status(job_id, JobStatus.PROCESSING, progress=10, stage="initializing")
|
174 |
|
175 |
+
# Images are already PIL Images
|
176 |
+
pil_images = images
|
|
|
|
|
177 |
|
178 |
# Extract options
|
179 |
enable_pbr = options.get("enable_pbr", True)
|
|
|
1319 |
|
1320 |
# API Endpoints
|
1321 |
@app.post("/api/generate")
|
1322 |
+
async def generate_3d_model(
|
1323 |
+
front: UploadFile = File(None),
|
1324 |
+
back: UploadFile = File(None),
|
1325 |
+
left: UploadFile = File(None),
|
1326 |
+
right: UploadFile = File(None),
|
1327 |
+
options: str = Form("{}"),
|
1328 |
+
background_tasks: BackgroundTasks = BackgroundTasks()
|
1329 |
+
):
|
1330 |
+
"""Generate 3D model from images using multipart/form-data."""
|
1331 |
try:
|
1332 |
+
# Parse options
|
1333 |
+
options_dict = json.loads(options) if options else {}
|
1334 |
+
generate_options = {
|
1335 |
+
"enable_pbr": options_dict.get("enable_pbr", True),
|
1336 |
+
"should_remesh": options_dict.get("should_remesh", True),
|
1337 |
+
"should_texture": options_dict.get("should_texture", True)
|
1338 |
+
}
|
1339 |
+
|
1340 |
+
# Process uploaded files
|
1341 |
+
images = {}
|
1342 |
|
1343 |
+
# Validate input - at least one image is required
|
1344 |
+
if not front:
|
1345 |
raise HTTPException(status_code=400, detail="Front image is required")
|
1346 |
+
|
1347 |
+
# Process each uploaded file
|
1348 |
+
for view, file in {
|
1349 |
+
"front": front,
|
1350 |
+
"back": back,
|
1351 |
+
"left": left,
|
1352 |
+
"right": right
|
1353 |
+
}.items():
|
1354 |
+
if file and file.filename:
|
1355 |
+
# Read file content
|
1356 |
+
contents = await file.read()
|
1357 |
+
# Convert to PIL Image
|
1358 |
+
try:
|
1359 |
+
img = Image.open(io.BytesIO(contents))
|
1360 |
+
images[view] = img
|
1361 |
+
except Exception as e:
|
1362 |
+
raise HTTPException(status_code=400, detail=f"Invalid image for {view}: {str(e)}")
|
1363 |
|
1364 |
# Create job
|
1365 |
job_id = create_job()
|
1366 |
|
1367 |
+
# Store job data (store file paths instead of actual images)
|
1368 |
+
jobs[job_id].images = {k: "<uploaded_file>" for k in images.keys()}
|
1369 |
+
jobs[job_id].options = generate_options
|
1370 |
|
1371 |
# Start background task
|
1372 |
background_tasks.add_task(
|
1373 |
process_generation_job,
|
1374 |
job_id,
|
1375 |
+
images,
|
1376 |
+
generate_options
|
1377 |
)
|
1378 |
|
1379 |
return JSONResponse({
|