duongve's picture
Upload 11 files
bcb0b13
const WIDTH = 350;
const HEIGHT = 350;
const STROKE_WEIGHT = 3;
const CROP_PADDING = (REPOS_PADDING = 2);
let model;
let pieChart;
let clicked = false;
let mousePosition = [];
// Coordinates of the current drawn stroke [[x1, x2, ..., xn], [y1, y2, ..., yn]]
let strokePixels = [[], []];
// Coordinates of all canvas strokes [[[x1, x2, ..., xn], [y1, y2, ..., yn]], [[x1, x2, ..., xn], [y1, y2, ..., yn]], ...]
let imageStrokes = [];
let imageStrokes_copy = [];
function inRange(n, from, to) {
return n >= from && n < to;
}
let speechEnabled = false;
function toggleSpeechMode() {
speechEnabled = !speechEnabled; // Toggle speechEnabled's state value
const speechButton = document.getElementById("speech");
speechButton.classList.toggle("active", speechEnabled);
if (speechEnabled && isSpeechSynthesisSupported() ) {
speechButton.style.backgroundColor = "green"; // To set the color of the Speech button to green when the speech mode is turned on
speechButton.style.color = "white";
// If the browser supports the speechSynthesis feature and is not currently speaking
if (isSpeechSynthesisSupported() && !window.speechSynthesis.speaking && predict_e.length > 0 && eraseMode == false) {
Speaking();
}
} else {
speechEnabled = false;
speechButton.style.backgroundColor = ""; // To set the color of the Speech button to orange when the speech mode is turned off
speechButton.style.color = "black";
// Stop the browser from speaking
if (isSpeechSynthesisSupported() && window.speechSynthesis.speaking) {
window.speechSynthesis.cancel();
}
}
}
function setup() {
createCanvas(WIDTH, HEIGHT);
strokeWeight(STROKE_WEIGHT);
stroke("black");
background("#FFFFFF");
const $canvas = document.getElementById("defaultCanvas0");
loadModel();
$canvas.addEventListener("mousedown", (e) => mouseDown(e));
$canvas.addEventListener("mousemove", (e) => mouseMoved(e));
$canvas.addEventListener("mouseup", (e) => mouseReleased(e));
const eraseButton = document.getElementById("erase");
eraseButton.addEventListener("click", toggleEraseMode);
const speechButton = document.getElementById("speech");
speechButton.addEventListener("click", toggleSpeechMode);
}
function mouseDown() {
clicked = true;
mousePosition = [mouseX, mouseY];
Stop_talking_clear_pie_iframe();
}
let eraseMode = false;
function mouseMoved() {
if (eraseMode && clicked && inRange(mouseX, 0, WIDTH) && inRange(mouseY, 0, HEIGHT)) {
// To remove a drawing at the mouse position
const mouseXPos = Math.floor(mouseX);
const mouseYPos = Math.floor(mouseY);
// To find and remove the drawing near the mouse position
for (let i = imageStrokes.length - 1; i >= 0; i--) {
const stroke = imageStrokes[i];
const xCoords = stroke[0];
const yCoords = stroke[1];
for (let j = 0; j < xCoords.length; j++) {
const x = xCoords[j];
const y = yCoords[j];
// To check if the mouse position is near a drawing point and perform an action accordingly
if (dist(x, y, mouseXPos, mouseYPos) < STROKE_WEIGHT * 2) {
// To clear the drawing and exit the clean loop
imageStrokes.splice(i, 1);
break;
}
}
}
// Clear the canvas and redraw the remaining segments
clear();
background("#FFFFFF");
for (const stroke of imageStrokes) {
const xCoords = stroke[0];
const yCoords = stroke[1];
for (let i = 1; i < xCoords.length; i++) {
const x1 = xCoords[i - 1];
const y1 = yCoords[i - 1];
const x2 = xCoords[i];
const y2 = yCoords[i];
line(x1, y1, x2, y2);
}
}
}
else {
// Check whether mouse position is within canvas
if (clicked && inRange(mouseX, 0, WIDTH) && inRange(mouseY, 0, HEIGHT)) {
//clicked = true;
strokePixels[0].push(Math.floor(mouseX));
strokePixels[1].push(Math.floor(mouseY));
line(mouseX, mouseY, mousePosition[0], mousePosition[1]);
mousePosition = [mouseX, mouseY];
}
}
}
function toggleEraseMode() {
eraseMode = !eraseMode; // Toggle eraseMode's state value
const eraseButton = document.getElementById("erase");
eraseButton.classList.toggle("active", eraseMode);
if (eraseMode) {
eraseButton.style.backgroundColor = "red"; // Set the erase button color to red when in erase mode
eraseButton.style.color = "white";
} else {
eraseButton.style.backgroundColor = ""; // Set the erase button color to default when not in erase mode
eraseButton.style.color = "black";
}
// Stop the browser from speaking
Stop_talking_clear_pie_iframe();
}
function mouseReleased() {
if (strokePixels[0].length) {
imageStrokes.push(strokePixels);
strokePixels = [[], []];
}
clicked = false;
}
function updateViewport() {
const deviceWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
const deviceHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
const scaleRatio = deviceWidth / deviceHeight; // Varies according to the original size of the HTML
// Update viewport property
const viewportMeta = document.querySelector('meta[name="viewport"]');
viewportMeta.content = `width=device-width, initial-scale=${scaleRatio}, user-scalable=no`;
}
const loadModel = async () => {
console.log("Model loading...");
model = await tflite.loadTFLiteModel("./models/model.tflite");
model.predict(tf.zeros([1, 28, 28, 1])); // warmup
console.log(`Model loaded! (${LABELS.length} classes)`);
};
const preprocess = async (cb) => {
const {min, max} = getBoundingBox();
// Resize to 28x28 pixel & crop
const imageBlob = await fetch("/transform", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
redirect: "follow",
referrerPolicy: "no-referrer",
body: JSON.stringify({
strokes: imageStrokes_copy,
box: [min.x, min.y, max.x, max.y],
}),
}).then((response) => response.blob());
const img = new Image(28, 28);
img.src = URL.createObjectURL(imageBlob);
img.onload = () => {
const tensor = tf.tidy(() =>
tf.browser.fromPixels(img, 1).toFloat().expandDims(0)
);
cb(tensor);
};
};
const drawPie = (top5) => {
const probs = [];
const labels = [];
for (const pred of top5) {
const prop = +pred.probability.toPrecision(3);
probs.push(prop);
labels.push(`${pred.className} (${prop})`);
}
const others = +(
1 - probs.reduce((prev, prob) => prev + prob, 0)
).toPrecision(3);
probs.push(others);
labels.push(`Others (${others})`);
if (pieChart) pieChart.destroy();
const ctx = document.getElementById("predictions").getContext("2d");
pieChart = new Chart(ctx, {
type: "pie",
options: {
plugins: {
legend: {
position: "bottom",
},
title: {
display: true,
text: "Top 5 Predictions",
},
},
},
data: {
labels,
datasets: [
{
label: "Top 5 predictions",
data: probs,
backgroundColor: [
"rgb(255, 99, 132)",
"rgb(54, 162, 235)",
"rgb(255, 205, 86)",
"rgb(0,255,0)",
"rgb(238,130,238)",
"rgb(97,96,96)",
],
},
],
},
});
};
const getMinimumCoordinates = () => {
let min_x = Number.MAX_SAFE_INTEGER;
let min_y = Number.MAX_SAFE_INTEGER;
for (const stroke of imageStrokes_copy) {
for (let i = 0; i < stroke[0].length; i++) {
min_x = Math.min(min_x, stroke[0][i]);
min_y = Math.min(min_y, stroke[1][i]);
}
}
return [Math.max(0, min_x), Math.max(0, min_y)];
};
const getBoundingBox = () => {
repositionImage();
const coords_x = [];
const coords_y = [];
for (const stroke of imageStrokes_copy) {
for (let i = 0; i < stroke[0].length; i++) {
coords_x.push(stroke[0][i]);
coords_y.push(stroke[1][i]);
}
}
const x_min = Math.min(...coords_x);
const x_max = Math.max(...coords_x);
const y_min = Math.min(...coords_y);
const y_max = Math.max(...coords_y);
// New width & height of cropped image
const width = Math.max(...coords_x) - Math.min(...coords_x);
const height = Math.max(...coords_y) - Math.min(...coords_y);
const coords_min = {
x: Math.max(0, x_min - CROP_PADDING), // Create link edge
y: Math.max(0, y_min - CROP_PADDING), // Create the top edge
};
let coords_max;
if (width > height)
// Left + right edge as boundary
coords_max = {
x: Math.min(WIDTH, x_max + CROP_PADDING), // Right edge
y: Math.max(0, y_min + CROP_PADDING) + width, // Lower edge
};
// Upper + lower edge as boundary
else
coords_max = {
x: Math.max(0, x_min + CROP_PADDING) + height, // Right edge
y: Math.min(HEIGHT, y_max + CROP_PADDING), // Lower edge
};
return {
min: coords_min,
max: coords_max,
};
};
// Reposition image to top left corner
const repositionImage = () => {
const [min_x, min_y] = getMinimumCoordinates();
for (const stroke of imageStrokes_copy) {
for (let i = 0; i < stroke[0].length; i++) {
stroke[0][i] = stroke[0][i] - min_x + REPOS_PADDING;
stroke[1][i] = stroke[1][i] - min_y + REPOS_PADDING;
}
}
};
function updateIframebutton(str_c) {
const bingFrame = document.getElementById("bingFrame");
bingFrame.style.display = str_c;
const showNextPredictionButton = document.getElementById("showNextPrediction");
showNextPredictionButton.style.display = str_c;
}
let predict_e =[];
let currentIndex = 0;
function help_copy_array(obj) {
if(obj == null || typeof(obj) != 'object') {
return obj;
}
var temp = new obj.constructor();
for(var key in obj) {
if (obj.hasOwnProperty(key)) {
temp[key] = help_copy_array(obj[key]);
}
}
return temp;
}
// Function to read an element in the array predict_e
function speakPrediction(prediction) {
var utterance = new SpeechSynthesisUtterance(prediction);
window.speechSynthesis.speak(utterance);
}
// Checking compatibility with SpeechSynthesis
function isSpeechSynthesisSupported() {
return 'speechSynthesis' in window && 'SpeechSynthesisUtterance' in window;
}
function Speaking() {
let sp_spech = 0;
for (let prediction of predict_e) {
if (sp_spech == 0)
{
prediction = "Well, I can see in your painting that may be "+ prediction;
sp_spech = 1;
}
else if(prediction == predict_e.at(-1))
{
prediction = ", or "+ prediction;
}
else{
prediction = ", "+ prediction;
}
speakPrediction(prediction);
}
}
// Prediction function with reading function
const predict = async () => {
if (!imageStrokes.length) return;
if (!LABELS.length) throw new Error("No labels found!");
if (isSpeechSynthesisSupported() && window.speechSynthesis.speaking) {
window.speechSynthesis.cancel();
}
imageStrokes_copy = [];
imageStrokes_copy = help_copy_array(imageStrokes);
preprocess(async (tensor) => {
const predictions = model.predict(tensor).dataSync();
top5 = Array.from(predictions)
.map((p, i) => ({
probability: p,
className: LABELS[i],
index: i,
}))
.sort((a, b) => b.probability - a.probability)
.slice(0, 5);
drawPie(top5);
predict_e = top5.map(pred => pred.className);
currentIndex = 0;
updateIframebutton("inline");
updateIframe(predict_e[currentIndex]);
// Checking compatibility with SpeechSynthesis
if (isSpeechSynthesisSupported() && speechEnabled == true) {
Speaking();
}
else {
console.log("Trình duyệt không hỗ trợ đọc văn bản.");
}
});
};
function showNextPrediction() {
currentIndex++;
if (currentIndex >= predict_e.length) {
currentIndex = 0;
}
const currentPrediction = predict_e[currentIndex];
updateIframe(currentPrediction);
}
function updateIframe(searchQuery) {
const bingFrame = document.getElementById("bingFrame");
if (searchQuery == 'line')
{
searchQuery = 'Straight line';
}
if (searchQuery == 'bush')
{
searchQuery = 'Bush Landscaping';
}
const bingSearchURL = `https://www.bing.com/images/search?q=${encodeURIComponent(searchQuery)} picture`;
bingFrame.src = bingSearchURL;
}
function Stop_talking_clear_pie_iframe()
{
// Stop browser speech if currently speaking
if (isSpeechSynthesisSupported() && window.speechSynthesis.speaking) {
window.speechSynthesis.cancel();
}
if (pieChart) pieChart.destroy();
updateIframebutton("none");
currentIndex = 0;
predict_e = [];
}
const clearCanvas = () => {
clear();
Stop_talking_clear_pie_iframe();
background("#FFFFFF");
imageStrokes = [];
imageStrokes_copy = [];
strokePixels = [[], []];
};
window.addEventListener('load', updateViewport);
window.addEventListener('resize', updateViewport);
window.addEventListener('load', () => {
const showNextPredictionButton = document.getElementById("showNextPrediction");
showNextPredictionButton.addEventListener("click", showNextPrediction);
});
window.onload = () => {
updateIframebutton("none");
const $submit = document.getElementById("predict");
const $clear = document.getElementById("clear");
$submit.addEventListener("click", () => predict());
$clear.addEventListener("click", clearCanvas);
};