duongve commited on
Commit
bcb0b13
·
1 Parent(s): 92dd8be

Upload 11 files

Browse files
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
+ }