Code viewer for World: Character Recognition -Neu...
                                        /*Student Name: Meenu Mathew
                                        Student Number: 20211335
                                        Assignment 2: Character Recognition -Neural Network*/



// --- defined by MNIST - do not change these ---------------------------------------

const PIXELS        = 28;                       // images in data set are tiny 
const PIXELSSQUARED = PIXELS * PIXELS;

//--- can modify all these --------------------------------------------------

// no of nodes in network 
const noinput  = PIXELSSQUARED;
const nohidden = 64;
const nooutput = 10;
const learningrate = 0.001;   // default 0.1  code by meenu

// multiply it by this to magnify for display 
const ZOOMFACTOR    = 7;                        
const ZOOMPIXELS    = ZOOMFACTOR * PIXELS; 
const canvaswidth = ( PIXELS + ZOOMPIXELS )+ 200;
const canvasheight = ( PIXELS + ZOOMPIXELS ) + 110;
const DOODLE_THICK = 18;    // thickness of doodle lines 
const DOODLE_BLUR = 3;      // blur factor applied to doodles 


let mnist;      
// all data is loaded into this 
// mnist.train_images
// mnist.train_labels
// mnist.test_images
// mnist.test_labels
let nn;
// images in LHS:
let doodle;
let mousedrag = false;      // are we in the middle of a mouse drag drawing?  
// save inputs to global var to inspect
// type these names in console 
var doodle_inputs;
// Matrix.randomize() is changed to point to this. Must be defined by user of Matrix. 
function randomWeight()
{
    return ( AB.randomFloatAtoB ( -0.5, 0.5 ) );
            // Coding Train default is -1 to 1
}    


// make run header bigger
AB.headerCSS ( { "max-height": "95vh" } );

//--- start of AB.msgs structure: ---------------------------------------------------------
// We output a serious of AB.msgs to put data at various places in the run header 
var thehtml;

  // 1 Doodle header 
  thehtml = "<hr> <h1> 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> ";
   AB.msg ( thehtml, 1 );
  
const greenspan = "<span style='font-weight:bold; font-size:x-large; color:darkgreen'> "  ;

//--- end of AB.msgs structure: ---------------------------------------------------------

function setup() 
{
  createCanvas ( canvaswidth, canvasheight );

  doodle = createGraphics ( ZOOMPIXELS, ZOOMPIXELS );       // doodle on larger canvas 
  doodle.pixelDensity(1);
  doodle.background("black");
// JS load other JS 
// maybe have a loading screen while loading the JS and the data set 

      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 );
            nn.setLearningRate ( learningrate );
            loadData();
        });
   });
 });
 //code by meenu
 // import convnetjs library 
 //The scrips are using and are based on Andrej Karpathy's ConvnetJS
 $.getScript ( "/uploads/meenumathew/convnet-min.js", function()
 {
    conv_layer_defs = [];
    conv_layer_defs.push({type:'input', out_sx:28, out_sy:28, out_depth:1});
    conv_layer_defs.push({type:'conv', sx:3, filters:16, stride:1, pad:2, activation:'relu'});
    conv_layer_defs.push({type:'pool', sx:3, stride:1});
    conv_layer_defs.push({type:'conv', sx:3, filters:16, stride:1, pad:2, activation:'relu'});
    conv_layer_defs.push({type:'pool', sx:3, stride:1});
    conv_layer_defs.push({type:'conv', sx:3, filters:16, stride:1, pad:2, activation:'relu'});
    conv_layer_defs.push({type:'pool', sx:3, stride:1});
    conv_layer_defs.push({type:'conv', sx:3, filters:16, stride:1, pad:2, activation:'relu'});
    conv_layer_defs.push({type:'pool', sx:3, stride:1});
    conv_layer_defs.push({type:'conv', sx:3, filters:16, stride:1, pad:2, activation:'relu'});
    conv_layer_defs.push({type:'pool', sx:3, stride:1});
    conv_layer_defs.push({type:'softmax', num_classes:10});
    conv_net = new convnetjs.Net();
    conv_net.makeLayers(conv_layer_defs);
    conv_trainer = new convnetjs.SGDTrainer(conv_net, {method:'adadelta', batch_size:20, l2_decay:0.001});    
 });
}





// load data set from local file (on this server)

function loadData()    
{
  loadMNIST ( function(data)    
  {
    mnist = data;
    console.log ("All data loaded into mnist object:")
    console.log(mnist);
    AB.removeLoading();     // if no loading screen exists, this does nothing 
  });
}



function getImage ( img )      // make a P5 image object from a raw data array   
{
    let theimage  = createImage (PIXELS, PIXELS);    // make blank image, then populate it 
    theimage.loadPixels();      
 
    for (let i = 0; i < PIXELSSQUARED ; i++) 
    {
        let bright = img[i];
        let index = i * 4;
        theimage.pixels[index + 0] = bright;
        theimage.pixels[index + 1] = bright;
        theimage.pixels[index + 2] = bright;
        theimage.pixels[index + 3] = 255;
    }
    
    theimage.updatePixels();
    return theimage;
}


//code by Meenu
// convert img array suitable for convolutional network 
function getConvInputs(img) {
    let conv_img = new convnetjs.Vol(PIXELS, PIXELS, 1, 0);
    //console.log(conv_img);
    for (let i = 0; i < PIXELSSQUARED ; i++)  {
      conv_img.w[i] = img[i];
    }
    return conv_img;
}

//--- find no.1 (and maybe no.2) output nodes ---------------------------------------
// (restriction) assumes array values start at 0 (which is true for output nodes) 


function find12 (a)         // return array showing indexes of no.1 and no.2 values in array 
{
  let no1 = 0;
  let no2 = 0;
  let no1value = 0;     
  let no2value = 0;
  
  for (let i = 0; i < a.length; i++) 
  {
    if (a[i] > no1value)   // new no1
    {
      // old no1 becomes no2
      no2 = no1;
      no2value = no1value;
      // now put in the new no1
      no1 = i;
      no1value = a[i];
    }
    else if (a[i] > no2value)  // new no2 
    {
      no2 = i;
      no2value = a[i];
    }
  }
  
  var b = [ no1, no2 ];
  return b;
}

// --- the draw function -------------------------------------------------------------
// every step:
 
function draw() 
{
  // check if libraries and data loaded yet:
  if ( typeof mnist == 'undefined' ) return;


/*how can we get white doodle on black background on yellow canvas?
answer: set backgroud  color as yellow as follows:
background ('yellow'); 
set bg as red*/
background ('red');// code by meenu
drawDoodle();
guessDoodle();

// detect doodle drawing 
// (restriction) the following assumes doodle starts at 0,0 

  if ( mouseIsPressed )         // gets called when we click buttons, as well as if in doodle corner  
  {
     // console.log ( mouseX + " " + mouseY + " " + pmouseX + " " + pmouseY );
     var MAX = ZOOMPIXELS + 20;     // can draw up to this pixels in corner 
     if ( (mouseX < MAX) && (mouseY < MAX) && (pmouseX < MAX) && (pmouseY < MAX) )
     {
        mousedrag = true;       // start a mouse drag 
       //set doodle letter color as white
        doodle.stroke('white');
        doodle.strokeWeight( DOODLE_THICK );
        doodle.line(mouseX, mouseY, pmouseX, pmouseY);    
        
     }
  }
  else 
  {
      // are we exiting a drawing
      if ( mousedrag )
      {
            mousedrag = false;
            // console.log ("Exiting draw. Now blurring.");
            doodle.filter (BLUR, DOODLE_BLUR);    // just blur once 
            //   console.log (doodle);
             
      }
  }
}


//--- doodle -------------------------------------------------------------

function drawDoodle()
{
    // doodle is createGraphics not createImage
    let theimage = doodle.get();
    console.log (theimage);
    
    image ( theimage,   0,                0,    ZOOMPIXELS,     ZOOMPIXELS  );      // original image
    image ( theimage,   ZOOMPIXELS -50 ,    0,    PIXELS,         PIXELS      );      // shrunk

   
}
      
 function guessDoodle() 
{
   // doodle is createGraphics not createImage
  let img = doodle.get();
   
  img.resize ( PIXELS, PIXELS );     
  img.loadPixels();//Loads the pixel data for the display window as in array format into img 

  // set up inputs  
  //normalising inputs array
  let inputs = [];
  for (let i = 0; i < PIXELSSQUARED ; i++) 
  {
     inputs[i] = img.pixels[i * 4] / 255;
  }
// finding neural network guess
  let nn_prediction    = nn.predict(inputs);       // array of outputs 
  let nn_guess         = find12(nn_prediction);       // get no.1 and no.2 guesses  
  
//code by meenu
// finding covolutional neural network guess based on Andrej Karpathy's ConvnetJS 
 let conv_inputs = getConvInputs(inputs);//get normalised inputs
 let conv_prediction    = conv_net.forward(conv_inputs).w;       // array of outputs 
 let conv_guess         = find12(conv_prediction);       // get no.1 and no.2 guesses  

 
//code by meenu
 
  thehtml =   " Neural Network classify it as: " + greenspan + nn_guess[0] + "</span> <br>" +
              " No.2 guess is: " + greenspan + nn_guess[1] + "</span>"+
              " <br/>Convolutional Neural Network(based on Andrej Karpathy's ConvnetJS) classify it as: " + greenspan + conv_guess[0] + "</span> <br>" +
              " No.2 guess is: " + greenspan + conv_guess[1] + "</span>";
  AB.msg ( thehtml, 2 );
}

function wipeDoodle()    
{
    doodle_exists = false;
    doodle.background('black');
}


// --- debugging --------------------------------------------------
// in console
// showInputs(demo_inputs);
// showInputs(doodle_inputs);


function showInputs ( inputs )
// display inputs row by row, corresponding to square of pixels 
{
    var str = "";
    for (let i = 0; i < inputs.length; i++) 
    {
      if ( i % PIXELS == 0 )    str = str + "\n";                                   // new line for each row of pixels 
      var value = inputs[i];
      str = str + " " + value.toFixed(2) ; 
    }
    console.log (str);
}