From 366eb64aa7b4bee6696cbf36b7aa244eed9b5f76 Mon Sep 17 00:00:00 2001 From: joeyklee Date: Sat, 22 Aug 2020 13:54:51 -0400 Subject: [PATCH 1/3] feat: adds testing utils for common functions --- src/utils/testingUtils/index.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/utils/testingUtils/index.js diff --git a/src/utils/testingUtils/index.js b/src/utils/testingUtils/index.js new file mode 100644 index 000000000..8f1b4e8d2 --- /dev/null +++ b/src/utils/testingUtils/index.js @@ -0,0 +1,26 @@ + +export const getRobin = async () => { + const img = new Image(); + img.crossOrigin = ""; + img.src = "https://cdn.jsdelivr.net/gh/ml5js/ml5-library@development/assets/bird.jpg"; + await new Promise(resolve => { + img.onload = resolve; + }); + return img; +} + +export const getImageData = async () => { + const arr = new Uint8ClampedArray(20000); + + // Iterate through every pixel + for (let i = 0; i < arr.length; i += 4) { + arr[i + 0] = 0; // R value + arr[i + 1] = 190; // G value + arr[i + 2] = 0; // B value + arr[i + 3] = 255; // A value + } + + // Initialize a new ImageData object + const img = new ImageData(arr, 200); + return img; +} \ No newline at end of file From 87e43e522afddc8e7dcd2ff930c29e98bd3ef200 Mon Sep 17 00:00:00 2001 From: joeyklee Date: Sat, 22 Aug 2020 15:18:07 -0400 Subject: [PATCH 2/3] tests: mocks for objectDetector --- src/ObjectDetector/CocoSsd/index.js | 6 +-- src/ObjectDetector/YOLO/index.js | 6 +-- src/ObjectDetector/index.js | 4 +- src/ObjectDetector/index_test.js | 62 ++++++++++++++++++----------- src/index.js | 6 ++- 5 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/ObjectDetector/CocoSsd/index.js b/src/ObjectDetector/CocoSsd/index.js index 134c004dd..435391178 100644 --- a/src/ObjectDetector/CocoSsd/index.js +++ b/src/ObjectDetector/CocoSsd/index.js @@ -17,7 +17,7 @@ const DEFAULTS = { modelUrl: undefined, }; -class CocoSsdBase { +export class CocoSsdBase { /** * Create CocoSsd model. Works on video and images. * @param {function} constructorCallback - Optional. A callback function that is called once the model has loaded. If no callback is provided, it will return a promise @@ -129,7 +129,7 @@ class CocoSsdBase { } } -const CocoSsd = (videoOr, optionsOr, cb) => { +export const CocoSsd = (videoOr, optionsOr, cb) => { let video = null; let options = {}; let callback = cb; @@ -153,4 +153,4 @@ const CocoSsd = (videoOr, optionsOr, cb) => { return new CocoSsdBase(video, options, callback); }; -export default CocoSsd; +// export default CocoSsd; diff --git a/src/ObjectDetector/YOLO/index.js b/src/ObjectDetector/YOLO/index.js index ec479ccc4..4035c2443 100644 --- a/src/ObjectDetector/YOLO/index.js +++ b/src/ObjectDetector/YOLO/index.js @@ -36,7 +36,7 @@ const DEFAULTS = { // Size of the video const imageSize = 416; -class YOLOBase extends Video { +export class YOLOBase extends Video { /** * @deprecated Please use ObjectDetector class instead */ @@ -236,7 +236,7 @@ class YOLOBase extends Video { } } -const YOLO = (videoOr, optionsOr, cb) => { +export const YOLO = (videoOr, optionsOr, cb) => { let video = null; let options = {}; let callback = cb; @@ -260,4 +260,4 @@ const YOLO = (videoOr, optionsOr, cb) => { return new YOLOBase(video, options, callback); }; -export default YOLO; \ No newline at end of file +// export default YOLO; \ No newline at end of file diff --git a/src/ObjectDetector/index.js b/src/ObjectDetector/index.js index b5a740801..b0af99cf0 100644 --- a/src/ObjectDetector/index.js +++ b/src/ObjectDetector/index.js @@ -7,8 +7,8 @@ ObjectDetection */ -import YOLO from "./YOLO/index"; -import CocoSsd from "./CocoSsd/index"; +import { YOLO } from "./YOLO/index"; +import {CocoSsd} from "./CocoSsd/index"; class ObjectDetector { /** diff --git a/src/ObjectDetector/index_test.js b/src/ObjectDetector/index_test.js index 07f208b27..c6f367eb0 100644 --- a/src/ObjectDetector/index_test.js +++ b/src/ObjectDetector/index_test.js @@ -3,6 +3,8 @@ // This software is released under the MIT License. // https://opensource.org/licenses/MIT +const { getImageData, getRobin } = ml5.testingUtils; + const COCOSSD_DEFAULTS = { base: "lite_mobilenet_v2", modelUrl: undefined, @@ -15,30 +17,32 @@ const YOLO_DEFAULTS = { size: 416, }; -async function getRobin() { - const img = new Image(); - img.crossOrigin = ""; - 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 getImageData() { - const arr = new Uint8ClampedArray(20000); +const mockYoloObject = { + IOUThreshold: YOLO_DEFAULTS.IOUThreshold, + classProbThreshold: YOLO_DEFAULTS.classProbThreshold, + filterBoxesThreshold: YOLO_DEFAULTS.filterBoxesThreshold, + size: YOLO_DEFAULTS.size, + detect: img => { + return [{ label: "bird", confidence: 0.9 }]; + }, +}; +const mockCocoObject = { + config: { ...COCOSSD_DEFAULTS }, + detect: img => { + console.log(img); + return [{ label: "bird", confidence: 0.9 }]; + }, +}; - // Iterate through every pixel - for (let i = 0; i < arr.length; i += 4) { - arr[i + 0] = 0; // R value - arr[i + 1] = 190; // G value - arr[i + 2] = 0; // B value - arr[i + 3] = 255; // A value +function mockObjectDetector(modelName) { + switch (modelName) { + case "yolo": + return mockYoloObject; + case "cocossd": + return mockCocoObject; + default: + return mockCocoObject; } - - // Initialize a new ImageData object - const img = new ImageData(arr, 200); - return img; } describe("objectDetector", () => { @@ -47,31 +51,37 @@ describe("objectDetector", () => { /** * Test cocossd object detector */ + describe("objectDetector: cocossd", () => { let cocoDetector; beforeAll(async () => { + spyOn(ml5, "objectDetector").and.callFake(mockObjectDetector); cocoDetector = await ml5.objectDetector("cocossd"); }); it("Should instantiate with the following defaults", () => { - expect(cocoDetector.config.base).toBe(COCOSSD_DEFAULTS.base); - expect(cocoDetector.config.modelUrl).toBe(COCOSSD_DEFAULTS.modelUrl); + expect(ml5.objectDetector).toHaveBeenCalled(); + expect(cocoDetector.config.toString()).toBe(COCOSSD_DEFAULTS.toString()); }); it("detects a robin", async () => { + spyOn(cocoDetector, "detect").and.returnValue([{ label: "bird", confidence: 0.9 }]); + const robin = await getRobin(); const detection = await cocoDetector.detect(robin); expect(detection[0].label).toBe("bird"); }); it("detects takes ImageData", async () => { + spyOn(cocoDetector, "detect").and.returnValue([]); const img = await getImageData(); const detection = await cocoDetector.detect(img); expect(detection).toEqual([]); }); it("throws error when a non image is trying to be detected", async () => { + spyOn(cocoDetector, "detect").and.throwError("Detection subject not supported"); const notAnImage = "not_an_image"; try { await cocoDetector.detect(notAnImage); @@ -88,10 +98,12 @@ describe("objectDetector", () => { describe("objectDetector: yolo", () => { let yolo; beforeAll(async () => { + spyOn(ml5, "objectDetector").and.callFake(mockObjectDetector); yolo = await ml5.objectDetector("yolo", { disableDeprecationNotice: true, ...YOLO_DEFAULTS }); }); it("instantiates the YOLO classifier with defaults", () => { + expect(ml5.objectDetector).toHaveBeenCalled(); expect(yolo.IOUThreshold).toBe(YOLO_DEFAULTS.IOUThreshold); expect(yolo.classProbThreshold).toBe(YOLO_DEFAULTS.classProbThreshold); expect(yolo.filterBoxesThreshold).toBe(YOLO_DEFAULTS.filterBoxesThreshold); @@ -99,12 +111,14 @@ describe("objectDetector", () => { }); it("detects a robin", async () => { + spyOn(yolo, "detect").and.returnValue([{ label: "bird", confidence: 0.9 }]); const robin = await getRobin(); const detection = await yolo.detect(robin); expect(detection[0].label).toBe("bird"); }); it("detects takes ImageData", async () => { + spyOn(yolo, "detect").and.returnValue([]); const img = await getImageData(); const detection = await yolo.detect(img); expect(detection).toEqual([]); diff --git a/src/index.js b/src/index.js index e131231ae..174e38550 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,8 @@ import soundClassifier from "./SoundClassifier/"; import KNNClassifier from "./KNNClassifier/"; import featureExtractor from "./FeatureExtractor/"; import word2vec from "./Word2vec/"; -import YOLO from "./ObjectDetector/YOLO"; +import {YOLO} from "./ObjectDetector/YOLO"; +import {CocoSsd} from "./ObjectDetector/CocoSsd"; import objectDetector from "./ObjectDetector"; import poseNet from "./PoseNet"; import * as imageUtils from "./utils/imageUtilities"; @@ -35,6 +36,7 @@ import facemesh from "./Facemesh"; import handpose from './Handpose'; import p5Utils from "./utils/p5Utils"; import communityStatement from "./utils/community"; +import * as testingUtils from "./utils/testingUtils"; const withPreload = { charRNN, @@ -52,6 +54,7 @@ const withPreload = { styleTransfer, word2vec, YOLO, + CocoSsd, objectDetector, uNet, sentiment, @@ -75,4 +78,5 @@ module.exports = Object.assign({ p5Utils }, preloadRegister(withPreload), { tfvis, version, neuralNetwork, + testingUtils }); From e61631fb7ac08d45c254ff5a6827f7434ccaf097 Mon Sep 17 00:00:00 2001 From: joeyklee Date: Sat, 22 Aug 2020 15:30:55 -0400 Subject: [PATCH 3/3] test: rm unused param --- src/ObjectDetector/index_test.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ObjectDetector/index_test.js b/src/ObjectDetector/index_test.js index c6f367eb0..682a690db 100644 --- a/src/ObjectDetector/index_test.js +++ b/src/ObjectDetector/index_test.js @@ -22,14 +22,13 @@ const mockYoloObject = { classProbThreshold: YOLO_DEFAULTS.classProbThreshold, filterBoxesThreshold: YOLO_DEFAULTS.filterBoxesThreshold, size: YOLO_DEFAULTS.size, - detect: img => { + detect: () => { return [{ label: "bird", confidence: 0.9 }]; }, }; const mockCocoObject = { config: { ...COCOSSD_DEFAULTS }, - detect: img => { - console.log(img); + detect: () => { return [{ label: "bird", confidence: 0.9 }]; }, };