179 lines
5.5 KiB
HTML
179 lines
5.5 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
<title>MNIST with GGML</title>
|
|
<script src="mnist.js"></script>
|
|
</head>
|
|
<body>
|
|
<h2>MNIST digit recognizer with <a href="https://github.com/ggerganov/ggml">GGML</a></h2>
|
|
<p id="msg">Loading model and data set, please wait ...</p>
|
|
<canvas id="ggCanvas" width="364" height="364" style="border:2px solid #d3d3d3;">
|
|
Your browser does not support the HTML canvas tag.
|
|
</canvas>
|
|
<div>
|
|
<button id="clear" onclick="onClear()">Clear</button>
|
|
<button id="random" onclick="onRandom()" disabled>Random</button>
|
|
<button id="download" onclick="onDownload()">Download</button>
|
|
</div>
|
|
<div>
|
|
<p id="prediction"></p>
|
|
</div>
|
|
<script>
|
|
"use strict";
|
|
const DIGIT_SIZE = 28; // digits are 28x28 pixels
|
|
var canvas = document.getElementById("ggCanvas");
|
|
var ctx = canvas.getContext("2d", { alpha: false, willReadFrequently: true });
|
|
ctx.fillStyle = "white";
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
var dragging = false;
|
|
var lastX, lastY;
|
|
|
|
function onClear(event) {
|
|
ctx.fillStyle = "white";
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
document.getElementById("prediction").innerHTML = "";
|
|
}
|
|
|
|
function predict(digit) {
|
|
let buf = Module._malloc(digit.length);
|
|
if (buf == 0) {
|
|
console.log("failed to allocate memory");
|
|
return;
|
|
}
|
|
Module.HEAPU8.set(digit, buf);
|
|
let prediction = Module.ccall('wasm_eval', null, ['number'], [buf]);
|
|
Module._free(buf);
|
|
if (prediction >= 0) {
|
|
document.getElementById("prediction").innerHTML = "Predicted digit is <b>" + prediction + "</b>";
|
|
}
|
|
}
|
|
|
|
function onRandom(event) {
|
|
onClear();
|
|
const bufLength = DIGIT_SIZE*DIGIT_SIZE;
|
|
var buf = Module._malloc(bufLength);
|
|
if (buf == 0) {
|
|
console.log("failed to allocate memory");
|
|
return;
|
|
}
|
|
let ret = Module.ccall('wasm_random_digit', null, ['number'], [buf]);
|
|
let digit = new Uint8Array(Module.HEAPU8.buffer, buf, bufLength);
|
|
for (let i = 0; i < digit.length; i++) {
|
|
let x = i % DIGIT_SIZE;
|
|
let y = Math.floor(i / DIGIT_SIZE);
|
|
setPixel(x, y, digit[i]);
|
|
}
|
|
Module._free(buf);
|
|
predict(digit);
|
|
}
|
|
|
|
function onDownload(event) {
|
|
let digit = scaleCanvas();
|
|
let digitBlob = new Blob([new Uint8Array(digit)], {type: "application/octet-stream"});
|
|
let url = URL.createObjectURL(digitBlob);
|
|
let link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = "image.raw";
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
// Get the position of the mouse relative to the canvas
|
|
function getMousePos(event) {
|
|
if (event.touches !== undefined && event.touches.length > 0) {
|
|
event = event.touches[0];
|
|
}
|
|
var rect = canvas.getBoundingClientRect();
|
|
return [Math.floor(event.clientX) - rect.left, Math.floor(event.clientY) - rect.top];
|
|
}
|
|
|
|
function setPixel(x, y, val) {
|
|
let canvasX = x * 13;
|
|
let canvasY = y * 13;
|
|
let color = 255 - val;
|
|
ctx.fillStyle = "#" + color.toString(16) + color.toString(16) + color.toString(16);
|
|
ctx.fillRect(canvasX, canvasY, 13, 13);
|
|
}
|
|
|
|
function onMouseDown(e) {
|
|
dragging = true;
|
|
[lastX, lastY] = getMousePos(e);
|
|
}
|
|
|
|
// scale the canvas to 28x28 pixels and return the pixel values as an array
|
|
function scaleCanvas() {
|
|
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
let tempCanvas = document.createElement('canvas');
|
|
tempCanvas.width = DIGIT_SIZE;
|
|
tempCanvas.height = DIGIT_SIZE;
|
|
let tempCtx = tempCanvas.getContext("2d");
|
|
tempCtx.drawImage(canvas, 0, 0, DIGIT_SIZE, DIGIT_SIZE);
|
|
let tempImgData = tempCtx.getImageData(0, 0, DIGIT_SIZE, DIGIT_SIZE);
|
|
let tempData = tempImgData.data;
|
|
let digit = new Array(DIGIT_SIZE*DIGIT_SIZE).fill(0);
|
|
for (let i = 0; i < tempData.length; i += 4) {
|
|
let val = 255 - tempData[i];
|
|
digit[i / 4] = val;
|
|
}
|
|
return digit;
|
|
}
|
|
|
|
function onMouseUp(e) {
|
|
dragging = false;
|
|
let digit = scaleCanvas();
|
|
predict(digit);
|
|
}
|
|
|
|
function onMouseMove(e) {
|
|
if (dragging) {
|
|
let [mouseX, mouseY] = getMousePos(e);
|
|
ctx.beginPath();
|
|
ctx.moveTo(lastX, lastY);
|
|
ctx.lineTo(mouseX, mouseY);
|
|
ctx.lineWidth = 20;
|
|
ctx.lineJoin = ctx.lineCap = 'round';
|
|
ctx.strokeStyle = "#000000";
|
|
ctx.stroke();
|
|
ctx.closePath();
|
|
lastX = mouseX;
|
|
lastY = mouseY;
|
|
}
|
|
}
|
|
|
|
// Prevent scrolling when touching the canvas
|
|
document.body.addEventListener("touchstart", function (e) {
|
|
if (e.target == canvas) {
|
|
e.preventDefault();
|
|
}
|
|
}, {passive: false});
|
|
document.body.addEventListener("touchend", function (e) {
|
|
if (e.target == canvas) {
|
|
e.preventDefault();
|
|
}
|
|
}, {passive: false});
|
|
document.body.addEventListener("touchmove", function (e) {
|
|
if (e.target == canvas) {
|
|
e.preventDefault();
|
|
}
|
|
}, {passive: false});
|
|
|
|
function onRuntimeInitialized() {
|
|
// Use the same handlers for mouse and touch events
|
|
canvas.onmousedown = onMouseDown;
|
|
canvas.onmouseup = onMouseUp;
|
|
canvas.onmousemove = onMouseMove;
|
|
canvas.ontouchstart = onMouseDown;
|
|
canvas.ontouchend = onMouseUp;
|
|
canvas.ontouchmove = onMouseMove;
|
|
document.getElementById("msg").innerHTML = "Draw a single digit on the canvas below:"
|
|
document.getElementById("random").disabled = false;
|
|
}
|
|
|
|
Module['onRuntimeInitialized'] = onRuntimeInitialized;
|
|
</script>
|
|
</body>
|
|
</html>
|