diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f58ab35..278c5ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### **HEAD -> main** 2022/08/31 mandic00@live.com +- minor bug fixes and increased test coverage - extend release tests - add model load exception handling - add softwarekernels config option diff --git a/TODO.md b/TODO.md index 0c974544..a3e2af7f 100644 --- a/TODO.md +++ b/TODO.md @@ -78,9 +78,13 @@ Enable via `about:config` -> `gfx.offscreencanvas.enabled` - Fix `human.match` when using mixed descriptor lengths - Fix **WASM** feature detection issue in TFJS with Edge/Chromium Example: `console.log(human.env.wasm)` -- Increased test coverage - **NodeJS**: Run using: `npm run test` - **Browser**: Run using: `demo/browser.html` +- Increased **NodeJS** test coverage + Run using: `npm run test` + Runs tests for `tfjs-node`, `tfjs-node-gpu` and `wasm` +- Increased **Browser** test coverage + Run using: `demo/browser.html` + Runs tests for `webgl`, `humangl`, `webgpu` and `wasm` + Runs tests for ESM and IIFE versions of library - Increase availability of alternative models See `models/model.json` for full list - Update profiling methods in `human.profile()` diff --git a/package.json b/package.json index 2c14939b..6f9aedf5 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "rimraf": "^3.0.2", "seedrandom": "^3.0.5", "tslib": "^2.4.0", - "typedoc": "0.23.12", + "typedoc": "0.23.13", "typescript": "4.8.2" } } diff --git a/test/build.log b/test/build.log index 1b4ebb63..e0b8805d 100644 --- a/test/build.log +++ b/test/build.log @@ -1,39 +1,39 @@ -2022-08-31 18:30:05 DATA:  Build {"name":"@vladmandic/human","version":"2.9.4"} -2022-08-31 18:30:05 INFO:  Application: {"name":"@vladmandic/human","version":"2.9.4"} -2022-08-31 18:30:05 INFO:  Environment: {"profile":"production","config":".build.json","package":"package.json","tsconfig":true,"eslintrc":true,"git":true} -2022-08-31 18:30:05 INFO:  Toolchain: {"build":"0.7.11","esbuild":"0.15.6","typescript":"4.8.2","typedoc":"0.23.12","eslint":"8.23.0"} -2022-08-31 18:30:05 INFO:  Build: {"profile":"production","steps":["clean","compile","typings","typedoc","lint","changelog"]} -2022-08-31 18:30:05 STATE: Clean: {"locations":["dist/*","types/lib/*","typedoc/*"]} -2022-08-31 18:30:05 STATE: Compile: {"name":"tfjs/nodejs/cpu","format":"cjs","platform":"node","input":"tfjs/tf-node.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":159,"outputBytes":608} -2022-08-31 18:30:06 STATE: Compile: {"name":"human/nodejs/cpu","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node.js","files":75,"inputBytes":655767,"outputBytes":308629} -2022-08-31 18:30:06 STATE: Compile: {"name":"tfjs/nodejs/gpu","format":"cjs","platform":"node","input":"tfjs/tf-node-gpu.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":167,"outputBytes":612} -2022-08-31 18:30:06 STATE: Compile: {"name":"human/nodejs/gpu","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node-gpu.js","files":75,"inputBytes":655771,"outputBytes":308633} -2022-08-31 18:30:06 STATE: Compile: {"name":"tfjs/nodejs/wasm","format":"cjs","platform":"node","input":"tfjs/tf-node-wasm.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":206,"outputBytes":664} -2022-08-31 18:30:06 STATE: Compile: {"name":"human/nodejs/wasm","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node-wasm.js","files":75,"inputBytes":655823,"outputBytes":308683} -2022-08-31 18:30:06 STATE: Compile: {"name":"tfjs/browser/version","format":"esm","platform":"browser","input":"tfjs/tf-version.ts","output":"dist/tfjs.version.js","files":1,"inputBytes":1125,"outputBytes":358} -2022-08-31 18:30:06 STATE: Compile: {"name":"tfjs/browser/esm/nobundle","format":"esm","platform":"browser","input":"tfjs/tf-browser.ts","output":"dist/tfjs.esm.js","files":2,"inputBytes":1088,"outputBytes":583} -2022-08-31 18:30:06 STATE: Compile: {"name":"human/browser/esm/nobundle","format":"esm","platform":"browser","input":"src/human.ts","output":"dist/human.esm-nobundle.js","files":75,"inputBytes":655742,"outputBytes":307503} -2022-08-31 18:30:06 STATE: Compile: {"name":"tfjs/browser/esm/custom","format":"esm","platform":"browser","input":"tfjs/tf-custom.ts","output":"dist/tfjs.esm.js","files":11,"inputBytes":1344,"outputBytes":2821914} -2022-08-31 18:30:06 STATE: Compile: {"name":"human/browser/iife/bundle","format":"iife","platform":"browser","input":"src/human.ts","output":"dist/human.js","files":75,"inputBytes":3477073,"outputBytes":1687675} -2022-08-31 18:30:06 STATE: Compile: {"name":"human/browser/esm/bundle","format":"esm","platform":"browser","input":"src/human.ts","output":"dist/human.esm.js","files":75,"inputBytes":3477073,"outputBytes":3108312} -2022-08-31 18:30:10 STATE: Typings: {"input":"src/human.ts","output":"types/lib","files":30} -2022-08-31 18:30:12 STATE: TypeDoc: {"input":"src/human.ts","output":"typedoc","objects":77,"generated":true} -2022-08-31 18:30:12 STATE: Compile: {"name":"demo/typescript","format":"esm","platform":"browser","input":"demo/typescript/index.ts","output":"demo/typescript/index.js","files":1,"inputBytes":6714,"outputBytes":3134} -2022-08-31 18:30:12 STATE: Compile: {"name":"demo/faceid","format":"esm","platform":"browser","input":"demo/faceid/index.ts","output":"demo/faceid/index.js","files":2,"inputBytes":15488,"outputBytes":7788} -2022-08-31 18:30:23 STATE: Lint: {"locations":["*.json","src/**/*.ts","test/**/*.js","demo/**/*.js"],"files":112,"errors":0,"warnings":0} -2022-08-31 18:30:23 STATE: ChangeLog: {"repository":"https://github.com/vladmandic/human","branch":"main","output":"CHANGELOG.md"} -2022-08-31 18:30:23 STATE: Copy: {"input":"tfjs/tfjs.esm.d.ts"} -2022-08-31 18:30:23 INFO:  Done... -2022-08-31 18:30:24 STATE: API-Extractor: {"succeeeded":true,"errors":0,"warnings":198} -2022-08-31 18:30:24 STATE: Copy: {"input":"types/human.d.ts"} -2022-08-31 18:30:24 INFO:  Analyze models: {"folders":8,"result":"models/models.json"} -2022-08-31 18:30:24 STATE: Models {"folder":"./models","models":13} -2022-08-31 18:30:24 STATE: Models {"folder":"../human-models/models","models":42} -2022-08-31 18:30:24 STATE: Models {"folder":"../blazepose/model/","models":4} -2022-08-31 18:30:24 STATE: Models {"folder":"../anti-spoofing/model","models":1} -2022-08-31 18:30:24 STATE: Models {"folder":"../efficientpose/models","models":3} -2022-08-31 18:30:24 STATE: Models {"folder":"../insightface/models","models":5} -2022-08-31 18:30:24 STATE: Models {"folder":"../movenet/models","models":3} -2022-08-31 18:30:24 STATE: Models {"folder":"../nanodet/models","models":4} -2022-08-31 18:30:24 STATE: Models: {"count":57,"totalSize":383017442} -2022-08-31 18:30:24 INFO:  Human Build complete... {"logFile":"test/build.log"} +2022-09-01 09:05:06 DATA:  Build {"name":"@vladmandic/human","version":"2.9.4"} +2022-09-01 09:05:06 INFO:  Application: {"name":"@vladmandic/human","version":"2.9.4"} +2022-09-01 09:05:06 INFO:  Environment: {"profile":"production","config":".build.json","package":"package.json","tsconfig":true,"eslintrc":true,"git":true} +2022-09-01 09:05:06 INFO:  Toolchain: {"build":"0.7.11","esbuild":"0.15.6","typescript":"4.8.2","typedoc":"0.23.13","eslint":"8.23.0"} +2022-09-01 09:05:06 INFO:  Build: {"profile":"production","steps":["clean","compile","typings","typedoc","lint","changelog"]} +2022-09-01 09:05:06 STATE: Clean: {"locations":["dist/*","types/lib/*","typedoc/*"]} +2022-09-01 09:05:06 STATE: Compile: {"name":"tfjs/nodejs/cpu","format":"cjs","platform":"node","input":"tfjs/tf-node.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":159,"outputBytes":608} +2022-09-01 09:05:06 STATE: Compile: {"name":"human/nodejs/cpu","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node.js","files":75,"inputBytes":655767,"outputBytes":308629} +2022-09-01 09:05:06 STATE: Compile: {"name":"tfjs/nodejs/gpu","format":"cjs","platform":"node","input":"tfjs/tf-node-gpu.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":167,"outputBytes":612} +2022-09-01 09:05:06 STATE: Compile: {"name":"human/nodejs/gpu","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node-gpu.js","files":75,"inputBytes":655771,"outputBytes":308633} +2022-09-01 09:05:06 STATE: Compile: {"name":"tfjs/nodejs/wasm","format":"cjs","platform":"node","input":"tfjs/tf-node-wasm.ts","output":"dist/tfjs.esm.js","files":1,"inputBytes":206,"outputBytes":664} +2022-09-01 09:05:06 STATE: Compile: {"name":"human/nodejs/wasm","format":"cjs","platform":"node","input":"src/human.ts","output":"dist/human.node-wasm.js","files":75,"inputBytes":655823,"outputBytes":308683} +2022-09-01 09:05:06 STATE: Compile: {"name":"tfjs/browser/version","format":"esm","platform":"browser","input":"tfjs/tf-version.ts","output":"dist/tfjs.version.js","files":1,"inputBytes":1125,"outputBytes":358} +2022-09-01 09:05:06 STATE: Compile: {"name":"tfjs/browser/esm/nobundle","format":"esm","platform":"browser","input":"tfjs/tf-browser.ts","output":"dist/tfjs.esm.js","files":2,"inputBytes":1088,"outputBytes":583} +2022-09-01 09:05:06 STATE: Compile: {"name":"human/browser/esm/nobundle","format":"esm","platform":"browser","input":"src/human.ts","output":"dist/human.esm-nobundle.js","files":75,"inputBytes":655742,"outputBytes":307503} +2022-09-01 09:05:07 STATE: Compile: {"name":"tfjs/browser/esm/custom","format":"esm","platform":"browser","input":"tfjs/tf-custom.ts","output":"dist/tfjs.esm.js","files":11,"inputBytes":1344,"outputBytes":2821914} +2022-09-01 09:05:07 STATE: Compile: {"name":"human/browser/iife/bundle","format":"iife","platform":"browser","input":"src/human.ts","output":"dist/human.js","files":75,"inputBytes":3477073,"outputBytes":1687675} +2022-09-01 09:05:07 STATE: Compile: {"name":"human/browser/esm/bundle","format":"esm","platform":"browser","input":"src/human.ts","output":"dist/human.esm.js","files":75,"inputBytes":3477073,"outputBytes":3108312} +2022-09-01 09:05:11 STATE: Typings: {"input":"src/human.ts","output":"types/lib","files":30} +2022-09-01 09:05:13 STATE: TypeDoc: {"input":"src/human.ts","output":"typedoc","objects":77,"generated":true} +2022-09-01 09:05:13 STATE: Compile: {"name":"demo/typescript","format":"esm","platform":"browser","input":"demo/typescript/index.ts","output":"demo/typescript/index.js","files":1,"inputBytes":6714,"outputBytes":3134} +2022-09-01 09:05:13 STATE: Compile: {"name":"demo/faceid","format":"esm","platform":"browser","input":"demo/faceid/index.ts","output":"demo/faceid/index.js","files":2,"inputBytes":15488,"outputBytes":7788} +2022-09-01 09:05:24 STATE: Lint: {"locations":["*.json","src/**/*.ts","test/**/*.js","demo/**/*.js"],"files":113,"errors":0,"warnings":0} +2022-09-01 09:05:24 STATE: ChangeLog: {"repository":"https://github.com/vladmandic/human","branch":"main","output":"CHANGELOG.md"} +2022-09-01 09:05:24 STATE: Copy: {"input":"tfjs/tfjs.esm.d.ts"} +2022-09-01 09:05:24 INFO:  Done... +2022-09-01 09:05:24 STATE: API-Extractor: {"succeeeded":true,"errors":0,"warnings":198} +2022-09-01 09:05:24 STATE: Copy: {"input":"types/human.d.ts"} +2022-09-01 09:05:24 INFO:  Analyze models: {"folders":8,"result":"models/models.json"} +2022-09-01 09:05:24 STATE: Models {"folder":"./models","models":13} +2022-09-01 09:05:24 STATE: Models {"folder":"../human-models/models","models":42} +2022-09-01 09:05:24 STATE: Models {"folder":"../blazepose/model/","models":4} +2022-09-01 09:05:24 STATE: Models {"folder":"../anti-spoofing/model","models":1} +2022-09-01 09:05:24 STATE: Models {"folder":"../efficientpose/models","models":3} +2022-09-01 09:05:24 STATE: Models {"folder":"../insightface/models","models":5} +2022-09-01 09:05:24 STATE: Models {"folder":"../movenet/models","models":3} +2022-09-01 09:05:24 STATE: Models {"folder":"../nanodet/models","models":4} +2022-09-01 09:05:25 STATE: Models: {"count":57,"totalSize":383017442} +2022-09-01 09:05:25 INFO:  Human Build complete... {"logFile":"test/build.log"} diff --git a/test/test-browser-esm.js b/test/test-browser-esm.js index 6d5b83ad..5c78226c 100644 --- a/test/test-browser-esm.js +++ b/test/test-browser-esm.js @@ -71,7 +71,7 @@ async function testDefault(title, testConfig = {}) { human.reset(); res = human.validate(testConfig); // validate if (res && res.length > 0) log(' invalid configuration', res); - log(`test ${title}/${human.tf.getBackend()}`, human.config); + log(`test ${title}/${human.tf.getBackend()}`); await human.load(); const models = Object.keys(human.models).map((model) => ({ name: model, loaded: (human.models[model] !== null) })); log(' models', models); @@ -93,6 +93,54 @@ async function testDefault(title, testConfig = {}) { return res; } +async function testMatch() { + human.reset(); + await human.warmup({ warmup: 'face' }); + const img1 = await image('../../samples/in/ai-body.jpg'); + const input1 = await human.image(img1); + const img2 = await image('../../samples/in/ai-face.jpg'); + const input2 = await human.image(img2); + const res1 = await human.detect(input1.tensor); + const res2 = await human.detect(input2.tensor); + const desc1 = res1?.face?.[0]?.embedding; + const desc2 = res2?.face?.[0]?.embedding; + const similarity = await human.similarity(desc1, desc2); + const descArray = []; + for (let i = 0; i < 100; i++) descArray.push(desc2); + const match = await human.match(desc1, descArray); + log(`test similarity/${human.tf.getBackend()}`, match, similarity); +} + +async function testWorker() { + log(`test webworker/${human.tf.getBackend()}`); + const img = await image('../../samples/in/ai-body.jpg'); + const canvas = document.createElement('canvas'); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const worker = new Worker('test-browser-worker.js'); + let res; + const userConfig = { + backend: human.tf.getBackend(), + debug: true, + face: { enabled: false }, + hand: { enabled: false }, + body: { enabled: true }, + object: { enabled: false }, + }; + return new Promise((resolve) => { + worker.addEventListener('message', (msg) => { + res = msg.data.result; + log(' summary', { face: res.face.length, body: res.body.length, hand: res.hand.length, object: res.object.length, gesture: res.gesture.length }); + resolve(); + }); + // pass image data as arraybuffer to worker by reference to avoid copy + worker.postMessage({ image: imageData.data.buffer, width: canvas.width, height: canvas.height, userConfig }, [imageData.data.buffer]); + }); +} + async function runBenchmark() { const img = await image('../../samples/in/ai-face.jpg'); human.reset(); @@ -142,12 +190,11 @@ async function main() { await testDefault('sync', { debug: true, async: false }); await testDefault('none', { debug: true, async: true, face: { enabled: false }, body: { enabled: false }, hand: { enabled: false }, gesture: { enabled: false }, segmentation: { enabled: false }, object: { enabled: false } }); await testDefault('object', { debug: true, async: true, face: { enabled: false }, body: { enabled: false }, hand: { enabled: false }, gesture: { enabled: false }, segmentation: { enabled: false }, object: { enabled: true } }); + await testMatch(); + await testWorker(); // TBD detectors only // TBD segmentation - // TBD face match // TBD non-default models - // TBD web workers - // TBD multiple instances } log('tests complete'); for (const backend of backends) { diff --git a/test/test-browser-worker.js b/test/test-browser-worker.js new file mode 100644 index 00000000..f77dad74 --- /dev/null +++ b/test/test-browser-worker.js @@ -0,0 +1,18 @@ +/// + +// load Human using IIFE script as Chome Mobile does not support Modules as Workers +self.importScripts('../dist/human.js'); // eslint-disable-line no-restricted-globals + +// eslint-disable-next-line new-cap, no-undef +const human = new Human.default(); + +onmessage = async (msg) => { // receive message from main thread + console.log('worker onmessage', msg.data); // eslint-disable-line no-console + const image = new ImageData(new Uint8ClampedArray(msg.data.image), msg.data.width, msg.data.height); + const result = await human.detect(image, msg.data.userConfig); + result.tensors = human.tf.engine().state.numTensors; // append to result object so main thread get info + result.backend = human.tf.getBackend(); // append to result object so main thread get info + result.canvas = null; // must strip original canvas from return value as it cannot be transfered from worker thread + console.log('worker result', result); // eslint-disable-line no-console + postMessage({ result }); // send message back to main thread without canvas +};