Spaces:
Sleeping
Sleeping
Upload 11 files
Browse files- Dockerfile +15 -0
- images/.gitkeep +1 -0
- main.py +45 -0
- requirements.txt +5 -0
- static/index.html +64 -0
- static/libs/chart.min.js +0 -0
- static/libs/p5.min.js +0 -0
- static/models/model.tflite +3 -0
- static/scripts/labels.js +347 -0
- static/scripts/sketch.js +478 -0
- static/style.css +26 -0
Dockerfile
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9
|
2 |
+
|
3 |
+
WORKDIR /code
|
4 |
+
|
5 |
+
COPY ./requirements.txt /code/requirements.txt
|
6 |
+
|
7 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
8 |
+
|
9 |
+
COPY ./main.py /code
|
10 |
+
COPY ./images /code/images
|
11 |
+
COPY ./static /code/static
|
12 |
+
|
13 |
+
EXPOSE 443
|
14 |
+
|
15 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "443"]
|
images/.gitkeep
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
|
main.py
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from uuid import uuid4
|
2 |
+
from os import remove
|
3 |
+
from PIL import Image, ImageDraw
|
4 |
+
from pydantic import BaseModel
|
5 |
+
from fastapi import FastAPI
|
6 |
+
from fastapi.staticfiles import StaticFiles
|
7 |
+
from starlette.responses import FileResponse
|
8 |
+
from starlette.background import BackgroundTask
|
9 |
+
|
10 |
+
|
11 |
+
class ImageData(BaseModel):
|
12 |
+
strokes: list
|
13 |
+
box: list
|
14 |
+
|
15 |
+
|
16 |
+
app = FastAPI()
|
17 |
+
|
18 |
+
|
19 |
+
@app.post("/transform")
|
20 |
+
async def transform(image_data: ImageData):
|
21 |
+
filepath = "./images/" + str(uuid4()) + ".png"
|
22 |
+
img = transform_img(image_data.strokes, image_data.box)
|
23 |
+
img.save(filepath)
|
24 |
+
|
25 |
+
return FileResponse(filepath, background=BackgroundTask(remove, path=filepath))
|
26 |
+
|
27 |
+
|
28 |
+
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
29 |
+
|
30 |
+
|
31 |
+
def transform_img(strokes, box):
|
32 |
+
# Calc cropped image size
|
33 |
+
width = box[2] - box[0]
|
34 |
+
height = box[3] - box[1]
|
35 |
+
|
36 |
+
image = Image.new("RGB", (width, height), color=(255, 255, 255))
|
37 |
+
image_draw = ImageDraw.Draw(image)
|
38 |
+
|
39 |
+
for stroke in strokes:
|
40 |
+
positions = []
|
41 |
+
for i in range(0, len(stroke[0])):
|
42 |
+
positions.append((stroke[0][i], stroke[1][i]))
|
43 |
+
image_draw.line(positions, fill=(0, 0, 0), width=3)
|
44 |
+
|
45 |
+
return image.resize((28, 28))
|
requirements.txt
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fastapi
|
2 |
+
Pillow
|
3 |
+
starlette
|
4 |
+
uvicorn
|
5 |
+
gunicorn
|
static/index.html
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
|
4 |
+
<head>
|
5 |
+
<meta charset="UTF-8" />
|
6 |
+
<title>Quick, Draw! Webapp</title>
|
7 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
|
8 |
+
|
9 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" crossorigin="anonymous" />
|
10 |
+
|
11 |
+
<link rel="stylesheet" href="./style.css" />
|
12 |
+
</head>
|
13 |
+
|
14 |
+
<body>
|
15 |
+
<div class="container p-2">
|
16 |
+
<!-- As a heading -->
|
17 |
+
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
18 |
+
<span class="navbar-brand mb-0 h1">Quick, Draw! Webapp</span>
|
19 |
+
<div class="collapse navbar-collapse">
|
20 |
+
<ul class="navbar-nav ml-auto">
|
21 |
+
<li class="nav-item">
|
22 |
+
<a class="nav-link" href="https://github.com/duongve13112002/" target="_blank">GitHub</a>
|
23 |
+
</li>
|
24 |
+
</ul>
|
25 |
+
</div>
|
26 |
+
</nav>
|
27 |
+
|
28 |
+
<div class="content py-3">
|
29 |
+
<div class="row">
|
30 |
+
<div class="col-12 col-md-auto">
|
31 |
+
<main></main>
|
32 |
+
<div class="buttons-container">
|
33 |
+
<button class="btn btn-primary btn-block" id="predict" type="button">Predict</button>
|
34 |
+
<button class="btn btn-light btn-block" id="erase">Erase</button>
|
35 |
+
<button class="btn btn-light btn-block" id="clear">Clear</button>
|
36 |
+
<button class="btn btn-light btn-block" id="speech">Speech</button>
|
37 |
+
</div>
|
38 |
+
</div>
|
39 |
+
<div class="col-12 col-md align-self-start">
|
40 |
+
<div class="d-flex">
|
41 |
+
<div class="col-md-6">
|
42 |
+
<canvas id="predictions" role="img" aria-label="Top 5 predictions" style="height: 400px;"></canvas>
|
43 |
+
</div>
|
44 |
+
<div class="col-md-6">
|
45 |
+
<iframe id="bingFrame" style="width: 100%; height: 400px;" frameborder="0"></iframe>
|
46 |
+
<button class="btn btn-primary btn-block mt-3" id="showNextPrediction" style="display: none;">Show next prediction</button>
|
47 |
+
</div>
|
48 |
+
</div>
|
49 |
+
</div>
|
50 |
+
</div>
|
51 |
+
</div>
|
52 |
+
</div>
|
53 |
+
|
54 |
+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js"></script>
|
55 |
+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf-tflite.min.js"></script>
|
56 |
+
|
57 |
+
<script src="./libs/p5.min.js"></script>
|
58 |
+
<script src="./libs/chart.min.js"></script>
|
59 |
+
|
60 |
+
<script src="./scripts/labels.js"></script>
|
61 |
+
<script src="./scripts/sketch.js"></script>
|
62 |
+
</body>
|
63 |
+
|
64 |
+
</html>
|
static/libs/chart.min.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
static/libs/p5.min.js
ADDED
The diff for this file is too large to render.
See raw diff
|
|
static/models/model.tflite
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:c8f1cbdb9bb6175ecc68866094a46a2f1f3566414018d50073efeb33a4faf8be
|
3 |
+
size 13253956
|
static/scripts/labels.js
ADDED
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const LABELS = [
|
2 |
+
"The Eiffel Tower",
|
3 |
+
"The Great Wall of China",
|
4 |
+
"The Mona Lisa",
|
5 |
+
"aircraft carrier",
|
6 |
+
"airplane",
|
7 |
+
"alarm clock",
|
8 |
+
"ambulance",
|
9 |
+
"angel",
|
10 |
+
"animal migration",
|
11 |
+
"ant",
|
12 |
+
"anvil",
|
13 |
+
"apple",
|
14 |
+
"arm",
|
15 |
+
"asparagus",
|
16 |
+
"axe",
|
17 |
+
"backpack",
|
18 |
+
"banana",
|
19 |
+
"bandage",
|
20 |
+
"barn",
|
21 |
+
"baseball",
|
22 |
+
"baseball bat",
|
23 |
+
"basket",
|
24 |
+
"basketball",
|
25 |
+
"bat",
|
26 |
+
"bathtub",
|
27 |
+
"beach",
|
28 |
+
"bear",
|
29 |
+
"beard",
|
30 |
+
"bed",
|
31 |
+
"bee",
|
32 |
+
"belt",
|
33 |
+
"bench",
|
34 |
+
"bicycle",
|
35 |
+
"binoculars",
|
36 |
+
"bird",
|
37 |
+
"birthday cake",
|
38 |
+
"blackberry",
|
39 |
+
"blueberry",
|
40 |
+
"book",
|
41 |
+
"boomerang",
|
42 |
+
"bottlecap",
|
43 |
+
"bowtie",
|
44 |
+
"bracelet",
|
45 |
+
"brain",
|
46 |
+
"bread",
|
47 |
+
"bridge",
|
48 |
+
"broccoli",
|
49 |
+
"broom",
|
50 |
+
"bucket",
|
51 |
+
"bulldozer",
|
52 |
+
"bus",
|
53 |
+
"bush",
|
54 |
+
"butterfly",
|
55 |
+
"cactus",
|
56 |
+
"cake",
|
57 |
+
"calculator",
|
58 |
+
"calendar",
|
59 |
+
"camel",
|
60 |
+
"camera",
|
61 |
+
"camouflage",
|
62 |
+
"campfire",
|
63 |
+
"candle",
|
64 |
+
"cannon",
|
65 |
+
"canoe",
|
66 |
+
"car",
|
67 |
+
"carrot",
|
68 |
+
"castle",
|
69 |
+
"cat",
|
70 |
+
"ceiling fan",
|
71 |
+
"cell phone",
|
72 |
+
"cello",
|
73 |
+
"chair",
|
74 |
+
"chandelier",
|
75 |
+
"church",
|
76 |
+
"circle",
|
77 |
+
"clarinet",
|
78 |
+
"clock",
|
79 |
+
"cloud",
|
80 |
+
"coffee cup",
|
81 |
+
"compass",
|
82 |
+
"computer",
|
83 |
+
"cookie",
|
84 |
+
"cooler",
|
85 |
+
"couch",
|
86 |
+
"cow",
|
87 |
+
"crab",
|
88 |
+
"crayon",
|
89 |
+
"crocodile",
|
90 |
+
"crown",
|
91 |
+
"cruise ship",
|
92 |
+
"cup",
|
93 |
+
"diamond",
|
94 |
+
"dishwasher",
|
95 |
+
"diving board",
|
96 |
+
"dog",
|
97 |
+
"dolphin",
|
98 |
+
"donut",
|
99 |
+
"door",
|
100 |
+
"dragon",
|
101 |
+
"dresser",
|
102 |
+
"drill",
|
103 |
+
"drums",
|
104 |
+
"duck",
|
105 |
+
"dumbbell",
|
106 |
+
"ear",
|
107 |
+
"elbow",
|
108 |
+
"elephant",
|
109 |
+
"envelope",
|
110 |
+
"eraser",
|
111 |
+
"eye",
|
112 |
+
"eyeglasses",
|
113 |
+
"face",
|
114 |
+
"fan",
|
115 |
+
"feather",
|
116 |
+
"fence",
|
117 |
+
"finger",
|
118 |
+
"fire hydrant",
|
119 |
+
"fireplace",
|
120 |
+
"firetruck",
|
121 |
+
"fish",
|
122 |
+
"flamingo",
|
123 |
+
"flashlight",
|
124 |
+
"flip flops",
|
125 |
+
"floor lamp",
|
126 |
+
"flower",
|
127 |
+
"flying saucer",
|
128 |
+
"foot",
|
129 |
+
"fork",
|
130 |
+
"frog",
|
131 |
+
"frying pan",
|
132 |
+
"garden",
|
133 |
+
"garden hose",
|
134 |
+
"giraffe",
|
135 |
+
"goatee",
|
136 |
+
"golf club",
|
137 |
+
"grapes",
|
138 |
+
"grass",
|
139 |
+
"guitar",
|
140 |
+
"hamburger",
|
141 |
+
"hammer",
|
142 |
+
"hand",
|
143 |
+
"harp",
|
144 |
+
"hat",
|
145 |
+
"headphones",
|
146 |
+
"hedgehog",
|
147 |
+
"helicopter",
|
148 |
+
"helmet",
|
149 |
+
"hexagon",
|
150 |
+
"hockey puck",
|
151 |
+
"hockey stick",
|
152 |
+
"horse",
|
153 |
+
"hospital",
|
154 |
+
"hot air balloon",
|
155 |
+
"hot dog",
|
156 |
+
"hot tub",
|
157 |
+
"hourglass",
|
158 |
+
"house",
|
159 |
+
"house plant",
|
160 |
+
"hurricane",
|
161 |
+
"ice cream",
|
162 |
+
"jacket",
|
163 |
+
"jail",
|
164 |
+
"kangaroo",
|
165 |
+
"key",
|
166 |
+
"keyboard",
|
167 |
+
"knee",
|
168 |
+
"knife",
|
169 |
+
"ladder",
|
170 |
+
"lantern",
|
171 |
+
"laptop",
|
172 |
+
"leaf",
|
173 |
+
"leg",
|
174 |
+
"light bulb",
|
175 |
+
"lighter",
|
176 |
+
"lighthouse",
|
177 |
+
"lightning",
|
178 |
+
"line",
|
179 |
+
"lion",
|
180 |
+
"lipstick",
|
181 |
+
"lobster",
|
182 |
+
"lollipop",
|
183 |
+
"mailbox",
|
184 |
+
"map",
|
185 |
+
"marker",
|
186 |
+
"matches",
|
187 |
+
"megaphone",
|
188 |
+
"mermaid",
|
189 |
+
"microphone",
|
190 |
+
"microwave",
|
191 |
+
"monkey",
|
192 |
+
"moon",
|
193 |
+
"mosquito",
|
194 |
+
"motorbike",
|
195 |
+
"mountain",
|
196 |
+
"mouse",
|
197 |
+
"moustache",
|
198 |
+
"mouth",
|
199 |
+
"mug",
|
200 |
+
"mushroom",
|
201 |
+
"nail",
|
202 |
+
"necklace",
|
203 |
+
"nose",
|
204 |
+
"ocean",
|
205 |
+
"octagon",
|
206 |
+
"octopus",
|
207 |
+
"onion",
|
208 |
+
"oven",
|
209 |
+
"owl",
|
210 |
+
"paint can",
|
211 |
+
"paintbrush",
|
212 |
+
"palm tree",
|
213 |
+
"panda",
|
214 |
+
"pants",
|
215 |
+
"paper clip",
|
216 |
+
"parachute",
|
217 |
+
"parrot",
|
218 |
+
"passport",
|
219 |
+
"peanut",
|
220 |
+
"pear",
|
221 |
+
"peas",
|
222 |
+
"pencil",
|
223 |
+
"penguin",
|
224 |
+
"piano",
|
225 |
+
"pickup truck",
|
226 |
+
"picture frame",
|
227 |
+
"pig",
|
228 |
+
"pillow",
|
229 |
+
"pineapple",
|
230 |
+
"pizza",
|
231 |
+
"pliers",
|
232 |
+
"police car",
|
233 |
+
"pond",
|
234 |
+
"pool",
|
235 |
+
"popsicle",
|
236 |
+
"postcard",
|
237 |
+
"potato",
|
238 |
+
"power outlet",
|
239 |
+
"purse",
|
240 |
+
"rabbit",
|
241 |
+
"raccoon",
|
242 |
+
"radio",
|
243 |
+
"rain",
|
244 |
+
"rainbow",
|
245 |
+
"rake",
|
246 |
+
"remote control",
|
247 |
+
"rhinoceros",
|
248 |
+
"rifle",
|
249 |
+
"river",
|
250 |
+
"roller coaster",
|
251 |
+
"rollerskates",
|
252 |
+
"sailboat",
|
253 |
+
"sandwich",
|
254 |
+
"saw",
|
255 |
+
"saxophone",
|
256 |
+
"school bus",
|
257 |
+
"scissors",
|
258 |
+
"scorpion",
|
259 |
+
"screwdriver",
|
260 |
+
"sea turtle",
|
261 |
+
"see saw",
|
262 |
+
"shark",
|
263 |
+
"sheep",
|
264 |
+
"shoe",
|
265 |
+
"shorts",
|
266 |
+
"shovel",
|
267 |
+
"sink",
|
268 |
+
"skateboard",
|
269 |
+
"skull",
|
270 |
+
"skyscraper",
|
271 |
+
"sleeping bag",
|
272 |
+
"smiley face",
|
273 |
+
"snail",
|
274 |
+
"snake",
|
275 |
+
"snorkel",
|
276 |
+
"snowflake",
|
277 |
+
"snowman",
|
278 |
+
"soccer ball",
|
279 |
+
"sock",
|
280 |
+
"speedboat",
|
281 |
+
"spider",
|
282 |
+
"spoon",
|
283 |
+
"spreadsheet",
|
284 |
+
"square",
|
285 |
+
"squiggle",
|
286 |
+
"squirrel",
|
287 |
+
"stairs",
|
288 |
+
"star",
|
289 |
+
"steak",
|
290 |
+
"stereo",
|
291 |
+
"stethoscope",
|
292 |
+
"stitches",
|
293 |
+
"stop sign",
|
294 |
+
"stove",
|
295 |
+
"strawberry",
|
296 |
+
"streetlight",
|
297 |
+
"string bean",
|
298 |
+
"submarine",
|
299 |
+
"suitcase",
|
300 |
+
"sun",
|
301 |
+
"swan",
|
302 |
+
"sweater",
|
303 |
+
"swing set",
|
304 |
+
"sword",
|
305 |
+
"syringe",
|
306 |
+
"t-shirt",
|
307 |
+
"table",
|
308 |
+
"teapot",
|
309 |
+
"teddy-bear",
|
310 |
+
"telephone",
|
311 |
+
"television",
|
312 |
+
"tennis racquet",
|
313 |
+
"tent",
|
314 |
+
"tiger",
|
315 |
+
"toaster",
|
316 |
+
"toe",
|
317 |
+
"toilet",
|
318 |
+
"tooth",
|
319 |
+
"toothbrush",
|
320 |
+
"toothpaste",
|
321 |
+
"tornado",
|
322 |
+
"tractor",
|
323 |
+
"traffic light",
|
324 |
+
"train",
|
325 |
+
"tree",
|
326 |
+
"triangle",
|
327 |
+
"trombone",
|
328 |
+
"truck",
|
329 |
+
"trumpet",
|
330 |
+
"umbrella",
|
331 |
+
"underwear",
|
332 |
+
"van",
|
333 |
+
"vase",
|
334 |
+
"violin",
|
335 |
+
"washing machine",
|
336 |
+
"watermelon",
|
337 |
+
"waterslide",
|
338 |
+
"whale",
|
339 |
+
"wheel",
|
340 |
+
"windmill",
|
341 |
+
"wine bottle",
|
342 |
+
"wine glass",
|
343 |
+
"wristwatch",
|
344 |
+
"yoga",
|
345 |
+
"zebra",
|
346 |
+
"zigzag",
|
347 |
+
];
|
static/scripts/sketch.js
ADDED
@@ -0,0 +1,478 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const WIDTH = 350;
|
2 |
+
const HEIGHT = 350;
|
3 |
+
const STROKE_WEIGHT = 3;
|
4 |
+
const CROP_PADDING = (REPOS_PADDING = 2);
|
5 |
+
|
6 |
+
let model;
|
7 |
+
let pieChart;
|
8 |
+
let clicked = false;
|
9 |
+
let mousePosition = [];
|
10 |
+
|
11 |
+
// Coordinates of the current drawn stroke [[x1, x2, ..., xn], [y1, y2, ..., yn]]
|
12 |
+
let strokePixels = [[], []];
|
13 |
+
|
14 |
+
// Coordinates of all canvas strokes [[[x1, x2, ..., xn], [y1, y2, ..., yn]], [[x1, x2, ..., xn], [y1, y2, ..., yn]], ...]
|
15 |
+
let imageStrokes = [];
|
16 |
+
let imageStrokes_copy = [];
|
17 |
+
function inRange(n, from, to) {
|
18 |
+
return n >= from && n < to;
|
19 |
+
}
|
20 |
+
|
21 |
+
let speechEnabled = false;
|
22 |
+
|
23 |
+
function toggleSpeechMode() {
|
24 |
+
speechEnabled = !speechEnabled; // Toggle speechEnabled's state value
|
25 |
+
const speechButton = document.getElementById("speech");
|
26 |
+
speechButton.classList.toggle("active", speechEnabled);
|
27 |
+
|
28 |
+
if (speechEnabled && isSpeechSynthesisSupported() ) {
|
29 |
+
speechButton.style.backgroundColor = "green"; // To set the color of the Speech button to green when the speech mode is turned on
|
30 |
+
speechButton.style.color = "white";
|
31 |
+
|
32 |
+
// If the browser supports the speechSynthesis feature and is not currently speaking
|
33 |
+
if (isSpeechSynthesisSupported() && !window.speechSynthesis.speaking && predict_e.length > 0 && eraseMode == false) {
|
34 |
+
Speaking();
|
35 |
+
}
|
36 |
+
} else {
|
37 |
+
speechEnabled = false;
|
38 |
+
speechButton.style.backgroundColor = ""; // To set the color of the Speech button to orange when the speech mode is turned off
|
39 |
+
speechButton.style.color = "black";
|
40 |
+
|
41 |
+
// Stop the browser from speaking
|
42 |
+
if (isSpeechSynthesisSupported() && window.speechSynthesis.speaking) {
|
43 |
+
window.speechSynthesis.cancel();
|
44 |
+
}
|
45 |
+
}
|
46 |
+
}
|
47 |
+
function setup() {
|
48 |
+
createCanvas(WIDTH, HEIGHT);
|
49 |
+
strokeWeight(STROKE_WEIGHT);
|
50 |
+
stroke("black");
|
51 |
+
background("#FFFFFF");
|
52 |
+
const $canvas = document.getElementById("defaultCanvas0");
|
53 |
+
loadModel();
|
54 |
+
$canvas.addEventListener("mousedown", (e) => mouseDown(e));
|
55 |
+
$canvas.addEventListener("mousemove", (e) => mouseMoved(e));
|
56 |
+
$canvas.addEventListener("mouseup", (e) => mouseReleased(e));
|
57 |
+
const eraseButton = document.getElementById("erase");
|
58 |
+
eraseButton.addEventListener("click", toggleEraseMode);
|
59 |
+
|
60 |
+
const speechButton = document.getElementById("speech");
|
61 |
+
speechButton.addEventListener("click", toggleSpeechMode);
|
62 |
+
|
63 |
+
}
|
64 |
+
|
65 |
+
|
66 |
+
function mouseDown() {
|
67 |
+
clicked = true;
|
68 |
+
mousePosition = [mouseX, mouseY];
|
69 |
+
Stop_talking_clear_pie_iframe();
|
70 |
+
}
|
71 |
+
let eraseMode = false;
|
72 |
+
function mouseMoved() {
|
73 |
+
if (eraseMode && clicked && inRange(mouseX, 0, WIDTH) && inRange(mouseY, 0, HEIGHT)) {
|
74 |
+
// To remove a drawing at the mouse position
|
75 |
+
const mouseXPos = Math.floor(mouseX);
|
76 |
+
const mouseYPos = Math.floor(mouseY);
|
77 |
+
|
78 |
+
// To find and remove the drawing near the mouse position
|
79 |
+
for (let i = imageStrokes.length - 1; i >= 0; i--) {
|
80 |
+
const stroke = imageStrokes[i];
|
81 |
+
const xCoords = stroke[0];
|
82 |
+
const yCoords = stroke[1];
|
83 |
+
|
84 |
+
for (let j = 0; j < xCoords.length; j++) {
|
85 |
+
const x = xCoords[j];
|
86 |
+
const y = yCoords[j];
|
87 |
+
|
88 |
+
// To check if the mouse position is near a drawing point and perform an action accordingly
|
89 |
+
if (dist(x, y, mouseXPos, mouseYPos) < STROKE_WEIGHT * 2) {
|
90 |
+
// To clear the drawing and exit the clean loop
|
91 |
+
imageStrokes.splice(i, 1);
|
92 |
+
break;
|
93 |
+
}
|
94 |
+
}
|
95 |
+
}
|
96 |
+
|
97 |
+
// Clear the canvas and redraw the remaining segments
|
98 |
+
clear();
|
99 |
+
background("#FFFFFF");
|
100 |
+
for (const stroke of imageStrokes) {
|
101 |
+
const xCoords = stroke[0];
|
102 |
+
const yCoords = stroke[1];
|
103 |
+
|
104 |
+
for (let i = 1; i < xCoords.length; i++) {
|
105 |
+
const x1 = xCoords[i - 1];
|
106 |
+
const y1 = yCoords[i - 1];
|
107 |
+
const x2 = xCoords[i];
|
108 |
+
const y2 = yCoords[i];
|
109 |
+
|
110 |
+
line(x1, y1, x2, y2);
|
111 |
+
}
|
112 |
+
}
|
113 |
+
}
|
114 |
+
else {
|
115 |
+
// Check whether mouse position is within canvas
|
116 |
+
if (clicked && inRange(mouseX, 0, WIDTH) && inRange(mouseY, 0, HEIGHT)) {
|
117 |
+
//clicked = true;
|
118 |
+
strokePixels[0].push(Math.floor(mouseX));
|
119 |
+
strokePixels[1].push(Math.floor(mouseY));
|
120 |
+
|
121 |
+
line(mouseX, mouseY, mousePosition[0], mousePosition[1]);
|
122 |
+
mousePosition = [mouseX, mouseY];
|
123 |
+
}
|
124 |
+
}
|
125 |
+
}
|
126 |
+
|
127 |
+
|
128 |
+
|
129 |
+
|
130 |
+
function toggleEraseMode() {
|
131 |
+
eraseMode = !eraseMode; // Toggle eraseMode's state value
|
132 |
+
const eraseButton = document.getElementById("erase");
|
133 |
+
eraseButton.classList.toggle("active", eraseMode);
|
134 |
+
|
135 |
+
if (eraseMode) {
|
136 |
+
eraseButton.style.backgroundColor = "red"; // Set the erase button color to red when in erase mode
|
137 |
+
eraseButton.style.color = "white";
|
138 |
+
} else {
|
139 |
+
eraseButton.style.backgroundColor = ""; // Set the erase button color to default when not in erase mode
|
140 |
+
eraseButton.style.color = "black";
|
141 |
+
}
|
142 |
+
// Stop the browser from speaking
|
143 |
+
Stop_talking_clear_pie_iframe();
|
144 |
+
}
|
145 |
+
|
146 |
+
|
147 |
+
function mouseReleased() {
|
148 |
+
if (strokePixels[0].length) {
|
149 |
+
imageStrokes.push(strokePixels);
|
150 |
+
strokePixels = [[], []];
|
151 |
+
}
|
152 |
+
clicked = false;
|
153 |
+
}
|
154 |
+
function updateViewport() {
|
155 |
+
const deviceWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
156 |
+
const deviceHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
|
157 |
+
const scaleRatio = deviceWidth / deviceHeight; // Varies according to the original size of the HTML
|
158 |
+
|
159 |
+
// Update viewport property
|
160 |
+
const viewportMeta = document.querySelector('meta[name="viewport"]');
|
161 |
+
viewportMeta.content = `width=device-width, initial-scale=${scaleRatio}, user-scalable=no`;
|
162 |
+
}
|
163 |
+
const loadModel = async () => {
|
164 |
+
console.log("Model loading...");
|
165 |
+
|
166 |
+
model = await tflite.loadTFLiteModel("./models/model.tflite");
|
167 |
+
model.predict(tf.zeros([1, 28, 28, 1])); // warmup
|
168 |
+
|
169 |
+
console.log(`Model loaded! (${LABELS.length} classes)`);
|
170 |
+
};
|
171 |
+
|
172 |
+
const preprocess = async (cb) => {
|
173 |
+
const {min, max} = getBoundingBox();
|
174 |
+
|
175 |
+
// Resize to 28x28 pixel & crop
|
176 |
+
const imageBlob = await fetch("/transform", {
|
177 |
+
method: "POST",
|
178 |
+
headers: {
|
179 |
+
"Content-Type": "application/json",
|
180 |
+
},
|
181 |
+
redirect: "follow",
|
182 |
+
referrerPolicy: "no-referrer",
|
183 |
+
body: JSON.stringify({
|
184 |
+
strokes: imageStrokes_copy,
|
185 |
+
box: [min.x, min.y, max.x, max.y],
|
186 |
+
}),
|
187 |
+
}).then((response) => response.blob());
|
188 |
+
|
189 |
+
const img = new Image(28, 28);
|
190 |
+
img.src = URL.createObjectURL(imageBlob);
|
191 |
+
|
192 |
+
img.onload = () => {
|
193 |
+
const tensor = tf.tidy(() =>
|
194 |
+
tf.browser.fromPixels(img, 1).toFloat().expandDims(0)
|
195 |
+
);
|
196 |
+
cb(tensor);
|
197 |
+
};
|
198 |
+
};
|
199 |
+
|
200 |
+
const drawPie = (top5) => {
|
201 |
+
const probs = [];
|
202 |
+
const labels = [];
|
203 |
+
|
204 |
+
for (const pred of top5) {
|
205 |
+
const prop = +pred.probability.toPrecision(3);
|
206 |
+
probs.push(prop);
|
207 |
+
labels.push(`${pred.className} (${prop})`);
|
208 |
+
}
|
209 |
+
|
210 |
+
const others = +(
|
211 |
+
1 - probs.reduce((prev, prob) => prev + prob, 0)
|
212 |
+
).toPrecision(3);
|
213 |
+
probs.push(others);
|
214 |
+
labels.push(`Others (${others})`);
|
215 |
+
|
216 |
+
if (pieChart) pieChart.destroy();
|
217 |
+
|
218 |
+
const ctx = document.getElementById("predictions").getContext("2d");
|
219 |
+
pieChart = new Chart(ctx, {
|
220 |
+
type: "pie",
|
221 |
+
options: {
|
222 |
+
plugins: {
|
223 |
+
legend: {
|
224 |
+
position: "bottom",
|
225 |
+
},
|
226 |
+
title: {
|
227 |
+
display: true,
|
228 |
+
text: "Top 5 Predictions",
|
229 |
+
},
|
230 |
+
},
|
231 |
+
},
|
232 |
+
data: {
|
233 |
+
labels,
|
234 |
+
datasets: [
|
235 |
+
{
|
236 |
+
label: "Top 5 predictions",
|
237 |
+
data: probs,
|
238 |
+
backgroundColor: [
|
239 |
+
"rgb(255, 99, 132)",
|
240 |
+
"rgb(54, 162, 235)",
|
241 |
+
"rgb(255, 205, 86)",
|
242 |
+
"rgb(0,255,0)",
|
243 |
+
"rgb(238,130,238)",
|
244 |
+
"rgb(97,96,96)",
|
245 |
+
],
|
246 |
+
},
|
247 |
+
],
|
248 |
+
},
|
249 |
+
});
|
250 |
+
};
|
251 |
+
|
252 |
+
const getMinimumCoordinates = () => {
|
253 |
+
let min_x = Number.MAX_SAFE_INTEGER;
|
254 |
+
let min_y = Number.MAX_SAFE_INTEGER;
|
255 |
+
|
256 |
+
for (const stroke of imageStrokes_copy) {
|
257 |
+
for (let i = 0; i < stroke[0].length; i++) {
|
258 |
+
min_x = Math.min(min_x, stroke[0][i]);
|
259 |
+
min_y = Math.min(min_y, stroke[1][i]);
|
260 |
+
}
|
261 |
+
}
|
262 |
+
|
263 |
+
return [Math.max(0, min_x), Math.max(0, min_y)];
|
264 |
+
};
|
265 |
+
|
266 |
+
const getBoundingBox = () => {
|
267 |
+
repositionImage();
|
268 |
+
|
269 |
+
const coords_x = [];
|
270 |
+
const coords_y = [];
|
271 |
+
|
272 |
+
for (const stroke of imageStrokes_copy) {
|
273 |
+
for (let i = 0; i < stroke[0].length; i++) {
|
274 |
+
coords_x.push(stroke[0][i]);
|
275 |
+
coords_y.push(stroke[1][i]);
|
276 |
+
}
|
277 |
+
}
|
278 |
+
|
279 |
+
const x_min = Math.min(...coords_x);
|
280 |
+
const x_max = Math.max(...coords_x);
|
281 |
+
const y_min = Math.min(...coords_y);
|
282 |
+
const y_max = Math.max(...coords_y);
|
283 |
+
|
284 |
+
// New width & height of cropped image
|
285 |
+
const width = Math.max(...coords_x) - Math.min(...coords_x);
|
286 |
+
const height = Math.max(...coords_y) - Math.min(...coords_y);
|
287 |
+
|
288 |
+
const coords_min = {
|
289 |
+
x: Math.max(0, x_min - CROP_PADDING), // Create link edge
|
290 |
+
y: Math.max(0, y_min - CROP_PADDING), // Create the top edge
|
291 |
+
};
|
292 |
+
let coords_max;
|
293 |
+
|
294 |
+
if (width > height)
|
295 |
+
// Left + right edge as boundary
|
296 |
+
coords_max = {
|
297 |
+
x: Math.min(WIDTH, x_max + CROP_PADDING), // Right edge
|
298 |
+
y: Math.max(0, y_min + CROP_PADDING) + width, // Lower edge
|
299 |
+
};
|
300 |
+
// Upper + lower edge as boundary
|
301 |
+
else
|
302 |
+
coords_max = {
|
303 |
+
x: Math.max(0, x_min + CROP_PADDING) + height, // Right edge
|
304 |
+
y: Math.min(HEIGHT, y_max + CROP_PADDING), // Lower edge
|
305 |
+
};
|
306 |
+
|
307 |
+
return {
|
308 |
+
min: coords_min,
|
309 |
+
max: coords_max,
|
310 |
+
};
|
311 |
+
};
|
312 |
+
|
313 |
+
// Reposition image to top left corner
|
314 |
+
const repositionImage = () => {
|
315 |
+
const [min_x, min_y] = getMinimumCoordinates();
|
316 |
+
for (const stroke of imageStrokes_copy) {
|
317 |
+
for (let i = 0; i < stroke[0].length; i++) {
|
318 |
+
stroke[0][i] = stroke[0][i] - min_x + REPOS_PADDING;
|
319 |
+
stroke[1][i] = stroke[1][i] - min_y + REPOS_PADDING;
|
320 |
+
}
|
321 |
+
}
|
322 |
+
};
|
323 |
+
|
324 |
+
function updateIframebutton(str_c) {
|
325 |
+
const bingFrame = document.getElementById("bingFrame");
|
326 |
+
bingFrame.style.display = str_c;
|
327 |
+
const showNextPredictionButton = document.getElementById("showNextPrediction");
|
328 |
+
showNextPredictionButton.style.display = str_c;
|
329 |
+
}
|
330 |
+
|
331 |
+
let predict_e =[];
|
332 |
+
let currentIndex = 0;
|
333 |
+
function help_copy_array(obj) {
|
334 |
+
if(obj == null || typeof(obj) != 'object') {
|
335 |
+
return obj;
|
336 |
+
}
|
337 |
+
|
338 |
+
var temp = new obj.constructor();
|
339 |
+
|
340 |
+
for(var key in obj) {
|
341 |
+
if (obj.hasOwnProperty(key)) {
|
342 |
+
temp[key] = help_copy_array(obj[key]);
|
343 |
+
}
|
344 |
+
}
|
345 |
+
|
346 |
+
return temp;
|
347 |
+
}
|
348 |
+
|
349 |
+
// Function to read an element in the array predict_e
|
350 |
+
function speakPrediction(prediction) {
|
351 |
+
var utterance = new SpeechSynthesisUtterance(prediction);
|
352 |
+
window.speechSynthesis.speak(utterance);
|
353 |
+
}
|
354 |
+
|
355 |
+
// Checking compatibility with SpeechSynthesis
|
356 |
+
function isSpeechSynthesisSupported() {
|
357 |
+
return 'speechSynthesis' in window && 'SpeechSynthesisUtterance' in window;
|
358 |
+
}
|
359 |
+
function Speaking() {
|
360 |
+
let sp_spech = 0;
|
361 |
+
for (let prediction of predict_e) {
|
362 |
+
if (sp_spech == 0)
|
363 |
+
{
|
364 |
+
prediction = "Well, I can see in your painting that may be "+ prediction;
|
365 |
+
sp_spech = 1;
|
366 |
+
}
|
367 |
+
else if(prediction == predict_e.at(-1))
|
368 |
+
{
|
369 |
+
prediction = ", or "+ prediction;
|
370 |
+
}
|
371 |
+
else{
|
372 |
+
prediction = ", "+ prediction;
|
373 |
+
}
|
374 |
+
speakPrediction(prediction);
|
375 |
+
}
|
376 |
+
}
|
377 |
+
// Prediction function with reading function
|
378 |
+
const predict = async () => {
|
379 |
+
if (!imageStrokes.length) return;
|
380 |
+
if (!LABELS.length) throw new Error("No labels found!");
|
381 |
+
if (isSpeechSynthesisSupported() && window.speechSynthesis.speaking) {
|
382 |
+
window.speechSynthesis.cancel();
|
383 |
+
}
|
384 |
+
imageStrokes_copy = [];
|
385 |
+
imageStrokes_copy = help_copy_array(imageStrokes);
|
386 |
+
|
387 |
+
preprocess(async (tensor) => {
|
388 |
+
const predictions = model.predict(tensor).dataSync();
|
389 |
+
|
390 |
+
top5 = Array.from(predictions)
|
391 |
+
.map((p, i) => ({
|
392 |
+
probability: p,
|
393 |
+
className: LABELS[i],
|
394 |
+
index: i,
|
395 |
+
}))
|
396 |
+
.sort((a, b) => b.probability - a.probability)
|
397 |
+
.slice(0, 5);
|
398 |
+
|
399 |
+
drawPie(top5);
|
400 |
+
predict_e = top5.map(pred => pred.className);
|
401 |
+
currentIndex = 0;
|
402 |
+
updateIframebutton("inline");
|
403 |
+
updateIframe(predict_e[currentIndex]);
|
404 |
+
// Checking compatibility with SpeechSynthesis
|
405 |
+
if (isSpeechSynthesisSupported() && speechEnabled == true) {
|
406 |
+
Speaking();
|
407 |
+
}
|
408 |
+
else {
|
409 |
+
console.log("Trình duyệt không hỗ trợ đọc văn bản.");
|
410 |
+
}
|
411 |
+
|
412 |
+
|
413 |
+
});
|
414 |
+
};
|
415 |
+
|
416 |
+
|
417 |
+
|
418 |
+
|
419 |
+
function showNextPrediction() {
|
420 |
+
currentIndex++;
|
421 |
+
if (currentIndex >= predict_e.length) {
|
422 |
+
currentIndex = 0;
|
423 |
+
}
|
424 |
+
|
425 |
+
const currentPrediction = predict_e[currentIndex];
|
426 |
+
updateIframe(currentPrediction);
|
427 |
+
}
|
428 |
+
|
429 |
+
function updateIframe(searchQuery) {
|
430 |
+
const bingFrame = document.getElementById("bingFrame");
|
431 |
+
if (searchQuery == 'line')
|
432 |
+
{
|
433 |
+
searchQuery = 'Straight line';
|
434 |
+
}
|
435 |
+
if (searchQuery == 'bush')
|
436 |
+
{
|
437 |
+
searchQuery = 'Bush Landscaping';
|
438 |
+
}
|
439 |
+
const bingSearchURL = `https://www.bing.com/images/search?q=${encodeURIComponent(searchQuery)} picture`;
|
440 |
+
bingFrame.src = bingSearchURL;
|
441 |
+
}
|
442 |
+
|
443 |
+
function Stop_talking_clear_pie_iframe()
|
444 |
+
{
|
445 |
+
// Stop browser speech if currently speaking
|
446 |
+
if (isSpeechSynthesisSupported() && window.speechSynthesis.speaking) {
|
447 |
+
window.speechSynthesis.cancel();
|
448 |
+
}
|
449 |
+
if (pieChart) pieChart.destroy();
|
450 |
+
updateIframebutton("none");
|
451 |
+
currentIndex = 0;
|
452 |
+
predict_e = [];
|
453 |
+
|
454 |
+
}
|
455 |
+
|
456 |
+
const clearCanvas = () => {
|
457 |
+
clear();
|
458 |
+
Stop_talking_clear_pie_iframe();
|
459 |
+
background("#FFFFFF");
|
460 |
+
imageStrokes = [];
|
461 |
+
imageStrokes_copy = [];
|
462 |
+
strokePixels = [[], []];
|
463 |
+
};
|
464 |
+
|
465 |
+
window.addEventListener('load', updateViewport);
|
466 |
+
window.addEventListener('resize', updateViewport);
|
467 |
+
window.addEventListener('load', () => {
|
468 |
+
const showNextPredictionButton = document.getElementById("showNextPrediction");
|
469 |
+
showNextPredictionButton.addEventListener("click", showNextPrediction);
|
470 |
+
});
|
471 |
+
window.onload = () => {
|
472 |
+
updateIframebutton("none");
|
473 |
+
const $submit = document.getElementById("predict");
|
474 |
+
const $clear = document.getElementById("clear");
|
475 |
+
$submit.addEventListener("click", () => predict());
|
476 |
+
$clear.addEventListener("click", clearCanvas);
|
477 |
+
};
|
478 |
+
|
static/style.css
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body {
|
2 |
+
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
3 |
+
}
|
4 |
+
|
5 |
+
a {
|
6 |
+
color: #00b7ff;
|
7 |
+
}
|
8 |
+
|
9 |
+
h1 {
|
10 |
+
border-left: 5px solid #333;
|
11 |
+
padding-left: 5px;
|
12 |
+
}
|
13 |
+
|
14 |
+
button {
|
15 |
+
padding: 15px;
|
16 |
+
cursor: pointer;
|
17 |
+
font-size: 16px;
|
18 |
+
}
|
19 |
+
|
20 |
+
.buttons-container {
|
21 |
+
margin-top: 15px;
|
22 |
+
}
|
23 |
+
|
24 |
+
#defaultCanvas0 {
|
25 |
+
border: 1px solid black;
|
26 |
+
}
|