From ded141a1615119f9af99d048f40e5f182f9252a5 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 20 Sep 2021 21:59:49 -0400 Subject: [PATCH] support for dynamic backend switching --- src/human.ts | 34 ++++++++++-------- src/tfjs/backend.ts | 6 ++-- src/warmup.ts | 18 ++++++---- test/browser.html | 88 +++++++++++++++++++++++++-------------------- test/test-main.js | 6 ++-- 5 files changed, 85 insertions(+), 67 deletions(-) diff --git a/src/human.ts b/src/human.ts index 467b1aa7..17800e41 100644 --- a/src/human.ts +++ b/src/human.ts @@ -2,7 +2,7 @@ * Human main module */ -import { log, now, mergeDeep, validate, wait } from './helpers'; +import { log, now, mergeDeep, validate } from './helpers'; import { Config, defaults } from './config'; import type { Result, FaceResult, HandResult, BodyResult, ObjectResult, GestureResult, PersonResult } from './result'; import * as tf from '../dist/tfjs.esm.js'; @@ -252,7 +252,11 @@ export class Human { } /** Reset configuration to default values */ - reset = () => this.config = JSON.parse(JSON.stringify(defaults)); + reset = () => { + const currentBackend = this.config.backend; // save backend; + this.config = JSON.parse(JSON.stringify(defaults)); + this.config.backend = currentBackend; + } /** Validate current configuration schema */ validate = (userConfig?: Partial) => validate(defaults, userConfig || this.config); @@ -314,12 +318,13 @@ export class Human { /** Explicit backend initialization * - Normally done implicitly during initial load phase * - Call to explictly register and initialize TFJS backend without any other operations - * - Used in webworker environments where there can be multiple instances of Human and not all initialized + * - Use when changing backend during runtime * * @return Promise */ - init() { - backend.check(this); + async init() { + await backend.check(this, true); + await this.tf.ready(); env.set(this.env); } @@ -396,7 +401,7 @@ export class Human { */ async detect(input: Input, userConfig?: Partial): Promise { // detection happens inside a promise - if (this.config.yield) await wait(1); + this.state = 'detect'; return new Promise(async (resolve) => { this.state = 'config'; let timeStamp; @@ -421,8 +426,8 @@ export class Human { // load models if enabled await this.load(); - if (this.config.yield) await wait(1); timeStamp = now(); + this.state = 'image'; let img = image.process(input, this.config); this.process = img; this.performance.image = Math.trunc(now() - timeStamp); @@ -431,7 +436,7 @@ export class Human { // run segmentation prethis.processing if (this.config.segmentation.enabled && this.process && img.tensor && img.canvas) { this.analyze('Start Segmentation:'); - this.state = 'run:segmentation'; + this.state = 'detect:segmentation'; timeStamp = now(); await segmentation.predict(img); elapsedTime = Math.trunc(now() - timeStamp); @@ -468,7 +473,7 @@ export class Human { let objectRes: ObjectResult[] | Promise | never[] = []; // run face detection followed by all models that rely on face bounding box: face mesh, age, gender, emotion - this.state = 'run:face'; + this.state = 'detect:face'; if (this.config.async) { faceRes = this.config.face.enabled ? face.detectFace(this, img.tensor) : []; if (this.performance.face) delete this.performance.face; @@ -481,7 +486,7 @@ export class Human { // run body: can be posenet, blazepose, efficientpose, movenet this.analyze('Start Body:'); - this.state = 'run:body'; + this.state = 'detect:body'; if (this.config.async) { if (this.config.body.modelPath?.includes('posenet')) bodyRes = this.config.body.enabled ? posenet.predict(img.tensor, this.config) : []; else if (this.config.body.modelPath?.includes('blazepose')) bodyRes = this.config.body.enabled ? blazepose.predict(img.tensor, this.config) : []; @@ -501,7 +506,7 @@ export class Human { // run handpose this.analyze('Start Hand:'); - this.state = 'run:hand'; + this.state = 'detect:hand'; if (this.config.async) { handRes = this.config.hand.enabled ? handpose.predict(img.tensor, this.config) : []; if (this.performance.hand) delete this.performance.hand; @@ -515,7 +520,7 @@ export class Human { // run nanodet this.analyze('Start Object:'); - this.state = 'run:object'; + this.state = 'detect:object'; if (this.config.async) { if (this.config.object.modelPath?.includes('nanodet')) objectRes = this.config.object.enabled ? nanodet.predict(img.tensor, this.config) : []; else if (this.config.object.modelPath?.includes('centernet')) objectRes = this.config.object.enabled ? centernet.predict(img.tensor, this.config) : []; @@ -530,12 +535,11 @@ export class Human { this.analyze('End Object:'); // if async wait for results - this.state = 'run:await'; - if (this.config.yield) await wait(1); + this.state = 'detect:await'; if (this.config.async) [faceRes, bodyRes, handRes, objectRes] = await Promise.all([faceRes, bodyRes, handRes, objectRes]); // run gesture analysis last - this.state = 'run:gesture'; + this.state = 'detect:gesture'; let gestureRes: GestureResult[] = []; if (this.config.gesture.enabled) { timeStamp = now(); diff --git a/src/tfjs/backend.ts b/src/tfjs/backend.ts index bc86bda2..d818541e 100644 --- a/src/tfjs/backend.ts +++ b/src/tfjs/backend.ts @@ -3,10 +3,10 @@ import * as humangl from './humangl'; import * as env from '../env'; import * as tf from '../../dist/tfjs.esm.js'; -export async function check(instance) { - if (env.env.initial || (instance.config.backend && (instance.config.backend.length > 0) && (tf.getBackend() !== instance.config.backend))) { +export async function check(instance, force = false) { + instance.state = 'backend'; + if (force || env.env.initial || (instance.config.backend && (instance.config.backend.length > 0) && (tf.getBackend() !== instance.config.backend))) { const timeStamp = now(); - instance.state = 'backend'; if (instance.config.backend && instance.config.backend.length > 0) { // detect web worker diff --git a/src/warmup.ts b/src/warmup.ts index 7ff04918..60e094fa 100644 --- a/src/warmup.ts +++ b/src/warmup.ts @@ -12,6 +12,7 @@ async function warmupBitmap(instance) { let res; switch (instance.config.warmup) { case 'face': blob = await b64toBlob(sample.face); break; + case 'body': case 'full': blob = await b64toBlob(sample.body); break; default: blob = null; } @@ -98,14 +99,17 @@ async function warmupNode(instance) { */ export async function warmup(instance, userConfig?: Partial): Promise { const t0 = now(); + instance.state = 'warmup'; if (userConfig) instance.config = mergeDeep(instance.config, userConfig) as Config; if (!instance.config.warmup || instance.config.warmup === 'none') return { error: 'null' }; let res; - if (typeof createImageBitmap === 'function') res = await warmupBitmap(instance); - else if (typeof Image !== 'undefined' || env.Canvas !== undefined) res = await warmupCanvas(instance); - else res = await warmupNode(instance); - const t1 = now(); - if (instance.config.debug) log('Warmup', instance.config.warmup, Math.round(t1 - t0), 'ms'); - instance.emit('warmup'); - return res; + return new Promise(async (resolve) => { + if (typeof createImageBitmap === 'function') res = await warmupBitmap(instance); + else if (typeof Image !== 'undefined' || env.Canvas !== undefined) res = await warmupCanvas(instance); + else res = await warmupNode(instance); + const t1 = now(); + if (instance.config.debug) log('Warmup', instance.config.warmup, Math.round(t1 - t0), 'ms'); + instance.emit('warmup'); + resolve(res); + }); } diff --git a/test/browser.html b/test/browser.html index 415dd2aa..0136dbb9 100644 --- a/test/browser.html +++ b/test/browser.html @@ -14,28 +14,30 @@ @font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../../assets/lato-light.woff2') } html { font-family: 'Lato', 'Segoe UI'; font-size: 14px; font-variant: small-caps; } body { margin: 0; background: black; color: white; } - canvas { position: absolute; bottom: 10px; right: 10px; width: 256px; height: 256px; } - pre { line-height: 150%; } - .events { position: absolute; top: 10px; right: 10px; width: 12rem; height: 1.25rem; background-color: grey; padding: 8px; } - .state { position: absolute; top: 60px; right: 10px; width: 12rem; height: 1.25rem; background-color: grey; padding: 8px; } + .canvas { position: absolute; bottom: 10px; right: 10px; width: 256px; height: 256px; z-index: 99; } + .pre { line-height: 150%; } + .events { position: absolute; top: 10px; right: 10px; width: 12rem; height: 1.25rem; background-color: grey; padding: 8px; z-index: 99; } + .state { position: absolute; top: 60px; right: 10px; width: 12rem; height: 1.25rem; background-color: grey; padding: 8px; z-index: 99; } -

+    

     
+