'use strict';
const colourMap = {
GREEN : "#00FF00",
YELLOW : "#FFFF00",
ORANGE : "#FFA500",
RED : "#B20000",
WHITE : "#FFFFFF"
};
/**
* @return {undefined}
*/
function clearCanvas() {
var ctx = document.getElementById("doodleCanvas").getContext("2d");
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
/** @type {!Array} */
clickX = new Array;
/** @type {!Array} */
clickY = new Array;
/** @type {!Array} */
clickDrag = new Array;
/** @type {string} */
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fill();
}
document.write('\n\x3c!-- Bootstrap CSS --\x3e\n<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">\n<style>\ntable, td, th {\n border: 1px solid black;\n text-align: center;\n}\n\ntd:hover {\n background-color: lightblue;\n}\n\n.table {\n border-collapse: collapse;\n width: 100%;\n}\n\n.slider {\n -webkit-appearance: none;\n height: 25px;\n background: #d3d3d3;\n outline: none;\n opacity: 0.7;\n -webkit-transition: .2s;\n transition: opacity .2s;\n}\n\n.slider:hover {\n opacity: 1;\n}\n\n.slider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 25px;\n height: 25px;\n background: #4CAF50;\n cursor: pointer;\n}\n\n.slider::-moz-range-thumb {\n width: 25px;\n height: 25px;\n background: #4CAF50;\n cursor: pointer;\n}\n\n</style>\n<div style="text-align: center;">\n <canvas id="doodleCanvas" width="280" height="280" style="border:1px solid black;"></canvas>\n <div>\n <button type="button" style="margin:5px" class="btn btn-secondary" onclick="clearCanvas()">Clear or click spacebar</button>\n </div>\n \n <div>\n <canvas id="smallCanvas" width="28" height="28" style="height:100;width:100; border:1px solid black;" >\n </div>\n \n <p>Brush Size:</p>\n <input type="range" min="1" max="100" value="25" class="slider" id="brushSize" onchange="updateBrushSize(this.value)">\n \n <p>Brush Type:</p>\n <input type="range" min="0" max="2" value="0" class="slider" id="brushType" onchange="updateBrushType(this.value)">\n \n <div>\n <h2>Select Correct Result To Retrain</h2>\n <table class="table" id="predictionTable">\n </table>\n </div>\n</div>\n\n');
/** @type {!Array} */
var lineJoin = ["round", "bevel", "miter"];
/** @type {number} */
var BRUSH_STYLE = 0;
/** @type {number} */
var BRUSH_SIZE = 25;
const PIXELS = 28;
const PIXELSSQUARED = 784;
var inputs;
var paint;
/** @type {!Array} */
var models = [];
/** @type {!Array} */
var clickX = [];
/** @type {!Array} */
var clickY = [];
/** @type {!Array} */
var clickDrag = [];
/**
* @param {number} canCreateDiscussions
* @return {undefined}
*/
function updateBrushSize(canCreateDiscussions) {
/** @type {number} */
BRUSH_SIZE = canCreateDiscussions;
redraw();
makePredictions();
}
/**
* @param {number} canCreateDiscussions
* @return {undefined}
*/
function updateBrushType(canCreateDiscussions) {
/** @type {number} */
BRUSH_STYLE = canCreateDiscussions;
redraw();
makePredictions();
}
/**
* @return {?}
*/
function loadTensorFlowLibraries() {
return $.when($.getScript("https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.0.0/dist/tf.min.js"), $.getScript("https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis@1.0.2/dist/tfjs-vis.umd.min.js"), $.getScript("uploads/michaelryan/data.js"));
}
/**
* @param {number} s
* @return {?}
*/
function getColourForPredictionValue(s) {
let color;
return s >= .8 ? color = colourMap.GREEN : s >= .6 && s < .8 ? color = colourMap.YELLOW : s >= .4 && s < .6 ? color = colourMap.ORANGE : s >= .2 && s < .4 && (color = colourMap.RED), color;
}
/**
* @param {number} indent
* @param {!NodeList} arr
* @return {undefined}
*/
function fillPredictionTable(indent, arr) {
indent = indent + 1;
let i = 0;
for (let j = 0; j < arr.length; j++) {
if (arr[j] > arr[i]) {
i = j;
}
$(`#${indent}_${j}`).css("background-color", colourMap.WHITE);
$(`#${indent}_${j}`).html(arr[j].toFixed(2));
}
let meterPos = getColourForPredictionValue(arr[i]);
$(`#${indent}_${i}`).css("background-color", meterPos);
}
/**
* @param {?} e
* @return {undefined}
*/
async function selectCorrectValue(e) {
console.log(e);
let row = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
/** @type {number} */
row[e] = 1;
const [n, sc] = tf.tidy(() => {
const options = data.nextTrainBatch(1);
return [options.xs, options.labels];
});
tf.tensor2d(inputs, [1, 784]);
tf.tensor2d(row, [1, 10]);
let tr = data.nextTrainArray();
tr.batchImagesArray.set(inputs, IMAGE_SIZE);
tr.batchLabelsArray.set(row, NUM_CLASSES);
const hugeDocs = tf.tensor2d(tr.batchImagesArray, [2, IMAGE_SIZE]);
const sortOption = tf.tensor2d(tr.batchLabelsArray, [2, NUM_CLASSES]);
console.log("Retraining models....");
await retrainModels(hugeDocs, sortOption);
console.log("Complete....");
makePredictions();
}
/**
* @param {?} docs
* @param {?} sortOption
* @return {undefined}
*/
async function retrainModels(docs, sortOption) {
const pingPromises = models.map(async(indexer) => {
await indexer.fit(docs.reshape([2, 28, 28, 1]), sortOption);
});
await Promise.all(pingPromises);
}
/**
* @return {undefined}
*/
function createPredictionTable() {
var e = $("#predictionTable");
/** @type {string} */
var tt = "Model 500";
for (let i = 0; i <= models.length; i++) {
/** @type {string} */
var filename = "<tr>";
for (let endAccelerationPoint = -1; endAccelerationPoint < 10; endAccelerationPoint++) {
if (0 === i) {
filename = filename + (-1 === endAccelerationPoint ? "<td></td>" : `<td onclick="selectCorrectValue(${endAccelerationPoint})">${endAccelerationPoint}</td>`);
} else {
if (-1 === endAccelerationPoint) {
filename = filename + `<td>${tt}</td>`;
/** @type {string} */
tt = tt + "0";
} else {
filename = filename + `<td id=${i}_${endAccelerationPoint}>0.00</td>`;
}
}
}
/** @type {string} */
filename = filename + "</tr>";
e.append(filename);
}
}
/**
* @return {undefined}
*/
function makePredictions() {
let captureContext = document.getElementById("smallCanvas").getContext("2d");
let imgData = captureContext.getImageData(0, 0, captureContext.canvas.width, captureContext.canvas.height);
/** @type {!Float32Array} */
inputs = new Float32Array(PIXELSSQUARED);
for (let i = 0; i < PIXELSSQUARED; i++) {
/** @type {number} */
inputs[i] = imgData.data[4 * i] / 255;
}
currentTensor = tf.tensor2d(inputs, [1, PIXELSSQUARED]);
for (let i = 0; i < models.length; i++) {
fillPredictionTable(i, models[i].predict(currentTensor.reshape([1, 28, 28, 1])).dataSync());
}
}
/**
* @return {undefined}
*/
async function setupCanvas() {
/**
* @param {number} x
* @param {number} y
* @param {boolean} dragging
* @return {undefined}
*/
function addClick(x, y, dragging) {
clickX.push(x);
clickY.push(y);
clickDrag.push(dragging);
}
var g = document.getElementById("doodleCanvas").getContext("2d");
document.getElementById("smallCanvas").getContext("2d").scale(.1, .1);
/** @type {string} */
g.fillStyle = "black";
g.fillRect(0, 0, g.canvas.width, g.canvas.height);
g.fill();
createPredictionTable();
$("#doodleCanvas").mousedown(function(event) {
event.pageX;
this.offsetLeft;
event.pageY;
this.offsetTop;
/** @type {boolean} */
paint = true;
addClick(event.pageX - this.offsetLeft, event.pageY - this.offsetTop);
redraw(g);
});
$("#doodleCanvas").mousemove(function(event) {
if (paint) {
addClick(event.pageX - this.offsetLeft, event.pageY - this.offsetTop, true);
redraw(g);
}
});
$("#doodleCanvas").mouseup(function(canCreateDiscussions) {
/** @type {boolean} */
paint = false;
makePredictions();
});
$("#doodleCanvas").mouseleave(function(canCreateDiscussions) {
/** @type {boolean} */
paint = false;
});
$("body").keyup(function(event) {
if (32 == event.keyCode) {
clearCanvas();
}
});
}
/**
* @param {!Object} context
* @return {undefined}
*/
function redraw(context) {
(context = document.getElementById("doodleCanvas").getContext("2d")).clearRect(0, 0, context.canvas.width, context.canvas.height);
/** @type {string} */
context.fillStyle = "black";
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.fill();
/** @type {string} */
context.strokeStyle = "white";
context.lineJoin = lineJoin[BRUSH_STYLE];
context.lineWidth = BRUSH_SIZE;
/** @type {number} */
var i = 0;
for (; i < clickX.length; i++) {
context.beginPath();
if (clickDrag[i] && i) {
context.moveTo(clickX[i - 1], clickY[i - 1]);
} else {
context.moveTo(clickX[i] - 1, clickY[i]);
}
context.lineTo(clickX[i], clickY[i]);
context.closePath();
context.stroke();
}
/** @type {string} */
context.filter = "blur(2px)";
drawSmallCanvas(doodleCanvas);
}
/**
* @param {?} canCreateDiscussions
* @return {undefined}
*/
function drawSmallCanvas(canCreateDiscussions) {
const ctx = document.getElementById("smallCanvas").getContext("2d");
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
/** @type {string} */
ctx.fillStyle = "black";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fill();
ctx.drawImage(document.getElementById("doodleCanvas"), 0, 0);
}
/**
* @param {!Array} items
* @return {undefined}
*/
function compileModels(items) {
console.log("compiling...");
const optimizer = tf.train.adam();
for (let i = 0; i < items.length; i++) {
items[i].compile({
optimizer : optimizer,
loss : "categoricalCrossentropy",
metrics : ["accuracy"]
});
}
console.log("Neural Networks loaded...");
}
let data;
/**
* @return {undefined}
*/
async function setup() {
console.log("Load external scripts...");
await loadTensorFlowLibraries();
console.log("Scripts loaded...");
models.push(await tf.loadLayersModel("uploads/michaelryan/500-model.json"));
models.push(await tf.loadLayersModel("uploads/michaelryan/5000-model.json"));
models.push(await tf.loadLayersModel("uploads/michaelryan/50000-model.json"));
compileModels(models);
data = new MnistData;
await data.load();
setupCanvas();
console.log("Ready to go");
}
setup();