Commit d4861938 authored by Mattia Monga's avatar Mattia Monga

Initial commit

parents
This diff is collapsed.
Un'applicazione molto semplice per esercitarsi con i conticini. Le cifre si
scrivono con il dito o il mouse e vengono riconosciute da una rete neurale
addestrata sul dataset MNIST (vedi
https://github.com/onnx/models/tree/master/vision/classification/mnist).
Questo programma è software libero (GPLv3) e può quindi essere usato, copiato,
distribuito, modificato a piacimento conservando gli stessi diritti ai nuovi
utenti.
/* Copyright (C) 2020 by Mattia Monga <[email protected]> */
body {
text-align: center;
font-size: 200%;
}
.content {
display: grid;
grid-template-columns: 0.5fr 0.5fr 0.5fr 0.5fr 1fr 1fr;
grid-template-rows: 1fr 3fr 1fr;
grid-gap: 1%;
align-items: center;
}
#operazione {
font-size: 200%;
grid-column: 1 / 5;
grid-row: 1 / 4;
}
#label_d {
grid-column: 5;
grid-row: 1;
}
#decine {
border: 3px solid magenta;
grid-column: 5;
grid-row: 2;
}
#label_u {
grid-column: 6;
grid-row: 1;
}
#unita {
border: 3px solid cyan;
grid-column: 6;
grid-row: 2;
}
#interazione {
grid-column: 1 / 7;
grid-row: 3;
}
#pulsante {
font-size: 150%;
}
.footnote {
margin-top: 50px;
font-size: 30%;
}
<!doctype html>
<html>
<head>
<title>Conticini</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="conticini.css" type="text/css" media="screen" />
</head>
<body onload="randomize();">
<h1>QUANTO FA?</h1>
<div class="content" id="conticino">
<div id="operazione">
<span id="operando_sx"></span>
<span id="operatore"></span>
<span id="operando_dx"></span>
<span id="uguale">=</span>
</div>
<div id="label_d">DECINE</div>
<div id="decine">
<canvas id="cifra_d"></canvas>
</div>
<div id="label_u">UNIT&Agrave;</div>
<div id="unita">
<canvas id="cifra_u"></canvas>
</div>
<div id="interazione">
<input type="button" id="pulsante" value="CONTROLLA" />
</div>
</div>
<canvas id="canvas_tmp" width="28" height="28" style="display: none;"></canvas>
<script type="text/javascript" src="drawing.js"></script>
<script type="text/javascript" src="randomize.js"></script>
<!-- Load ONNX.js -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/onnxjs/dist/onnx.min.js"></script>
<script type="text/javascript" src="nn.js"></script>
<p class="footnote">Copyright (C) 2020 by Mattia Monga</p>
</body>
</html>
// Copyright (C) 2020 by Mattia Monga <[email protected]>
// Basic idea from https://gist.github.com/peshoicov/ab3286875d980948ad3f5f434fec37a9
// and http://bencentra.com/code/2014/12/05/html5-canvas-touch-events.html
// HTML elements
const CONTICINO = document.getElementById('operazione');
const CIFRA_D = document.getElementById('cifra_d');
const CIFRA_U = document.getElementById('cifra_u');
// some variables we'll need ..
var drawing = null;
var mousePos = {x:0, y:0};
var lastPos = mousePos;
var isMobile = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
const canvasDim = 0.45*CONTICINO.clientWidth;
setupCanvas(CIFRA_D);
setupCanvas(CIFRA_U);
function setupCanvas(canvas){
// Setup canvas ..
const ctx = canvas.getContext('2d');
canvas.width = canvasDim;
canvas.height = canvas.width;
// setup lines styles ..
//ctx.strokeStyle = "#22222";
ctx.lineWidth = 15;
ctx.lineCap = 'round';
// mouse/touch events ..
canvas.addEventListener((isMobile ? 'touchstart' : 'mousedown'), function(e) {
drawing = ctx.canvas.id;
lastPos = getMousePos(canvas, e);
mousePos = lastPos;
});
canvas.addEventListener((isMobile ? 'touchmove' : 'mousemove'), function(e) {
mousePos = getMousePos(canvas, e);
});
canvas.addEventListener((isMobile ? 'touchend' : 'mouseup'), function(e) {
drawing = null;
});
// drawing ..
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000/60);
};
})();
function drawLoop() {
window.requestAnimFrame(drawLoop);
renderCanvas(ctx);
}
drawLoop();
}
// helper functions ..
function getMousePos(canvasDom, touchOrMouseEvent) {
const rect = canvasDom.getBoundingClientRect();
return {
x: (isMobile ? touchOrMouseEvent.touches[0].clientX : touchOrMouseEvent.clientX) - rect.left,
y: (isMobile ? touchOrMouseEvent.touches[0].clientY : touchOrMouseEvent.clientY) - rect.top
};
}
function renderCanvas(ctx) {
if (drawing === ctx.canvas.id) {
ctx.moveTo(lastPos.x, lastPos.y);
ctx.lineTo(mousePos.x, mousePos.y);
ctx.stroke();
lastPos = mousePos;
}
}
File added
// Copyright (C) 2020 by Mattia Monga <[email protected]>
// Code that consume ONNX.js mainly from https://github.com/microsoft/onnxjs-demo
// HTML elements
const CANVAS_TMP = document.getElementById('canvas_tmp');
async function getNumber(canvas) {
const myOnnxSession = new onnx.InferenceSession({ backendHint: "webgl" });
// load the ONNX model file
await myOnnxSession.loadModel("./model.onnx");
// Preprocess the image data to match input dimension requirement, which is 1*1*28*28.
const width = 28;
const height = 28;
const preprocessedData = preprocess(canvas, width, height);
const inputTensor = new onnx.Tensor(preprocessedData, 'float32', [1, 1, width, height]);
const outputMap = await myOnnxSession.run([inputTensor]);
const outputData = outputMap.values().next().value.data;
const probabilities = softmax(outputData);
console.log(probabilities);
return probabilities.findIndex((x) => x > .5); // -1 is not found
}
function softmax(arr) {
const C = Math.max(...arr);
const d = arr.map((y) => Math.exp(y - C)).reduce((a, b) => a + b);
return arr.map((value, index) => {
return Math.exp(value - C) / d;
});
}
function preprocess(canvas, width, height) {
const ctx = canvas.getContext('2d');
const ctxScaled = CANVAS_TMP.getContext('2d');
ctxScaled.save();
ctxScaled.scale(width / ctx.canvas.width, height / ctx.canvas.height);
ctxScaled.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctxScaled.drawImage(canvas, 0, 0);
const imageDataScaled = ctxScaled.getImageData(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height);
ctxScaled.restore();
// process image data for model input
const data = imageDataScaled.data;
const input = new Float32Array(width * height);
for (let i = 0; i < data.length; i += 4) {
input[i / 4] = data[i + 3] / 255;
}
return input;
}
// Copyright (C) 2020 by Mattia Monga <[email protected]>
const MIN_OPERANDO = 0;
const MAX_OPERANDO = 9;
const MIN_EXPECTED = 0;
const MAX_EXPECTED = 20;
const OPS = ['+', '-'];
const OPERANDO_SX = document.getElementById('operando_sx');
const OPERANDO_DX = document.getElementById('operando_dx');
const OPERATORE = document.getElementById('operatore');
const PULSANTE = document.getElementById('pulsante');
var g_operando_sx, g_operando_dx, g_operatore, g_expected;
function randomize() {
do {
g_operando_sx = Math.floor(MIN_OPERANDO + Math.random()*(MAX_OPERANDO - MIN_OPERANDO + 1));
g_operando_dx = Math.floor(MIN_OPERANDO + Math.random()*(MAX_OPERANDO - MIN_OPERANDO + 1));
g_operatore = OPS[Math.floor(Math.random()*OPS.length)];
g_expected = eval(g_operando_sx + g_operatore + g_operando_dx);
} while (g_expected < MIN_EXPECTED || g_expected > MAX_EXPECTED);
OPERANDO_SX.innerHTML = "" + g_operando_sx;
OPERANDO_DX.innerHTML = "" + g_operando_dx;
OPERATORE.innerHTML = g_operatore;
reset();
}
function reset() {
const ctx_u = CIFRA_U.getContext('2d');
const ctx_d = CIFRA_D.getContext('2d');
ctx_u.clearRect(0, 0, ctx_u.canvas.width, ctx_u.canvas.height);
ctx_u.beginPath();
ctx_d.clearRect(0, 0, ctx_d.canvas.width, ctx_d.canvas.height);
ctx_d.beginPath();
PULSANTE.value = "CONTROLLA";
PULSANTE.style.background = 'lightblue';
PULSANTE.onclick = controlla;
}
function ko_msg(timeout){
PULSANTE.value = "HAI SCRITTO MALE, RIPROVA!";
PULSANTE.style.background = 'red';
window.setTimeout(reset, timeout);
}
function ok_msg(timeout){
PULSANTE.value = "Benissimo!";
PULSANTE.style.background = 'lightgreen';
window.setTimeout(randomize, timeout);
}
async function controlla(){
const u = await getNumber(CIFRA_U);
if (u === -1) {
ko_msg(3000);
return;
}
const d = await getNumber(CIFRA_D);
if ((d === -1 && u === g_expected) || (d > -1 && 10*d + u === g_expected)) {
ok_msg(5000);
return;
}
ko_msg(3000);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment