Code viewer for World: Doodle recognition, Konw w...
//***********************************************nn.js ****************************************************************************************************

//**********************************rewrite the ActivationFunction() functiong and some activation function  **************************************
var showMatrix = !1;
class ActivationFunction {
    constructor(t, e, i = !1) {
        this.func = t, this.dfunc = e, this.use_X_values = i
    }
}
let sigmoid = new ActivationFunction(t => 1 / (1 + Math.exp(-t)), t => t * (1 - t)),
    tanh = new ActivationFunction(t => Math.tanh(t), t => 1 - t * t),
    arctan = new ActivationFunction(t => Math.atan(t), t => 1 / (t * t + 1), use_X_values = !0),
    softsign = new ActivationFunction(t => t / (1 + Math.abs(t)), t => 1 / Math.pow(Math.abs(t) + 1, 2), use_X_values = !0),
    relu = new ActivationFunction(t => t < 0 ? 0 : t, t => t < 0 ? 0 : 1, use_X_values = !0),
    leaky_relu = new ActivationFunction(t => t < 0 ? .01 * t : t, t => t < 0 ? .01 : 1, use_X_values = !0),
    softplus = new ActivationFunction(t => Math.log(1 + Math.exp(t)), t => 1 / (1 + Math.exp(-t)), use_X_values = !0),
    gaussian = new ActivationFunction(t => Math.exp(t * t * -1), t => -2 * t * Math.exp(t * t * -1), use_X_values = !0);
    
//*************************************************************************************************Yuwei's code  end  *********************************8
    
class NeuralNetwork {
  /*
  * if first argument is a NeuralNetwork the constructor clones it
  * USAGE: cloned_nn = new NeuralNetwork(to_clone_nn);
  */
  constructor(in_nodes, hid_nodes, out_nodes) {
    if (in_nodes instanceof NeuralNetwork) {
      let a = in_nodes;
      this.input_nodes = a.input_nodes;
      this.hidden_nodes = a.hidden_nodes;
      this.output_nodes = a.output_nodes;

      this.weights_ih = a.weights_ih.copy();
      this.weights_ho = a.weights_ho.copy();

      this.bias_h = a.bias_h.copy();
      this.bias_o = a.bias_o.copy();
    } else {
      this.input_nodes = in_nodes;
      this.hidden_nodes = hid_nodes;
      this.output_nodes = out_nodes;

      this.weights_ih = new Matrix(this.hidden_nodes, this.input_nodes);
      this.weights_ho = new Matrix(this.output_nodes, this.hidden_nodes);
      this.weights_ih.randomize();
      this.weights_ho.randomize();

      this.bias_h = new Matrix(this.hidden_nodes, 1);
      this.bias_o = new Matrix(this.output_nodes, 1);
      this.bias_h.randomize();
      this.bias_o.randomize();
    }

    // TODO: copy these as well
    this.setLearningRate();
    this.setActivationFunction();


  }

  predict(input_array) {

    // Generating the Hidden Outputs
    let inputs = Matrix.fromArray(input_array);
    let hidden = Matrix.multiply(this.weights_ih, inputs);
    hidden.add(this.bias_h);
    // activation function!
    hidden.map(this.activation_function.func);

    // Generating the output's output!
    let output = Matrix.multiply(this.weights_ho, hidden);
    output.add(this.bias_o);
    output.map(this.activation_function.func);

    // Sending back to the caller!
    return output.toArray();
  }

  setLearningRate(learning_rate = 0.1) {
    this.learning_rate = learning_rate;
  }
//////////////////////////////
//  setActivationFunction(func = sigmoid) 
	setActivationFunction(func = sigmoid){
    this.activation_function = func;
  }

  train(input_array, target_array) {
    // Generating the Hidden Outputs
    let inputs = Matrix.fromArray(input_array);
    let hidden = Matrix.multiply(this.weights_ih, inputs);
    hidden.add(this.bias_h);
    // activation function!
    hidden.map(this.activation_function.func);

    // Generating the output's output!
    let outputs = Matrix.multiply(this.weights_ho, hidden);
    outputs.add(this.bias_o);
    outputs.map(this.activation_function.func);

    // Convert array to matrix object
    let targets = Matrix.fromArray(target_array);

    // Calculate the error
    // ERROR = TARGETS - OUTPUTS
    let output_errors = Matrix.subtract(targets, outputs);

    // let gradient = outputs * (1 - outputs);
    // Calculate gradient
    let gradients = Matrix.map(outputs, this.activation_function.dfunc);
    gradients.multiply(output_errors);
    gradients.multiply(this.learning_rate);


    // Calculate deltas
    let hidden_T = Matrix.transpose(hidden);
    let weight_ho_deltas = Matrix.multiply(gradients, hidden_T);

    // Adjust the weights by deltas
    this.weights_ho.add(weight_ho_deltas);
    // Adjust the bias by its deltas (which is just the gradients)
    this.bias_o.add(gradients);

    // Calculate the hidden layer errors
    let who_t = Matrix.transpose(this.weights_ho);
    let hidden_errors = Matrix.multiply(who_t, output_errors);

    // Calculate hidden gradient
    let hidden_gradient = Matrix.map(hidden, this.activation_function.dfunc);
    hidden_gradient.multiply(hidden_errors);
    hidden_gradient.multiply(this.learning_rate);

    // Calcuate input->hidden deltas
    let inputs_T = Matrix.transpose(inputs);
    let weight_ih_deltas = Matrix.multiply(hidden_gradient, inputs_T);

    this.weights_ih.add(weight_ih_deltas);
    // Adjust the bias by its deltas (which is just the gradients)
    this.bias_h.add(hidden_gradient);

    // outputs.print();
    // targets.print();
    // error.print();
  }

  serialize() {
    return JSON.stringify(this);
  }

  static deserialize(data) {
    if (typeof data == 'string') {
      data = JSON.parse(data);
    }
    let nn = new NeuralNetwork(data.input_nodes, data.hidden_nodes, data.output_nodes);
    nn.weights_ih = Matrix.deserialize(data.weights_ih);
    nn.weights_ho = Matrix.deserialize(data.weights_ho);
    nn.bias_h = Matrix.deserialize(data.bias_h);
    nn.bias_o = Matrix.deserialize(data.bias_o);
    nn.learning_rate = data.learning_rate;
    return nn;
  }


  // Adding function for neuro-evolution
  copy() {
    return new NeuralNetwork(this);
  }

  // Accept an arbitrary function for mutation
  mutate(func) {
    this.weights_ih.map(func);
    this.weights_ho.map(func);
    this.bias_h.map(func);
    this.bias_o.map(func);
  }

}




/*********************************************************************************************************************************************
I tried to write a multilayer neural network training model in nn.js. I spent a lot of time getting it to work,
but I failed, and I'm chagrined that I don't know what the problem is. It dose nnot work.


Reference websit :https://towardsdatascience.com/the-multi-channel-neural-network-26551bdfab6c

*************************************************************************************************************************************************/
/* Define the class of multilayer neural networks */

class NeuralNetworkMulti {
    constructor(t, e) {
        this.nodes = t, this.lr = e || .01, this.activation = NeuralNetworkMulti.rhlu, this.dactivation = NeuralNetworkMulti.drhlu, this.weights = [], this.biases = [];
        for (let t = 0; t < this.nodes.length - 1; t++) this.weights.push(new Matrix(this.nodes[t + 1], this.nodes[t]).randomize());
        for (let t = 1; t < this.nodes.length; t++) this.biases.push(new Matrix(this.nodes[t], 1).randomize())
    }

    
    /* Static function: derivative function of the activation function*/
	
    static dtanh(t) {
        return 1 / pow(Math.cosh(t), 2)
    }
    static sigmoid(t) {
        return 1 / (1 + Math.exp(-t))
    }
    static dsigmoid(t) {
        return t * (1 - t)
    }
    static rhlu(t) {
        return t < 0 ? 0 : t
    }
    static drhlu(t) {
        return t < 0 ? 0 : 1
    }
     /* Function: Input t, calculated by neural network, return the output */
    predict(t) {
        let e = Matrix.fromArray(t);
        for (let t = 0; t < this.weights.length; t++)(e = Matrix.multiply(this.weights[t], e)).add(this.biases[t]), e.map(this.activation);
        return e.toArray()
    }
   /* Function: Training of the neural network, t is the network input, e is the label ground truth that should correspond to input t. */ train(t, e) {
        let i = Matrix.fromArray(e),  // e is converted into matrix form and assigned to i
            s = Matrix.fromArray(this.predict(t)),// input t, call the prediction function, convert the output value to matrix form, and assign the input to s
            a = [], 
            n = Matrix.fromArray(t); 
        
        for (let t = 0; t < this.weights.length; t++)
        {
            a.push(n), // Put n in list a, list FIFO
            (n = Matrix.multiply(this.weights[t], n)).add(this.biases[t]), // 权重weight x 输入n + 偏置bias, 输出结果重新赋值给n
            n.map(this.activation); // Execute the activation function on n
        }

          /* find the loss function, calculate the error, back propagate to update the weight parameters, gradient */
        let o = Matrix.subtract(i, s),// Find the difference between the network output s and the label i, understood as a loss function
            r = Matrix.map(s, this.dactivation); 
        r.multiply(o), r.multiply(this.lr); 
        for (let t = a.length - 1; t >= 0; t--) { 
            let e = Matrix.multiply(r, Matrix.transpose(a[t]));
            this.weights[t].add(e), this.biases[t].add(r), o = Matrix.multiply(Matrix.transpose(this.weights[t]), o), (r = Matrix.map(a[t], this.dactivation)).multiply(o), r.multiply(this.lr)
        }
    }
    /* Function: Get network model model, return network configuration with network nodes, learning rate, activation function, derivatives of activation function, weights, bias */  getModel() {
        let t = this,
            /*  */
            e = {
                nodes: t.nodes,
                lr: t.lr,
                activation: t.activation,
                dactivation: t.dactivation,
                weights: [],
                biases: []
            };
        /* Define the size (rows and columns) of the weights (itself a matrix), the data content */
        for (let i of t.weights) {
            let t = {
                rows: i.rows, 
                cols: i.cols,
                data: []
            };
            for (let e of i.data) {
                let i = [];
                for (let t of e) i.push(t);
                t.data.push(i)
            }
            e.weights.push(t)
        }
        /* Define the size row and content of bias bias */
        for (let i of t.biases) {
            let t = {
                rows: i.rows,
                cols: i.cols,
                data: i.data
            };
            e.biases.push(t)
        }
        return e
    }
    /* Function: Pass in the network configuration parameters and return the network model Model class */
    static formModel(t) {
        let e = new NeuralNetworkMulti(t.nodes, t.lr);
        e.nodes = t.nodes, e.lr = t.lr, e.activation = t.activation, e.dactivation = t.dactivation;
        for (let i = 0; i < e.weights.length; i++) {
            e.weights[i].rows = t.weights[i].rows, e.weights[i].cols = t.weights[i].cols;
            for (let s = 0; s < t.weights[i].rows; s++)
                for (let a = 0; a < t.weights[i].cols; a++) e.weights[i].data[s][a] = t.weights[i].data[s][a];
            e.weights[i].rows = t.weights[i].rows
        }
        return e
    }
    /* Function: Define model, set model */
    copy() {
        let t = this.getModel();
        return NeuralNetworkMulti.formModel(t)
    }
     /* Perform the operation of function t on weights, biases, */
    mutate(t) {
        for (let e of this.weights)
             e.map(t);
        for (let e of this.biases)
             e.map(t)
    }

   /* Function: Do merge operation on current network weight bias, merge content from t */
    merge(t, e = .5) {
        let i = 1 - e,
            s = e;
        for (let e = 0; e < this.nodes.length; e++)
            if (this.nodes[e] != t.nodes[e]) return void console.error("Neural Networks can not be merged");
        this.lr = this.lr * i + t.lr * s;
        for (let e = 0; e < this.weights.length; e++)
            for (let a = 0; a < this.weights[e].rows; a++)
                for (let n = 0; n < this.weights[e].cols; n++) 
                    this.weights[e].data[a][n] = this.weights[e].data[a][n] * i + t.weights[e].data[a][n] * s;
        for (let e = 0; e < this.biases.length; e++)
            for (let a = 0; a < this.biases[e].rows; a++)
                for (let n = 0; n < this.biases[e].cols; n++) 
                    this.biases[e].data[a][n] = this.biases[e].data[a][n] * i + t.biases[e].data[a][n] * s;
        return this
    }
     /* Set the activation function and the derivative of the activation function */
    setActivation(t, e) {
        this.activation = t, this.dactivation = e
    }
    /* set the learning rate */
    setLearningRate(t) {
        this.lr = t
    }
}

//************************************************************* nn.js code end ******************************************************************************


// --- 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 = 128;
const nohidden = 24;  //change the hidden number 60 to 24
const nooutput = 10;
const learningrate = 0.1;   // default 0.1  

// should we train every timestep or not 
let do_training = true;

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

// 3 rows of
// large image + 50 gap + small image    
// 50 gap between rows 

//const canvaswidth = ( PIXELS + ZOOMPIXELS ) + 50;
//const canvasheight = ( ZOOMPIXELS * 3 ) + 100;
//**********************************************
const canvaswidth = 2 * ZOOMPIXELS + 120;
const canvasheight = 3 * ZOOMPIXELS + 102;
const doodlewidth = PIXELS;
const DOODLE_THICK = 15;
///////*****************************

//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, canvas, dst, src, hierarchy, contours, img, diffX, diffY, M, cvOutput = [];


let trainrun = 1;
let train_index = 0;

let testrun = 1;
let test_index = 0;
let total_tests = 0;
let total_correct = 0;

// images in LHS:
let doodle, demo;
let doodle_exists = false;
let demo_exists = false;

let mousedrag = false;      // are we in the middle of a mouse drag drawing?  

let hidden_no_slider, learning_rate_slider;

// save inputs to global var to inspect
// type these names in console 
var train_inputs, test_inputs, demo_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
}    



// CSS trick 
// make run header bigger 
 $("#runheaderbox").css ( { "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 
  
  // 7 Demo header 
  thehtml = "<hr> <h1> 3. Demo </h1> 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, 7 );
   
  // 8 Demo variable data (random demo ID)
  // 9 Demo variable data (changing guess)
  
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);
  
// // 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()
//  {  //https://ancientbrain.com/uploads/finally/nn.js
//   //$.getScript ( "/uploads/finally/nn.js", function()
   
//         $.getScript ( "/uploads/codingtrain/mnist.js", function()
//         {
//             console.log ("All JS loaded");
//             nn = new NeuralNetwork(  noinput, nohidden, nooutput );
//             nn.setLearningRate ( learningrate );
//             loadData();
//         });
//  });
// }


//*******************************************************Yuwei's code: re-write the setsup function *************************************************************
function setup() {
    
    //define the canvas 
    canvas = createCanvas(canvaswidth, canvasheight);	 
    canvas.position(10, 20);
    canvas.background(51, 51, 51);
    
    
    // doodle on larger canvas 
    doodle = createGraphics(ZOOMPIXELS, ZOOMPIXELS); 
    doodle.pixelDensity(1);     
    wipeDoodle();
    
    
    //Draw a rectangle s the Doodle area
    strokeWeight(3);
    stroke(255,0,0);
    rect(0, 0, ZOOMPIXELS, ZOOMPIXELS);
	textSize(20);
    textAlign(CENTER),
    text("DOODLE AREA", ZOOMPIXELS / 2, ZOOMPIXELS / 2.2);
  
    //image train area
    rect(0, ZOOMPIXELS + 50, ZOOMPIXELS, ZOOMPIXELS);

    //demo area
    rect(0, canvasheight - ZOOMPIXELS - 2, ZOOMPIXELS, ZOOMPIXELS); 
    textSize(16); 
    textAlign(CENTER); 
    text(" DEMO AREA ", 100, canvasheight - ZOOMPIXELS / 1.8);
    
    //rectangle for slider
    
    rect(ZOOMPIXELS + 20, ZOOMPIXELS + 100, ZOOMPIXELS + 90, ZOOMPIXELS + 100);
    text("HIDDEN_NO SLIDER ", ZOOMPIXELS + 120, ZOOMPIXELS + 130);
    hidden_no_slider = createSlider(10, 300, nohidden); 
    hidden_no_slider.position(ZOOMPIXELS + 100, ZOOMPIXELS + 180);
    
    
    //learning rate slider
    text("LEARNING_RATE SLIDER ", ZOOMPIXELS + 120, ZOOMPIXELS + 230);
    learning_rate_slider = createSlider(0.01, 0.2, learningrate); 
    learning_rate_slider.position(ZOOMPIXELS + 100, ZOOMPIXELS + 280);
    
   // console.log(hidden_no_slider.value);
    
    // // JS load other JS 
    // // maybe have a loading screen while loading the JS and the data set 
    AB.loadingScreen(); 
    
    $.getScript("/uploads/finally/opencv.js", function() {     //here I import the opencv API to deal with the doodle picture
        $.getScript("/uploads/codingtrain/matrix.js", function() {
            $.getScript("/uploads/finally/nn.js", function() {
                $.getScript("/uploads/codingtrain/mnist.js", function() {
                    console.log("All JS loaded");
					(nn = new NeuralNetwork(noinput, nohidden, nooutput)).setLearningRate(learningrate);
					loadData();
                })
            })
        })
    })
}

//***************************************************************************************setup function end  ************************************************88

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


function getInputs ( img )      // convert img array into normalised input array 
{
    let inputs = [];
    for (let i = 0; i < PIXELSSQUARED ; i++)          
    {
        let bright = img[i];
        inputs[i] = bright / 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 img   = mnist.train_images[train_index];
  let label = mnist.train_labels[train_index];
  
  // optional - show visual of the image 
  if (show)                
  {
    var theimage = getImage ( 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 inputs = getInputs ( img );       // get inputs from data array 

  // set up the outputs
  let targets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
  targets[label] = 1;       // change one output location to 1, the rest stay at 0 

  // console.log(train_index);
  // console.log(inputs);
  // console.log(targets);

  train_inputs = inputs;        // can inspect in console 
  nn.train ( inputs, targets );

  thehtml = " trainrun: " + trainrun + "<br> no: " + train_index ;
  AB.msg ( thehtml, 4 );

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


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

  // set up the inputs
  let inputs = getInputs ( img ); 
  
  test_inputs = inputs;        // can inspect in console 
  let prediction    = nn.predict(inputs);       // array of outputs 
  let guess         = findMax(prediction);      // the top output 

  total_tests++;
  if (guess == label)  total_correct++;

  let percent = (total_correct / total_tests) * 100 ;
  
  thehtml =  " testrun: " + testrun + "<br> no: " + total_tests + " <br> " +
        " correct: " + total_correct + "<br>" +
        "  score: " + greenspan + percent.toFixed(2) + "</span>";
  AB.msg ( thehtml, 6 );

  test_index++;
  if ( test_index == NOTEST ) 
  {
    console.log( "finished testrun: " + testrun + " score: " + percent.toFixed(2) );
    testrun++;
    test_index = 0;
    total_tests = 0;
    total_correct = 0;
  }
}




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


//****************************************** change the find12 function to output the no.1 , no.1 and no.3 nodes***********************************************

function find123(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;
  
  let no3 = 0;
  let no3value = 0;
  
  for (let i = 0; i < a.length; i++) 
  {
    
    a[i] > no1value ? (no1 = i, no1value = a[i]) : 
    
	a[i] > no2value ? (no2 = i, no2value = a[i]) : 
		
	a[i] > no3value && (no3 = i, no3value = a[i]);
	
  }
  
  var b = [ no1, no2, no3];
  return b;
}

//**************************************************************************find123 function end ******************************************************


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


// // how can we get white doodle on black background on yellow canvas?
// //        background('#ffffcc');    doodle.background('black');

//       background ('black');
    
// if ( do_training )    
// {
//   // do some training per step 
//     for (let i = 0; i < TRAINPERSTEP; i++) 
//     {
//       if (i == 0)    trainit(true);    // show only one per step - still flashes by  
//       else           trainit(false);
//     }
    
//   // do some testing per step 
//     for (let i = 0; i < TESTPERSTEP; i++) 
//       testit();
// }

//   // keep drawing demo and doodle images 
//   // and keep guessing - we will update our guess as time goes on 
  
//   if ( demo_exists )
//   {
//     drawDemo();
//     guessDemo();
//   }
//   if ( doodle_exists ) 
//   {
//     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 
//         doodle_exists = true;
//         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);
//       }
//   }
// }

//**********************************************************Yuwei's code, re-write the draw()function *****************************************************************************8
function draw() {
    
    if (void 0 !== mnist) 
    {
        if (strokeWeight(3),
            stroke(255,0,0),
            rect(0, 0, ZOOMPIXELS, ZOOMPIXELS),
        	textSize(20),
            textAlign(CENTER),
            text("DOODLE AREA", ZOOMPIXELS / 2, ZOOMPIXELS / 2.2), do_training) 
		// do some training per step 
		{
            for (let t = 0; t < TRAINPERSTEP; t++) trainit(0 === t);   // show only one per step - still flashes by  
            for (let t = 0; t < TESTPERSTEP; t++) testit()     // do some testing per step 
        }
        
        
        // detect doodle drawing 
        // (restriction) the following assumes doodle starts at 0,0 
        if (demo_exists && (drawDemo(),
        guessDemo()), doodle_exists && (drawDoodle(), 
        guessDoodle()), mouseIsPressed) 
        
		{
            var MAX = ZOOMPIXELS - 2;
            mouseX < MAX && mouseY < MAX && pmouseX < MAX && pmouseY < MAX && 
            (mousedrag = !0, doodle_exists = !0, doodle.stroke("cream"),
            strokeJoin(BEVEL), doodle.strokeWeight(DOODLE_THICK),
            doodle.line(mouseX, mouseY, pmouseX, pmouseY))
            
        } else if (mousedrag) {
            
            mousedrag = !1,
			console.log("Doodle detected"), 
			(img = doodle.get()).resize(PIXELS, PIXELS), 
			img.loadPixels(), 
			imagedata = img.imageData, 
			src = cv.matFromImageData(imagedata), //create a cv.Mat
			dst = cv.Mat.zeros(src.cols, src.rows, cv.CV_8UC3), //creating a black diagram with 0 for each channel of each pixel,Scalar(0,0,0);
			cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0), //Conversion from RBG color space to grayscale space
			cv.threshold(src, src, 120, 255, cv.THRESH_BINARY), //Image denoising to better snap out numbers
			
			
			contours = new cv.MatVector,  //create a new cv.MatVector and a new cv.Mat
			hierarchy = new cv.Mat, 
			
			cv.findContours(src, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE); // find the contour of the doodle digit
       
            let contour =  contours.get(0), //get  contours
				m = cv.moments(contour, 0); //calculate moments of binary image 
				
				
			//# calculate x,y coordinate of center
			M = m.m00;
            let x = Math.round(m.m10 / m.m00 * 100) / 100,
                y = Math.round(m.m01 / m.m00 * 100) / 100,
                xcen = PIXELS / 2,
                ycen = PIXELS / 2;
				
            diffX = Math.round(xcen - x), 
			diffY = Math.round(ycen - y);
			
            let r = cv.matFromArray(2, 3, cv.CV_64FC1, [1, 0, diffX, 0, 1, diffY]);  //a new cv.Mat
			
            return dsize = new cv.Size(src.rows, src.cols), //reset image size,Zoom in on the digit
			
			//Imitation of the image using the transformation matrix dst
			cv.warpAffine(src, dst, r, dsize, cv.INTER_LINEAR,
			cv.BORDER_CONSTANT, new cv.Scalar),
			
			//output final doodle digit picture
			cvOutput = getInputs(dst.data8S), 
			image(img, ZOOMPIXELS + 120 + diffX * ZOOMFACTOR, 0 + diffY * ZOOMFACTOR, ZOOMPIXELS, ZOOMPIXELS), 
			cvOutput
        }
    }
}
//**********************************************************draw() function end*****************************************************************************8



//--- demo -------------------------------------------------------------
// demo some test image and predict it
// get it from test set so have not used it in training


function makeDemo()
{
    demo_exists = true;
    var  i = AB.randomIntAtoB ( 0, NOTEST - 1 );  
    
    demo        = mnist.test_images[i];     
    var label   = mnist.test_labels[i];
    
   thehtml =  "Test image no: " + i + "<br>" + 
            "Classification: " + label + "<br>" ;
   AB.msg ( thehtml, 8 );
   
   // type "demo" in console to see raw data 
}


function drawDemo()
{
    var theimage = getImage ( demo );
     //  console.log (theimage);
     
    image ( theimage,   0,                canvasheight - ZOOMPIXELS,    ZOOMPIXELS,     ZOOMPIXELS  );      // magnified 
    image ( theimage,   ZOOMPIXELS+50,    canvasheight - ZOOMPIXELS,    PIXELS,         PIXELS      );      // original
}


function guessDemo()
{
   let inputs = getInputs ( demo ); 
   
  demo_inputs = inputs;  // can inspect in console 
  
  let prediction    = nn.predict(inputs);       // array of outputs 
  let guess         = findMax(prediction);      // the top output 

   thehtml =   " We classify it as: " + greenspan + guess + "</span>" ;
   AB.msg ( thehtml, 9 );
}




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

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

function guessDoodle() {
    (img = doodle.get()).resize(PIXELS, PIXELS), img.loadPixels();
    let t = [];
    for (let e = 0; e < PIXELSSQUARED; e++) t[e] = img.pixels[4 * e] / 255;
    let e = Array.from(t);
    for (let t = 0; t < e.length; t++) e[t] = cvOutput[t];
    t = Array.from(e);
    for (let e = 0; e < t.length; e++) t[e] = -255 * t[e];
    doodle_inputs = t;
    let n = find123(nn.predict(t));
    thehtml = " We classify it as: " + greenspan + n[0] + "</span> <br> No.2 guess is: " + greenspan + n[1] + "</span> <br>No.3 guess is: " + greenspan + n[2] + "</span>", AB.msg(thehtml, 2), 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);
}