constructNNFromJSON = (nnJSON, inputImageArray) => {
  console.log(nnJSON);
  console.log(inputImageArray);
  let nn = [];

  // Add the first layer (input layer)
  let inputLayer = [];
  let inputShape = nnJSON[0].input_shape;

  // First layer's three nodes' outputs are the channels of inputImageArray
  for (let i = 0; i < inputShape[2]; i++) {
    let node = new Node('input', i, nodeType.INPUT, 0, inputImageArray[i]);
    inputLayer.push(node);
  }
                                                                                                                   
  nn.push(inputLayer);
  let curLayerIndex = 1;

  nnJSON.forEach(layer => {
    let curLayerNodes = [];
    let curLayerType;

    if (layer.name.includes('conv')) {
      curLayerType = nodeType.CONV;
    } else if (layer.name.includes('pool')) {
      curLayerType = nodeType.POOL;
    } else if (layer.name.includes('relu')) {
      curLayerType = nodeType.RELU;
    } else if (layer.name.includes('output')) {
      curLayerType = nodeType.FC;
    } else if (layer.name.includes('flatten')) {
      curLayerType = nodeType.FLATTEN;
    } else {
      console.log('Find unknown type');
    }

    let shape = layer.output_shape.slice(0, 2);
    let bias = 0;
    let output;
    if (curLayerType === nodeType.FLATTEN || curLayerType === nodeType.FC) {
      output = 0;
    } else {
      output = init2DArray(shape[0], shape[1], 0);
    }

    // Add neurons into this layer
    for (let i = 0; i < layer.num_neurons; i++) {
      if (curLayerType === nodeType.CONV || curLayerType === nodeType.FC) {
        bias = layer.weights[i].bias;
      }
      let node = new Node(layer.name, i, curLayerType, bias, output)

      // Connect this node to all previous nodes (create links)
      if (curLayerType === nodeType.CONV || curLayerType === nodeType.FC) {
        // CONV and FC layers have weights in links. Links are one-to-multiple
        for (let j = 0; j < nn[curLayerIndex - 1].length; j++) {
          let preNode = nn[curLayerIndex - 1][j];
          let curLink = new Link(preNode, node, layer.weights[i].weights[j]);
          preNode.outputLinks.push(curLink);
          node.inputLinks.push(curLink);
        }
      } else if (curLayerType === nodeType.RELU || curLayerType === nodeType.POOL) {
        // RELU and POOL layers have no weights. Links are one-to-one
        let preNode = nn[curLayerIndex - 1][i];
        let link = new Link(preNode, node, null);
        preNode.outputLinks.push(link);
        node.inputLinks.push(link);
      } else if (curLayerType === nodeType.FLATTEN) {
        // Flatten layer has no weights. Links are multiple-to-one.
        // Use dummy weights to store the corresponding entry in the previsou
        // node as (row, column)
        // The flatten() in tf2.keras has order: channel -> row -> column
        let preNodeWidth = nn[curLayerIndex - 1][0].output.length,
          preNodeNum = nn[curLayerIndex - 1].length,
          preNodeIndex = i % preNodeNum,
          preNodeRow = Math.floor(Math.floor(i / preNodeNum) / preNodeWidth),
          preNodeCol = Math.floor(i / preNodeNum) % preNodeWidth,
          link = new Link(nn[curLayerIndex - 1][preNodeIndex],
            node, [preNodeRow, preNodeCol]);

        nn[curLayerIndex - 1][preNodeIndex].outputLinks.push(link);
        node.inputLinks.push(link);
      }
      curLayerNodes.push(node);
    }

    // Add current layer to the NN
    nn.push(curLayerNodes);
    curLayerIndex++;
  });

  return nn;
}
Class structure for each neuron node.

@param {string} layerName Name of the node's layer.
@param {int} index Index of this node in its layer.
@param {string} type Node type {input, conv, pool, relu, fc}. 
@param {number} bias The bias assocated to this node.
@param {[[number]]} output Output of this node.