// Cloned by Abdelshafa Abdala on 17 Nov 2021 from World "Preprocessing to improve character recognition" by Philip
// Please leave this clone trail here.
const PIXELS=28,
PIXELSSQUARED=PIXELS*PIXELS,
NOTRAIN=6e4,
NOTEST=1e4,
noinput=PIXELSSQUARED,
nohidden=64,
nooutput=10,
learningrate=.1;
let do_training=!0;
const TRAINPERSTEP=30,
TESTPERSTEP=5,
ZOOMFACTOR=7,
ZOOMPIXELS=7*PIXELS,
canvaswidth=PIXELS+ZOOMPIXELS+50,
canvasheight=3*ZOOMPIXELS+100,
DOODLE_THICK=11,
DOODLE_BLUR=3;
let mnist,
nn,
doodle,demo,
trainrun=1,
train_index=0,
testrun=1,
test_index=0,
total_tests=0,
total_correct=0,
doodle_exists=!1,
demo_exists=!1,
mousedrag=!1;
var train_inputs,test_inputs,demo_inputs,doodle_inputs;
function randomWeight()
{
return AB.randomFloatAtoB(-.5,.5)}$("#runheaderbox").css({"max-height":"95vh"});
let htmlPositions=[0,1,3,4,5,6,7,8,9,10,2,11,12,13];
var thehtml;
thehtml="<hr> <h1> 1. Doodle </h1> Top row: Doodle (left) and shrunk (right). <br> Draw your doodle in top LHS. <button onclick='wipeDoodle();' class='normbutton' >Clear doodle</button> <br> "+validateDoodleHtml(),
AB.msg(thehtml,htmlPositions[1]),
thehtml="<hr> <h1> 2. Training </h1> Middle row: Training image magnified (left) and original (right). <br> <button onclick='do_training = false;' class='normbutton' >Stop training</button> <br> ",
AB.msg(thehtml,htmlPositions[3]),
thehtml="<h3> Hidden tests </h3> ",
AB.msg(thehtml,htmlPositions[5]),
thehtml="<hr> <h1> 3. Demo </h1>The Demos begin to run automatically at 80% accuracy unless turned off! Bottom row: Test image magnified (left) and original (right). <br> The network is <i>not</i> trained on any of these images. <br> <button onclick='makeDemo();' class='normbutton' >Demo test image</button> <br> ",
AB.msg(thehtml,htmlPositions[7]),
thehtml="<hr> <h1> 4. Test against Stored Doodles </h1> <br> <button onclick='testSavedDoodles();' class='normbutton' >This became pointless as I began to modify different things such as the filters and how the images were preprocessed</button> <br>",
AB.msg(thehtml,htmlPositions[12]);
const greenspan="<span style='font-weight:bold; font-size:x-large; color:darkgreen'> ";
function setup()
{
startTime=Math.round((new Date).getTime()/1e3),createCanvas(canvaswidth,canvasheight),
(
doodle=createGraphics(ZOOMPIXELS,ZOOMPIXELS)).pixelDensity(1),
AB.loadingScreen(),
$.getScript("/uploads/codingtrain/matrix.js",function()
{
$.getScript("/uploads/codingtrain/nn.js",function()
{
$.getScript("/uploads/codingtrain/mnist.js",function()
{
console.log("All JS loaded"),
nn=new NeuralNetwork(noinput,nohidden,nooutput);
new ActivationFunction(e=>e>0?e:0,e=>e>0?1:0);
nn.setLearningRate(learningrate),
loadData()
})
})
})}
function loadData()
{
loadMNIST(function(e)
{
mnist=e,
console.log("All data loaded into mnist object:"),
console.log(mnist),AB.removeLoading()
})
}
function getImage(e)
{
let t=createImage(PIXELS,PIXELS);
t.loadPixels();
for(let o=0;o<PIXELSSQUARED;o++)
{
let n=e[o],
r=4*o;
t.pixels[r+0]=n,
t.pixels[r+1]=n,
t.pixels[r+2]=n,
t.pixels[r+3]=n
}
return t.updatePixels(),
t
}
function getInputs(e)
{
let t=[];
for(let o=0;o<PIXELSSQUARED;o++)
{let n=e[o];
t[o]=n/255
}
return t
}
function trainit(e)
{
let t=mnist.train_images[train_index],
o=mnist.train_labels[train_index];
if(e)
{
var n=getImage(t);
image(n,0,ZOOMPIXELS+50,
ZOOMPIXELS,ZOOMPIXELS),
image(n,ZOOMPIXELS+50,
ZOOMPIXELS+50,
PIXELS,PIXELS
)
}
//////////
let r=getInputs(t),
i=[0,0,0,0,0,0,0,0,0,0];i[o]=1,
train_inputs=r,nn.train(arrayManipulations(r,!1,!0),i),
thehtml=" trainrun: "+trainrun+"<br> no: "+train_index+"<br> Time Passed: "+runTime()+" seconds",
AB.msg(thehtml,htmlPositions[4]),++train_index==NOTRAIN&&(train_index=0,
console.log("finished trainrun: "+trainrun),trainrun++)
}
function testit()
{
let e=mnist.test_images[test_index],
t=mnist.test_labels[test_index],
o=getInputs(e);test_inputs=o;
let n=findMax(nn.predict(arrayManipulations(o,!1,!0)));
total_tests++,n==t&&total_correct++;
let r=total_correct/total_tests*100;
currentPercent=r,calculateLandmarkTimes(r),
thehtml=" testrun: "+testrun+"<br> no: "+total_tests+" <br> correct: "+total_correct+"<br> score: "+greenspan+r.toFixed(2)+"</span> <br> Time to 10%: "+timeToArray[0]+" <br> Time to 20%: "+timeToArray[1]+" <br> Time to 30%: "+timeToArray[2]+" <br> Time to 40%: "+timeToArray[3]+" <br> Time to 50%: "+timeToArray[4]+" <br> Time to 60%: "+timeToArray[5]+" <br> Time to 70%: "+timeToArray[6]+" <br> Time to 80%: "+timeToArray[7]+" <br> Time to 85%: "+timeToArray[8]+" <br> Time to 90%: "+timeToArray[9]+" <br> Time to 100%: "+timeToArray[10],
AB.msg(thehtml,htmlPositions[6]),
++test_index==NOTEST&&
(
console.log("finished testrun: "+testrun+" score: "+r.toFixed(2)),
testrun++,test_index=0,total_tests=0,total_correct=0)
}
function find12(e)
{
let t=0,
o=0,
n=0,
r=0;
for(let i=0;i<e.length;i++)
e[i]>n?(t=i,n=e[i])
:e[i]>r&&(o=i,r=e[i]);
return[t,o]
}
function findMax(e)
{
let t=0,
o=0;
for(let n=0;
n<e.length;n++)e[n]>o&&(t=n,o=e[n]);
return t
}
function draw()
{
if(reportDoodleAccuracy(),
void 0!==mnist)
{
if(background("black"),do_training)
{
for(let e=0;
e<TRAINPERSTEP;e++)trainit(0==e);
for(let e=0;
e<TESTPERSTEP;e++)testit()
}
if(demo_exists&&(drawDemo(),
guessDemo()),
currentPercent>demoThreshold&&makeDemoBlind(),
doodle_exists&&(drawDoodle(),guessDoodle()),mouseIsPressed)
{
var e=ZOOMPIXELS+20;
mouseX<e&&mouseY<e&&pmouseX<e&&pmouseY<e&&(mousedrag=!0,
doodle_exists=!0,
doodle.stroke("white"),
doodle.strokeWeight(DOODLE_THICK),
doodle.line(mouseX,mouseY,pmouseX,pmouseY))
}
else mousedrag&&(rawDoodle=getRawDoodleInputs(doodle),
mousedrag=!1,
applyDoodleFilters())}
}
function makeDemo()
{
demo_exists=!0;
var e=AB.randomIntAtoB(0,NOTEST-1);
demo=mnist.test_images[e],
demoLabel=mnist.test_labels[e],
thehtml="Test image no: "+e+"<br>Classification: "+label+"<br>",
AB.msg(thehtml,htmlPositions[8])
}
function drawDemo()
{
var e=getImage(demo);
image(e,0,canvasheight-ZOOMPIXELS,ZOOMPIXELS,ZOOMPIXELS),
image(e,ZOOMPIXELS+50,canvasheight-ZOOMPIXELS,PIXELS,PIXELS)
}
function guessDemo()
{
let e=getInputs(demo);demo_inputs=e;
let t=findMax(nn.predict(e));
thehtml=" We classify it as: "+greenspan+t+"</span>",
AB.msg(thehtml,htmlPositions[9])
}
function drawDoodle()
{
let e=doodle.get();e.loadPixels(),
drawFromImage(e)
}
function drawFromImage(e)
{
image(e,0,0,ZOOMPIXELS,ZOOMPIXELS),
image(e,ZOOMPIXELS+50,0,PIXELS,PIXELS)
}
function guessDoodle(){getDoodleInputs(doodle);
let e=find12(nn.predict(arrayManipulations(doodle_inputs,!0,!1)));
doodleGuess=e[0],
thehtml=" We classify it as: "+greenspan+e[0]+"</span> <br> No.2 guess is: "+greenspan+e[1]+"</span>",
AB.msg(thehtml,htmlPositions[2])
}
function wipeDoodle()
{
doodle_exists=!1,doodle.background("black")
}
function showInputs(e)
{
var t="";
for(let o=0;
o<e.length;o++){o%PIXELS==0&&(t+="\n"),t=t+" "+e[o].toFixed(2)
}
console.log(t)
}
let startTime=0,timeToArray=[0,0,0,0,0,0,0,0,0,0,0],
doodleAccuracy=0,
numberOfDoodles=0,
correctDoodles=0,
incorrectDoodles=0,
currentPercent=0,
doodleGuess=null,
demoLabel=null,numberOfDemos=0,
numberOfCorrectDemoGuesses=0,
demoAccuracy=0,
demoThreshold=80,
rawDoodle=[],localStoragePrefix="doodle_";
function runTime()
{
return Math.round((new Date).getTime()/1e3)-startTime
}
function calculateLandmarkTimes(e)
{
updateTimeArray(0,10,e),
updateTimeArray(1,20,e)
,updateTimeArray(2,30,e),
updateTimeArray(3,40,e),
updateTimeArray(4,50,e),
updateTimeArray(5,60,e),
updateTimeArray(6,70,e),
updateTimeArray(7,80,e),
updateTimeArray(8,85,e),
updateTimeArray(9,90,e),
updateTimeArray(10,100,e)
}
function updateTimeArray(e,t,o)
{
0==timeToArray[e]&&o>t&&(timeToArray[e]=runTime())
}
function validateDoodle(e){numberOfDoodles+=1,e==doodleGuess?correctDoodles+=1:incorrectDoodles+=1,
getDoodleInputs(doodle),wipeDoodle()
}
function calculateDoodleAccuracy(){doodleAccuracy=correctDoodles/numberOfDoodles*100
}
function calculateDemoAccuracy(e){numberOfDemos>0&&(demoLabel==e&&(numberOfCorrectDemoGuesses+=1),
demoAccuracy=numberOfCorrectDemoGuesses/numberOfDemos*100);
var t="<br> Score : "+demoAccuracy+"<br>";AB.msg(t,htmlPositions[11])
}
function reportDoodleAccuracy()
{
if(numberOfDoodles>0){calculateDoodleAccuracy();
var e="<br> Score : "+doodleAccuracy+"<br>";AB.msg(e,htmlPositions[10])}
}
function validateDoodleHtml()
{return" Validate: <br> <button onclick='validateDoodle(0);' class='normbutton' >0</button> <br> <button onclick='validateDoodle(1);' class='normbutton' >1</button> <br> <button onclick='validateDoodle(2);' class='normbutton' >2</button> <br> <button onclick='validateDoodle(3);' class='normbutton' >3</button> <br> <button onclick='validateDoodle(4);' class='normbutton' >4</button> <br> <button onclick='validateDoodle(5);' class='normbutton' >5</button> <br> <button onclick='validateDoodle(6);' class='normbutton' >6</button> <br> <button onclick='validateDoodle(7);' class='normbutton' >7</button> <br> <button onclick='validateDoodle(8);' class='normbutton' >8</button> <br> <button onclick='validateDoodle(9);' class='normbutton' >9</button> <br> "
}
function makeDemoBlind()
{
numberOfDemos+=1;
var e=AB.randomIntAtoB(0,NOTEST-1);
demo=mnist.test_images[e],demoLabel=mnist.test_labels[e];
let t=getInputs(demo);
demo_inputs=t,
calculateDemoAccuracy(findMax(nn.predict(arrayManipulations(t,!1,!0))))
}
function getDoodleInputs(e)
{
let t=e.get();t.resize(PIXELS,PIXELS),t.loadPixels();
let o=[];
for(let e=0;
e<PIXELSSQUARED;e++)o[e]=t.pixels[4*e]/255;
return doodle_inputs=o,o
}
function testSavedDoodles()
{
var e=0,t=0;AB.restoreData(
function(o)
{
for(var n=0;
n<o.length;n++)
{
var r=o[n];e+=1;
var i=getDoodleAsImage(r.doodle);applyFilters(i),i.resize(PIXELS,PIXELS),i.loadPixels();
for(var a=[],l=0;
l<i.pixels.length;l+=4)a.push(i.pixels[l]/255);find12(nn.predict(a))[0]==r.classification&&(t+=1)
}
var s=" <br> Score : "+(t/e*100).toFixed(2)+" <br> Saved Doodles : "+e;AB.msg(s,htmlPositions[13])})
}
function getRawDoodleInputs(e)
{
let t=e.get();t.loadPixels();
let o=[];
for(let e=0;e<ZOOMPIXELS*ZOOMPIXELS;e++)o[e]=t.pixels[4*e];
return o
}
function convertRawDoodleToPixelsSquared(e)
{
let t=[];
for(let o=0;
o<ZOOMPIXELS*ZOOMPIXELS;o++)t[o]=e[4*o];
return t
}
function getDoodleAsImage(e)
{
let t=createImage(ZOOMPIXELS,ZOOMPIXELS);t.loadPixels();
for(
var o=0;
o<e.length;o++)t.pixels[o]=e[o],
t.pixels[o+1]=e[o],
t.pixels[o+2]=e[o],
t.pixels[o+3]=e[o];
return t.updatePixels(),
t
}
function addDoodleToHistory(e)
{
AB.runloggedin,AB.queryDataExists
(
function(t){t?AB.restoreData(function(t){t.push(e),
AB.saveData(t)}):AB.saveData([e])})
}
function removeRandomPixels(e)
{
for(var t=0;t<e.length;t++)t-1>0&&0!=e[t-1]||t+1<e.length&&0!=e[t+1]||t+28<e.length&&0!=e[t+28]||t-28>0&&0!=e[t-28]||(e[t]=0);
return e}function fillHoles(e){for(var t=0;t<e.length;t++)t-1>0&&0!=e[t-1]||t+1<e.length&&0!=e[t+1]||t+28<e.length&&0!=e[t+28]||t-28>0&&0!=e[t-28]||(e[t]=(e[t-1]+e[t+1]+e[t+28]+e[t-28])/4);
return e}function thickenLines(e){for(var t=0;t<e.length;t++)e[t]&&(t-1>0&&t+1<e.length&&0==e[t-1]&&0==e[t+1]&&(e[t-1]=e[t+1]=e[t]),t+28<e.length&&t-28>0&&0==e[t+28]&&0==e[t-28]&&(e[t-28]=e[t+28]=e[t]));
return e}function shiftToTopLeft(e)
{
for(var t=0;t<e.length;t++)if(0!=e[t]){firstValuedIndex=t;
break}var o=Math.floor(firstValuedIndex/28);if(o>0){e=e.slice(28*o,e.length);
for(t=0;t<28*o;t++)e.push(0)}var n=0,r=999;
for(t=0;t<PIXELS;t++)for(var i=0;i<PIXELS;i++)0!=e[t+i*PIXELS]&&t<r&&(n=t+i*PIXELS,r=t);
if(n>0&&r<999)for(t=0;t<PIXELS;t++)for(i=0;i<PIXELS;i++)i+r>PIXELS-1?(e[t*PIXELS+i-r]=e[t*PIXELS+i],e[t*PIXELS+i]=0):i-r>=0&&(e[t*PIXELS+i-r]=e[t*PIXELS+i]);
return e}function addNoise(e){for(var t=0;t<e.length;t++)0==e[t]&&(e[t]=AB.randomPick(0,Math.round(255*Math.random())));
return e}function rotateImage(e,t){for(var o=[],n=999,r=0;r<PIXELS;r++)for(var i=0;i<PIXELS;i++)ir=r*cos(t)-i*sin(t),jr=r*sin(t)+i*cos(t),ir=Math.round(ir)+100,jr=Math.round(jr)+100,ir<n&&(n=ir),o[ir]?o[ir][jr]=e[r*PIXELS+i]+(o[ir][jr]?o[ir][jr]:0):(o[ir]=[],o[ir][jr]=e[r*PIXELS+i]+0);
var a=o.length-n;
for(r=0;r<a;r++)o[r]=o[n+r];o=o.slice(0,a);
var l=0,s=999;for(r=0;r<o.length;r++)o[r].length>l&&(l=o[r].length);
for(r=0;r<o.length;r++)for(i=l-1;i>0;i--)
void 0!==o[r][i]&&i<s&&(s=i);
for(r=0;r<o.length;r++)
for(i=s;i<l;i++)o[r][i]||(o[r][i]=0);
var d=l-s,u=d-PIXELS;
for(r=0;r<o.length;r++)
{
for(i=0;i<d;i++)o[r][i]=o[r][i+s];
if(o[r]=o[r].slice(Math.floor(u/2),l-Math.floor(u/2)),o[r].length>PIXELS)o[r]=o[r].slice(0,PIXELS);
else if(o[r].length<PIXELS)
for(i=PIXELS-1;i>0;i++)o[r][i]||(o[r][i]=0)}
var m=o.length-PIXELS;
if((o=o.slice(m/2,o.length-m/2)).length<PIXELS)
for(r=28;r>0&&!o[r];i++){o[r]=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}o=o.flat();
for(r=0;r<PIXELS;r++)
for(i=3;i<PIXELS-3;i++)0!=o[r*PIXELS+i]&&((0==o[r*PIXELS+i+1]&&0==o[r*PIXELS+i+2]&&0!=o[r*PIXELS+i+3]||0==o[r*PIXELS+i+1]&&0!=o[r*PIXELS+i+2])&&(o[r*PIXELS+i+1]=o[r*PIXELS+i+2]=(o[r*PIXELS+i+3]+o[r*PIXELS+i])/2),(0==o[r*PIXELS+i-1]&&0==o[r*PIXELS+i-2]&&0!=o[r*PIXELS+i-3]||0==o[r*PIXELS+i-1]&&0!=o[r*PIXELS+i-2])&&(o[r*PIXELS+i+1]=o[r*PIXELS+i+2]=(o[r*PIXELS+i+3]+o[r*PIXELS+i])/2));
return o}function applyDoodleFilters(){applyFilters(doodle)
}
function applyFilters(e){doodle.filter(BLUR,3)
}
function arrayManipulations(e,t,o)
{
return e=shiftToTopLeft(e=removeRandomPixels(e=Array.from(e)))}