Source: common/Example.js

import rapidMixAdapters from 'rapid-mix-adapters';

// source : https://stackoverflow.com/questions/15251879/how-to-check-if-a-variable-is-a-typed-array-in-javascript
const isArray = v => {
  return v.constructor === Float32Array ||
         v.constructor === Float64Array ||
         Array.isArray(v);
};

/**
 * Class that represents a training example (e.g. time serie of vectors
 * that represents a gesture).
 * If no parameters are given, the dimensions will be guessed from the first
 * added element after instantiation of the class and after each call to clear.
 * If parameters are given, they will be used to strictly check any new element,
 * anytime.
 *
 * @param {Number} [inputDimension=null] - If defined, definitive input dimension
 *    that will be checked to validate any new element added.
 * @param {Number} [outputDimension=null] - If defined, definitive output dimension
 *    that will be checked to validate any new element added.
 *
 * @example
 * import * as mano from 'mano-js/client';
 *
 * const example = new mano.Example();
 * const trainingSet = new mano.TrainingSet();
 *
 * example.setLabel('test');
 * example.addElement([0, 1, 2, 3]);
 * const rapidMixJsonExample = example.toJSON();
 *
 * trainingSet.addExample(rapidMixJsonExample);
 */
class Example {
  constructor(inputDimension = null, outputDimension = null) {
    if (inputDimension !== null) {
      this.fixedDimensions = true;
      this.inputDimension = inputDimension;
      this.outputDimension = outputDimension !== null ? outputDimension : 0;
    } else {
      this.fixedDimensions = false;
    }

    this.label = rapidMixAdapters.RAPID_MIX_DEFAULT_LABEL;
    this.clear();

    this.addElement = this.addElement.bind(this);
  }

  /**
   * Clear the internal variables so that we are ready to record a new example.
   */
  clear() {
    if (!this.fixedDimensions) {
      this.inputDimension = null;
      this.outputDimension = null;
    }

    this.input = [];
    this.output = [];
  }

  /**
   * Set the example's current label.
   *
   * @param {String} label - The new label to assign to the class.
   */
  setLabel(label) {
    this.label = label;
  }

  /**
   * Add an element to the current example.
   *
   * @param {Array.Number|Float32Array|Float64Array} inputVector - The input
   * part of the element to add.
   * @param {Array.Number|Float32Array|Float64Array} [outputVector=null] - The
   * output part of the element to add.
   *
   * @throws An error if inputVector or outputVector dimensions mismatch.
   */
  addElement(inputVector, outputVector = null) {
    this._validateInputAndUpdateDimensions(inputVector, outputVector);

    if (inputVector instanceof Float32Array ||
        inputVector instanceof Float64Array)
      inputVector = Array.from(inputVector);

    if (outputVector instanceof Float32Array ||
        outputVector instanceof Float64Array)
      outputVector = Array.from(outputVector);

    this.input.push(inputVector);

    if (this.outputDimension > 0)
      this.output.push(outputVector);
  }

  /**
   * Get the example in rapid-mix JSON format.
   *
   * @returns {Object} A rapid-mix compliant example object.
   */
  toJSON() {
    return {
      docType: 'rapid-mix:ml-example',
      docVersion: rapidMixAdapters.RAPID_MIX_DOC_VERSION,
      payload: {
        label: this.label,
        // inputDimension: this.inputDimension,
        // outputDimension: this.outputDimension,
        input: this.input.slice(0),
        output: this.output.slice(0),
      }
    };
  }

  /** @private */
  _validateInputAndUpdateDimensions(inputVector, outputVector) {
    if (!isArray(inputVector) || (outputVector && !isArray(outputVector))) {
      throw new Error('inputVector and outputVector must be arrays');
    }

    if (!this.inputDimension || !this.outputDimension) {
      this.inputDimension = inputVector.length;
      this.outputDimension = outputVector ? outputVector.length : 0;
      // this._empty = false;
    } else if (inputVector.length != this.inputDimension ||
              outputVector.length != this.outputDimension) {
      throw new Error('dimensions mismatch');
    }
  }
}

export default Example;