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;

// number of training and test exemplars in the data set:
const NOTRAIN = 60000;
const NOTEST  = 10000;


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

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

// should we train every timestep or not 
let do_training = true;
let doodle_exists = true;// code by meenu //to load doodle canvas when the screen loaded
// how many to train and test per timestep 
const TRAINPERSTEP = 30;
const TESTPERSTEP  = 5;



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


let mnist,nn, conv_net, conv_trainer, doodle;//code by Meenu


let trainrun = 1;
let train_index = 0;

let testrun = 1;
let test_index = 0;
let total_tests = 0;
let ts_total_correct = 0;
let conv_total_tests = 0;//code by Meenu
let conv_ts_total_correct = 0;//code by Meenu

// save inputs to global var to inspect
// type these names in console 
var train_inputs, test_inputs, doodle_inputs;

// images in LHS:
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 train_inputs, test_inputs, 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>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> ";
   AB.msg ( thehtml, 1 );
  // 2 Doodle variable data (guess)
  
  // 3 Training header
  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, 3 );
  
   // 4 variable training data 
  
  // 5 Testing header
  thehtml = "<h3> Hidden tests </h3> " ;
  AB.msg ( thehtml, 5 );
           
  // 6 variable testing data 
  
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:4, 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:4, 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:12, l2_decay:0.01});    
 });
}

// 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 convoultional 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;
}

function getInputs( img )      // convert img array into normalised input array 
{
    let inputs = [];
    for (let i = 0; i < PIXELSSQUARED ; i++)          
    {
        inputs[i] = img[i]/ 255;       // normalise to 0 to 1
    } 
    return inputs;
}


 

function trainit (show)        // train the network with a single exemplar, from global var "train_index", show visual on or off 
{
  let tr_img   = mnist.train_images[train_index];
  let tr_label = mnist.train_labels[train_index];
  
  // optional - show visual of the image 
  if (show)                
  {
    var theimage = getImage(tr_img);    // get image from data array 
    image ( theimage,   0,                ZOOMPIXELS+50,    ZOOMPIXELS,     ZOOMPIXELS  );      // magnified 
    image ( theimage,   ZOOMPIXELS+50,    ZOOMPIXELS+50,    PIXELS,         PIXELS      );      // original
  }


  // set up the inputs
  let tr_inputs      = getInputs(tr_img);       // get inputs from data array 
  let tr_conv_inputs = getConvInputs(tr_inputs);       // get inputs for convulational network
  let tr_targets     = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];  // set up the outputs
  tr_targets[tr_label] = 1;       // change one output location to 1, the rest stay at 0 

  nn.train ( tr_inputs, tr_targets );
  conv_trainer.train(tr_conv_inputs,tr_label);//code by Meenu
	
  
  thehtml = " Train run: " + trainrun + "<br> no: " + train_index ;
  AB.msg ( thehtml, 4 );

  train_index++;
  
  if (train_index == NOTRAIN ) 
  {
    train_index = 0;
    console.log( "Finished Train run: " + trainrun );
    trainrun++;
  }
 
  
}

function testit()    // test the network with a single exemplar, from global var "test_index"
{ 
  let ts_img   = mnist.test_images[test_index];
  let ts_label = mnist.test_labels[test_index];

  // set up the inputs
  let ts_inputs = getInputs ( ts_img ); 
  //test_inputs = inputs;        // can inspect in console 

  // finding neural network guess
  let ts_nn_prediction    = nn.predict(ts_inputs);       // array of outputs 
  let ts_nn_guess         = findMax(ts_nn_prediction);       // the top output


  //code by Meenu
  // finding covolutional neural network guess based on Andrej Karpathy's ConvnetJS 
  let ts_conv_inputs = getConvInputs(ts_inputs);//inputs for conv nw
  let ts_conv_prediction    = conv_net.forward(ts_conv_inputs).w;       // array of outputs 
  let ts_conv_guess         = findMax(ts_conv_prediction);       // the top output  


  total_tests++;// calculate no of test performed 
  
  if (ts_nn_guess == ts_label)  ts_total_correct++;//to find the total no of correct inputs obtained by NN
  let percent = (ts_total_correct / total_tests) * 100 ;//find percentage of correct predicted output

  //code by Meenu
  conv_total_tests++;
  if (ts_conv_guess == ts_label)  conv_ts_total_correct++;//to find the total no of correct inputs obtained by NN
  let conv_percent = (conv_ts_total_correct / conv_total_tests) * 100 ;//find percentage of correct predicted output

//code by Meenu
  thehtml =  " Test run: " + testrun + "<br> No: " + total_tests + 
        " <br/> Neural N/w Correct: " + ts_total_correct + " Conv N/w Correct: " + conv_ts_total_correct + 
        " <br/> Neural N/w  Score( %): " + greenspan + percent.toFixed(2) + "</span> Conv N/w Score(%): " + greenspan + conv_percent.toFixed(2) + "</span>";
       
  AB.msg ( thehtml, 6 );
  

  test_index++;
  if ( test_index == NOTEST ) 
  {
    console.log( "Finished testrun: " + testrun + " Neural N/w  Score: " + percent.toFixed(2) +"Conv N/w Score:" + conv_percent.toFixed(2));
    testrun++;
    test_index = 0;
    total_tests = 0;
    ts_total_correct = 0;
    conv_ts_total_correct=0;  //code by Meenu
  }
 

}



//--- 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;
}



// just get the maximum - separate function for speed - done many times 
// find our guess - the max of the output nodes array

function findMax (a)        
{
  let no1 = 0;
  let no1value = 0;     
  
  for (let i = 0; i < a.length; i++) 
  {
    if (a[i] > no1value) 
    {
      no1 = i;
      no1value = a[i];
    }
  }
  
  return no1;
}

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

    if ( do_training )    
    {
        // code by meenu 
       //set bg as red
        background ('red');
    
       // do some training per step 
        for (let j = 0; j < TRAINPERSTEP; j++) 
        {
          if (j == 0)    trainit(true);    // show only one per step - still flashes by  
          else           trainit(false);
        }
       
        // do some testing per step 
       for (let j = 0; j < TESTPERSTEP; j++) 
         testit();
       
    }
  // -------------------------doodle images ------------------------------------
    
    if(doodle_exists){
        
        drawDoodle();
        //code by Meenu
        line(0, ZOOMPIXELS+12,  canvaswidth, ZOOMPIXELS+12);  //draw horizonatal to show seperation of each canvas
        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 
        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 doodle_image = doodle.get();
   // console.log (doodle_image);
    image ( doodle_image,   0,                0,    ZOOMPIXELS,     ZOOMPIXELS  );      // original image
    image ( doodle_image,   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 into the pixels[] array

  // 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_img = new convnetjs.Vol(PIXELS, PIXELS, 1, 0);
    //console.log(conv_img);
    for (let i = 0; i < PIXELSSQUARED ; i++)  {
      conv_img.w[i] = inputs[i];
    }
 let conv_inputs = conv_img;
 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 

 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.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);
}