Spaces:
Configuration error
Configuration error
Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -380,7 +380,6 @@ def preprocess_image(input: Image.Image) -> Image.Image:
|
|
| 380 |
return output
|
| 381 |
|
| 382 |
|
| 383 |
-
@spaces.GPU(duration=60)
|
| 384 |
def preprocess_images(images):
|
| 385 |
"""
|
| 386 |
Preprocess a list of input images for multi-image conditioning.
|
|
@@ -483,8 +482,18 @@ def split_image(image: Image.Image) -> List[Image.Image]:
|
|
| 483 |
return [preprocess_image(image) for image in images]
|
| 484 |
|
| 485 |
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
def image_to_3d(
|
|
|
|
| 488 |
seed,
|
| 489 |
resolution,
|
| 490 |
ss_guidance_strength,
|
|
@@ -499,19 +508,11 @@ def image_to_3d(
|
|
| 499 |
tex_slat_guidance_rescale,
|
| 500 |
tex_slat_sampling_steps,
|
| 501 |
tex_slat_rescale_t,
|
| 502 |
-
multiimages,
|
| 503 |
multiimage_algo,
|
| 504 |
tex_multiimage_algo,
|
| 505 |
req: gr.Request,
|
| 506 |
progress=gr.Progress(track_tqdm=True),
|
| 507 |
):
|
| 508 |
-
if not multiimages:
|
| 509 |
-
raise gr.Error("Please upload images or select an example first.")
|
| 510 |
-
|
| 511 |
-
# Preprocess images (background removal, cropping, etc.)
|
| 512 |
-
images = [image[0] for image in multiimages]
|
| 513 |
-
processed_images = [preprocess_image(img) for img in images]
|
| 514 |
-
|
| 515 |
# --- Sampling ---
|
| 516 |
outputs, latents = pipeline.run_multi_image(
|
| 517 |
processed_images,
|
|
@@ -546,15 +547,19 @@ def image_to_3d(
|
|
| 546 |
)
|
| 547 |
mesh = outputs[0]
|
| 548 |
mesh.simplify(16777216) # nvdiffrast limit
|
| 549 |
-
|
| 550 |
state = pack_state(latents)
|
| 551 |
torch.cuda.empty_cache()
|
|
|
|
|
|
|
| 552 |
|
| 553 |
-
|
| 554 |
-
|
|
|
|
|
|
|
| 555 |
def encode_preview_image(args):
|
| 556 |
m_idx, s_idx, render_key = args
|
| 557 |
-
img_base64 = image_to_base64(Image.fromarray(
|
| 558 |
return (m_idx, s_idx, img_base64)
|
| 559 |
|
| 560 |
encode_tasks = [
|
|
@@ -587,7 +592,6 @@ def image_to_3d(
|
|
| 587 |
btns_html = ""
|
| 588 |
for idx, mode in enumerate(MODES):
|
| 589 |
active_class = "active" if idx == DEFAULT_MODE else ""
|
| 590 |
-
# Note: onclick calls the JS function defined in Head
|
| 591 |
btns_html += f"""
|
| 592 |
<img src="{mode['icon_base64']}"
|
| 593 |
class="mode-btn {active_class}"
|
|
@@ -626,7 +630,7 @@ def image_to_3d(
|
|
| 626 |
return state, full_html
|
| 627 |
|
| 628 |
|
| 629 |
-
@spaces.GPU(duration=
|
| 630 |
def extract_glb(
|
| 631 |
state,
|
| 632 |
decimation_target,
|
|
@@ -675,18 +679,19 @@ def extract_glb(
|
|
| 675 |
|
| 676 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange", neutral_hue="slate"), css=css, head=head) as demo:
|
| 677 |
gr.HTML("""
|
| 678 |
-
<div style="display: flex; align-items:
|
| 679 |
-
<a href="https://www.opsiclear.com" target="_blank">
|
| 680 |
-
<img src="https://www.opsiclear.com/assets/logos/Logo_v2_compact_name.svg" alt="OpsiClear"
|
|
|
|
| 681 |
</a>
|
| 682 |
-
<div>
|
| 683 |
-
<h2 style="margin: 0;">Multi-View to 3D with <a href="https://microsoft.github.io/TRELLIS.2" target="_blank">TRELLIS.2</a></h2>
|
| 684 |
-
<ul style="margin:
|
| 685 |
<li>Upload multiple images from different viewpoints to create a 3D asset with multi-image conditioning.</li>
|
| 686 |
<li>Click an example below to load a pre-made multi-view set, or upload your own images.</li>
|
| 687 |
<li>Click <b>Generate</b> to create the 3D model, then <b>Extract GLB</b> to export.</li>
|
| 688 |
-
<li style="color: #e67300;"><b>
|
| 689 |
-
<li style="color: #cc3333;"><b>
|
| 690 |
</ul>
|
| 691 |
</div>
|
| 692 |
</div>
|
|
@@ -745,6 +750,8 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange", neutral_hue="slate"),
|
|
| 745 |
)
|
| 746 |
|
| 747 |
output_buf = gr.State()
|
|
|
|
|
|
|
| 748 |
|
| 749 |
|
| 750 |
# Handlers
|
|
@@ -760,15 +767,23 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange", neutral_hue="slate"),
|
|
| 760 |
get_seed,
|
| 761 |
inputs=[randomize_seed, seed],
|
| 762 |
outputs=[seed],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 763 |
).then(
|
| 764 |
image_to_3d,
|
| 765 |
inputs=[
|
| 766 |
-
seed, resolution,
|
| 767 |
ss_guidance_strength, ss_guidance_rescale, ss_sampling_steps, ss_rescale_t,
|
| 768 |
shape_slat_guidance_strength, shape_slat_guidance_rescale, shape_slat_sampling_steps, shape_slat_rescale_t,
|
| 769 |
tex_slat_guidance_strength, tex_slat_guidance_rescale, tex_slat_sampling_steps, tex_slat_rescale_t,
|
| 770 |
-
|
| 771 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
outputs=[output_buf, preview_output],
|
| 773 |
)
|
| 774 |
|
|
|
|
| 380 |
return output
|
| 381 |
|
| 382 |
|
|
|
|
| 383 |
def preprocess_images(images):
|
| 384 |
"""
|
| 385 |
Preprocess a list of input images for multi-image conditioning.
|
|
|
|
| 482 |
return [preprocess_image(image) for image in images]
|
| 483 |
|
| 484 |
|
| 485 |
+
def preprocess_for_generate(multiimages):
|
| 486 |
+
"""Preprocess images before GPU generation. No GPU needed — background
|
| 487 |
+
removal is a network call to the BRIA RMBG Space."""
|
| 488 |
+
if not multiimages:
|
| 489 |
+
raise gr.Error("Please upload images or select an example first.")
|
| 490 |
+
images = [image[0] for image in multiimages]
|
| 491 |
+
return [preprocess_image(img) for img in images]
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
@spaces.GPU(duration=180)
|
| 495 |
def image_to_3d(
|
| 496 |
+
processed_images,
|
| 497 |
seed,
|
| 498 |
resolution,
|
| 499 |
ss_guidance_strength,
|
|
|
|
| 508 |
tex_slat_guidance_rescale,
|
| 509 |
tex_slat_sampling_steps,
|
| 510 |
tex_slat_rescale_t,
|
|
|
|
| 511 |
multiimage_algo,
|
| 512 |
tex_multiimage_algo,
|
| 513 |
req: gr.Request,
|
| 514 |
progress=gr.Progress(track_tqdm=True),
|
| 515 |
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
# --- Sampling ---
|
| 517 |
outputs, latents = pipeline.run_multi_image(
|
| 518 |
processed_images,
|
|
|
|
| 547 |
)
|
| 548 |
mesh = outputs[0]
|
| 549 |
mesh.simplify(16777216) # nvdiffrast limit
|
| 550 |
+
render_images = render_utils.render_snapshot(mesh, resolution=1024, r=2, fov=36, nviews=STEPS, envmap=envmap)
|
| 551 |
state = pack_state(latents)
|
| 552 |
torch.cuda.empty_cache()
|
| 553 |
+
return state, render_images
|
| 554 |
+
|
| 555 |
|
| 556 |
+
def build_preview_html(state, render_images):
|
| 557 |
+
"""Encode rendered images to base64 and build the HTML preview.
|
| 558 |
+
No GPU needed — pure CPU work."""
|
| 559 |
+
# Encode 48 images in parallel
|
| 560 |
def encode_preview_image(args):
|
| 561 |
m_idx, s_idx, render_key = args
|
| 562 |
+
img_base64 = image_to_base64(Image.fromarray(render_images[render_key][s_idx]))
|
| 563 |
return (m_idx, s_idx, img_base64)
|
| 564 |
|
| 565 |
encode_tasks = [
|
|
|
|
| 592 |
btns_html = ""
|
| 593 |
for idx, mode in enumerate(MODES):
|
| 594 |
active_class = "active" if idx == DEFAULT_MODE else ""
|
|
|
|
| 595 |
btns_html += f"""
|
| 596 |
<img src="{mode['icon_base64']}"
|
| 597 |
class="mode-btn {active_class}"
|
|
|
|
| 630 |
return state, full_html
|
| 631 |
|
| 632 |
|
| 633 |
+
@spaces.GPU(duration=180)
|
| 634 |
def extract_glb(
|
| 635 |
state,
|
| 636 |
decimation_target,
|
|
|
|
| 679 |
|
| 680 |
with gr.Blocks(theme=gr.themes.Soft(primary_hue="orange", neutral_hue="slate"), css=css, head=head) as demo:
|
| 681 |
gr.HTML("""
|
| 682 |
+
<div style="display: flex; align-items: flex-start; gap: 24px; padding: 16px 20px; margin-bottom: 12px; border-radius: 12px; background: var(--block-background-fill); border: 1px solid var(--border-color-primary);">
|
| 683 |
+
<a href="https://www.opsiclear.com" target="_blank" style="flex-shrink: 0; display: flex; align-items: center;">
|
| 684 |
+
<img src="https://www.opsiclear.com/assets/logos/Logo_v2_compact_name.svg" alt="OpsiClear"
|
| 685 |
+
style="width: 72px; height: 72px; object-fit: contain;">
|
| 686 |
</a>
|
| 687 |
+
<div style="min-width: 0;">
|
| 688 |
+
<h2 style="margin: 0 0 8px 0; font-size: 1.25rem; line-height: 1.3;">Multi-View to 3D with <a href="https://microsoft.github.io/TRELLIS.2" target="_blank" style="text-decoration: none; color: var(--color-accent);">TRELLIS.2</a></h2>
|
| 689 |
+
<ul style="margin: 0; padding-left: 18px; font-size: 0.9rem; line-height: 1.6; color: var(--body-text-color-subdued, var(--body-text-color));">
|
| 690 |
<li>Upload multiple images from different viewpoints to create a 3D asset with multi-image conditioning.</li>
|
| 691 |
<li>Click an example below to load a pre-made multi-view set, or upload your own images.</li>
|
| 692 |
<li>Click <b>Generate</b> to create the 3D model, then <b>Extract GLB</b> to export.</li>
|
| 693 |
+
<li style="color: #e67300;"><b>Note:</b> Generation quality is highly sensitive to parameters. Adjust settings in Advanced Settings if results are unsatisfactory.</li>
|
| 694 |
+
<li style="color: #cc3333;"><b>Non-Commercial:</b> This space uses models with licenses that <b>forbid commercial use</b> (BRIA RMBG-2.0: CC BY-NC 4.0, nvdiffrast/nvdiffrec: NVIDIA Source Code License).</li>
|
| 695 |
</ul>
|
| 696 |
</div>
|
| 697 |
</div>
|
|
|
|
| 750 |
)
|
| 751 |
|
| 752 |
output_buf = gr.State()
|
| 753 |
+
processed_buf = gr.State()
|
| 754 |
+
render_buf = gr.State()
|
| 755 |
|
| 756 |
|
| 757 |
# Handlers
|
|
|
|
| 767 |
get_seed,
|
| 768 |
inputs=[randomize_seed, seed],
|
| 769 |
outputs=[seed],
|
| 770 |
+
).then(
|
| 771 |
+
preprocess_for_generate,
|
| 772 |
+
inputs=[multiimage_prompt],
|
| 773 |
+
outputs=[processed_buf],
|
| 774 |
).then(
|
| 775 |
image_to_3d,
|
| 776 |
inputs=[
|
| 777 |
+
processed_buf, seed, resolution,
|
| 778 |
ss_guidance_strength, ss_guidance_rescale, ss_sampling_steps, ss_rescale_t,
|
| 779 |
shape_slat_guidance_strength, shape_slat_guidance_rescale, shape_slat_sampling_steps, shape_slat_rescale_t,
|
| 780 |
tex_slat_guidance_strength, tex_slat_guidance_rescale, tex_slat_sampling_steps, tex_slat_rescale_t,
|
| 781 |
+
multiimage_algo, tex_multiimage_algo
|
| 782 |
],
|
| 783 |
+
outputs=[output_buf, render_buf],
|
| 784 |
+
).then(
|
| 785 |
+
build_preview_html,
|
| 786 |
+
inputs=[output_buf, render_buf],
|
| 787 |
outputs=[output_buf, preview_output],
|
| 788 |
)
|
| 789 |
|