fix(db): fix Trellis connector for updated Gradio API
- Add session_hash to all API calls (maintains gr.State between image_to_3d and extract_glb) - Pass is_multiimage=False at position 2 (9-param image_to_3d) - Pass output_buf=None at position 0 (3-param extract_glb) - Document full API parameter reference and failure modes - Add trellis-batch.sh for gentle sequential batch generation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,32 @@ Pipeline: upload image → start session → image_to_3d → extract_glb → dow
|
||||
Usage:
|
||||
python3 trellis_connector.py health
|
||||
python3 trellis_connector.py generate image.png [--output model.glb] [--simplify 0.95] [--texture-size 1024] [--seed 42] [--timeout 600]
|
||||
|
||||
Gradio API Parameter Reference (TRELLIS v1, microsoft/TRELLIS):
|
||||
|
||||
/image_to_3d — 9 inputs:
|
||||
0: image (Image) preprocessed image from /preprocess_image_1
|
||||
1: multiimages (Gallery) [] for single-image mode
|
||||
2: is_multiimage (State) False for single-image, True for multi-image
|
||||
3: seed (Slider) int, 0-2147483647
|
||||
4: ss_guidance (Slider) float, sparse structure guidance strength (default 7.5)
|
||||
5: ss_steps (Slider) int, sparse structure sampling steps (default 12)
|
||||
6: slat_guidance (Slider) float, structured latent guidance strength (default 3.0)
|
||||
7: slat_steps (Slider) int, structured latent sampling steps (default 12)
|
||||
8: multiimage_algo (Radio) "stochastic" or "multidiffusion"
|
||||
|
||||
/extract_glb — 3 inputs:
|
||||
0: output_buf (State) None — server uses internal state from image_to_3d
|
||||
1: simplify (Slider) float, mesh simplification ratio (default 0.95)
|
||||
2: texture_size (Slider) int, texture resolution (default 1024)
|
||||
|
||||
Common failure modes:
|
||||
- "needed 9, got 8": missing is_multiimage (position 2) — must pass False
|
||||
- "needed 3, got 2": missing output_buf (position 0) — must pass None
|
||||
- "'float' cannot be interpreted as int": numpy version issue on server,
|
||||
Gradio Sliders send all values as float. Fix: patch flow_euler.py on
|
||||
the server to cast steps to int, or pin numpy < 2.0
|
||||
- CUDA device mismatch after crash: restart the container to clear GPU state
|
||||
"""
|
||||
|
||||
import base64
|
||||
@@ -55,12 +81,20 @@ def health():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _call_api(base, endpoint, data, timeout=600):
|
||||
"""Call a Gradio API endpoint. Tries sync /api/ first, falls back to SSE /gradio_api/call/."""
|
||||
# Trellis uses the sync /api/ pattern
|
||||
def _call_api(base, endpoint, data, timeout=600, session_hash=None):
|
||||
"""Call a Gradio API endpoint with optional session tracking.
|
||||
|
||||
Gradio gr.State components are stored per session_hash on the server.
|
||||
All calls in a pipeline (image_to_3d → extract_glb) must share the same
|
||||
session_hash so the server can pass state between them.
|
||||
"""
|
||||
api_url = f"{base}/api{endpoint}"
|
||||
|
||||
payload = json.dumps({"data": data})
|
||||
body_dict = {"data": data}
|
||||
if session_hash:
|
||||
body_dict["session_hash"] = session_hash
|
||||
|
||||
payload = json.dumps(body_dict)
|
||||
req = urllib.request.Request(
|
||||
api_url,
|
||||
data=payload.encode(),
|
||||
@@ -73,7 +107,6 @@ def _call_api(base, endpoint, data, timeout=600):
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||||
result = json.loads(resp.read())
|
||||
# Sync Gradio returns {"data": [...], "is_generating": false, ...}
|
||||
if isinstance(result, dict) and "data" in result:
|
||||
return result["data"]
|
||||
return result
|
||||
@@ -179,9 +212,16 @@ def generate(image_path, output=None, simplify=0.95, texture_size=1024,
|
||||
name = os.path.splitext(os.path.basename(image_path))[0]
|
||||
output = f"{name}.glb"
|
||||
|
||||
# Generate a session hash — Gradio uses this to maintain gr.State between
|
||||
# separate API calls. Without it, image_to_3d's output state is lost before
|
||||
# extract_glb can read it.
|
||||
import random, string
|
||||
session = ''.join(random.choices(string.ascii_lowercase + string.digits, k=12))
|
||||
print(f" Session: {session}", file=sys.stderr)
|
||||
|
||||
# Step 1: Start session
|
||||
print("Step 1/5: Starting session...", file=sys.stderr)
|
||||
_call_api(base, "/start_session", [], timeout=30)
|
||||
session_result = _call_api(base, "/start_session", [], timeout=30, session_hash=session)
|
||||
|
||||
# Step 2: Upload and preprocess image
|
||||
print("Step 2/5: Uploading and preprocessing image...", file=sys.stderr)
|
||||
@@ -190,7 +230,7 @@ def generate(image_path, output=None, simplify=0.95, texture_size=1024,
|
||||
"path": uploaded_path,
|
||||
"meta": {"_type": "gradio.FileData"}
|
||||
}
|
||||
preprocess_result = _call_api(base, "/preprocess_image_1", [file_ref], timeout=60)
|
||||
preprocess_result = _call_api(base, "/preprocess_image_1", [file_ref], timeout=60, session_hash=session)
|
||||
|
||||
# _call_api returns the "data" array directly
|
||||
if isinstance(preprocess_result, list) and len(preprocess_result) > 0:
|
||||
@@ -200,28 +240,34 @@ def generate(image_path, output=None, simplify=0.95, texture_size=1024,
|
||||
|
||||
# Step 3: Get seed
|
||||
print("Step 3/5: Generating 3D model...", file=sys.stderr)
|
||||
seed_result = _call_api(base, "/get_seed", [True, seed], timeout=10)
|
||||
seed_result = _call_api(base, "/get_seed", [True, seed], timeout=10, session_hash=session)
|
||||
if isinstance(seed_result, list) and seed_result:
|
||||
actual_seed = seed_result[0]
|
||||
else:
|
||||
actual_seed = seed
|
||||
|
||||
# Step 4: Image to 3D
|
||||
# Parameters: image, multiimages, seed, ss_guidance, ss_steps, slat_guidance, slat_steps, algo
|
||||
# Gradio app has 9 inputs: Image, Gallery, State, Slider×5, Radio
|
||||
# State is a hidden session component at position 3 — pass None.
|
||||
# Slider order (from /info): seed, ss_guidance, ss_steps, slat_guidance, slat_steps
|
||||
# Note: server-side numpy bug requires patching flow_euler.py to cast steps to int.
|
||||
gen_result = _call_api(base, "/image_to_3d", [
|
||||
preprocessed_ref, # image
|
||||
[], # multiimages (empty)
|
||||
actual_seed, # seed
|
||||
False, # is_multiimage (boolean flag, not session state)
|
||||
actual_seed, # seed (Slider, 0-2147483647)
|
||||
7.5, # ss_guidance_strength
|
||||
12, # ss_sampling_steps
|
||||
3.0, # slat_guidance_strength
|
||||
12, # slat_sampling_steps
|
||||
"stochastic", # multiimage_algo
|
||||
], timeout=timeout)
|
||||
], timeout=timeout, session_hash=session)
|
||||
|
||||
# Step 5: Extract GLB
|
||||
# State (output_buf) is maintained server-side via session_hash.
|
||||
# extract_glb reads it automatically. Pass None as placeholder for the State component.
|
||||
print("Step 4/5: Extracting GLB...", file=sys.stderr)
|
||||
glb_result = _call_api(base, "/extract_glb", [simplify, texture_size], timeout=120)
|
||||
glb_result = _call_api(base, "/extract_glb", [None, simplify, texture_size], timeout=120, session_hash=session)
|
||||
|
||||
# _call_api returns the "data" array: [model_viewer_data, download_button_data]
|
||||
glb_url = None
|
||||
|
||||
Reference in New Issue
Block a user