choaslord2010 commited on
Commit
6d18893
·
verified ·
1 Parent(s): 9f32d7d

Create hg_app.py

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