From 8c4497ffc9ca765a3aaf084d7c096239c89554f0 Mon Sep 17 00:00:00 2001
From: shiffman <daniel@shiffman.net>
Date: Tue, 22 Oct 2019 16:12:27 -0400
Subject: [PATCH 1/3] only lowercase if not a URL

---
 src/ImageClassifier/index.js | 78 ++++++++++++++++++------------------
 1 file changed, 39 insertions(+), 39 deletions(-)

diff --git a/src/ImageClassifier/index.js b/src/ImageClassifier/index.js
index 1359ca68b..8cf509b74 100644
--- a/src/ImageClassifier/index.js
+++ b/src/ImageClassifier/index.js
@@ -76,46 +76,46 @@ class ImageClassifier {
    */
   async loadModel(modelUrl) {
     if (modelUrl) this.model = await this.loadModelFrom(modelUrl);
-    else this.model = await this.modelToUse.load({version: this.version, alpha: this.alpha});
+    else this.model = await this.modelToUse.load({ version: this.version, alpha: this.alpha });
 
     return this;
   }
 
   async loadModelFrom(path = null) {
     fetch(path)
-    .then(r => r.json())
-    .then((r) => {
-      if (r.ml5Specs) {
-        this.mapStringToIndex = r.ml5Specs.mapStringToIndex;
-      }
-    })
-    // When loading model generated by Teachable Machine 2.0, the r.ml5Specs is missing,
-    // which is causing imageClassifier failing to display lables.
-    // In this case, labels are stored in path/./metadata.json
-    // Therefore, I'm fetching the metadata and feeding the labels into this.mapStringToIndex
-    // by Yang Yang, yy2473@nyu.edu, Oct 2, 2019
-    .then(()=>{
-      if (this.mapStringToIndex.length === 0) {
-        const split = path.split("/");
-        const prefix = split.slice(0, split.length - 1).join("/");
-        const metadataUrl = `${prefix}/metadata.json`;
-        fetch(metadataUrl)
-        .then((res) => {
-          if (!res.ok) {
-            console.log("Tried to fetch metadata.json, but it seems to be missing.");
-            throw Error(res.statusText);
-          }
-          return res;
-        })
-        .then(metadataJson => metadataJson.json())
-        .then((metadataJson)=> {
-          if (metadataJson.labels){
-            this.mapStringToIndex = metadataJson.labels;
-          }
-        })
-        .catch(() => console.log("Error when loading metadata.json"));
-      }
-    });
+      .then(r => r.json())
+      .then((r) => {
+        if (r.ml5Specs) {
+          this.mapStringToIndex = r.ml5Specs.mapStringToIndex;
+        }
+      })
+      // When loading model generated by Teachable Machine 2.0, the r.ml5Specs is missing,
+      // which is causing imageClassifier failing to display lables.
+      // In this case, labels are stored in path/./metadata.json
+      // Therefore, I'm fetching the metadata and feeding the labels into this.mapStringToIndex
+      // by Yang Yang, yy2473@nyu.edu, Oct 2, 2019
+      .then(() => {
+        if (this.mapStringToIndex.length === 0) {
+          const split = path.split("/");
+          const prefix = split.slice(0, split.length - 1).join("/");
+          const metadataUrl = `${prefix}/metadata.json`;
+          fetch(metadataUrl)
+            .then((res) => {
+              if (!res.ok) {
+                console.log("Tried to fetch metadata.json, but it seems to be missing.");
+                throw Error(res.statusText);
+              }
+              return res;
+            })
+            .then(metadataJson => metadataJson.json())
+            .then((metadataJson) => {
+              if (metadataJson.labels) {
+                this.mapStringToIndex = metadataJson.labels;
+              }
+            })
+            .catch(() => console.log("Error when loading metadata.json"));
+        }
+      });
     // end of the Oct 2, 2019 fix
     this.model = await tf.loadLayersModel(path);
     return this.model;
@@ -206,7 +206,7 @@ class ImageClassifier {
         || inputNumOrCallback.elt instanceof HTMLCanvasElement
         || inputNumOrCallback.elt instanceof ImageData)
     ) {
-        imgToPredict = inputNumOrCallback.elt; // Handle p5.js image
+      imgToPredict = inputNumOrCallback.elt; // Handle p5.js image
     } else if (typeof inputNumOrCallback === 'object' && inputNumOrCallback.canvas instanceof HTMLCanvasElement) {
       imgToPredict = inputNumOrCallback.canvas; // Handle p5.js image
     } else if (!(this.video instanceof HTMLVideoElement)) {
@@ -242,15 +242,15 @@ class ImageClassifier {
 }
 
 const imageClassifier = (modelName, videoOrOptionsOrCallback, optionsOrCallback, cb) => {
-  let model;
   let video;
   let options = {};
   let callback = cb;
 
-  if (typeof modelName === 'string') {
-    model = modelName.toLowerCase();
-  } else {
+  let model = modelName;
+  if (typeof model !== 'string') {
     throw new Error('Please specify a model to use. E.g: "MobileNet"');
+  } else if (model.indexOf('http') === -1) {
+    model = modelName.toLowerCase();
   }
 
   if (videoOrOptionsOrCallback instanceof HTMLVideoElement) {

From b9535e78868f5c58cc3c8d6deca02e758dc9e868 Mon Sep 17 00:00:00 2001
From: shiffman <daniel@shiffman.net>
Date: Tue, 22 Oct 2019 16:57:38 -0400
Subject: [PATCH 2/3] same fix for sound classifier

---
 src/SoundClassifier/index.js | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/SoundClassifier/index.js b/src/SoundClassifier/index.js
index e9a0a5334..b42449f34 100644
--- a/src/SoundClassifier/index.js
+++ b/src/SoundClassifier/index.js
@@ -59,7 +59,7 @@ class SoundClassifier {
 
   /**
    * Classifies the audio from microphone and takes a callback to handle the results
-   * @param {function | number} numOrCallback - 
+   * @param {function | number} numOrCallback -
    *    takes any of the following params
    * @param {function} cb - a callback function that handles the results of the function.
    * @return {function} a promise or the results of a given callback, cb.
@@ -82,14 +82,14 @@ class SoundClassifier {
 }
 
 const soundClassifier = (modelName, optionsOrCallback, cb) => {
-  let model;
   let options = {};
   let callback = cb;
 
-  if (typeof modelName === 'string') {
-    model = modelName.toLowerCase();
-  } else {
+  let model = modelName;
+  if (typeof model !== 'string') {
     throw new Error('Please specify a model to use. E.g: "SpeechCommands18w"');
+  } else if (model.indexOf('http') === -1) {
+    model = modelName.toLowerCase();
   }
 
   if (typeof optionsOrCallback === 'object') {

From 36c0c428706c037da7ddd9721cbe59a53e32121a Mon Sep 17 00:00:00 2001
From: joeyklee <jsphknglee@gmail.com>
Date: Wed, 23 Oct 2019 17:38:25 -0400
Subject: [PATCH 3/3] only call modelInstantiation with beforeAll() rather than
 beforeEach()

---
 CONTRIBUTING.md                   |   2 +-
 src/BodyPix/index_test.js         |   2 +-
 src/CharRNN/index_test.js         |  16 ++--
 src/FaceApi/index_test.js         |  13 +--
 src/ImageClassifier/index_test.js | 150 +++++++++++++++++++-----------
 src/KMeans/index_test.js          |  10 +-
 src/NeuralNetwork/index_test.js   |  34 +++----
 7 files changed, 134 insertions(+), 93 deletions(-)

diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index df592d8d5..ea8fd1b3c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -249,7 +249,7 @@ We’re still rolling out all of our unit tests, but if you want to contribute t
    ```npm run test:single```
 
  - To run a test on a single model
-   ```npm run test -- --model:YourModelNameHere```
+   ```npm run test -- --model=YourModelNameHere```
 
 This last one is case sensitive!
 
diff --git a/src/BodyPix/index_test.js b/src/BodyPix/index_test.js
index b1c767a31..bc4f6e29f 100644
--- a/src/BodyPix/index_test.js
+++ b/src/BodyPix/index_test.js
@@ -47,7 +47,7 @@ describe('bodyPix', () => {
     return img;
   }
 
-  beforeEach(async () => {
+  beforeAll(async () => {
     jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
     bp = await bodyPix();
   });
diff --git a/src/CharRNN/index_test.js b/src/CharRNN/index_test.js
index 573c64e2c..5f9dde22e 100644
--- a/src/CharRNN/index_test.js
+++ b/src/CharRNN/index_test.js
@@ -33,20 +33,20 @@ describe('charRnn', () => {
     rnn  = await charRNN(RNN_MODEL_URL, undefined);
   });
 
-  it('instantiates an rnn with all the defaults', async () => {
-    expect(rnn.ready).toBeTruthy();
-    expect(rnn.defaults.seed).toBe(RNN_DEFAULTS.seed);
-    expect(rnn.defaults.length).toBe(RNN_DEFAULTS.length);
-    expect(rnn.defaults.temperature).toBe(RNN_DEFAULTS.temperature);
-    expect(rnn.defaults.stateful).toBe(RNN_DEFAULTS.stateful);
-  });
-
   //  it('loads the model with all the defaults', async () => {
   //    expect(rnn.cellsAmount).toBe(RNN_MODEL_DEFAULTS.cellsAmount);
   //    expect(rnn.vocabSize).toBe(RNN_MODEL_DEFAULTS.vocabSize);
   //  });
 
   describe('generate', () => {
+    it('instantiates an rnn with all the defaults', async () => {
+      expect(rnn.ready).toBeTruthy();
+      expect(rnn.defaults.seed).toBe(RNN_DEFAULTS.seed);
+      expect(rnn.defaults.length).toBe(RNN_DEFAULTS.length);
+      expect(rnn.defaults.temperature).toBe(RNN_DEFAULTS.temperature);
+      expect(rnn.defaults.stateful).toBe(RNN_DEFAULTS.stateful);
+    });
+    
     it('Should generate content that follows default options if given an empty object', async() => {
       const result = await rnn.generate({});
       expect(result.sample.length).toBe(20);
diff --git a/src/FaceApi/index_test.js b/src/FaceApi/index_test.js
index 35b965733..5dfa6fe25 100644
--- a/src/FaceApi/index_test.js
+++ b/src/FaceApi/index_test.js
@@ -41,17 +41,18 @@ describe('faceApi', () => {
     //     return canvas;
     // }
 
-    beforeEach(async () => {
+    beforeAll(async () => {
         jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
         faceapi = await faceApi();
     });
-
-    it('Should create faceApi with all the defaults', async () => {
-        expect(faceapi.config.withLandmarks).toBe(FACEAPI_DEFAULTS.withLandmarks);
-        expect(faceapi.config.withDescriptors).toBe(FACEAPI_DEFAULTS.withDescriptors);
-    });
  
     describe('landmarks', () => {
+
+        it('Should create faceApi with all the defaults', async () => {
+            expect(faceapi.config.withLandmarks).toBe(FACEAPI_DEFAULTS.withLandmarks);
+            expect(faceapi.config.withDescriptors).toBe(FACEAPI_DEFAULTS.withDescriptors);
+        });
+
         it('Should get landmarks for Frida', async () => {
             const img = await getImage();
             await faceapi.detectSingle(img)
diff --git a/src/ImageClassifier/index_test.js b/src/ImageClassifier/index_test.js
index 31b0b4bdb..617f6478e 100644
--- a/src/ImageClassifier/index_test.js
+++ b/src/ImageClassifier/index_test.js
@@ -3,7 +3,11 @@
 // This software is released under the MIT License.
 // https://opensource.org/licenses/MIT
 
-const { imageClassifier } = ml5;
+const {
+  imageClassifier
+} = ml5;
+
+const TM_URL = 'https://storage.googleapis.com/tm-models/WfgKPytY/model.json';
 
 const DEFAULTS = {
   learningRate: 0.0001,
@@ -16,67 +20,109 @@ const DEFAULTS = {
   version: 2,
 };
 
+async function getImage() {
+  const img = new Image();
+  img.crossOrigin = true;
+  img.src = 'https://cdn.jsdelivr.net/gh/ml5js/ml5-library@development/assets/bird.jpg';
+  await new Promise((resolve) => {
+    img.onload = resolve;
+  });
+  return img;
+}
+
+async function getCanvas() {
+  const img = await getImage();
+  const canvas = document.createElement('canvas');
+  canvas.width = img.width;
+  canvas.height = img.height;
+  canvas.getContext('2d').drawImage(img, 0, 0);
+  return canvas;
+}
+
 describe('imageClassifier', () => {
   let classifier;
 
-  async function getImage() {
-    const img = new Image();
-    img.crossOrigin = true;
-    img.src = 'https://cdn.jsdelivr.net/gh/ml5js/ml5-library@development/assets/bird.jpg';
-    await new Promise((resolve) => { img.onload = resolve; });
-    return img;
-  }
-
-  async function getCanvas() {
-    const img = await getImage();
-    const canvas = document.createElement('canvas');
-    canvas.width = img.width;
-    canvas.height = img.height;
-    canvas.getContext('2d').drawImage(img, 0, 0);
-    return canvas;
-  }
-
-  beforeEach(async () => {
-    jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
-    classifier = await imageClassifier('MobileNet', undefined, {});
-  });
+  /**
+   * Test imageClassifier with teachable machine
+   */
+  // Teachable machine model
+  describe('with Teachable Machine model', () => {
+    
+    beforeAll(async () => {
+      jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
+      classifier = await imageClassifier(TM_URL, undefined, {});
+    });
+
+    describe('instantiate', () => {
+      it('Should create a classifier with all the defaults', async () => {
+        console.log(classifier)
+        expect(classifier.modelUrl).toBe(TM_URL);
+      });
+    });
 
-  it('Should create a classifier with all the defaults', async () => {
-    expect(classifier.version).toBe(DEFAULTS.version);
-    expect(classifier.alpha).toBe(DEFAULTS.alpha);
-    expect(classifier.topk).toBe(DEFAULTS.topk);
-    expect(classifier.ready).toBeTruthy();
   });
 
-  describe('classify', () => {
-    it('Should classify an image of a Robin', async () => {
-      const img = await getImage();
-      await classifier.classify(img)
-        .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
-    });
 
-    it('Should support p5 elements with an image on .elt', async () => {
-      const img = await getImage();
-      await classifier.classify({ elt: img })
-        .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
-    });
 
-    it('Should support HTMLCanvasElement', async () => {
-      const canvas = await getCanvas();
-      await classifier.classify(canvas)
-        .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
-    });
+  /**
+   * Test imageClassifier with Mobilenet
+   */
+  describe('imageClassifier with Mobilenet', () => {
 
-    it('Should support p5 elements with canvas on .canvas', async () => {
-      const canvas = await getCanvas();
-      await classifier.classify({ canvas })
-        .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
+    beforeAll(async () => {
+      jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
+      classifier = await imageClassifier('MobileNet', undefined, {});
     });
 
-    it('Should support p5 elements with canvas on .elt', async () => {
-      const canvas = await getCanvas();
-      await classifier.classify({ elt: canvas })
-        .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
+    describe('instantiate', () => {
+      
+      it('Should create a classifier with all the defaults', async () => {
+        expect(classifier.version).toBe(DEFAULTS.version);
+        expect(classifier.alpha).toBe(DEFAULTS.alpha);
+        expect(classifier.topk).toBe(DEFAULTS.topk);
+        expect(classifier.ready).toBeTruthy();
+      });
+    })
+
+    describe('classify', () => {
+
+      it('Should classify an image of a Robin', async () => {
+        const img = await getImage();
+        await classifier.classify(img)
+          .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
+      });
+
+      it('Should support p5 elements with an image on .elt', async () => {
+        const img = await getImage();
+        await classifier.classify({
+            elt: img
+          })
+          .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
+      });
+
+      it('Should support HTMLCanvasElement', async () => {
+        const canvas = await getCanvas();
+        await classifier.classify(canvas)
+          .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
+      });
+
+      it('Should support p5 elements with canvas on .canvas', async () => {
+        const canvas = await getCanvas();
+        await classifier.classify({
+            canvas
+          })
+          .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
+      });
+
+      it('Should support p5 elements with canvas on .elt', async () => {
+        const canvas = await getCanvas();
+        await classifier.classify({
+            elt: canvas
+          })
+          .then(results => expect(results[0].label).toBe('robin, American robin, Turdus migratorius'));
+      });
     });
+
   });
-});
+
+})
\ No newline at end of file
diff --git a/src/KMeans/index_test.js b/src/KMeans/index_test.js
index c90cb960c..a14bdecac 100644
--- a/src/KMeans/index_test.js
+++ b/src/KMeans/index_test.js
@@ -16,7 +16,8 @@ const KMEANS_DEFAULTS = {
 describe('kMeans', () => {
     let kmeansModel;
     const dataurl = 'https://raw.githubusercontent.com/ml5js/ml5-examples/development/d3/KMeans/KMeans_GaussianClusterDemo/data/gaussian2d_2clusters.csv'
-    beforeEach(async () => {
+
+    beforeAll(async () => {
         jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
         kmeansModel = await kmeans(dataurl, KMEANS_DEFAULTS, (err, result) => {
             return;
@@ -43,11 +44,4 @@ describe('kMeans', () => {
         expect(unique).toBe(2);
     });
 
-
-
-
-
-
-
-
 });
\ No newline at end of file
diff --git a/src/NeuralNetwork/index_test.js b/src/NeuralNetwork/index_test.js
index fdb079270..ca09f47ce 100644
--- a/src/NeuralNetwork/index_test.js
+++ b/src/NeuralNetwork/index_test.js
@@ -9,27 +9,27 @@ const {
 } = ml5;
 
 const NN_DEFAULTS = {
-  task: 'regression',
-  activationHidden: 'sigmoid',
-  activationOutput: 'sigmoid',
-  debug: false,
-  learningRate: 0.25,
-  inputs: 2,
-  outputs: 1,
-  noVal: null,
-  hiddenUnits: 16,
-  modelMetrics: ['accuracy'],
-  modelLoss: 'meanSquaredError',
-  modelOptimizer: null,
-  batchSize: 64,
-  epochs: 32,
+    task: 'regression',
+    activationHidden: 'sigmoid',
+    activationOutput: 'sigmoid',
+    debug: false,
+    learningRate: 0.25,
+    inputs: 2,
+    outputs: 1,
+    noVal: null,
+    hiddenUnits: 16,
+    modelMetrics: ['accuracy'],
+    modelLoss: 'meanSquaredError',
+    modelOptimizer: null,
+    batchSize: 64,
+    epochs: 32,
 }
 
 
 describe('neuralNetwork', () => {
     let nn;
 
-    beforeEach(async () => {
+    beforeAll(async () => {
         jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
         nn = await neuralNetwork();
     });
@@ -50,7 +50,7 @@ describe('neuralNetwork', () => {
         // expect(nn.config.training.modelMetrics).toBe(NN_DEFAULTS.modelMetrics);
         expect(nn.config.training.modelLoss).toBe(NN_DEFAULTS.modelLoss);
         // expect(nn.config.training.modelOptimizer).toBe();
-        
+
         // data defaults
         // expect(nn.config.dataOptions.dataUrl).toBe();
         // expect(nn.config.dataOptions.inputs).toBe(NN_DEFAULTS.inputs);
@@ -60,4 +60,4 @@ describe('neuralNetwork', () => {
 
     });
 
-});
+});
\ No newline at end of file