davidvgilmore commited on
Commit
b54ca85
·
verified ·
1 Parent(s): f9644c0

Upload hg_app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. hg_app.py +438 -0
hg_app.py ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # pip install gradio==4.44.1
2
+ import argparse
3
+ parser = argparse.ArgumentParser()
4
+ parser.add_argument('--port', type=int, default=8080)
5
+ parser.add_argument('--cache-path', type=str, default='gradio_cache')
6
+ parser.add_argument('--enable_t23d', default=True)
7
+ parser.add_argument('--local', action="store_true")
8
+ args = parser.parse_args()
9
+
10
+ print(f"Running on {'local' if args.local else 'huggingface'}")
11
+ if not args.local:
12
+ import os
13
+ import spaces
14
+ import subprocess
15
+ import sys
16
+ import shlex
17
+
18
+ print("cd /home/user/app/hy3dgen/texgen/differentiable_renderer/ && bash compile_mesh_painter.sh")
19
+ os.system("cd /home/user/app/hy3dgen/texgen/differentiable_renderer/ && bash compile_mesh_painter.sh")
20
+ print('install custom')
21
+ subprocess.run(shlex.split("pip install custom_rasterizer-0.1-cp310-cp310-linux_x86_64.whl"), check=True)
22
+
23
+ IP = "0.0.0.0"
24
+ PORT = 7860
25
+
26
+ else:
27
+ IP = "0.0.0.0"
28
+ PORT = 8080
29
+ class spaces:
30
+ class GPU:
31
+ def __init__(self, duration=60):
32
+ self.duration = duration
33
+ def __call__(self, func):
34
+ return func
35
+
36
+ import os
37
+ import shutil
38
+ import time
39
+ from glob import glob
40
+ from pathlib import Path
41
+ from PIL import Image
42
+ from datetime import datetime
43
+ import uuid
44
+ import gradio as gr
45
+ import torch
46
+ import uvicorn
47
+ from fastapi import FastAPI
48
+ from fastapi.staticfiles import StaticFiles
49
+
50
+
51
+ def start_session(req: gr.Request):
52
+ save_folder = os.path.join(SAVE_DIR, str(req.session_hash))
53
+ os.makedirs(save_folder, exist_ok=True)
54
+
55
+ def end_session(req: gr.Request):
56
+ save_folder = os.path.join(SAVE_DIR, str(req.session_hash))
57
+ shutil.rmtree(save_folder)
58
+
59
+ def get_example_img_list():
60
+ print('Loading example img list ...')
61
+ return sorted(glob('./assets/example_images/*.png'))
62
+
63
+
64
+ def get_example_txt_list():
65
+ print('Loading example txt list ...')
66
+ txt_list = list()
67
+ for line in open('./assets/example_prompts.txt'):
68
+ txt_list.append(line.strip())
69
+ return txt_list
70
+
71
+
72
+ def export_mesh(mesh, save_folder, textured=False):
73
+ if textured:
74
+ path = os.path.join(save_folder, f'textured_mesh.glb')
75
+ else:
76
+ path = os.path.join(save_folder, f'white_mesh.glb')
77
+ mesh.export(path, include_normals=textured)
78
+ return path
79
+
80
+ def build_model_viewer_html(save_folder, height=660, width=790, textured=False):
81
+ if textured:
82
+ related_path = f"./textured_mesh.glb"
83
+ template_name = './assets/modelviewer-textured-template.html'
84
+ output_html_path = os.path.join(save_folder, f'{uuid.uuid4()}_textured_mesh.html')
85
+ else:
86
+ related_path = f"./white_mesh.glb"
87
+ template_name = './assets/modelviewer-template.html'
88
+ output_html_path = os.path.join(save_folder, f'{uuid.uuid4()}_white_mesh.html')
89
+
90
+ with open(os.path.join(CURRENT_DIR, template_name), 'r') as f:
91
+ template_html = f.read()
92
+ obj_html = f"""
93
+ <div class="column is-mobile is-centered">
94
+ <model-viewer style="height: {height - 10}px; width: {width}px;" rotation-per-second="10deg" id="modelViewer"
95
+ src="{related_path}/" disable-tap
96
+ environment-image="neutral" auto-rotate camera-target="0m 0m 0m" orientation="0deg 0deg 170deg" shadow-intensity=".9"
97
+ ar auto-rotate camera-controls>
98
+ </model-viewer>
99
+ </div>
100
+ """
101
+
102
+ with open(output_html_path, 'w') as f:
103
+ f.write(template_html.replace('<model-viewer>', obj_html))
104
+
105
+ output_html_path = output_html_path.replace(SAVE_DIR + '/', '')
106
+ iframe_tag = f'<iframe src="/static/{output_html_path}" height="{height}" width="100%" frameborder="0"></iframe>'
107
+ print(f'Find html {output_html_path}, {os.path.exists(output_html_path)}')
108
+
109
+ # rel_path = os.path.relpath(output_html_path, SAVE_DIR)
110
+ # iframe_tag = f'<iframe src="/static/{rel_path}" height="{height}" width="100%" frameborder="0"></iframe>'
111
+ # print(f'Find html file {output_html_path}, {os.path.exists(output_html_path)}, relative HTML path is /static/{rel_path}')
112
+
113
+ return f"""
114
+ <div style='height: {height}; width: 100%;'>
115
+ {iframe_tag}
116
+ </div>
117
+ """
118
+
119
+
120
+ @spaces.GPU(duration=100)
121
+ def _gen_shape(
122
+ caption: str,
123
+ image: Image.Image,
124
+ steps: int,
125
+ guidance_scale: float,
126
+ seed: int,
127
+ octree_resolution: int,
128
+ check_box_rembg: bool,
129
+ req: gr.Request,
130
+ ):
131
+ if caption: print('prompt is', caption)
132
+ save_folder = os.path.join(SAVE_DIR, str(req.session_hash))
133
+ os.makedirs(save_folder, exist_ok=True)
134
+
135
+ stats = {}
136
+ time_meta = {}
137
+ start_time_0 = time.time()
138
+
139
+ if image is None:
140
+ start_time = time.time()
141
+ try:
142
+ image = t2i_worker(caption)
143
+ except Exception as e:
144
+ raise gr.Error(f"Text to 3D is disable. Please enable it by `python gradio_app.py --enable_t23d`.")
145
+ time_meta['text2image'] = time.time() - start_time
146
+
147
+ image.save(os.path.join(save_folder, 'input.png'))
148
+
149
+ print(f"[{datetime.now()}][HunYuan3D-2]]", str(req.session_hash), image.mode)
150
+ if check_box_rembg or image.mode == "RGB":
151
+ start_time = time.time()
152
+ image = rmbg_worker(image.convert('RGB'))
153
+ time_meta['rembg'] = time.time() - start_time
154
+
155
+ image.save(os.path.join(save_folder, 'rembg.png'))
156
+
157
+ # image to white model
158
+ start_time = time.time()
159
+
160
+ generator = torch.Generator()
161
+ generator = generator.manual_seed(int(seed))
162
+ mesh = i23d_worker(
163
+ image=image,
164
+ num_inference_steps=steps,
165
+ guidance_scale=guidance_scale,
166
+ generator=generator,
167
+ octree_resolution=octree_resolution
168
+ )[0]
169
+
170
+ mesh = FloaterRemover()(mesh)
171
+ mesh = DegenerateFaceRemover()(mesh)
172
+ mesh = FaceReducer()(mesh)
173
+
174
+ stats['number_of_faces'] = mesh.faces.shape[0]
175
+ stats['number_of_vertices'] = mesh.vertices.shape[0]
176
+
177
+ time_meta['image_to_textured_3d'] = {'total': time.time() - start_time}
178
+ time_meta['total'] = time.time() - start_time_0
179
+ stats['time'] = time_meta
180
+
181
+ torch.cuda.empty_cache()
182
+ return mesh, save_folder, image
183
+
184
+ @spaces.GPU(duration=150)
185
+ def generation_all(
186
+ caption: str,
187
+ image: Image.Image,
188
+ steps: int,
189
+ guidance_scale: float,
190
+ seed: int,
191
+ octree_resolution: int,
192
+ check_box_rembg: bool,
193
+ req: gr.Request,
194
+ ):
195
+ mesh, save_folder, image = _gen_shape(
196
+ caption,
197
+ image,
198
+ steps=steps,
199
+ guidance_scale=guidance_scale,
200
+ seed=seed,
201
+ octree_resolution=octree_resolution,
202
+ check_box_rembg=check_box_rembg,
203
+ req=req
204
+ )
205
+ path = export_mesh(mesh, save_folder, textured=False)
206
+ model_viewer_html = build_model_viewer_html(save_folder, height=596, width=700)
207
+
208
+ textured_mesh = texgen_worker(mesh, image)
209
+ path_textured = export_mesh(textured_mesh, save_folder, textured=True)
210
+ model_viewer_html_textured = build_model_viewer_html(save_folder, height=596, width=700, textured=True)
211
+
212
+ torch.cuda.empty_cache()
213
+ return (
214
+ path,
215
+ path_textured,
216
+ model_viewer_html,
217
+ model_viewer_html_textured,
218
+ )
219
+
220
+ @spaces.GPU(duration=100)
221
+ def shape_generation(
222
+ caption: str,
223
+ image: Image.Image,
224
+ steps: int,
225
+ guidance_scale: float,
226
+ seed: int,
227
+ octree_resolution: int,
228
+ check_box_rembg: bool,
229
+ req: gr.Request,
230
+ ):
231
+ mesh, save_folder, image = _gen_shape(
232
+ caption,
233
+ image,
234
+ steps=steps,
235
+ guidance_scale=guidance_scale,
236
+ seed=seed,
237
+ octree_resolution=octree_resolution,
238
+ check_box_rembg=check_box_rembg,
239
+ req=req,
240
+ )
241
+
242
+ path = export_mesh(mesh, save_folder, textured=False)
243
+ model_viewer_html = build_model_viewer_html(save_folder, height=596, width=700)
244
+
245
+ return (
246
+ path,
247
+ model_viewer_html,
248
+ )
249
+
250
+
251
+ def build_app():
252
+ title_html = """
253
+ <div style="font-size: 2em; font-weight: bold; text-align: center; margin-bottom: 5px">
254
+
255
+ Hunyuan3D-2: Scaling Diffusion Models for High Resolution Textured 3D Assets Generation
256
+ </div>
257
+ <div align="center">
258
+ Tencent Hunyuan3D Team
259
+ </div>
260
+ <div align="center">
261
+ <a href="https://github.com/tencent/Hunyuan3D-2">Github Page</a> &ensp;
262
+ <a href="http://3d-models.hunyuan.tencent.com">Homepage</a> &ensp;
263
+ <a href="https://arxiv.org/abs/2501.12202">Technical Report</a> &ensp;
264
+ <a href="https://huggingface.co/Tencent/Hunyuan3D-2"> Models</a> &ensp;
265
+ </div>
266
+ """
267
+
268
+ with gr.Blocks(theme=gr.themes.Base(), title='Hunyuan-3D-2.0', delete_cache=(1000,1000)) as demo:
269
+ gr.HTML(title_html)
270
+
271
+ with gr.Row():
272
+ with gr.Column(scale=2):
273
+ with gr.Tabs() as tabs_prompt:
274
+ with gr.Tab('Image Prompt', id='tab_img_prompt') as tab_ip:
275
+ image = gr.Image(label='Image', type='pil', image_mode='RGBA', height=290)
276
+ with gr.Row():
277
+ check_box_rembg = gr.Checkbox(value=True, label='Remove Background')
278
+
279
+ with gr.Tab('Text Prompt', id='tab_txt_prompt', visible=HAS_T2I) as tab_tp:
280
+ caption = gr.Textbox(label='Text Prompt',
281
+ placeholder='HunyuanDiT will be used to generate image.',
282
+ info='Example: A 3D model of a cute cat, white background')
283
+
284
+ with gr.Accordion('Advanced Options', open=False):
285
+ num_steps = gr.Slider(maximum=50, minimum=20, value=50, step=1, label='Inference Steps')
286
+ octree_resolution = gr.Dropdown([256, 384, 512], value=256, label='Octree Resolution')
287
+ cfg_scale = gr.Number(value=5.5, label='Guidance Scale')
288
+ seed = gr.Slider(maximum=1e7, minimum=0, value=1234, label='Seed')
289
+
290
+ with gr.Group():
291
+ btn = gr.Button(value='Generate Shape Only', variant='primary')
292
+ btn_all = gr.Button(value='Generate Shape and Texture', variant='primary', visible=HAS_TEXTUREGEN)
293
+
294
+ # with gr.Group():
295
+ # file_out = gr.File(label="File", visible=False)
296
+ # file_out2 = gr.File(label="File", visible=False)
297
+
298
+ with gr.Group():
299
+ file_out = gr.DownloadButton(label="Download White Mesh", interactive=False)
300
+ file_out2 = gr.DownloadButton(label="Download Textured Mesh", interactive=False)
301
+
302
+ with gr.Column(scale=5):
303
+ with gr.Tabs():
304
+ with gr.Tab('Generated Mesh') as mesh1:
305
+ html_output1 = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')
306
+ with gr.Tab('Generated Textured Mesh') as mesh2:
307
+ html_output2 = gr.HTML(HTML_OUTPUT_PLACEHOLDER, label='Output')
308
+
309
+ with gr.Column(scale=2):
310
+ with gr.Tabs() as gallery:
311
+ with gr.Tab('Image to 3D Gallery', id='tab_img_gallery') as tab_gi:
312
+ with gr.Row():
313
+ gr.Examples(examples=example_is, inputs=[image],
314
+ label="Image Prompts", examples_per_page=18)
315
+
316
+ with gr.Tab('Text to 3D Gallery', id='tab_txt_gallery', visible=HAS_T2I) as tab_gt:
317
+ with gr.Row():
318
+ gr.Examples(examples=example_ts, inputs=[caption],
319
+ label="Text Prompts", examples_per_page=18)
320
+
321
+ if not HAS_TEXTUREGEN:
322
+ gr.HTML("""
323
+ <div style="margin-top: 20px;">
324
+ <b>Warning: </b>
325
+ Texture synthesis is disable due to missing requirements,
326
+ please install requirements following README.md to activate it.
327
+ </div>
328
+ """)
329
+ if not args.enable_t23d:
330
+ gr.HTML("""
331
+ <div style="margin-top: 20px;">
332
+ <b>Warning: </b>
333
+ Text to 3D is disable. To activate it, please run `python gradio_app.py --enable_t23d`.
334
+ </div>
335
+ """)
336
+
337
+ tab_gi.select(fn=lambda: gr.update(selected='tab_img_prompt'), outputs=tabs_prompt)
338
+ if HAS_T2I:
339
+ tab_gt.select(fn=lambda: gr.update(selected='tab_txt_prompt'), outputs=tabs_prompt)
340
+
341
+ btn.click(
342
+ shape_generation,
343
+ inputs=[
344
+ caption,
345
+ image,
346
+ num_steps,
347
+ cfg_scale,
348
+ seed,
349
+ octree_resolution,
350
+ check_box_rembg,
351
+ ],
352
+ outputs=[file_out, html_output1]
353
+ ).then(
354
+ lambda: gr.Button(interactive=True),
355
+ outputs=[file_out],
356
+ )
357
+
358
+ btn_all.click(
359
+ generation_all,
360
+ inputs=[
361
+ caption,
362
+ image,
363
+ num_steps,
364
+ cfg_scale,
365
+ seed,
366
+ octree_resolution,
367
+ check_box_rembg,
368
+ ],
369
+ outputs=[file_out, file_out2, html_output1, html_output2]
370
+ ).then(
371
+ lambda: (gr.Button(interactive=True),gr.Button(interactive=True)),
372
+ outputs=[file_out, file_out2],
373
+ )
374
+
375
+ # demo.load(start_session)
376
+ # demo.unload(end_session)
377
+
378
+ return demo
379
+
380
+
381
+ if __name__ == '__main__':
382
+
383
+ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
384
+ SAVE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), args.cache_path)
385
+ os.makedirs(SAVE_DIR, exist_ok=True)
386
+
387
+ HTML_OUTPUT_PLACEHOLDER = """
388
+ <div style='height: 596px; width: 100%; border-radius: 8px; border-color: #e5e7eb; order-style: solid; border-width: 1px;'></div>
389
+ """
390
+
391
+ INPUT_MESH_HTML = """
392
+ <div style='height: 490px; width: 100%; border-radius: 8px;
393
+ border-color: #e5e7eb; order-style: solid; border-width: 1px;'>
394
+ </div>
395
+ """
396
+ example_is = get_example_img_list()
397
+ example_ts = get_example_txt_list()
398
+
399
+ try:
400
+ from hy3dgen.texgen import Hunyuan3DPaintPipeline
401
+
402
+ texgen_worker = Hunyuan3DPaintPipeline.from_pretrained('tencent/Hunyuan3D-2')
403
+ HAS_TEXTUREGEN = True
404
+ except Exception as e:
405
+ print(e)
406
+ print("Failed to load texture generator.")
407
+ print('Please try to install requirements by following README.md')
408
+ HAS_TEXTUREGEN = False
409
+
410
+ HAS_T2I = False
411
+ if args.enable_t23d:
412
+ from hy3dgen.text2image import HunyuanDiTPipeline
413
+
414
+ t2i_worker = HunyuanDiTPipeline('Tencent-Hunyuan/HunyuanDiT-v1.1-Diffusers-Distilled')
415
+ HAS_T2I = True
416
+
417
+ from hy3dgen.shapegen import FaceReducer, FloaterRemover, DegenerateFaceRemover, \
418
+ Hunyuan3DDiTFlowMatchingPipeline
419
+ from hy3dgen.rembg import BackgroundRemover
420
+
421
+ rmbg_worker = BackgroundRemover()
422
+ i23d_worker = Hunyuan3DDiTFlowMatchingPipeline.from_pretrained('tencent/Hunyuan3D-2')
423
+ floater_remove_worker = FloaterRemover()
424
+ degenerate_face_remove_worker = DegenerateFaceRemover()
425
+ face_reduce_worker = FaceReducer()
426
+
427
+ # https://discuss.huggingface.co/t/how-to-serve-an-html-file/33921/2
428
+ # create a FastAPI app
429
+ app = FastAPI()
430
+ # create a static directory to store the static files
431
+ static_dir = Path('./gradio_cache')
432
+ static_dir.mkdir(parents=True, exist_ok=True)
433
+ app.mount("/static", StaticFiles(directory=static_dir), name="static")
434
+
435
+ demo = build_app()
436
+ demo.queue(max_size=10)
437
+ app = gr.mount_gradio_app(app, demo, path="/")
438
+ uvicorn.run(app, host=IP, port=PORT)