kendaaa Michael-Lobanoff commited on
Commit
144b79d
·
verified ·
1 Parent(s): cac982a

Upload app.py (#1)

Browse files

- Upload app.py (9cf3d6349d362e183d2d53d432d5375e03d282ac)


Co-authored-by: Michael Lobanov <Michael-Lobanoff@users.noreply.huggingface.co>

Files changed (1) hide show
  1. app.py +105 -279
app.py CHANGED
@@ -6,9 +6,6 @@ import torch
6
  from PIL import Image
7
  import trimesh
8
  import random
9
- import urllib.request
10
- import io
11
- import base64
12
  from transformers import AutoModelForImageSegmentation
13
  from torchvision import transforms
14
  from huggingface_hub import hf_hub_download, snapshot_download
@@ -49,24 +46,17 @@ sys.path.append(MV_ADAPTER_CODE_DIR)
49
  sys.path.append(os.path.join(MV_ADAPTER_CODE_DIR, "scripts"))
50
 
51
  HEADER = """
52
-
53
  # 🔮 Image to 3D with [TripoSG](https://github.com/VAST-AI-Research/TripoSG)
54
-
55
  ## State-of-the-art Open Source 3D Generation Using Large-Scale Rectified Flow Transformers
56
-
57
  <p style="font-size: 1.1em;">By <a href="https://www.tripo3d.ai/" style="color: #1E90FF; text-decoration: none; font-weight: bold;">Tripo</a></p>
58
-
59
  ## 📋 Quick Start Guide:
60
  1. **Upload an image** (single object works best)
61
  2. Click **Generate Shape** to create the 3D mesh
62
  3. Click **Apply Texture** to add textures
63
  4. Use **Download GLB** to save your 3D model
64
  5. Adjust parameters under **Generation Settings** for fine-tuning
65
-
66
  Best results come from clean, well-lit images with clear subject isolation. Try it now!
67
-
68
  <p style="font-size: 0.9em; margin-top: 10px;">Texture generation powered by <a href="https://github.com/huanngzh/MV-Adapter" style="color: #1E90FF; text-decoration: none;">MV-Adapter</a> - a versatile multi-view adapter for consistent texture generation. Try the <a href="https://huggingface.co/spaces/VAST-AI/MV-Adapter-I2MV-SDXL" style="color: #1E90FF; text-decoration: none;">MV-Adapter demo</a> for multi-view image generation.</p>
69
-
70
  """
71
 
72
  # # triposg
@@ -122,184 +112,38 @@ def end_session(req: gr.Request):
122
  save_dir = os.path.join(TMP_DIR, str(req.session_hash))
123
  shutil.rmtree(save_dir)
124
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  def get_random_hex():
126
  random_bytes = os.urandom(8)
127
  random_hex = random_bytes.hex()
128
  return random_hex
129
 
130
- def _image_from_value(value):
131
- if value is None:
132
- return None
133
- if isinstance(value, Image.Image):
134
- return value
135
- if isinstance(value, np.ndarray):
136
- return Image.fromarray(value)
137
- if isinstance(value, dict):
138
- path = value.get("path")
139
- url = value.get("url")
140
- data = value.get("data")
141
- if isinstance(path, str):
142
- try:
143
- return Image.open(path)
144
- except Exception:
145
- return None
146
- if isinstance(url, str) and url.startswith(("http://", "https://")):
147
- try:
148
- with urllib.request.urlopen(url) as resp:
149
- data = resp.read()
150
- return Image.open(io.BytesIO(data))
151
- except Exception:
152
- return None
153
- if data is not None:
154
- try:
155
- if isinstance(data, bytes):
156
- return Image.open(io.BytesIO(data))
157
- if isinstance(data, str):
158
- if data.startswith("data:image/"):
159
- _, b64 = data.split(",", 1)
160
- return Image.open(io.BytesIO(base64.b64decode(b64)))
161
- return Image.open(io.BytesIO(base64.b64decode(data)))
162
- except Exception:
163
- return None
164
- if hasattr(value, "path"):
165
- path = getattr(value, "path", None)
166
- if isinstance(path, str):
167
- try:
168
- return Image.open(path)
169
- except Exception:
170
- return None
171
- if isinstance(value, str) and value.startswith("data:image/"):
172
- try:
173
- header, b64 = value.split(",", 1)
174
- data = base64.b64decode(b64)
175
- return Image.open(io.BytesIO(data))
176
- except Exception:
177
- return None
178
- if isinstance(value, str):
179
- try:
180
- return Image.open(value)
181
- except Exception:
182
- return None
183
- return None
184
-
185
- def _build_composite(image_dict):
186
- composite_img = _image_from_value(image_dict.get("composite"))
187
- if composite_img is not None:
188
- return composite_img
189
-
190
- background = _image_from_value(image_dict.get("background"))
191
- layers = image_dict.get("layers") or []
192
- layer_images = [_image_from_value(layer) for layer in layers]
193
- layer_images = [img for img in layer_images if img is not None]
194
-
195
- if background is None and not layer_images:
196
- return None
197
-
198
- if background is None and layer_images:
199
- background = Image.new("RGBA", layer_images[0].size, (255, 255, 255, 255))
200
- elif background is not None:
201
- background = background.convert("RGBA")
202
-
203
- base = background
204
- for layer_img in layer_images:
205
- base = Image.alpha_composite(base, layer_img.convert("RGBA"))
206
-
207
- return base.convert("RGB")
208
-
209
- def _to_image_path(image_input, suffix=".png"):
210
- if isinstance(image_input, dict):
211
- path = image_input.get("path")
212
- url = image_input.get("url")
213
- data = image_input.get("data")
214
- if isinstance(path, str):
215
- try:
216
- img = Image.open(path)
217
- out_path = os.path.join(TMP_DIR, f"input_{get_random_hex()}{suffix}")
218
- img.save(out_path)
219
- return out_path
220
- except Exception:
221
- pass
222
- if isinstance(url, str) and url.startswith(("http://", "https://")):
223
- try:
224
- with urllib.request.urlopen(url) as resp:
225
- data = resp.read()
226
- out_path = os.path.join(TMP_DIR, f"input_{get_random_hex()}{suffix}")
227
- with open(out_path, "wb") as f:
228
- f.write(data)
229
- return out_path
230
- except Exception:
231
- pass
232
- if data is not None:
233
- try:
234
- if isinstance(data, bytes):
235
- raw = data
236
- elif isinstance(data, str):
237
- if data.startswith("data:image/"):
238
- _, b64 = data.split(",", 1)
239
- raw = base64.b64decode(b64)
240
- else:
241
- raw = base64.b64decode(data)
242
- else:
243
- raw = None
244
- if raw:
245
- out_path = os.path.join(TMP_DIR, f"input_{get_random_hex()}{suffix}")
246
- with open(out_path, "wb") as f:
247
- f.write(raw)
248
- return out_path
249
- except Exception:
250
- pass
251
- image = _build_composite(image_input)
252
- else:
253
- if hasattr(image_input, "path"):
254
- path = getattr(image_input, "path", None)
255
- if isinstance(path, str):
256
- try:
257
- img = Image.open(path)
258
- out_path = os.path.join(TMP_DIR, f"input_{get_random_hex()}{suffix}")
259
- img.save(out_path)
260
- return out_path
261
- except Exception:
262
- pass
263
- if isinstance(image_input, str) and os.path.isfile(image_input):
264
- return image_input
265
- image = _image_from_value(image_input)
266
- if image is None:
267
- return None
268
- out_path = os.path.join(TMP_DIR, f"input_{get_random_hex()}{suffix}")
269
- image.save(out_path)
270
- return out_path
271
-
272
- def _describe_image_input(value):
273
- try:
274
- if isinstance(value, dict):
275
- path = value.get("path")
276
- url = value.get("url")
277
- keys = list(value.keys())
278
- return f"dict keys={keys} path={path} url={url}"
279
- if hasattr(value, "path"):
280
- return f"obj path={getattr(value, 'path', None)}"
281
- return f"type={type(value)}"
282
- except Exception as e:
283
- return f"describe_error={e}"
284
-
285
  def get_random_seed(randomize_seed, seed):
286
  if randomize_seed:
287
  seed = random.randint(0, MAX_SEED)
288
  return seed
289
 
290
  @spaces.GPU(duration=180)
291
- def run_full(image: object, req: gr.Request):
292
  seed = 0
293
  num_inference_steps = 50
294
  guidance_scale = 7.5
295
  simplify = True
296
  target_face_num = DEFAULT_FACE_NUMBER
297
 
298
- image_path = _to_image_path(image)
299
- if image_path is None:
300
- raise ValueError(f"No valid image provided. input={_describe_image_input(image)}")
301
-
302
- image_seg = prepare_image(image_path, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
303
 
304
  outputs = triposg_pipe(
305
  image=image_seg,
@@ -359,7 +203,6 @@ def run_full(image: object, req: gr.Request):
359
  .to(DEVICE)
360
  )
361
 
362
- image = Image.open(image_path)
363
  image = remove_bg_fn(image)
364
  image = preprocess_image(image, height, width)
365
 
@@ -411,12 +254,13 @@ def run_full(image: object, req: gr.Request):
411
 
412
  @spaces.GPU()
413
  @torch.no_grad()
414
- def run_segmentation(image: object):
415
- image_path = _to_image_path(image)
416
- if image_path is None:
417
- raise ValueError(f"No valid image provided. input={_describe_image_input(image)}")
418
- image = prepare_image(image_path, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
419
- return image
 
420
 
421
  @spaces.GPU(duration=90)
422
  @torch.no_grad()
@@ -429,6 +273,7 @@ def image_to_3d(
429
  target_face_num: int,
430
  req: gr.Request
431
  ):
 
432
  outputs = triposg_pipe(
433
  image=image,
434
  generator=torch.Generator(device=triposg_pipe.device).manual_seed(seed),
@@ -491,10 +336,7 @@ def run_texture(image: Image, mesh_path: str, seed: int, req: gr.Request):
491
  .to(DEVICE)
492
  )
493
 
494
- image_path = _to_image_path(image)
495
- if image_path is None:
496
- raise ValueError("No valid image provided.")
497
- image = Image.open(image_path)
498
  image = remove_bg_fn(image)
499
  image = preprocess_image(image, height, width)
500
 
@@ -545,111 +387,95 @@ def run_texture(image: Image, mesh_path: str, seed: int, req: gr.Request):
545
  return textured_glb_path
546
 
547
 
 
 
 
548
  with gr.Blocks(title="TripoSG") as demo:
549
  gr.Markdown(HEADER)
550
 
551
- def debug_echo_image(image_input: object):
552
- image = _build_composite(image_input) if isinstance(image_input, dict) else _image_from_value(image_input)
553
- if image is None:
554
- raise ValueError("No valid image provided.")
555
- return image.convert("RGB")
556
-
557
- with gr.Tabs():
558
- with gr.Tab("Main"):
559
  with gr.Row():
560
- with gr.Column():
561
- with gr.Row():
562
- image_prompts = gr.ImageEditor(label="Input Image", image_mode="RGB")
563
- seg_image = gr.Image(
564
- label="Segmentation Result", type="pil", format="png", interactive=False
565
- )
566
-
567
- with gr.Accordion("Generation Settings", open=True):
568
- seed = gr.Slider(
569
- label="Seed",
570
- minimum=0,
571
- maximum=MAX_SEED,
572
- step=0,
573
- value=0
574
- )
575
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
576
- num_inference_steps = gr.Slider(
577
- label="Number of inference steps",
578
- minimum=8,
579
- maximum=50,
580
- step=1,
581
- value=50,
582
- )
583
- guidance_scale = gr.Slider(
584
- label="CFG scale",
585
- minimum=0.0,
586
- maximum=20.0,
587
- step=0.1,
588
- value=7.0,
589
- )
590
-
591
- with gr.Row():
592
- reduce_face = gr.Checkbox(label="Simplify Mesh", value=True)
593
- target_face_num = gr.Slider(maximum=1000000, minimum=10000, value=DEFAULT_FACE_NUMBER, label="Target Face Number")
594
-
595
- gen_button = gr.Button("Generate Shape", variant="primary")
596
- gen_texture_button = gr.Button("Apply Texture", interactive=False)
597
-
598
- with gr.Column():
599
- model_output = gr.Model3D(label="Generated GLB", interactive=False)
600
- textured_model_output = gr.Model3D(label="Textured GLB", interactive=False)
601
 
602
- with gr.Row():
603
- examples = gr.Examples(
604
- examples=[
605
- f"{TRIPOSG_CODE_DIR}/assets/example_data/{image}"
606
- for image in os.listdir(f"{TRIPOSG_CODE_DIR}/assets/example_data")
607
- ],
608
- fn=run_full,
609
- inputs=[image_prompts],
610
- outputs=[seg_image, model_output, textured_model_output],
611
- cache_examples=True,
 
 
 
 
 
612
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
613
 
614
- gen_button.click(
615
- run_segmentation,
616
- inputs=[image_prompts],
617
- outputs=[seg_image]
618
- ).then(
619
- get_random_seed,
620
- inputs=[randomize_seed, seed],
621
- outputs=[seed],
622
- ).then(
623
- image_to_3d,
624
- inputs=[
625
- seg_image,
626
- seed,
627
- num_inference_steps,
628
- guidance_scale,
629
- reduce_face,
630
- target_face_num
631
- ],
632
- outputs=[model_output]
633
- ).then(lambda: gr.Button(interactive=True), outputs=[gen_texture_button])
634
-
635
- gen_texture_button.click(
636
- run_texture,
637
- inputs=[image_prompts, model_output, seed],
638
- outputs=[textured_model_output]
639
- )
640
-
641
- with gr.Tab("Test Upload"):
642
- test_input = gr.ImageEditor(label="Test Image Input", image_mode="RGB")
643
- test_button = gr.Button("Test Input")
644
- test_output = gr.Image(label="Composite Output", type="pil")
645
-
646
- test_button.click(
647
- debug_echo_image,
648
- inputs=[test_input],
649
- outputs=[test_output]
650
- )
 
651
 
652
  demo.load(start_session)
653
  demo.unload(end_session)
654
 
655
- demo.launch(show_error=True)
 
6
  from PIL import Image
7
  import trimesh
8
  import random
 
 
 
9
  from transformers import AutoModelForImageSegmentation
10
  from torchvision import transforms
11
  from huggingface_hub import hf_hub_download, snapshot_download
 
46
  sys.path.append(os.path.join(MV_ADAPTER_CODE_DIR, "scripts"))
47
 
48
  HEADER = """
 
49
  # 🔮 Image to 3D with [TripoSG](https://github.com/VAST-AI-Research/TripoSG)
 
50
  ## State-of-the-art Open Source 3D Generation Using Large-Scale Rectified Flow Transformers
 
51
  <p style="font-size: 1.1em;">By <a href="https://www.tripo3d.ai/" style="color: #1E90FF; text-decoration: none; font-weight: bold;">Tripo</a></p>
 
52
  ## 📋 Quick Start Guide:
53
  1. **Upload an image** (single object works best)
54
  2. Click **Generate Shape** to create the 3D mesh
55
  3. Click **Apply Texture** to add textures
56
  4. Use **Download GLB** to save your 3D model
57
  5. Adjust parameters under **Generation Settings** for fine-tuning
 
58
  Best results come from clean, well-lit images with clear subject isolation. Try it now!
 
59
  <p style="font-size: 0.9em; margin-top: 10px;">Texture generation powered by <a href="https://github.com/huanngzh/MV-Adapter" style="color: #1E90FF; text-decoration: none;">MV-Adapter</a> - a versatile multi-view adapter for consistent texture generation. Try the <a href="https://huggingface.co/spaces/VAST-AI/MV-Adapter-I2MV-SDXL" style="color: #1E90FF; text-decoration: none;">MV-Adapter demo</a> for multi-view image generation.</p>
 
60
  """
61
 
62
  # # triposg
 
112
  save_dir = os.path.join(TMP_DIR, str(req.session_hash))
113
  shutil.rmtree(save_dir)
114
 
115
+ def normalize_image(image):
116
+ if image is None:
117
+ raise ValueError("Image is None")
118
+
119
+ if isinstance(image, Image.Image):
120
+ return image.convert("RGB")
121
+
122
+ if isinstance(image, np.ndarray):
123
+ return Image.fromarray(image).convert("RGB")
124
+
125
+ raise TypeError(f"Unsupported image type: {type(image)}")
126
+
127
  def get_random_hex():
128
  random_bytes = os.urandom(8)
129
  random_hex = random_bytes.hex()
130
  return random_hex
131
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  def get_random_seed(randomize_seed, seed):
133
  if randomize_seed:
134
  seed = random.randint(0, MAX_SEED)
135
  return seed
136
 
137
  @spaces.GPU(duration=180)
138
+ def run_full(image: Image, req: gr.Request):
139
  seed = 0
140
  num_inference_steps = 50
141
  guidance_scale = 7.5
142
  simplify = True
143
  target_face_num = DEFAULT_FACE_NUMBER
144
 
145
+ image = normalize_image(image)
146
+ image_seg = prepare_image(image, bg_color=np.array([1.0, 1.0, 1.0]), rmbg_net=rmbg_net)
 
 
 
147
 
148
  outputs = triposg_pipe(
149
  image=image_seg,
 
203
  .to(DEVICE)
204
  )
205
 
 
206
  image = remove_bg_fn(image)
207
  image = preprocess_image(image, height, width)
208
 
 
254
 
255
  @spaces.GPU()
256
  @torch.no_grad()
257
+ def run_segmentation(image: Image.Image):
258
+ image = normalize_image(image)
259
+ return prepare_image(
260
+ image,
261
+ bg_color=np.array([1.0, 1.0, 1.0]),
262
+ rmbg_net=rmbg_net
263
+ )
264
 
265
  @spaces.GPU(duration=90)
266
  @torch.no_grad()
 
273
  target_face_num: int,
274
  req: gr.Request
275
  ):
276
+ image = normalize_image(image)
277
  outputs = triposg_pipe(
278
  image=image,
279
  generator=torch.Generator(device=triposg_pipe.device).manual_seed(seed),
 
336
  .to(DEVICE)
337
  )
338
 
339
+ image = normalize_image(image)
 
 
 
340
  image = remove_bg_fn(image)
341
  image = preprocess_image(image, height, width)
342
 
 
387
  return textured_glb_path
388
 
389
 
390
+
391
+
392
+
393
  with gr.Blocks(title="TripoSG") as demo:
394
  gr.Markdown(HEADER)
395
 
396
+ with gr.Row():
397
+ with gr.Column():
 
 
 
 
 
 
398
  with gr.Row():
399
+ image_prompts = gr.Image(label="Input Image", type="pil")
400
+ seg_image = gr.Image(
401
+ label="Segmentation Result", type="pil", format="png", interactive=False
402
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
 
404
+ with gr.Accordion("Generation Settings", open=True):
405
+ seed = gr.Slider(
406
+ label="Seed",
407
+ minimum=0,
408
+ maximum=MAX_SEED,
409
+ step=0,
410
+ value=0
411
+ )
412
+ randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
413
+ num_inference_steps = gr.Slider(
414
+ label="Number of inference steps",
415
+ minimum=8,
416
+ maximum=50,
417
+ step=1,
418
+ value=50,
419
  )
420
+ guidance_scale = gr.Slider(
421
+ label="CFG scale",
422
+ minimum=0.0,
423
+ maximum=20.0,
424
+ step=0.1,
425
+ value=7.0,
426
+ )
427
+
428
+ with gr.Row():
429
+ reduce_face = gr.Checkbox(label="Simplify Mesh", value=True)
430
+ target_face_num = gr.Slider(maximum=1000000, minimum=10000, value=DEFAULT_FACE_NUMBER, label="Target Face Number")
431
+
432
+ gen_button = gr.Button("Generate Shape", variant="primary")
433
+ gen_texture_button = gr.Button("Apply Texture", interactive=False)
434
+
435
+ with gr.Column():
436
+ model_output = gr.Model3D(label="Generated GLB", interactive=False)
437
+ textured_model_output = gr.Model3D(label="Textured GLB", interactive=False)
438
 
439
+ with gr.Row():
440
+ examples = gr.Examples(
441
+ examples=[
442
+ f"{TRIPOSG_CODE_DIR}/assets/example_data/{image}"
443
+ for image in os.listdir(f"{TRIPOSG_CODE_DIR}/assets/example_data")
444
+ ],
445
+ fn=run_full,
446
+ inputs=[image_prompts],
447
+ outputs=[seg_image, model_output, textured_model_output],
448
+ cache_examples=True,
449
+ )
450
+
451
+ gen_button.click(
452
+ run_segmentation,
453
+ inputs=[image_prompts],
454
+ outputs=[seg_image]
455
+ ).then(
456
+ get_random_seed,
457
+ inputs=[randomize_seed, seed],
458
+ outputs=[seed],
459
+ ).then(
460
+ image_to_3d,
461
+ inputs=[
462
+ seg_image,
463
+ seed,
464
+ num_inference_steps,
465
+ guidance_scale,
466
+ reduce_face,
467
+ target_face_num
468
+ ],
469
+ outputs=[model_output]
470
+ ).then(lambda: gr.Button(interactive=True), outputs=[gen_texture_button])
471
+
472
+ gen_texture_button.click(
473
+ run_texture,
474
+ inputs=[image_prompts, model_output, seed],
475
+ outputs=[textured_model_output]
476
+ )
477
 
478
  demo.load(start_session)
479
  demo.unload(end_session)
480
 
481
+ demo.launch()