Spaces:
Running
Running
demo init
Browse files- .gitignore +2 -0
- Dockerfile +30 -0
- launch.py +428 -0
- style.css +13 -0
.gitignore
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
examples_data/
|
2 |
+
examples_display/
|
Dockerfile
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.12-slim
|
2 |
+
|
3 |
+
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
|
4 |
+
ENV TZ=America/Los_Angeles
|
5 |
+
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
6 |
+
|
7 |
+
RUN pip3 install --no-cache-dir \
|
8 |
+
numpy \
|
9 |
+
uuid \
|
10 |
+
json \
|
11 |
+
trimesh \
|
12 |
+
Pillow \
|
13 |
+
gradio==4.39.0
|
14 |
+
|
15 |
+
# Set up a new user named "user" with user ID 1000
|
16 |
+
RUN useradd -m -u 1000 user
|
17 |
+
# Switch to the "user" user
|
18 |
+
USER user
|
19 |
+
# Set home to the user's home directory
|
20 |
+
ENV HOME=/home/user \
|
21 |
+
PATH=/home/user/.local/bin:$PATH \
|
22 |
+
PYTHONPATH=$HOME/app \
|
23 |
+
PYTHONUNBUFFERED=1 \
|
24 |
+
SYSTEM=spaces
|
25 |
+
# Set the working directory to the user's home directory
|
26 |
+
WORKDIR $HOME/app
|
27 |
+
# Copy the current directory contents into the container at $HOME/app setting the owner to the user
|
28 |
+
COPY --chown=user . $HOME/app
|
29 |
+
|
30 |
+
CMD ["python3", "launch.py"]
|
launch.py
ADDED
@@ -0,0 +1,428 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import uuid
|
4 |
+
import numpy as np
|
5 |
+
import gradio as gr
|
6 |
+
import trimesh
|
7 |
+
import zipfile
|
8 |
+
import subprocess
|
9 |
+
from datetime import datetime
|
10 |
+
from functools import partial
|
11 |
+
from PIL import Image, ImageChops
|
12 |
+
from huggingface_hub import snapshot_download
|
13 |
+
|
14 |
+
|
15 |
+
is_local_run = os.path.exists("../SpaRP_API")
|
16 |
+
|
17 |
+
code_dir = snapshot_download("sudo-ai/SpaRP_API", token=os.environ['HF_TOKEN']) if not is_local_run else "../SpaRP_API"
|
18 |
+
|
19 |
+
if not is_local_run:
|
20 |
+
zip_file_path = f'{code_dir}/examples.zip'
|
21 |
+
# Unzipping the file into the current directory
|
22 |
+
with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
|
23 |
+
zip_ref.extractall(os.getcwd())
|
24 |
+
|
25 |
+
|
26 |
+
os.system(f"pip install {code_dir}/gradio_model3dcolor-0.0.1-py3-none-any.whl")
|
27 |
+
os.system(f"pip install {code_dir}/gradio_model3dnormal-0.0.1-py3-none-any.whl")
|
28 |
+
from gradio_model3dcolor import Model3DColor
|
29 |
+
from gradio_model3dnormal import Model3DNormal
|
30 |
+
|
31 |
+
with open(f'{code_dir}/api.json', 'r') as file:
|
32 |
+
api_dict = json.load(file)
|
33 |
+
SEGM_i_CALL = api_dict["SEGM_i_CALL"]
|
34 |
+
SEGM_CALL = api_dict["SEGM_CALL"]
|
35 |
+
UNPOSED_CALL = api_dict["UNPOSED_CALL"]
|
36 |
+
MESH_CALL = api_dict["MESH_CALL"]
|
37 |
+
|
38 |
+
|
39 |
+
_TITLE = (
|
40 |
+
"""SpaRP: Fast 3D Object Reconstruction and Pose Estimation from Sparse Views"""
|
41 |
+
)
|
42 |
+
_DESCRIPTION = (
|
43 |
+
"""Try SpaRP to reconstruct 3D textured mesh from one or a few unposed images!"""
|
44 |
+
)
|
45 |
+
_PR = """
|
46 |
+
<div>
|
47 |
+
<b><em>Check out <a href="https://www.sudo.ai/3dgen">Hillbot (sudoAI)</a> for more details and advanced features.</em></b>
|
48 |
+
</div>
|
49 |
+
"""
|
50 |
+
|
51 |
+
|
52 |
+
STYLE = """
|
53 |
+
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
54 |
+
<style>
|
55 |
+
.alert, .alert div, .alert b {
|
56 |
+
color: black !important;
|
57 |
+
}
|
58 |
+
</style>
|
59 |
+
"""
|
60 |
+
# info (info-circle-fill), cursor (hand-index-thumb), wait (hourglass-split), done (check-circle)
|
61 |
+
ICONS = {
|
62 |
+
"info": """<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#0d6efd" class="bi bi-info-circle-fill flex-shrink-0 me-2" viewBox="0 0 16 16">
|
63 |
+
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
|
64 |
+
</svg>""",
|
65 |
+
"cursor": """<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#0dcaf0" class="bi bi-hand-index-thumb-fill flex-shrink-0 me-2" viewBox="0 0 16 16">
|
66 |
+
<path d="M8.5 1.75v2.716l.047-.002c.312-.012.742-.016 1.051.046.28.056.543.18.738.288.273.152.456.385.56.642l.132-.012c.312-.024.794-.038 1.158.108.37.148.689.487.88.716.075.09.141.175.195.248h.582a2 2 0 0 1 1.99 2.199l-.272 2.715a3.5 3.5 0 0 1-.444 1.389l-1.395 2.441A1.5 1.5 0 0 1 12.42 16H6.118a1.5 1.5 0 0 1-1.342-.83l-1.215-2.43L1.07 8.589a1.517 1.517 0 0 1 2.373-1.852L5 8.293V1.75a1.75 1.75 0 0 1 3.5 0z"/>
|
67 |
+
</svg>""",
|
68 |
+
"wait": """<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#6c757d" class="bi bi-hourglass-split flex-shrink-0 me-2" viewBox="0 0 16 16">
|
69 |
+
<path d="M2.5 15a.5.5 0 1 1 0-1h1v-1a4.5 4.5 0 0 1 2.557-4.06c.29-.139.443-.377.443-.59v-.7c0-.213-.154-.451-.443-.59A4.5 4.5 0 0 1 3.5 3V2h-1a.5.5 0 0 1 0-1h11a.5.5 0 0 1 0 1h-1v1a4.5 4.5 0 0 1-2.557 4.06c-.29.139-.443.377-.443.59v.7c0 .213.154.451.443.59A4.5 4.5 0 0 1 12.5 13v1h1a.5.5 0 0 1 0 1h-11zm2-13v1c0 .537.12 1.045.337 1.5h6.326c.216-.455.337-.963.337-1.5V2h-7zm3 6.35c0 .701-.478 1.236-1.011 1.492A3.5 3.5 0 0 0 4.5 13s.866-1.299 3-1.48V8.35zm1 0v3.17c2.134.181 3 1.48 3 1.48a3.5 3.5 0 0 0-1.989-3.158C8.978 9.586 8.5 9.052 8.5 8.351z"/>
|
70 |
+
</svg>""",
|
71 |
+
"done": """<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#198754" class="bi bi-check-circle-fill flex-shrink-0 me-2" viewBox="0 0 16 16">
|
72 |
+
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
|
73 |
+
</svg>""",
|
74 |
+
}
|
75 |
+
|
76 |
+
icons2alert = {
|
77 |
+
"info": "primary", # blue
|
78 |
+
"cursor": "info", # light blue
|
79 |
+
"wait": "secondary", # gray
|
80 |
+
"done": "success", # green
|
81 |
+
}
|
82 |
+
|
83 |
+
|
84 |
+
def message(text, icon_type="info"):
|
85 |
+
return f"""{STYLE} <div class="alert alert-{icons2alert[icon_type]} d-flex align-items-center" role="alert"> {ICONS[icon_type]}
|
86 |
+
<div>
|
87 |
+
{text}
|
88 |
+
</div>
|
89 |
+
</div>"""
|
90 |
+
|
91 |
+
|
92 |
+
def create_tmp_dir():
|
93 |
+
tmp_dir = (
|
94 |
+
"../demo_exp/"
|
95 |
+
+ datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
96 |
+
+ "_"
|
97 |
+
+ str(uuid.uuid4())[:4]
|
98 |
+
)
|
99 |
+
os.makedirs(tmp_dir, exist_ok=True)
|
100 |
+
print("create tmp_exp_dir", tmp_dir)
|
101 |
+
return tmp_dir
|
102 |
+
|
103 |
+
|
104 |
+
def preprocess_imgs(tmp_dir, input_img, idx=None):
|
105 |
+
if isinstance(input_img, list) and idx is None:
|
106 |
+
for i, img_tuple in enumerate(input_img):
|
107 |
+
Image.open(img_tuple[0]).save(f"{tmp_dir}/input_{i}.png")
|
108 |
+
os.system(SEGM_i_CALL.replace("{tmp_dir}", tmp_dir).replace("{i}", str(i)))
|
109 |
+
return [Image.open(f"{tmp_dir}/seg_{i}.png") for i in range(len(input_img))]
|
110 |
+
|
111 |
+
if idx is not None:
|
112 |
+
print("image idx:", int(idx))
|
113 |
+
input_img = Image.open(input_img[int(idx)][0])
|
114 |
+
input_img.save(f"{tmp_dir}/input.png")
|
115 |
+
os.system(SEGM_CALL.replace("{tmp_dir}", tmp_dir))
|
116 |
+
processed_img = Image.open(f"{tmp_dir}/seg.png")
|
117 |
+
return processed_img.resize((320, 320), Image.Resampling.LANCZOS)
|
118 |
+
|
119 |
+
|
120 |
+
def ply_to_glb(ply_path):
|
121 |
+
script_path = "../utils/ply2glb.py"
|
122 |
+
result = subprocess.run(
|
123 |
+
["python", script_path, "--", ply_path],
|
124 |
+
capture_output=True,
|
125 |
+
text=True,
|
126 |
+
)
|
127 |
+
|
128 |
+
print("Output of blender script:")
|
129 |
+
print(result.stdout)
|
130 |
+
|
131 |
+
glb_path = ply_path.replace(".ply", ".glb")
|
132 |
+
return glb_path
|
133 |
+
|
134 |
+
|
135 |
+
def mesh_gen(tmp_dir, use_seg):
|
136 |
+
os.system(UNPOSED_CALL.replace("{tmp_dir}", tmp_dir).replace("{use_seg}", str(use_seg)))
|
137 |
+
os.system(MESH_CALL.replace("{tmp_dir}", tmp_dir))
|
138 |
+
|
139 |
+
mesh = trimesh.load_mesh(f"{tmp_dir}/mesh.ply")
|
140 |
+
vertex_normals = mesh.vertex_normals
|
141 |
+
colors = (-vertex_normals + 1) / 2.0
|
142 |
+
colors = (colors * 255).astype(np.uint8) # Convert to 8-bit color
|
143 |
+
mesh.visual.vertex_colors = colors
|
144 |
+
mesh.export(f"{tmp_dir}/mesh_normal.ply", file_type="ply")
|
145 |
+
|
146 |
+
color_path = ply_to_glb(f"{tmp_dir}/mesh.ply")
|
147 |
+
normal_path = ply_to_glb(f"{tmp_dir}/mesh_normal.ply")
|
148 |
+
|
149 |
+
return color_path, normal_path
|
150 |
+
|
151 |
+
|
152 |
+
def feed_example_to_gallery(img):
|
153 |
+
for display_img in display_imgs:
|
154 |
+
display_img = display_img[0]
|
155 |
+
diff = ImageChops.difference(img, display_img)
|
156 |
+
if not diff.getbbox(): # two images are the same
|
157 |
+
img_id = display_img.filename
|
158 |
+
data_dir = os.path.join(data_folder, str(img_id))
|
159 |
+
data_fns = os.listdir(data_dir)
|
160 |
+
data_fns.sort()
|
161 |
+
data_imgs = []
|
162 |
+
for data_fn in data_fns:
|
163 |
+
file_path = os.path.join(data_dir, data_fn)
|
164 |
+
img = Image.open(file_path)
|
165 |
+
data_imgs.append(img)
|
166 |
+
return data_imgs
|
167 |
+
return [img]
|
168 |
+
|
169 |
+
|
170 |
+
custom_theme = gr.themes.Soft(primary_hue="blue").set(
|
171 |
+
button_secondary_background_fill="*neutral_100",
|
172 |
+
button_secondary_background_fill_hover="*neutral_200",
|
173 |
+
)
|
174 |
+
|
175 |
+
# Gradio blocks
|
176 |
+
with gr.Blocks(title=_TITLE, css="style.css", theme=custom_theme) as demo:
|
177 |
+
tmp_dir_unposed = gr.State("./demo_exp/placeholder")
|
178 |
+
display_folder = os.path.join(os.path.dirname(__file__), "examples_display")
|
179 |
+
display_fns = os.listdir(display_folder)
|
180 |
+
display_fns.sort()
|
181 |
+
display_imgs = []
|
182 |
+
for i, display_fn in enumerate(display_fns):
|
183 |
+
file_path = os.path.join(display_folder, display_fn)
|
184 |
+
img = Image.open(file_path)
|
185 |
+
img.filename = i
|
186 |
+
display_imgs.append([img])
|
187 |
+
data_folder = os.path.join(os.path.dirname(__file__), "examples_data")
|
188 |
+
|
189 |
+
# UI
|
190 |
+
with gr.Row():
|
191 |
+
gr.Markdown("# " + _TITLE)
|
192 |
+
with gr.Row():
|
193 |
+
gr.Markdown("### " + _DESCRIPTION)
|
194 |
+
with gr.Row():
|
195 |
+
gr.Markdown(_PR)
|
196 |
+
with gr.Row():
|
197 |
+
guide_text = gr.HTML(
|
198 |
+
message("Input image(s) of object that you want to generate mesh with.")
|
199 |
+
)
|
200 |
+
with gr.Row(variant="panel"):
|
201 |
+
with gr.Column():
|
202 |
+
with gr.Row():
|
203 |
+
with gr.Column(scale=5):
|
204 |
+
input_gallery = gr.Gallery(
|
205 |
+
label="Input Images",
|
206 |
+
show_label=False,
|
207 |
+
columns=[3],
|
208 |
+
rows=[2],
|
209 |
+
object_fit="contain",
|
210 |
+
height=400,
|
211 |
+
)
|
212 |
+
input_image = gr.Image(
|
213 |
+
type="pil",
|
214 |
+
image_mode="RGBA",
|
215 |
+
visible=False,
|
216 |
+
)
|
217 |
+
with gr.Column(scale=5):
|
218 |
+
processed_gallery = gr.Gallery(
|
219 |
+
label="Background Removal",
|
220 |
+
columns=[3],
|
221 |
+
rows=[2],
|
222 |
+
object_fit="contain",
|
223 |
+
height=400,
|
224 |
+
interactive=False,
|
225 |
+
)
|
226 |
+
with gr.Row():
|
227 |
+
with gr.Column(scale=5):
|
228 |
+
example = gr.Examples(
|
229 |
+
examples=display_imgs,
|
230 |
+
inputs=[input_image],
|
231 |
+
outputs=[input_gallery],
|
232 |
+
fn=feed_example_to_gallery,
|
233 |
+
label="Image Examples (Click one of the images below to start)",
|
234 |
+
examples_per_page=10,
|
235 |
+
run_on_click=True,
|
236 |
+
)
|
237 |
+
with gr.Column(scale=5):
|
238 |
+
with gr.Row():
|
239 |
+
bg_removed_checkbox = gr.Checkbox(
|
240 |
+
value=True,
|
241 |
+
label="Use background removed images (uncheck to use original)",
|
242 |
+
interactive=True,
|
243 |
+
)
|
244 |
+
with gr.Row():
|
245 |
+
run_btn = gr.Button(
|
246 |
+
"Generate",
|
247 |
+
variant="primary",
|
248 |
+
interactive=False,
|
249 |
+
)
|
250 |
+
with gr.Row():
|
251 |
+
with gr.Column(scale=5):
|
252 |
+
mesh_output = Model3DColor(
|
253 |
+
label="Generated Mesh (color)",
|
254 |
+
elem_id="mesh-out",
|
255 |
+
height=400,
|
256 |
+
)
|
257 |
+
with gr.Column(scale=5):
|
258 |
+
mesh_output_normal = Model3DNormal(
|
259 |
+
label="Generated Mesh (normal)",
|
260 |
+
elem_id="mesh-normal-out",
|
261 |
+
height=400,
|
262 |
+
)
|
263 |
+
|
264 |
+
# Callbacks
|
265 |
+
disable_button = lambda: gr.Button(interactive=False)
|
266 |
+
enable_button = lambda: gr.Button(interactive=True)
|
267 |
+
update_guide = lambda GUIDE_TEXT, icon_type="info": gr.HTML(
|
268 |
+
value=message(GUIDE_TEXT, icon_type)
|
269 |
+
)
|
270 |
+
|
271 |
+
def is_cleared(content):
|
272 |
+
if content:
|
273 |
+
raise ValueError # gr.Error(visible=False) doesn't work, trick for not showing error message
|
274 |
+
|
275 |
+
def not_cleared(content):
|
276 |
+
if not content:
|
277 |
+
raise ValueError # gr.Error(visible=False) doesn't work, trick for not showing error message
|
278 |
+
|
279 |
+
# Upload event listener for input gallery
|
280 |
+
input_gallery.upload(
|
281 |
+
fn=disable_button,
|
282 |
+
outputs=[run_btn],
|
283 |
+
queue=False,
|
284 |
+
).success(
|
285 |
+
fn=create_tmp_dir,
|
286 |
+
outputs=[tmp_dir_unposed],
|
287 |
+
queue=False,
|
288 |
+
).success(
|
289 |
+
fn=partial(
|
290 |
+
update_guide, "Removing background of the input image(s)...", "wait"
|
291 |
+
),
|
292 |
+
outputs=[guide_text],
|
293 |
+
queue=False,
|
294 |
+
).success(
|
295 |
+
fn=preprocess_imgs,
|
296 |
+
inputs=[tmp_dir_unposed, input_gallery],
|
297 |
+
outputs=[processed_gallery],
|
298 |
+
queue=True,
|
299 |
+
).success(
|
300 |
+
fn=partial(update_guide, "Click <b>Generate</b> to generate mesh.", "cursor"),
|
301 |
+
outputs=[guide_text],
|
302 |
+
queue=False,
|
303 |
+
).success(
|
304 |
+
fn=enable_button,
|
305 |
+
outputs=[run_btn],
|
306 |
+
queue=False,
|
307 |
+
)
|
308 |
+
|
309 |
+
# Clear event listener for input gallery
|
310 |
+
input_gallery.change(
|
311 |
+
fn=is_cleared,
|
312 |
+
inputs=[input_gallery],
|
313 |
+
queue=False,
|
314 |
+
).success(
|
315 |
+
fn=disable_button,
|
316 |
+
outputs=[run_btn],
|
317 |
+
queue=False,
|
318 |
+
).success(
|
319 |
+
fn=lambda: None,
|
320 |
+
outputs=[input_image],
|
321 |
+
queue=False,
|
322 |
+
).success(
|
323 |
+
fn=lambda: None,
|
324 |
+
outputs=[processed_gallery],
|
325 |
+
queue=False,
|
326 |
+
).success(
|
327 |
+
fn=lambda: None,
|
328 |
+
outputs=[mesh_output],
|
329 |
+
queue=False,
|
330 |
+
).success(
|
331 |
+
fn=lambda: None,
|
332 |
+
outputs=[mesh_output_normal],
|
333 |
+
queue=False,
|
334 |
+
).success(
|
335 |
+
fn=partial(
|
336 |
+
update_guide,
|
337 |
+
"Input image(s) of object that you want to generate mesh with.",
|
338 |
+
"info",
|
339 |
+
),
|
340 |
+
outputs=[guide_text],
|
341 |
+
queue=False,
|
342 |
+
)
|
343 |
+
|
344 |
+
# Change event listener for input image
|
345 |
+
input_image.change(
|
346 |
+
fn=not_cleared,
|
347 |
+
inputs=[input_image],
|
348 |
+
queue=False,
|
349 |
+
).success(
|
350 |
+
fn=disable_button,
|
351 |
+
outputs=run_btn,
|
352 |
+
queue=False,
|
353 |
+
).success(
|
354 |
+
fn=lambda: None,
|
355 |
+
outputs=[mesh_output],
|
356 |
+
queue=False,
|
357 |
+
).success(
|
358 |
+
fn=lambda: None,
|
359 |
+
outputs=[mesh_output_normal],
|
360 |
+
queue=False,
|
361 |
+
).success(
|
362 |
+
fn=create_tmp_dir,
|
363 |
+
outputs=tmp_dir_unposed,
|
364 |
+
queue=False,
|
365 |
+
).success(
|
366 |
+
fn=partial(
|
367 |
+
update_guide, "Removing background of the input image(s)...", "wait"
|
368 |
+
),
|
369 |
+
outputs=[guide_text],
|
370 |
+
queue=False,
|
371 |
+
).success(
|
372 |
+
fn=preprocess_imgs,
|
373 |
+
inputs=[tmp_dir_unposed, input_gallery],
|
374 |
+
outputs=[processed_gallery],
|
375 |
+
queue=True,
|
376 |
+
).success(
|
377 |
+
fn=partial(update_guide, "Click <b>Generate</b> to generate mesh.", "cursor"),
|
378 |
+
outputs=[guide_text],
|
379 |
+
queue=False,
|
380 |
+
).success(
|
381 |
+
fn=enable_button,
|
382 |
+
outputs=run_btn,
|
383 |
+
queue=False,
|
384 |
+
)
|
385 |
+
|
386 |
+
# Click event listener for run button
|
387 |
+
run_btn.click(
|
388 |
+
fn=disable_button,
|
389 |
+
outputs=[run_btn],
|
390 |
+
queue=False,
|
391 |
+
).success(
|
392 |
+
fn=lambda: None,
|
393 |
+
outputs=[mesh_output],
|
394 |
+
queue=False,
|
395 |
+
).success(
|
396 |
+
fn=lambda: None,
|
397 |
+
outputs=[mesh_output_normal],
|
398 |
+
queue=False,
|
399 |
+
).success(
|
400 |
+
fn=partial(update_guide, "Generating the mesh...", "wait"),
|
401 |
+
outputs=[guide_text],
|
402 |
+
queue=False,
|
403 |
+
).success(
|
404 |
+
fn=mesh_gen,
|
405 |
+
inputs=[tmp_dir_unposed, bg_removed_checkbox],
|
406 |
+
outputs=[mesh_output, mesh_output_normal],
|
407 |
+
queue=True,
|
408 |
+
).success(
|
409 |
+
fn=partial(
|
410 |
+
update_guide,
|
411 |
+
"Successfully generated the mesh. (It might take a few seconds to load the mesh)",
|
412 |
+
"done",
|
413 |
+
),
|
414 |
+
outputs=[guide_text],
|
415 |
+
queue=False,
|
416 |
+
).success(
|
417 |
+
fn=enable_button,
|
418 |
+
outputs=[run_btn],
|
419 |
+
queue=False,
|
420 |
+
)
|
421 |
+
|
422 |
+
demo.queue().launch(
|
423 |
+
debug=False,
|
424 |
+
share=False,
|
425 |
+
inline=False,
|
426 |
+
show_api=False,
|
427 |
+
server_name="0.0.0.0",
|
428 |
+
)
|
style.css
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.centered {
|
2 |
+
text-align: center; /* Horizontally center the content */
|
3 |
+
}
|
4 |
+
|
5 |
+
.centered img {
|
6 |
+
display: block; /* Make the image a block element */
|
7 |
+
margin: 0 auto; /* Center the block element (the image) horizontally */
|
8 |
+
height: 100px;
|
9 |
+
}
|
10 |
+
|
11 |
+
.tab_at_top button.selected{
|
12 |
+
font-size: 24px !important;
|
13 |
+
}
|