faneggg commited on
Commit
d89af41
·
1 Parent(s): 5c812ce
Files changed (1) hide show
  1. app.py +413 -14
app.py CHANGED
@@ -1,14 +1,413 @@
1
- import gradio as gr
2
- import spaces
3
- import torch
4
-
5
- zero = torch.Tensor([0]).cuda()
6
- print(zero.device) # <-- 'cpu' 🤔
7
-
8
- @spaces.GPU
9
- def greet(n):
10
- print(zero.device) # <-- 'cuda:0' 🤗
11
- return f"Hello {zero + n} Tensor"
12
-
13
- demo = gr.Interface(fn=greet, inputs=gr.Number(), outputs=gr.Text())
14
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, subprocess, shlex, sys, gc
2
+ import time
3
+ import torch
4
+ import numpy as np
5
+ import shutil
6
+ import argparse
7
+ import gradio as gr
8
+ import uuid
9
+ import spaces
10
+
11
+ subprocess.run(shlex.split("pip install wheel/diff_gaussian_rasterization-0.0.0-cp310-cp310-linux_x86_64.whl"))
12
+ subprocess.run(shlex.split("pip install wheel/simple_knn-0.0.0-cp310-cp310-linux_x86_64.whl"))
13
+ subprocess.run(shlex.split("pip install wheel/curope-0.0.0-cp310-cp310-linux_x86_64.whl"))
14
+
15
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
16
+ os.sys.path.append(os.path.abspath(os.path.join(BASE_DIR, "submodules", "mast3r")))
17
+ os.sys.path.append(os.path.abspath(os.path.join(BASE_DIR, "submodules", "mast3r", "dust3r")))
18
+ # os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'expandable_segments:True'
19
+ from dust3r.inference import inference
20
+ from dust3r.model import AsymmetricCroCo3DStereo
21
+ from dust3r.utils.device import to_numpy
22
+ from dust3r.image_pairs import make_pairs
23
+ from dust3r.cloud_opt import global_aligner, GlobalAlignerMode
24
+ from utils.dust3r_utils import compute_global_alignment, load_images, storePly, save_colmap_cameras, save_colmap_images
25
+
26
+ from argparse import ArgumentParser
27
+ from arguments import ModelParams, PipelineParams, OptimizationParams
28
+ from train_feat2gs import training
29
+ from run_video import render_sets
30
+ GRADIO_CACHE_FOLDER = './gradio_cache_folder'
31
+
32
+ from utils.feat_utils import FeatureExtractor
33
+ from dust3r.demo import _convert_scene_output_to_glb
34
+ #############################################################################################################################################
35
+
36
+
37
+ def get_dust3r_args_parser():
38
+ parser = argparse.ArgumentParser()
39
+ parser.add_argument("--image_size", type=int, default=512, choices=[512, 224], help="image size")
40
+ parser.add_argument("--model_path", type=str, default="submodules/mast3r/checkpoints/DUSt3R_ViTLarge_BaseDecoder_512_dpt.pth", help="path to the model weights")
41
+ parser.add_argument("--device", type=str, default='cuda', help="pytorch device")
42
+ parser.add_argument("--batch_size", type=int, default=1)
43
+ parser.add_argument("--schedule", type=str, default='linear')
44
+ parser.add_argument("--lr", type=float, default=0.01)
45
+ parser.add_argument("--niter", type=int, default=300)
46
+ parser.add_argument("--focal_avg", type=bool, default=True)
47
+ parser.add_argument("--n_views", type=int, default=3)
48
+ parser.add_argument("--base_path", type=str, default=GRADIO_CACHE_FOLDER)
49
+ parser.add_argument("--feat_dim", type=int, default=256, help="PCA dimension. If None, PCA is not applied, and the original feature dimension is retained.")
50
+ parser.add_argument("--feat_type", type=str, nargs='*', default=["dust3r",], help="Feature type(s). Multiple types can be specified for combination.")
51
+ parser.add_argument("--vis_feat", action="store_true", default=True, help="Visualize features")
52
+ parser.add_argument("--vis_key", type=str, default=None, help="Feature type to visualize (only for mast3r), e.g., 'decfeat' or 'desc'")
53
+ parser.add_argument("--method", type=str, default='dust3r', help="Method of Initialization, e.g., 'dust3r' or 'mast3r'")
54
+
55
+ return parser
56
+
57
+
58
+ @spaces.GPU(duration=150)
59
+ def run_dust3r(inputfiles, input_path=None):
60
+
61
+ if input_path is not None:
62
+ imgs_path = './assets/example/' + input_path
63
+ imgs_names = sorted(os.listdir(imgs_path))
64
+
65
+ inputfiles = []
66
+ for imgs_name in imgs_names:
67
+ file_path = os.path.join(imgs_path, imgs_name)
68
+ print(file_path)
69
+ inputfiles.append(file_path)
70
+ print(inputfiles)
71
+
72
+ # ------ Step(1) DUSt3R initialization & Feature extraction ------
73
+ # os.system(f"rm -rf {GRADIO_CACHE_FOLDER}")
74
+ parser = get_dust3r_args_parser()
75
+ opt = parser.parse_args()
76
+
77
+ method = opt.method
78
+
79
+ tmp_user_folder = str(uuid.uuid4()).replace("-", "")
80
+ opt.img_base_path = os.path.join(opt.base_path, tmp_user_folder)
81
+ img_folder_path = os.path.join(opt.img_base_path, "images")
82
+
83
+ model = AsymmetricCroCo3DStereo.from_pretrained(opt.model_path).to(opt.device)
84
+ os.makedirs(img_folder_path, exist_ok=True)
85
+
86
+ opt.n_views = len(inputfiles)
87
+ if opt.n_views == 1:
88
+ raise gr.Error("The number of input images should be greater than 1.")
89
+ print("Multiple images: ", inputfiles)
90
+ # for image_file in inputfiles:
91
+ # image_path = image_file.name if hasattr(image_file, 'name') else image_file
92
+ # shutil.copy(image_path, img_folder_path)
93
+ for image_path in inputfiles:
94
+ if input_path is not None:
95
+ shutil.copy(image_path, img_folder_path)
96
+ else:
97
+ shutil.move(image_path, img_folder_path)
98
+ train_img_list = sorted(os.listdir(img_folder_path))
99
+ assert len(train_img_list)==opt.n_views, f"Number of images in the folder is not equal to {opt.n_views}"
100
+ images, ori_size = load_images(img_folder_path, size=512)
101
+ # images, ori_size, imgs_resolution = load_images(img_folder_path, size=512)
102
+ # resolutions_are_equal = len(set(imgs_resolution)) == 1
103
+ # if resolutions_are_equal == False:
104
+ # raise gr.Error("The resolution of the input image should be the same.")
105
+ print("ori_size", ori_size)
106
+ start_time = time.time()
107
+ ######################################################
108
+ pairs = make_pairs(images, scene_graph='complete', prefilter=None, symmetrize=True)
109
+ output = inference(pairs, model, opt.device, batch_size=opt.batch_size)
110
+
111
+ scene = global_aligner(output, device=opt.device, mode=GlobalAlignerMode.PointCloudOptimizer)
112
+ loss = compute_global_alignment(scene=scene, init="mst", niter=opt.niter, schedule=opt.schedule, lr=opt.lr, focal_avg=opt.focal_avg)
113
+ scene = scene.clean_pointcloud()
114
+
115
+ imgs = to_numpy(scene.imgs)
116
+ focals = scene.get_focals()
117
+ poses = to_numpy(scene.get_im_poses())
118
+ pts3d = to_numpy(scene.get_pts3d())
119
+ scene.min_conf_thr = float(scene.conf_trf(torch.tensor(1.0)))
120
+ confidence_masks = to_numpy(scene.get_masks())
121
+ intrinsics = to_numpy(scene.get_intrinsics())
122
+
123
+ ######################################################
124
+ end_time = time.time()
125
+ print(f"Time taken for {opt.n_views} views: {end_time-start_time} seconds")
126
+
127
+ output_colmap_path=img_folder_path.replace("images", f"sparse/0/{method}")
128
+
129
+ # Feature extraction for per point(per pixel)
130
+ extractor = FeatureExtractor(images, opt, method)
131
+ feats = extractor(scene=scene)
132
+ feat_type_str = '-'.join(extractor.feat_type)
133
+ output_colmap_path = os.path.join(output_colmap_path, feat_type_str)
134
+ os.makedirs(output_colmap_path, exist_ok=True)
135
+
136
+ outfile = _convert_scene_output_to_glb(output_colmap_path, imgs, pts3d, confidence_masks, focals, poses, as_pointcloud=True, cam_size=0.03)
137
+ feat_image_path = os.path.join(opt.img_base_path, "feat_dim0-9_dust3r.png")
138
+
139
+ save_colmap_cameras(ori_size, intrinsics, os.path.join(output_colmap_path, 'cameras.txt'))
140
+ save_colmap_images(poses, os.path.join(output_colmap_path, 'images.txt'), train_img_list)
141
+ pts_4_3dgs = np.concatenate([p[m] for p, m in zip(pts3d, confidence_masks)])
142
+ color_4_3dgs = np.concatenate([p[m] for p, m in zip(imgs, confidence_masks)])
143
+ color_4_3dgs = (color_4_3dgs * 255.0).astype(np.uint8)
144
+ feat_4_3dgs = np.concatenate([p[m] for p, m in zip(feats, confidence_masks)])
145
+ storePly(os.path.join(output_colmap_path, f"points3D.ply"), pts_4_3dgs, color_4_3dgs, feat_4_3dgs)
146
+
147
+ del scene
148
+ torch.cuda.empty_cache()
149
+ gc.collect()
150
+
151
+ return outfile, feat_image_path, opt, None, None
152
+
153
+ @spaces.GPU(duration=150)
154
+ def run_feat2gs(opt, niter=2000):
155
+
156
+ if opt is None:
157
+ raise gr.Error("Please run Step 1 first!")
158
+
159
+ try:
160
+ if not os.path.exists(opt.img_base_path):
161
+ raise ValueError(f"Input path does not exist: {opt.img_base_path}")
162
+
163
+ if not os.path.exists(os.path.join(opt.img_base_path, "images")):
164
+ raise ValueError("Input images not found. Please run Step 1 first")
165
+
166
+ if not os.path.exists(os.path.join(opt.img_base_path, f"sparse/0/{opt.method}")):
167
+ raise ValueError("DUSt3R output not found. Please run Step 1 first")
168
+
169
+ # ------ Step(2) Readout 3DGS from features & Jointly optimize pose ------
170
+ parser = ArgumentParser(description="Training script parameters")
171
+ lp = ModelParams(parser)
172
+ op = OptimizationParams(parser)
173
+ pp = PipelineParams(parser)
174
+ parser.add_argument('--debug_from', type=int, default=-1)
175
+ parser.add_argument("--test_iterations", nargs="+", type=int, default=[])
176
+ parser.add_argument("--save_iterations", nargs="+", type=int, default=[])
177
+ parser.add_argument("--checkpoint_iterations", nargs="+", type=int, default=[])
178
+ parser.add_argument("--start_checkpoint", type=str, default = None)
179
+ parser.add_argument("--scene", type=str, default="demo")
180
+ parser.add_argument("--n_views", type=int, default=3)
181
+ parser.add_argument("--get_video", action="store_true")
182
+ parser.add_argument("--optim_pose", type=bool, default=True)
183
+ parser.add_argument("--feat_type", type=str, nargs='*', default=["dust3r",], help="Feature type(s). Multiple types can be specified for combination.")
184
+ parser.add_argument("--method", type=str, default='dust3r', help="Method of Initialization, e.g., 'dust3r' or 'mast3r'")
185
+ parser.add_argument("--feat_dim", type=int, default=256, help="Feture dimension after PCA . If None, PCA is not applied.")
186
+ parser.add_argument("--model", type=str, default='Gft', help="Model of Feat2gs, 'G'='geometry'/'T'='texture'/'A'='all'")
187
+ parser.add_argument("--dataset", default="demo", type=str)
188
+ parser.add_argument("--resize", action="store_true", default=True,
189
+ help="If True, resize rendering to square")
190
+
191
+ args = parser.parse_args(sys.argv[1:])
192
+ args.iterations = niter
193
+ args.save_iterations.append(args.iterations)
194
+ args.model_path = opt.img_base_path + '/output/'
195
+ args.source_path = opt.img_base_path
196
+ # args.model_path = GRADIO_CACHE_FOLDER + '/output/'
197
+ # args.source_path = GRADIO_CACHE_FOLDER
198
+ args.iteration = niter
199
+ os.makedirs(args.model_path, exist_ok=True)
200
+ training(lp.extract(args), op.extract(args), pp.extract(args), args.test_iterations, args.save_iterations, args.checkpoint_iterations, args.start_checkpoint, args.debug_from, args)
201
+
202
+ output_ply_path = opt.img_base_path + f'/output/point_cloud/iteration_{args.iteration}/point_cloud.ply'
203
+ # output_ply_path = GRADIO_CACHE_FOLDER+ f'/output/point_cloud/iteration_{args.iteration}/point_cloud.ply'
204
+
205
+ torch.cuda.empty_cache()
206
+ gc.collect()
207
+
208
+ return output_ply_path, args, None
209
+
210
+ except Exception as e:
211
+ raise gr.Error(f"Step 2 failed: {str(e)}")
212
+
213
+
214
+ @spaces.GPU(duration=150)
215
+ def run_render(opt, args, cam_traj='ellipse'):
216
+ if opt is None or args is None:
217
+ raise gr.Error("Please run Steps 1 and 2 first!")
218
+
219
+ try:
220
+ iteration_path = os.path.join(opt.img_base_path, f"output/point_cloud/iteration_{args.iteration}/point_cloud.ply")
221
+ if not os.path.exists(iteration_path):
222
+ raise ValueError("Training results not found. Please run Step 2 first")
223
+
224
+ # ------ Step(3) Render video with camera trajectory ------
225
+ parser = ArgumentParser(description="Testing script parameters")
226
+ model = ModelParams(parser, sentinel=True)
227
+ pipeline = PipelineParams(parser)
228
+ args.eval = True
229
+ args.get_video = True
230
+ args.n_views = opt.n_views
231
+ args.cam_traj = cam_traj
232
+ render_sets(
233
+ model.extract(args),
234
+ args.iteration,
235
+ pipeline.extract(args),
236
+ args,
237
+ )
238
+
239
+ output_video_path = opt.img_base_path + f'/output/videos/demo_{opt.n_views}_view_{args.cam_traj}.mp4'
240
+
241
+ torch.cuda.empty_cache()
242
+ gc.collect()
243
+
244
+ return output_video_path
245
+
246
+ except Exception as e:
247
+ raise gr.Error(f"Step 3 failed: {str(e)}")
248
+
249
+
250
+ def process_example(inputfiles, input_path):
251
+ dust3r_model, feat_image, dust3r_state, _, _ = run_dust3r(inputfiles, input_path=input_path)
252
+
253
+ output_model, feat2gs_state, _ = run_feat2gs(dust3r_state, niter=2000)
254
+
255
+ output_video = run_render(dust3r_state, feat2gs_state, cam_traj='interpolated')
256
+
257
+ return dust3r_model, feat_image, output_model, output_video
258
+
259
+ def reset_dust3r_state():
260
+ return None, None, None, None, None
261
+
262
+ def reset_feat2gs_state():
263
+ return None, None, None
264
+
265
+ _TITLE = '''Feat2GS Demo'''
266
+ _DESCRIPTION = '''
267
+ <div style="display: flex; justify-content: center; align-items: center;">
268
+ <div style="width: 100%; text-align: center; font-size: 30px;">
269
+ <strong><span style="font-family: 'Comic Sans MS';"><span style="color: #E0933F">Feat</span><span style="color: #B24C33">2</span><span style="color: #E0933F">GS</span></span>: Probing Visual Foundation Models with Gaussian Splatting</strong>
270
+ </div>
271
+ </div>
272
+ <p></p>
273
+ <div align="center">
274
+ <a style="display:inline-block" href="https://fanegg.github.io/Feat2GS/"><img src='https://img.shields.io/badge/Project-Website-green.svg'></a>&nbsp;
275
+ <a style="display:inline-block" href="https://arxiv.org/abs/2412.09606"><img src="https://img.shields.io/badge/Arxiv-2412.09606-b31b1b.svg?logo=arXiv" alt='arxiv'></a>&nbsp;
276
+ <a style="display:inline-block" href="https://youtu.be/4fT5lzcAJqo?si=_fCSIuXNBSmov2VA"><img src='https://img.shields.io/badge/Video-E33122?logo=Youtube'></a>&nbsp;
277
+ <a style="display:inline-block" href="https://github.com/fanegg/Feat2GS"><img src="https://img.shields.io/badge/Code-black?logo=Github" alt='Code'></a>&nbsp;
278
+ <a title="X" href="https://twitter.com/faneggchen" target="_blank" rel="noopener noreferrer" style="display: inline-block;">
279
+ <img src="https://img.shields.io/badge/@Yue%20Chen-black?logo=X" alt="X">
280
+ </a>&nbsp;
281
+ <a title="Bluesky" href="https://bsky.app/profile/fanegg.bsky.social" target="_blank" rel="noopener noreferrer" style="display: inline-block;">
282
+ <img src="https://img.shields.io/badge/@Yue%20Chen-white?logo=Bluesky" alt="Bluesky">
283
+ </a>
284
+ </div>
285
+ <p></p>
286
+ '''
287
+
288
+
289
+ # demo = gr.Blocks(title=_TITLE).queue()
290
+ demo = gr.Blocks(css=""".gradio-container {margin: 0 !important; min-width: 100%};""", title="Feat2GS Demo").queue()
291
+ with demo:
292
+ dust3r_state = gr.State(None)
293
+ feat2gs_state = gr.State(None)
294
+ render_state = gr.State(None)
295
+
296
+ with gr.Row():
297
+ with gr.Column(scale=1):
298
+ with gr.Accordion("🚀 Quickstart", open=False):
299
+ gr.Markdown("""
300
+ 1. **Input Images**
301
+ * Upload 2 or more images of the same scene from different views
302
+ * For best results, ensure images have good overlap
303
+
304
+ 2. **Step 1: DUSt3R Initialization & Feature Extraction**
305
+ * Click "RUN Step 1" to process your images
306
+ * This step estimates initial DUSt3R point cloud and camera poses, and extracts DUSt3R features for each pixel
307
+
308
+ 3. **Step 2: Readout 3DGS from Features**
309
+ * Set the number of training iterations, larger number leads to better quality but longer time (default: 2000, max: 8000)
310
+ * Click "RUN Step 2" to optimize the 3D model
311
+
312
+ 4. **Step 3: Video Rendering**
313
+ * Choose a camera trajectory
314
+ * Click "RUN Step 3" to generate a video of your 3D model
315
+ """)
316
+
317
+ with gr.Accordion("💡 Tips", open=False):
318
+ gr.Markdown("""
319
+ * Processing time depends on image resolution and quantity
320
+ * For optimal performance, test on high-end GPUs (A100/4090)
321
+ * Use the mouse to interact with 3D models:
322
+ - Left button: Rotate
323
+ - Scroll wheel: Zoom
324
+ - Right button: Pan
325
+ """)
326
+
327
+ with gr.Row():
328
+ with gr.Column(scale=1):
329
+ # gr.Markdown('# ' + _TITLE)
330
+ gr.Markdown(_DESCRIPTION)
331
+
332
+ with gr.Row(variant='panel'):
333
+ with gr.Tab("Input"):
334
+ inputfiles = gr.File(file_count="multiple", label="images")
335
+ input_path = gr.Textbox(visible=False, label="example_path")
336
+ # button_gen = gr.Button("RUN")
337
+
338
+ with gr.Row(variant='panel'):
339
+ with gr.Tab("Step 1: DUSt3R initialization & Feature extraction"):
340
+ dust3r_run = gr.Button("RUN Step 1")
341
+ with gr.Column(scale=2):
342
+ with gr.Group():
343
+ dust3r_model = gr.Model3D(
344
+ label="DUSt3R Output",
345
+ interactive=False,
346
+ # camera_position=[0.5, 0.5, 1],
347
+ )
348
+ feat_image = gr.Image(
349
+ label="Feature Visualization",
350
+ type="filepath"
351
+ )
352
+
353
+ with gr.Row(variant='panel'):
354
+ with gr.Tab("Step 2: Readout 3DGS from features & Jointly optimize pose"):
355
+ niter = gr.Number(value=2000, precision=0, minimum=1000, maximum=8000, label="Training iterations")
356
+ feat2gs_run = gr.Button("RUN Step 2")
357
+ with gr.Column(scale=1):
358
+ with gr.Group():
359
+ output_model = gr.Model3D(
360
+ label="3D Gaussian Splats Output, need more time to visualize",
361
+ interactive=False,
362
+ # camera_position=[0.5, 0.5, 1],
363
+ )
364
+ gr.Markdown(
365
+ """
366
+ <div class="model-description">
367
+ &nbsp;&nbsp;Use the left mouse button to rotate, the scroll wheel to zoom, and the right mouse button to move.
368
+ </div>
369
+ """
370
+ )
371
+
372
+ with gr.Row(variant='panel'):
373
+ with gr.Tab("Step 3: Render video with camera trajectory"):
374
+ cam_traj = gr.Dropdown(["arc", "spiral", "lemniscate", "wander", "ellipse", "interpolated"], value='ellipse', label="Camera trajectory")
375
+ render_run = gr.Button("RUN Step 3")
376
+ with gr.Column(scale=1):
377
+ output_video = gr.Video(label="video", height=800)
378
+
379
+ dust3r_run.click(
380
+ fn=reset_dust3r_state,
381
+ inputs=None,
382
+ outputs=[dust3r_model, feat_image, dust3r_state, feat2gs_state, render_state],
383
+ queue=False
384
+ ).then(
385
+ fn=run_dust3r,
386
+ inputs=[inputfiles],
387
+ outputs=[dust3r_model, feat_image, dust3r_state, feat2gs_state, render_state]
388
+ )
389
+ feat2gs_run.click(
390
+ fn=reset_feat2gs_state,
391
+ inputs=None,
392
+ outputs=[output_model, feat2gs_state, render_state],
393
+ queue=False
394
+ ).then(
395
+ fn=run_feat2gs,
396
+ inputs=[dust3r_state, niter],
397
+ outputs=[output_model, feat2gs_state, render_state]
398
+ )
399
+ render_run.click(run_render, inputs=[dust3r_state, feat2gs_state, cam_traj], outputs=[output_video])
400
+
401
+
402
+ # gr.Examples(
403
+ # examples=[
404
+ # "plushies",
405
+ # ],
406
+ # inputs=[input_path],
407
+ # outputs=[dust3r_model, feat_image, output_model, output_video],
408
+ # fn=lambda x: process_example(inputfiles=None, input_path=x),
409
+ # cache_examples=True,
410
+ # label='Examples'
411
+ # )
412
+
413
+ demo.launch(server_name="0.0.0.0", share=False)