diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc1a2c2..0f58ab35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,9 @@ ## Changelog -### **HEAD -> main** 2022/08/30 mandic00@live.com +### **HEAD -> main** 2022/08/31 mandic00@live.com +- extend release tests - add model load exception handling - add softwarekernels config option - expand type safety diff --git a/TODO.md b/TODO.md index 4310d414..0c974544 100644 --- a/TODO.md +++ b/TODO.md @@ -61,12 +61,12 @@ Enable via `about:config` -> `gfx.offscreencanvas.enabled` - Treat models that cannot be found & loaded as non-critical error Instead of creating runtime exception, `human` will now report that model could not be loaded - Improve `human.reset()` method to reset all config values to defaults -- Host models in +- Host models in Models can be directly used without downloading to local storage - Example: `modelPath: 'https://vladmandic.github.io/human-models/models/facemesh.json'` + Example: `modelBasePath: 'https://vladmandic.github.io/human-models/models/'` - Allow hosting models in **Google Cloud Bucket** Hosted models can be directly used without downloading to local storage - Example: `modelPath: 'https://storage.googleapis.com/human-models/facemesh.json'` + Example: `modelBasePath: 'https://storage.googleapis.com/human-models/'` - Stricter linting rules for both **TypeScript** and **JavaScript** See `./eslintrc.json` for details - Enhanced type safety across entire library @@ -76,7 +76,7 @@ Enable via `about:config` -> `gfx.offscreencanvas.enabled` - Fix **NanoDet** module as alternative object detection - Fix `demo/multithread/node-multiprocess.js` demo - Fix `human.match` when using mixed descriptor lengths -- Fix WASM feature detection issue in TFJS with Edge/Chromium +- 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` diff --git a/test/README.md b/test/README.md index 0baea202..7f78202f 100644 --- a/test/README.md +++ b/test/README.md @@ -1,24 +1,3 @@ -# Test Results - -## Automatic Tests +# Automatic Tests Not required for normal funcioning of library - -### NodeJS using TensorFlow library - -- Image filters are disabled due to lack of Canvas and WebGL access - -### NodeJS using WASM - -- Requires dev http server - See -- Image filters are disabled due to lack of Canvas and WeBGL access -- Only supported input is Tensor due to missing image decoders - -
- -## Browser Tests - -- Chrome/Edge: **All Passing** -- Firefox: WebWorkers not supported due to missing support for `OffscreenCanvas` -- Safari: **Limited Testing** diff --git a/test/browser.sh b/test/browser.sh index ba4d77a8..be096943 100755 --- a/test/browser.sh +++ b/test/browser.sh @@ -1,2 +1,7 @@ #!/bin/sh -"/mnt/c/Users/mandi/AppData/Local/Google/Chrome SxS/Application/chrome.exe" --enable-unsafe-gpu --allow-insecure-localhost --auto-open-devtools-for-tabs https://localhost:10031/test/browser.html + +BROWSER="/mnt/c/Users/mandi/AppData/Local/Google/Chrome SxS/Application/chrome.exe" +PARAMS="--enable-unsafe-gpu --allow-insecure-localhost --auto-open-devtools-for-tabs" + +"$BROWSER" $PARAMS https://localhost:10031/test/test-browser-iife.html +"$BROWSER" $PARAMS https://localhost:10031/test/test-browser-esm.html diff --git a/test/build.log b/test/build.log index 568396c7..1b4ebb63 100644 --- a/test/build.log +++ b/test/build.log @@ -1,39 +1,39 @@ -2022-08-31 11:27:08 DATA:  Build {"name":"@vladmandic/human","version":"2.9.4"} -2022-08-31 11:27:08 INFO:  Application: {"name":"@vladmandic/human","version":"2.9.4"} -2022-08-31 11:27:08 INFO:  Environment: {"profile":"production","config":".build.json","package":"package.json","tsconfig":true,"eslintrc":true,"git":true} -2022-08-31 11:27:08 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 11:27:08 INFO:  Build: {"profile":"production","steps":["clean","compile","typings","typedoc","lint","changelog"]} -2022-08-31 11:27:08 STATE: Clean: {"locations":["dist/*","types/lib/*","typedoc/*"]} -2022-08-31 11:27:08 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 11:27:08 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 11:27:08 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 11:27:08 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 11:27:08 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 11:27:08 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 11:27:08 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 11:27:08 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 11:27:09 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 11:27:09 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 11:27:09 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 11:27:09 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 11:27:13 STATE: Typings: {"input":"src/human.ts","output":"types/lib","files":30} -2022-08-31 11:27:15 STATE: TypeDoc: {"input":"src/human.ts","output":"typedoc","objects":77,"generated":true} -2022-08-31 11:27:15 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 11:27:15 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 11:27:25 STATE: Lint: {"locations":["*.json","src/**/*.ts","test/**/*.js","demo/**/*.js"],"files":111,"errors":0,"warnings":0} -2022-08-31 11:27:25 STATE: ChangeLog: {"repository":"https://github.com/vladmandic/human","branch":"main","output":"CHANGELOG.md"} -2022-08-31 11:27:25 STATE: Copy: {"input":"tfjs/tfjs.esm.d.ts"} -2022-08-31 11:27:25 INFO:  Done... -2022-08-31 11:27:26 STATE: API-Extractor: {"succeeeded":true,"errors":0,"warnings":198} -2022-08-31 11:27:26 STATE: Copy: {"input":"types/human.d.ts"} -2022-08-31 11:27:26 INFO:  Analyze models: {"folders":8,"result":"models/models.json"} -2022-08-31 11:27:26 STATE: Models {"folder":"./models","models":13} -2022-08-31 11:27:26 STATE: Models {"folder":"../human-models/models","models":42} -2022-08-31 11:27:26 STATE: Models {"folder":"../blazepose/model/","models":4} -2022-08-31 11:27:26 STATE: Models {"folder":"../anti-spoofing/model","models":1} -2022-08-31 11:27:26 STATE: Models {"folder":"../efficientpose/models","models":3} -2022-08-31 11:27:26 STATE: Models {"folder":"../insightface/models","models":5} -2022-08-31 11:27:26 STATE: Models {"folder":"../movenet/models","models":3} -2022-08-31 11:27:26 STATE: Models {"folder":"../nanodet/models","models":4} -2022-08-31 11:27:27 STATE: Models: {"count":57,"totalSize":383017442} -2022-08-31 11:27:27 INFO:  Human Build complete... {"logFile":"test/build.log"} +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"} diff --git a/test/browser.html b/test/test-browser-esm.html similarity index 96% rename from test/browser.html rename to test/test-browser-esm.html index 0e53bff0..613d9ff2 100644 --- a/test/browser.html +++ b/test/test-browser-esm.html @@ -25,6 +25,6 @@
- + diff --git a/test/browser.js b/test/test-browser-esm.js similarity index 95% rename from test/browser.js rename to test/test-browser-esm.js index 5a8b0d40..6d5b83ad 100644 --- a/test/browser.js +++ b/test/test-browser-esm.js @@ -66,10 +66,7 @@ async function testDefault(title, testConfig = {}) { const t0 = human.now(); let res; for (const model of Object.keys(human.models)) { // unload models - if (human.models[model]) { - // if (human.models[model].dispose) human.models[model].dispose(); - human.models[model] = null; - } + if (human.models[model]) human.models[model] = null; } human.reset(); res = human.validate(testConfig); // validate @@ -91,8 +88,6 @@ async function testDefault(title, testConfig = {}) { human.next(); // run interpolation const persons = res.persons; // run persons getter log(' summary', { persons: persons.length, face: res.face.length, body: res.body.length, hand: res.hand.length, object: res.object.length, gesture: res.gesture.length }); - // log(' memory', human.tf.memory()); - // log(' performance', human.performance); human.tf.dispose(input.tensor); log(` finished ${title}/${human.tf.getBackend()}`, { init: Math.round(t1 - t0), detect: Math.round(t2 - t1) }); return res; @@ -123,11 +118,7 @@ async function runBenchmark() { async function main() { log('human tests'); - - // create instance human = new Human({ debug: true }); - - // explicit init await human.init(); human.events.addEventListener('warmup', () => events('warmup')); human.events.addEventListener('image', () => events('image')); @@ -139,7 +130,6 @@ async function main() { const env = JSON.parse(JSON.stringify(human.env)); env.kernels = human.env.kernels.length; detailed('environment', env); - // detailed('config', human.config); for (const backend of backends) { human.config.backend = backend; diff --git a/test/test-browser-iife.html b/test/test-browser-iife.html new file mode 100644 index 00000000..345c2d6d --- /dev/null +++ b/test/test-browser-iife.html @@ -0,0 +1,31 @@ + + + + Human Browser Tests + + + + + + + + + + + +

+    
+
+ + + + + diff --git a/test/test-browser-iife.js b/test/test-browser-iife.js new file mode 100644 index 00000000..de1e3ebf --- /dev/null +++ b/test/test-browser-iife.js @@ -0,0 +1,117 @@ +/* global Human */ + +let human; + +const backends = ['wasm', 'humangl', 'webgl', 'webgpu']; + +const start = performance.now(); + +function str(long, ...msg) { + if (!Array.isArray(msg)) return msg; + let line = ''; + for (const entry of msg) { + if (typeof entry === 'object') line += ' ' + JSON.stringify(entry, null, long ? 2 : 0).replace(/"/g, '').replace(/,/g, ', ').replace(/:/g, ': '); + else line += ' ' + entry; + } + return line + '\n'; +} + +let last = new Date(); +async function log(...msgs) { + const dt = new Date(); + const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`; + const elap = (dt - last).toString().padStart(5, '0'); + document.getElementById('log').innerHTML += ts + ' +' + elap + 'ms  ' + str(false, ...msgs); + document.documentElement.scrollTop = document.documentElement.scrollHeight; + console.log(ts, elap, ...msgs); // eslint-disable-line no-console + last = dt; +} + +async function detailed(...msgs) { + const dt = new Date(); + const ts = `${dt.getHours().toString().padStart(2, '0')}:${dt.getMinutes().toString().padStart(2, '0')}:${dt.getSeconds().toString().padStart(2, '0')}.${dt.getMilliseconds().toString().padStart(3, '0')}`; + const elap = (dt - last).toString().padStart(5, '0'); + document.getElementById('log').innerHTML += ts + ' +' + elap + 'ms  ' + str(true, ...msgs); + document.documentElement.scrollTop = document.documentElement.scrollHeight; + console.log(ts, elap, ...msgs); // eslint-disable-line no-console + last = dt; +} + +async function image(url) { + const el = document.createElement('img'); + el.id = 'image'; + const loaded = new Promise((resolve) => { el.onload = () => resolve(true); }); + el.src = url; + await loaded; + return el; +} + +function draw(canvas = null) { + const c = document.getElementById('canvas'); + const ctx = c.getContext('2d'); + if (canvas) ctx.drawImage(canvas, 0, 0, c.width, c.height); + else ctx.clearRect(0, 0, c.width, c.height); +} + +async function events(event) { + document.getElementById('events').innerText = `${Math.round(performance.now() - start)}ms Event: ${event}`; +} + +async function testDefault(title, testConfig = {}) { + const t0 = human.now(); + let res; + for (const model of Object.keys(human.models)) { // unload models + if (human.models[model]) human.models[model] = null; + } + human.reset(); + res = human.validate(testConfig); // validate + if (res && res.length > 0) log(' invalid configuration', res); + log(`test ${title}/${human.tf.getBackend()}`, human.config); + await human.load(); + const models = Object.keys(human.models).map((model) => ({ name: model, loaded: (human.models[model] !== null) })); + log(' models', models); + const ops = await human.check(); + if (ops && ops.length > 0) log(' missing ops', ops); + const img = await image('../../samples/in/ai-body.jpg'); + const input = await human.image(img); // process image + draw(input.canvas); + res = await human.warmup({ warmup: 'face' }); // warmup + draw(res.canvas); + const t1 = human.now(); + res = await human.detect(input.tensor, testConfig); // run detect + const t2 = human.now(); + human.next(); // run interpolation + const persons = res.persons; // run persons getter + log(' summary', { persons: persons.length, face: res.face.length, body: res.body.length, hand: res.hand.length, object: res.object.length, gesture: res.gesture.length }); + human.tf.dispose(input.tensor); + log(` finished ${title}/${human.tf.getBackend()}`, { init: Math.round(t1 - t0), detect: Math.round(t2 - t1) }); + return res; +} + +async function main() { + log('human tests'); + human = new Human.Human({ debug: true }); + await human.init(); + human.events.addEventListener('warmup', () => events('warmup')); + human.events.addEventListener('image', () => events('image')); + human.events.addEventListener('detect', () => events('detect')); + const timer = setInterval(() => { document.getElementById('state').innerText = `State: ${human.state}`; }, 10); + log('version', human.version); + log('tfjs', human.tf.version.tfjs); + const env = JSON.parse(JSON.stringify(human.env)); + env.kernels = human.env.kernels.length; + detailed('environment', env); + for (const backend of backends) { + human.config.backend = backend; + await human.init(); // init + if (human.tf.getBackend() !== backend) { + log('desired', backend, 'detected', human.tf.getBackend()); + continue; // wrong backend + } + await testDefault('default', { debug: true }); + } + log('tests complete'); + clearInterval(timer); +} + +main();