Spaces:
Running
on
Zero
Running
on
Zero
just uplaod file to server first then process
Browse files
app.py
CHANGED
|
@@ -16,7 +16,6 @@ import requests
|
|
| 16 |
import base64
|
| 17 |
import io
|
| 18 |
import tempfile
|
| 19 |
-
import traceback
|
| 20 |
MAX_SEED = np.iinfo(np.int32).max
|
| 21 |
TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
|
| 22 |
os.makedirs(TMP_DIR, exist_ok=True)
|
|
@@ -142,167 +141,144 @@ def generate_model_from_images_and_upload(
|
|
| 142 |
model_description: str,
|
| 143 |
req: gr.Request
|
| 144 |
) -> str:
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
print(f"Error processing base64 image data for item {i} ('{current_image_name}'): {e_b64}")
|
| 198 |
-
traceback.print_exc()
|
| 199 |
-
continue
|
| 200 |
-
else:
|
| 201 |
-
print(f"Error: Unrecognized input_type '{input_type}' for item {i}. Skipping.")
|
| 202 |
-
continue
|
| 203 |
-
|
| 204 |
-
if not img_to_open_path:
|
| 205 |
-
print(f"Error: No valid image path could be derived for item {i} (name: '{current_image_name}', type: '{input_type}'). Skipping.")
|
| 206 |
-
continue
|
| 207 |
-
try:
|
| 208 |
-
print(f"Python INFO: Opening image from path: {img_to_open_path} (intended name for prompt: {current_image_name})")
|
| 209 |
-
img = Image.open(img_to_open_path)
|
| 210 |
-
image_basenames_for_prompt.append(os.path.splitext(current_image_name)[0] or f"image_{i}")
|
| 211 |
-
if img.mode == 'RGBA' or img.mode == 'P':
|
| 212 |
-
print(f"Converting image '{current_image_name}' from {img.mode} to RGB")
|
| 213 |
-
img = img.convert('RGB')
|
| 214 |
-
processed_img = pipeline.preprocess_image(img)
|
| 215 |
-
pil_images.append(processed_img)
|
| 216 |
-
print(f"Image '{current_image_name}' (item {i+1}) processed successfully and added to list.")
|
| 217 |
-
except Exception as e_img_proc:
|
| 218 |
-
print(f"Error opening or processing image at '{img_to_open_path}' (item {i}, name: '{current_image_name}'): {e_img_proc}")
|
| 219 |
-
traceback.print_exc()
|
| 220 |
-
finally:
|
| 221 |
-
if input_type == 'base64' and img_to_open_path and os.path.exists(img_to_open_path):
|
| 222 |
-
if TMP_DIR in os.path.abspath(img_to_open_path):
|
| 223 |
-
try:
|
| 224 |
-
os.remove(img_to_open_path)
|
| 225 |
-
print(f"Python INFO: Removed temporary base64 file: {img_to_open_path}")
|
| 226 |
-
except Exception as e_remove:
|
| 227 |
-
print(f"Python WARNING: Could not remove temp file {img_to_open_path}: {e_remove}")
|
| 228 |
-
else:
|
| 229 |
-
print(f"Python WARNING: Skipped deletion of temp file as it's not in TMP_DIR (or was a Gradio-managed URL path): {img_to_open_path}")
|
| 230 |
-
|
| 231 |
-
if not pil_images:
|
| 232 |
-
print("Python ERROR: No valid images could be processed from the input list.")
|
| 233 |
-
raise gr.Error("No valid images could be processed.")
|
| 234 |
-
print(f"Python INFO: Total PIL images ready for pipeline: {len(pil_images)}")
|
| 235 |
-
print("Running multi-image pipeline...")
|
| 236 |
-
outputs = pipeline.run_multi_image(
|
| 237 |
-
pil_images,
|
| 238 |
seed=seed_val,
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
)
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
print(f"
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
temp_glb_path = os.path.join(user_dir, temp_glb_filename)
|
| 258 |
-
print(f"Exporting GLB to temporary path: {temp_glb_path}")
|
| 259 |
-
glb_data.export(temp_glb_path)
|
| 260 |
-
torch.cuda.empty_cache()
|
| 261 |
-
print("CUDA cache cleared.")
|
| 262 |
-
print(f"Uploading GLB from {temp_glb_path} to {NODE_SERVER_UPLOAD_URL}")
|
| 263 |
persistent_url = None
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
print(f"No persistent URL in Node.js server response: {result}")
|
| 281 |
-
raise ValueError("Upload successful, but no persistent URL returned from Node.js server")
|
| 282 |
-
print(f"Successfully uploaded to Node server. Persistent URL: {persistent_url}")
|
| 283 |
-
except requests.exceptions.RequestException as upload_err:
|
| 284 |
-
print(f"FAILED to upload GLB to Node server: {upload_err}")
|
| 285 |
-
if hasattr(upload_err, 'response') and upload_err.response is not None:
|
| 286 |
-
print(f"Node server response status: {upload_err.response.status_code}")
|
| 287 |
-
print(f"Node server response text: {upload_err.response.text}")
|
| 288 |
-
raise gr.Error(f"Failed to upload result to backend server: {upload_err}")
|
| 289 |
-
except Exception as e:
|
| 290 |
-
print(f"UNEXPECTED error during upload: {e}", exc_info=True)
|
| 291 |
-
raise gr.Error(f"Unexpected error during upload: {e}")
|
| 292 |
-
finally:
|
| 293 |
-
if os.path.exists(temp_glb_path):
|
| 294 |
-
print(f"Cleaning up temporary GLB: {temp_glb_path}")
|
| 295 |
-
os.remove(temp_glb_path)
|
| 296 |
-
if not persistent_url:
|
| 297 |
-
print("Failed to obtain a persistent URL for the generated model.")
|
| 298 |
-
raise gr.Error("Failed to obtain a persistent URL for the generated model.")
|
| 299 |
-
print(f"Returning persistent URL: {persistent_url}")
|
| 300 |
return persistent_url
|
| 301 |
|
| 302 |
-
except Exception as
|
| 303 |
-
print(f"
|
|
|
|
| 304 |
traceback.print_exc()
|
| 305 |
-
raise gr.Error(f"
|
| 306 |
|
| 307 |
# Interfaz Gradio
|
| 308 |
with gr.Blocks(delete_cache=(600, 600)) as demo:
|
|
|
|
| 16 |
import base64
|
| 17 |
import io
|
| 18 |
import tempfile
|
|
|
|
| 19 |
MAX_SEED = np.iinfo(np.int32).max
|
| 20 |
TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'tmp')
|
| 21 |
os.makedirs(TMP_DIR, exist_ok=True)
|
|
|
|
| 141 |
model_description: str,
|
| 142 |
req: gr.Request
|
| 143 |
) -> str:
|
| 144 |
+
user_dir = os.path.join(TMP_DIR, str(req.session_hash))
|
| 145 |
+
os.makedirs(user_dir, exist_ok=True)
|
| 146 |
+
|
| 147 |
+
# --- DEBUG LOGS ---
|
| 148 |
+
print(f"Python DEBUG: Raw image_inputs (as received by function): {image_inputs}")
|
| 149 |
+
print(f"Python DEBUG: Type of image_inputs: {type(image_inputs)}")
|
| 150 |
+
if isinstance(image_inputs, list):
|
| 151 |
+
print(f"Python DEBUG: Length of image_inputs list: {len(image_inputs)}")
|
| 152 |
+
if len(image_inputs) > 0 and isinstance(image_inputs[0], dict):
|
| 153 |
+
print(f"Python DEBUG: First element of image_inputs (should be a dict): {image_inputs[0]}")
|
| 154 |
+
print(f"Python DEBUG: Type of first element: {type(image_inputs[0])}")
|
| 155 |
+
print(f"Python DEBUG: Received input_type from Node.js: '{input_type}'") # Should always be 'url' now
|
| 156 |
+
# --- END DEBUG LOGS ---
|
| 157 |
+
|
| 158 |
+
pil_images = []
|
| 159 |
+
image_basenames_for_prompt = []
|
| 160 |
+
|
| 161 |
+
for i, file_data_obj in enumerate(image_inputs): # file_data_obj is one dict from the list
|
| 162 |
+
img_to_open_path = None
|
| 163 |
+
current_image_name = file_data_obj.get('name', f"image_{i}.png")
|
| 164 |
+
|
| 165 |
+
print(f"Python DEBUG: Processing item {i}: {file_data_obj}, current_image_name: {current_image_name}")
|
| 166 |
+
|
| 167 |
+
# For URLs (which is now always the case from Node.js),
|
| 168 |
+
# Gradio should have downloaded the image and put its local path in file_data_obj.get('path')
|
| 169 |
+
img_to_open_path = file_data_obj.get('path')
|
| 170 |
+
if not img_to_open_path:
|
| 171 |
+
print(f"Error: 'path' was missing in item {i}: {file_data_obj}. Skipping.")
|
| 172 |
+
continue
|
| 173 |
+
print(f"Python INFO: Using Gradio-provided path for '{current_image_name}': {img_to_open_path}")
|
| 174 |
+
|
| 175 |
+
# Now, process the image using img_to_open_path
|
| 176 |
+
try:
|
| 177 |
+
print(f"Python INFO: Opening image from path: {img_to_open_path} (intended name for prompt: {current_image_name})")
|
| 178 |
+
img = Image.open(img_to_open_path)
|
| 179 |
+
|
| 180 |
+
image_basenames_for_prompt.append(os.path.splitext(current_image_name)[0] or f"image_{i}")
|
| 181 |
+
|
| 182 |
+
if img.mode == 'RGBA' or img.mode == 'P':
|
| 183 |
+
print(f"Converting image '{current_image_name}' from {img.mode} to RGB")
|
| 184 |
+
img = img.convert('RGB')
|
| 185 |
+
|
| 186 |
+
processed_img = pipeline.preprocess_image(img)
|
| 187 |
+
pil_images.append(processed_img)
|
| 188 |
+
print(f"Image '{current_image_name}' (item {i+1}) processed successfully and added to list.")
|
| 189 |
+
|
| 190 |
+
except Exception as e_img_proc:
|
| 191 |
+
print(f"Error opening or processing image at '{img_to_open_path}' (item {i}, name: '{current_image_name}'): {e_img_proc}")
|
| 192 |
+
import traceback
|
| 193 |
+
traceback.print_exc()
|
| 194 |
+
# Continue to next image if one fails
|
| 195 |
|
| 196 |
+
# No finally block needed here anymore for deleting temp base64 files
|
| 197 |
+
|
| 198 |
+
if not pil_images:
|
| 199 |
+
print("Error: No images could be processed from the input. Aborting generation.")
|
| 200 |
+
raise gr.Error("Failed to process any input images.")
|
| 201 |
+
|
| 202 |
+
print(f"Python INFO: Total images processed for pipeline: {len(pil_images)}")
|
| 203 |
+
effective_model_description = model_description
|
| 204 |
+
if not effective_model_description and image_basenames_for_prompt:
|
| 205 |
+
effective_model_description = "_prompted_by_" + "_and_".join(image_basenames_for_prompt)
|
| 206 |
+
effective_model_description = effective_model_description[:100] # Keep it reasonably short
|
| 207 |
+
elif not effective_model_description:
|
| 208 |
+
effective_model_description = "ImageGenModel"
|
| 209 |
+
print(f"Python INFO: Using model_description for upload: {effective_model_description}")
|
| 210 |
+
|
| 211 |
+
# Generate 3D model using the Trellis image pipeline
|
| 212 |
+
try:
|
| 213 |
+
print(f"Python INFO: Calling internal image_to_3d with {len(pil_images)} images.")
|
| 214 |
+
# The image_to_3d function expects a list of tuples (PIL.Image, str_filename_or_label)
|
| 215 |
+
# We have processed_img in pil_images which are already PIL.Image objects after pipeline.preprocess_image
|
| 216 |
+
# We can use the current_image_name (or derived basenames) as the string part if needed by image_to_3d,
|
| 217 |
+
# but Trellis's run_multi_image takes a list of PIL images directly.
|
| 218 |
+
# Let's adapt multiimages for image_to_3d to be List[Tuple[Image.Image, str]]
|
| 219 |
+
multiimages_for_pipeline = []
|
| 220 |
+
for idx, p_img in enumerate(pil_images):
|
| 221 |
+
# Create a simple label for each image for the tuple structure
|
| 222 |
+
label = image_basenames_for_prompt[idx] if idx < len(image_basenames_for_prompt) else f"image_{idx}"
|
| 223 |
+
multiimages_for_pipeline.append((p_img, label)) # p_img here is already the *processed* image tensor.
|
| 224 |
+
# image_to_3d will take image[0] from this list.
|
| 225 |
+
# This might need adjustment if image_to_3d expects raw PIL Images.
|
| 226 |
+
# Re-checking Trellis: pipeline.run_multi_image takes List[Image.Image]
|
| 227 |
+
# and preprocesses them internally if preprocess_image=True (default).
|
| 228 |
+
# Since we pre-process above, we should pass preprocess_image=False if run_multi_image allows.
|
| 229 |
+
# The `image_to_3d` in this file is a wrapper for run_multi_image.
|
| 230 |
+
# It passes `preprocess_image=False`.
|
| 231 |
+
# So, `pil_images` containing already processed images is what `image_to_3d` expects for `[image[0] for image in multiimages]`
|
| 232 |
+
|
| 233 |
+
state, _ = image_to_3d(
|
| 234 |
+
multiimages=[(img, name) for img, name in zip(pil_images, image_basenames_for_prompt)], # Pass list of (processed_PIL_image, name_str)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
seed=seed_val,
|
| 236 |
+
ss_guidance_strength=ss_guidance_strength_val,
|
| 237 |
+
ss_sampling_steps=ss_sampling_steps_val,
|
| 238 |
+
slat_guidance_strength=slat_guidance_strength_val,
|
| 239 |
+
slat_sampling_steps=slat_sampling_steps_val,
|
| 240 |
+
multiimage_algo=multiimage_algo_val,
|
| 241 |
+
req=req
|
| 242 |
+
)
|
| 243 |
+
if state is None:
|
| 244 |
+
print("Error: Internal image_to_3d returned None state!")
|
| 245 |
+
raise ValueError("Internal image_to_3d failed to return state")
|
| 246 |
+
print(f"Python INFO: Internal image_to_3d completed. State type: {type(state)}")
|
| 247 |
+
|
| 248 |
+
print("Python INFO: Calling internal extract_glb...")
|
| 249 |
+
glb_path, _ = extract_glb(
|
| 250 |
+
state, mesh_simplify_val, texture_size_val, req
|
| 251 |
)
|
| 252 |
+
if glb_path is None or not os.path.isfile(glb_path):
|
| 253 |
+
print(f"Error: Internal extract_glb returned None or invalid path: {glb_path}")
|
| 254 |
+
raise FileNotFoundError(f"Generated GLB file not found at {glb_path}")
|
| 255 |
+
print(f"Python INFO: Internal extract_glb completed. GLB path: {glb_path}")
|
| 256 |
+
|
| 257 |
+
print(f"Python INFO: Uploading GLB from {glb_path} to {NODE_SERVER_UPLOAD_URL}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
persistent_url = None
|
| 259 |
+
with open(glb_path, "rb") as f:
|
| 260 |
+
files = {"modelFile": (os.path.basename(glb_path), f, "model/gltf-binary")}
|
| 261 |
+
payload = {
|
| 262 |
+
"clientType": "imagen",
|
| 263 |
+
"modelStage": "imagen_mesh",
|
| 264 |
+
"prompt": effective_model_description # Use the description here
|
| 265 |
+
}
|
| 266 |
+
print(f"Python INFO: Upload payload: {payload}")
|
| 267 |
+
response = requests.post(NODE_SERVER_UPLOAD_URL, files=files, data=payload)
|
| 268 |
+
response.raise_for_status() # Raise an exception for bad status codes
|
| 269 |
+
result = response.json()
|
| 270 |
+
persistent_url = result.get("persistentUrl")
|
| 271 |
+
if not persistent_url:
|
| 272 |
+
print(f"Error: No persistent URL in Node.js server response: {result}")
|
| 273 |
+
raise ValueError("Upload successful, but no persistent URL returned")
|
| 274 |
+
print(f"Python INFO: Successfully uploaded to Node server. Persistent URL: {persistent_url}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
return persistent_url
|
| 276 |
|
| 277 |
+
except Exception as e:
|
| 278 |
+
print(f"ERROR in Image-to-3D pipeline: {e}")
|
| 279 |
+
import traceback
|
| 280 |
traceback.print_exc()
|
| 281 |
+
raise gr.Error(f"Image-to-3D pipeline failed: {e}")
|
| 282 |
|
| 283 |
# Interfaz Gradio
|
| 284 |
with gr.Blocks(delete_cache=(600, 600)) as demo:
|