diff --git a/CHANGELOG.md b/CHANGELOG.md index d3fa7ef4..c7f8b00c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # @vladmandic/human -Version: **1.5.0** +Version: **1.5.1** Description: **Human: AI-powered 3D Face Detection & Rotation Tracking, Face Description & Recognition, Body Pose Tracking, 3D Hand & Finger Tracking, Iris Analysis, Age & Gender & Emotion Prediction, Gesture Recognition** Author: **Vladimir Mandic ** @@ -9,8 +9,10 @@ Repository: **** ## Changelog -### **HEAD -> main** 2021/04/13 mandic00@live.com +### **1.5.1** 2021/04/13 mandic00@live.com +- fix for safari imagebitmap +- refactored human.config and human.draw ### **1.4.3** 2021/04/12 mandic00@live.com diff --git a/TODO.md b/TODO.md index 15da0d74..e6c18058 100644 --- a/TODO.md +++ b/TODO.md @@ -2,19 +2,15 @@ ## Big Ticket Items -- Strong(er) typing -- Automated testing framework -- TypeDoc comments +- Improve automated testing framework ## Explore Models -- EfficientPose - - - InsightFace - RetinaFace detetor and ArcFace recognition + RetinaFace detector and ArcFace recognition ## Issues - box sizing on mobile +- canvas.js for wasm on node diff --git a/package.json b/package.json index 905041df..c479a4c1 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "start": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught --no-deprecation demo/node.js", "dev": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught server/serve.js", "build": "rimraf dist/* typedoc/* types/* && node --trace-warnings --unhandled-rejections=strict --trace-uncaught server/build.js", - "lint": "eslint src server demo", + "lint": "eslint src server demo test", "test": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught --no-deprecation test/test-node.js" }, "keywords": [ diff --git a/src/human.ts b/src/human.ts index ce3e3e7d..e52f2c2f 100644 --- a/src/human.ts +++ b/src/human.ts @@ -329,13 +329,14 @@ export class Human { if (this.config.backend && this.config.backend.length > 0) { // force browser vs node backend if (this.tf.ENV.flags.IS_BROWSER && this.config.backend === 'tensorflow') this.config.backend = 'webgl'; - if (this.tf.ENV.flags.IS_NODE && (this.config.backend === 'webgl' || this.config.backend === 'wasm')) this.config.backend = 'tensorflow'; + if (this.tf.ENV.flags.IS_NODE && (this.config.backend === 'webgl' || this.config.backend === 'humangl')) this.config.backend = 'tensorflow'; if (this.config.debug) log('setting backend:', this.config.backend); if (this.config.backend === 'wasm') { if (this.config.debug) log('wasm path:', this.config.wasmPath); - this.tf.setWasmPaths(this.config.wasmPath); + if (typeof this.tf?.setWasmPaths !== 'undefined') this.tf.setWasmPaths(this.config.wasmPath); + else throw new Error('Human: WASM backend is not loaded'); const simd = await this.tf.env().getAsync('WASM_HAS_SIMD_SUPPORT'); const mt = await this.tf.env().getAsync('WASM_HAS_MULTITHREAD_SUPPORT'); if (this.config.debug) log(`wasm execution: ${simd ? 'SIMD' : 'no SIMD'} ${mt ? 'multithreaded' : 'singlethreaded'}`); @@ -573,8 +574,16 @@ export class Human { /** @hidden */ #warmupNode = async () => { + // @ts-ignore + if (typeof tf.node === 'undefined') { + if (this.config.debug) log('Warmup tfjs-node not loaded'); + return null; + } const atob = (str) => Buffer.from(str, 'base64'); - const img = this.config.warmup === 'face' ? atob(sample.face) : atob(sample.body); + let img; + if (this.config.warmup === 'face') img = atob(sample.face); + if (this.config.warmup === 'body' || this.config.warmup === 'full') img = atob(sample.body); + if (!img) return null; // @ts-ignore // tf.node is only defined when compiling for nodejs const data = tf.node?.decodeJpeg(img); const expanded = data.expandDims(0); @@ -592,6 +601,7 @@ export class Human { async warmup(userConfig: Config | Object = {}): Promise { const t0 = now(); if (userConfig) this.config = mergeDeep(this.config, userConfig); + if (!this.config.warmup || this.config.warmup === 'none') return { error: 'null' }; const save = this.config.videoOptimized; this.config.videoOptimized = false; let res; diff --git a/src/tfjs/tf-node-wasm.ts b/src/tfjs/tf-node-wasm.ts new file mode 100644 index 00000000..79e5f492 --- /dev/null +++ b/src/tfjs/tf-node-wasm.ts @@ -0,0 +1,2 @@ +export * from '@tensorflow/tfjs'; +export * from '@tensorflow/tfjs-backend-wasm'; diff --git a/test/test-node.js b/test/test-node.js index 642df098..23fd14bc 100644 --- a/test/test-node.js +++ b/test/test-node.js @@ -50,36 +50,51 @@ async function testInstance(human) { log.error('failed: load models'); } - const warmup = await human.warmup(); + let warmup; + try { + warmup = await human.warmup(); + } catch (err) { + log.error('error warmup'); + } if (warmup) { log.state('passed: warmup:', config.warmup); - log.data(' result: face:', warmup.face.length, 'body:', warmup.body.length, 'hand:', warmup.hand.length, 'gesture:', warmup.gesture.length, 'object:', warmup.object.length); - log.data(' result: performance:', 'load:', warmup.performance.load, 'total:', warmup.performance.total); + log.data(' result: face:', warmup.face?.length, 'body:', warmup.body?.length, 'hand:', warmup.hand?.length, 'gesture:', warmup.gesture?.length, 'object:', warmup.object?.length); + log.data(' result: performance:', 'load:', warmup.performance?.load, 'total:', warmup.performance?.total); } else { log.error('failed: warmup'); } const random = tf.randomNormal([1, 1024, 1024, 3]); - const detect = await human.detect(random); + let detect; + try { + detect = await human.detect(random); + } catch (err) { + log.error('error: detect', err); + } tf.dispose(random); if (detect) { log.state('passed: detect:', 'random'); - log.data(' result: face:', detect.face.length, 'body:', detect.body.length, 'hand:', detect.hand.length, 'gesture:', detect.gesture.length, 'object:', detect.object.length); - log.data(' result: performance:', 'load:', detect.performance.load, 'total:', detect.performance.total); + log.data(' result: face:', detect.face?.length, 'body:', detect.body?.length, 'hand:', detect.hand?.length, 'gesture:', detect.gesture?.length, 'object:', detect.object?.length); + log.data(' result: performance:', 'load:', detect?.performance.load, 'total:', detect.performance?.total); } else { log.error('failed: detect'); } } async function test() { - log.info('testing instance#1'); - config.warmup = 'face'; + log.info('testing instance#1 - none'); + config.warmup = 'none'; const human1 = new Human(config); await testInstance(human1); - log.info('testing instance#2'); - config.warmup = 'body'; + log.info('testing instance#2 - face'); + config.warmup = 'face'; const human2 = new Human(config); await testInstance(human2); + + log.info('testing instance#3 - body'); + config.warmup = 'body'; + const human3 = new Human(config); + await testInstance(human3); } test();