Jiayuan Gu commited on
Commit
a123cb5
·
0 Parent(s):

point-sam demo

Browse files
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ *.safetensors
Dockerfile ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM nvidia/cuda:12.1.1-devel-ubuntu20.04
2
+ ENV NVIDIA_VISIBLE_DEVICES ${NVIDIA_VISIBLE_DEVICES:-all}
3
+ ENV NVIDIA_DRIVER_CAPABILITIES ${NVIDIA_DRIVER_CAPABILITIES:+$NVIDIA_DRIVER_CAPABILITIES,}graphics
4
+
5
+ ARG PYTHON_VERSION=3.10
6
+
7
+ # Install os-level packages
8
+ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
9
+ bash-completion \
10
+ build-essential \
11
+ ca-certificates \
12
+ cmake \
13
+ curl \
14
+ git \
15
+ htop \
16
+ libegl1 \
17
+ libxext6 \
18
+ libjpeg-dev \
19
+ libpng-dev \
20
+ rsync \
21
+ tmux \
22
+ unzip \
23
+ vim \
24
+ wget \
25
+ xvfb \
26
+ && rm -rf /var/lib/apt/lists/*
27
+
28
+ # Install (mini) conda
29
+ RUN curl -o ~/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \
30
+ chmod +x ~/miniconda.sh && \
31
+ ~/miniconda.sh -b -p /opt/conda && \
32
+ rm ~/miniconda.sh && \
33
+ /opt/conda/bin/conda init && \
34
+ /opt/conda/bin/conda install -y python="$PYTHON_VERSION" && \
35
+ /opt/conda/bin/conda clean -ya
36
+
37
+ ENV PATH /opt/conda/bin:$PATH
38
+ SHELL ["/bin/bash", "-c"]
39
+
40
+ RUN pip install \
41
+ numpy==1.26.4 \
42
+ scipy \
43
+ ninja \
44
+ torch==2.1.2 \
45
+ torchvision==0.16.2 \
46
+ h5py \
47
+ matplotlib \
48
+ "trimesh>=4.2.0" \
49
+ "pyglet<2" \
50
+ "accelerate>=0.28.0" \
51
+ wandb \
52
+ timm \
53
+ datasets \
54
+ hydra-core \
55
+ && pip cache purge
56
+
57
+ RUN FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST="6.0;7.0;7.5;8.0;8.6;9.0" pip install "git+https://github.com/Jiayuan-Gu/torkit3d.git@235ecf60497271136f5552cb45bb7cf75ab1cb09" && pip cache purge
58
+
59
+ # Install apex
60
+ RUN git clone --single-branch https://github.com/NVIDIA/apex && \
61
+ cd apex && git checkout 810ffae374a2b9cb4b5c5e28eaeca7d7998fca0c && \
62
+ pip install -v --disable-pip-version-check --no-cache-dir --no-build-isolation --config-settings "--build-option=--cpp_ext" --config-settings "--build-option=--cuda_ext" ./ && pip cache purge && \
63
+ cd .. && rm -rf apex
64
+
65
+ RUN useradd -m -u 1000 user
66
+
67
+ WORKDIR /app
68
+
69
+ RUN pip install git+https://github.com/zyc00/Point-SAM.git && pip cache purge
70
+ RUN pip install flask flask_cors && pip cache purge
71
+
72
+ COPY --chown=user . /app
73
+
74
+ RUN wget https://yuchen-service.nrp-nautilus.io/yuchen_fast/pointcloud-sam/pretrained/ours/mixture_10k/model-2.safetensors
75
+
76
+ CMD [ "python3", "app.py", "--host=0.0.0.0", "--port=7860"]
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Point SAM
3
+ emoji: 🏆
4
+ colorFrom: purple
5
+ colorTo: yellow
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dataclasses
2
+ import os
3
+
4
+ import hydra
5
+ import numpy as np
6
+ import torch
7
+ from flask import Flask, jsonify, request, render_template
8
+ from flask_cors import CORS
9
+ from omegaconf import OmegaConf
10
+ from safetensors.torch import load_model
11
+ from scipy.spatial.transform import Rotation
12
+
13
+ from point_sam import build_point_sam
14
+ import argparse
15
+
16
+ app = Flask(__name__, static_folder="static")
17
+ CORS(app)
18
+
19
+ @dataclasses.dataclass
20
+ class AuxInputs:
21
+ coords: torch.Tensor
22
+ features: torch.Tensor
23
+ centers: torch.Tensor
24
+ interp_index: torch.Tensor = None
25
+ interp_weight: torch.Tensor = None
26
+
27
+ def repeat_interleave(x: torch.Tensor, repeats: int, dim: int):
28
+ if repeats == 1:
29
+ return x
30
+ shape = list(x.shape)
31
+ shape.insert(dim + 1, 1)
32
+ shape[dim + 1] = repeats
33
+ x = x.unsqueeze(dim + 1).expand(shape).flatten(dim, dim + 1)
34
+ return x
35
+
36
+
37
+ class PointCloudProcessor:
38
+ def __init__(self, device="cuda", batch=True, return_tensors="pt"):
39
+ self.device = device
40
+ self.batch = batch
41
+ self.return_tensors = return_tensors
42
+
43
+ self.center = None
44
+ self.scale = None
45
+
46
+ def __call__(self, xyz: np.ndarray, rgb: np.ndarray):
47
+ # # The original data is z-up. Make it y-up.
48
+ # rot = Rotation.from_euler("x", -90, degrees=True)
49
+ # xyz = rot.apply(xyz)
50
+
51
+ if self.center is None or self.scale is None:
52
+ self.center = xyz.mean(0)
53
+ self.scale = np.max(np.linalg.norm(xyz - self.center, axis=-1))
54
+
55
+ xyz = (xyz - self.center) / self.scale
56
+ rgb = ((rgb / 255.0) - 0.5) * 2
57
+
58
+ if self.return_tensors == "np":
59
+ coords = np.float32(xyz)
60
+ feats = np.float32(rgb)
61
+ if self.batch:
62
+ coords = np.expand_dims(coords, 0)
63
+ feats = np.expand_dims(feats, 0)
64
+ elif self.return_tensors == "pt":
65
+ coords = torch.tensor(xyz, dtype=torch.float32, device=self.device)
66
+ feats = torch.tensor(rgb, dtype=torch.float32, device=self.device)
67
+ if self.batch:
68
+ coords = coords.unsqueeze(0)
69
+ feats = feats.unsqueeze(0)
70
+ else:
71
+ raise ValueError(self.return_tensors)
72
+
73
+ return coords, feats
74
+
75
+ def normalize(self, xyz):
76
+ return (xyz - self.center) / self.scale
77
+
78
+
79
+ class PointCloudSAMPredictor:
80
+ input_xyz: np.ndarray
81
+ input_rgb: np.ndarray
82
+ prompt_coords: list[tuple[float, float, float]]
83
+ prompt_labels: list[int]
84
+
85
+ coords: torch.Tensor
86
+ feats: torch.Tensor
87
+
88
+ pc_embedding: torch.Tensor
89
+ patches: dict[str, torch.Tensor]
90
+ prompt_mask: torch.Tensor
91
+
92
+ def __init__(self):
93
+ print("Created model")
94
+ model = build_point_sam("./model-2.safetensors")
95
+ model.pc_encoder.patch_embed.grouper.num_groups = 1024
96
+ model.pc_encoder.patch_embed.grouper.group_size = 128
97
+ if torch.cuda.is_available():
98
+ model = model.cuda()
99
+ model.eval()
100
+
101
+ self.model = model
102
+
103
+ self.input_rgb = None
104
+ self.input_xyz = None
105
+
106
+ self.input_processor = None
107
+ self.coords = None
108
+ self.feats = None
109
+
110
+ self.pc_embedding = None
111
+ self.patches = None
112
+
113
+ self.prompt_coords = None
114
+ self.prompt_labels = None
115
+ self.prompt_mask = None
116
+ self.candidate_index = 0
117
+
118
+ @torch.no_grad()
119
+ def set_pointcloud(self, xyz, rgb):
120
+ self.input_xyz = xyz
121
+ self.input_rgb = rgb
122
+
123
+ self.input_processor = PointCloudProcessor()
124
+ coords, feats = self.input_processor(xyz, rgb)
125
+ self.coords = coords
126
+ self.feats = feats
127
+
128
+ pc_embedding, patches = self.model.pc_encoder(self.coords, self.feats)
129
+ self.pc_embedding = pc_embedding
130
+ self.patches = patches
131
+ self.prompt_mask = None
132
+
133
+ def set_prompts(self, prompt_coords, prompt_labels):
134
+ self.prompt_coords = prompt_coords
135
+ self.prompt_labels = prompt_labels
136
+
137
+ @torch.no_grad()
138
+ def predict_mask(self):
139
+ normalized_prompt_coords = self.input_processor.normalize(
140
+ np.array(self.prompt_coords)
141
+ )
142
+ prompt_coords = torch.tensor(
143
+ normalized_prompt_coords, dtype=torch.float32, device="cuda"
144
+ )
145
+ prompt_labels = torch.tensor(
146
+ self.prompt_labels, dtype=torch.bool, device="cuda"
147
+ )
148
+ prompt_coords = prompt_coords.reshape(1, -1, 3)
149
+ prompt_labels = prompt_labels.reshape(1, -1)
150
+
151
+ multimask_output = prompt_coords.shape[1] == 1
152
+
153
+ # [B * M, num_outputs, num_points], [B * M, num_outputs]
154
+ def decode_masks(coords, feats, pc_embedding, patches, prompt_coords, prompt_labels, prompt_masks, multimask_output):
155
+ pc_embeddings, patches = pc_embedding, patches
156
+ centers = patches["centers"]
157
+ knn_idx = patches["knn_idx"]
158
+ coords = patches["coords"]
159
+ feats = patches["feats"]
160
+ aux_inputs = AuxInputs(coords=coords, features=feats, centers=centers)
161
+
162
+ pc_pe = self.model.point_encoder.pe_layer(centers)
163
+ sparse_embeddings = self.model.point_encoder(prompt_coords, prompt_labels)
164
+ dense_embeddings = self.model.mask_encoder(prompt_masks, coords, centers, knn_idx)
165
+ dense_embeddings = repeat_interleave(
166
+ dense_embeddings, sparse_embeddings.shape[0] // dense_embeddings.shape[0], 0
167
+ )
168
+
169
+ logits, iou_preds = self.model.mask_decoder(
170
+ pc_embeddings,
171
+ pc_pe,
172
+ sparse_embeddings,
173
+ dense_embeddings,
174
+ aux_inputs=aux_inputs,
175
+ multimask_output=multimask_output,
176
+ )
177
+ return logits, iou_preds
178
+
179
+ logits, scores = decode_masks(
180
+ self.coords,
181
+ self.feats,
182
+ self.pc_embedding,
183
+ self.patches,
184
+ prompt_coords,
185
+ prompt_labels,
186
+ self.prompt_mask[self.candidate_index].unsqueeze(0) if self.prompt_mask is not None else None,
187
+ multimask_output,
188
+ )
189
+ logits = logits.squeeze(0)
190
+ scores = scores.squeeze(0)
191
+
192
+ # if multimask_output:
193
+ # index = scores.argmax(0).item()
194
+ # logit = logits[index]
195
+ # else:
196
+ # logit = logits.squeeze(0)
197
+
198
+ # self.prompt_mask = logit.unsqueeze(0)
199
+
200
+ # pred_mask = logit > 0
201
+ # return pred_mask.cpu().numpy()
202
+
203
+ # Sort according to scores
204
+ _, indices = scores.sort(descending=True)
205
+ logits = logits[indices]
206
+
207
+ self.prompt_mask = logits # [num_outputs, num_points]
208
+ self.candidate_index = 0
209
+
210
+ return (logits > 0).cpu().numpy()
211
+
212
+ def set_candidate(self, index):
213
+ self.candidate_index = index
214
+
215
+
216
+ predictor = PointCloudSAMPredictor()
217
+
218
+
219
+ @app.route("/")
220
+ def index():
221
+ return app.send_static_file("index.html")
222
+
223
+ @app.route("/assets/<path:path>")
224
+ def assets_route(path):
225
+ print(path)
226
+ return app.send_static_file(f"assets/{path}")
227
+
228
+
229
+ @app.route("/hello_world", methods=["GET"])
230
+ def hello_world():
231
+ return "Hello, World!"
232
+
233
+
234
+ @app.route("/set_pointcloud", methods=["POST"])
235
+ def set_pointcloud():
236
+ request_data = request.get_json()
237
+ # print(request_data)
238
+ # print(type(request_data["points"]))
239
+ # print(type(request_data["colors"]))
240
+
241
+ xyz = request_data["points"]
242
+ xyz = np.array(xyz).reshape(-1, 3)
243
+ rgb = request_data["colors"]
244
+ rgb = np.array(list(rgb)).reshape(-1, 3)
245
+ predictor.set_pointcloud(xyz, rgb)
246
+
247
+ pc_embedding = predictor.pc_embedding.cpu().numpy()
248
+ patches = {"centers": predictor.patches["centers"].cpu().numpy().tolist(), "knn_idx": predictor.patches["knn_idx"].cpu().numpy().tolist(), "coords": predictor.coords.cpu().numpy().tolist(), "feats": predictor.feats.cpu().numpy().tolist()}
249
+ center = predictor.input_processor.center
250
+ scale = predictor.input_processor.scale
251
+ return jsonify({"pc_embedding": pc_embedding.tolist(), "patches": patches, "center": center.tolist(), "scale": scale})
252
+
253
+
254
+ @app.route("/set_candidate", methods=["POST"])
255
+ def set_candidate():
256
+ request_data = request.get_json()
257
+ candidate_index = request_data["index"]
258
+ predictor.set_candidate(candidate_index)
259
+ return "success"
260
+
261
+
262
+ def visualize_pcd_with_prompts(xyz, rgb, prompt_coords, prompt_labels):
263
+ import trimesh
264
+
265
+ pcd = trimesh.PointCloud(xyz, rgb)
266
+ prompt_spheres = []
267
+ for i, coord in enumerate(prompt_coords):
268
+ sphere = trimesh.creation.icosphere()
269
+ sphere.apply_scale(0.02)
270
+ sphere.apply_translation(coord)
271
+ sphere.visual.vertex_colors = [255, 0, 0] if prompt_labels[i] else [0, 255, 0]
272
+ prompt_spheres.append(sphere)
273
+
274
+ return trimesh.Scene([pcd] + prompt_spheres)
275
+
276
+
277
+ @app.route("/set_prompts", methods=["POST"])
278
+ def set_prompts():
279
+ request_data = request.get_json()
280
+ print(request_data.keys())
281
+
282
+ # [n_prompts, 3]
283
+ prompt_coords = request_data["prompt_coords"]
284
+ # [n_prompts]. 0 for negative, 1 for positive
285
+ prompt_labels = request_data["prompt_labels"]
286
+ embedding = torch.tensor(request_data["embeddings"]).cuda()
287
+ patches = request_data["patches"]
288
+ patches = {k: torch.tensor(v).cuda() for k, v in patches.items()}
289
+ predictor.pc_embedding = embedding
290
+ predictor.patches = patches
291
+ predictor.input_processor.center = np.array(request_data["center"])
292
+ predictor.input_processor.scale = request_data["scale"]
293
+ if request_data["prompt_mask"] is not None:
294
+ predictor.prompt_mask = torch.tensor(request_data["prompt_mask"]).cuda()
295
+ # instance_id = request_data["instance_id"] # int
296
+ if len(prompt_coords) == 0:
297
+ predictor.prompt_mask = None
298
+ pred_mask = np.zeros([len(prompt_coords)], dtype=np.bool_)
299
+ return jsonify({"mask": pred_mask.tolist()})
300
+
301
+ predictor.set_prompts(prompt_coords, prompt_labels)
302
+ pred_mask = predictor.predict_mask()
303
+ prompt_mask = predictor.prompt_mask.cpu().numpy()
304
+
305
+ # # Visualize
306
+ # xyz = predictor.coords.cpu().numpy()[0]
307
+ # rgb = predictor.feats.cpu().numpy()[0] * 0.5 + 0.5
308
+ # prompt_coords = predictor.input_processor.normalize(np.array(predictor.prompt_coords))
309
+ # scene = visualize_pcd_with_prompts(xyz, rgb, prompt_coords, predictor.prompt_labels)
310
+ # scene.show()
311
+
312
+ return jsonify({"mask": pred_mask.tolist(), "prompt_mask": prompt_mask.tolist()})
313
+
314
+
315
+ if __name__ == "__main__":
316
+ parser = argparse.ArgumentParser()
317
+ parser.add_argument("--host", type=str, default="0.0.0.0")
318
+ parser.add_argument("--port", type=int, default=7860)
319
+ args = parser.parse_args()
320
+ app.run(host=args.host, port=args.port, debug=True)
requirements.txt ADDED
File without changes
static/assets/Inter-italic.var-DhD-tpjY.woff2 ADDED
Binary file (245 kB). View file
 
static/assets/Inter-roman.var-C-r5W2Hj.woff2 ADDED
Binary file (227 kB). View file
 
static/assets/PlaygroundView-B3hETUCz.css ADDED
@@ -0,0 +1 @@
 
 
1
+ .p-tabview[data-v-afb8912f]{position:absolute;margin:.5rem;top:0;left:0;height:calc(100% - 1rem);width:300px;resize:horizontal;overflow:auto;background:#ffffffe6;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}[data-v-afb8912f] .p-tabview .p-tabview-panels,[data-v-afb8912f] .p-tabview .p-treenode-content,[data-v-afb8912f] .p-tabview .p-treenode-children,[data-v-afb8912f] .p-tabview .p-tabview-panel,[data-v-afb8912f] .p-tabview .p-tree{background:transparent}#canvas_container[data-v-afb8912f]{position:relative;height:100vh}[data-v-afb8912f] .p-tree-selectable,[data-v-afb8912f] .p-treenode-content{padding:0}[data-v-afb8912f] .p-inputtext{width:100%}.p-inputswitch[data-v-afb8912f]{margin:.5rem .5rem 0 0}
static/assets/PlaygroundView-C4xMHWDB.js ADDED
The diff for this file is too large to render. See raw diff
 
static/assets/index-Bxec23rk.css ADDED
The diff for this file is too large to render. See raw diff
 
static/assets/index-CWsTxQ9u.js ADDED
The diff for this file is too large to render. See raw diff
 
static/assets/primeicons-C6QP2o4f.woff2 ADDED
Binary file (35.1 kB). View file
 
static/assets/primeicons-DMOk5skT.eot ADDED
Binary file (85.2 kB). View file
 
static/assets/primeicons-Dr5RGzOO.svg ADDED
static/assets/primeicons-MpK4pl85.ttf ADDED
Binary file (85 kB). View file
 
static/assets/primeicons-WjwUDZjB.woff ADDED
Binary file (85.1 kB). View file
 
static/index.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <link rel="icon" href="src/assets/favicon.ico">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Point-SAM</title>
8
+ <script type="module" crossorigin src="/assets/index-CWsTxQ9u.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Bxec23rk.css">
10
+ </head>
11
+ <body>
12
+ <div id="app"></div>
13
+ </body>
14
+ </html>