2021-09-25 17:51:15 +02:00
|
|
|
/**
|
2021-12-28 10:34:24 +01:00
|
|
|
* Warmup algorithm that uses embedded images to exercise loaded models for faster future inference
|
2021-09-25 17:51:15 +02:00
|
|
|
*/
|
|
|
|
|
2021-09-27 19:58:13 +02:00
|
|
|
import { log, now, mergeDeep } from './util/util';
|
2021-09-13 00:37:06 +02:00
|
|
|
import * as sample from './sample';
|
|
|
|
import * as tf from '../dist/tfjs.esm.js';
|
2021-09-13 19:28:35 +02:00
|
|
|
import * as image from './image/image';
|
2022-03-19 16:02:30 +01:00
|
|
|
import { env } from './util/env';
|
2021-09-13 19:28:35 +02:00
|
|
|
import type { Config } from './config';
|
|
|
|
import type { Result } from './result';
|
2022-03-19 16:02:30 +01:00
|
|
|
import type { Human, Models } from './human';
|
2022-04-15 13:54:27 +02:00
|
|
|
import type { Tensor } from './tfjs/types';
|
2021-09-13 00:37:06 +02:00
|
|
|
|
2022-03-19 16:02:30 +01:00
|
|
|
async function warmupBitmap(instance: Human): Promise<Result | undefined> {
|
2021-09-13 00:37:06 +02:00
|
|
|
const b64toBlob = (base64: string, type = 'application/octet-stream') => fetch(`data:${type};base64,${base64}`).then((res) => res.blob());
|
|
|
|
let blob;
|
|
|
|
let res;
|
|
|
|
switch (instance.config.warmup) {
|
|
|
|
case 'face': blob = await b64toBlob(sample.face); break;
|
2021-09-21 03:59:49 +02:00
|
|
|
case 'body':
|
2021-09-13 00:37:06 +02:00
|
|
|
case 'full': blob = await b64toBlob(sample.body); break;
|
|
|
|
default: blob = null;
|
|
|
|
}
|
|
|
|
if (blob) {
|
|
|
|
const bitmap = await createImageBitmap(blob);
|
|
|
|
res = await instance.detect(bitmap, instance.config);
|
|
|
|
bitmap.close();
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2021-12-27 16:59:56 +01:00
|
|
|
async function warmupCanvas(instance: Human): Promise<Result | undefined> {
|
2021-09-13 00:37:06 +02:00
|
|
|
return new Promise((resolve) => {
|
|
|
|
let src;
|
2021-09-13 19:28:35 +02:00
|
|
|
// let size = 0;
|
2021-09-13 00:37:06 +02:00
|
|
|
switch (instance.config.warmup) {
|
|
|
|
case 'face':
|
2021-09-13 19:28:35 +02:00
|
|
|
// size = 256;
|
2021-09-13 00:37:06 +02:00
|
|
|
src = 'data:image/jpeg;base64,' + sample.face;
|
|
|
|
break;
|
|
|
|
case 'full':
|
|
|
|
case 'body':
|
2021-09-13 19:28:35 +02:00
|
|
|
// size = 1200;
|
2021-09-13 00:37:06 +02:00
|
|
|
src = 'data:image/jpeg;base64,' + sample.body;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
src = null;
|
|
|
|
}
|
|
|
|
// src = encodeURI('../assets/human-sample-upper.jpg');
|
2021-11-17 22:50:21 +01:00
|
|
|
let img: HTMLImageElement;
|
2021-09-13 19:28:35 +02:00
|
|
|
if (typeof Image !== 'undefined') img = new Image();
|
|
|
|
// @ts-ignore env.image is an external monkey-patch
|
|
|
|
else if (env.Image) img = new env.Image();
|
2021-11-17 22:50:21 +01:00
|
|
|
else return;
|
2021-09-13 00:37:06 +02:00
|
|
|
img.onload = async () => {
|
2021-09-13 19:28:35 +02:00
|
|
|
const canvas = image.canvas(img.naturalWidth, img.naturalHeight);
|
|
|
|
if (!canvas) {
|
|
|
|
log('Warmup: Canvas not found');
|
2021-12-27 16:59:56 +01:00
|
|
|
resolve(undefined);
|
2021-09-13 19:28:35 +02:00
|
|
|
} else {
|
|
|
|
const ctx = canvas.getContext('2d');
|
2021-09-17 20:30:57 +02:00
|
|
|
if (ctx) ctx.drawImage(img, 0, 0);
|
2021-09-13 19:28:35 +02:00
|
|
|
// const data = ctx?.getImageData(0, 0, canvas.height, canvas.width);
|
|
|
|
const tensor = await instance.image(canvas);
|
2021-11-12 21:07:23 +01:00
|
|
|
const res = await instance.detect(tensor.tensor as Tensor, instance.config);
|
2021-09-13 19:28:35 +02:00
|
|
|
resolve(res);
|
|
|
|
}
|
2021-09-13 00:37:06 +02:00
|
|
|
};
|
|
|
|
if (src) img.src = src;
|
2021-12-27 16:59:56 +01:00
|
|
|
else resolve(undefined);
|
2021-09-13 00:37:06 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-12-27 16:59:56 +01:00
|
|
|
async function warmupNode(instance: Human): Promise<Result | undefined> {
|
2021-09-13 00:37:06 +02:00
|
|
|
const atob = (str: string) => Buffer.from(str, 'base64');
|
|
|
|
let img;
|
|
|
|
if (instance.config.warmup === 'face') img = atob(sample.face);
|
2021-12-27 16:59:56 +01:00
|
|
|
else img = atob(sample.body);
|
2021-09-13 00:37:06 +02:00
|
|
|
let res;
|
2021-12-27 16:59:56 +01:00
|
|
|
if ('node' in tf) {
|
|
|
|
// @ts-ignore tf.node may be undefined
|
2021-09-13 00:37:06 +02:00
|
|
|
const data = tf['node'].decodeJpeg(img);
|
|
|
|
const expanded = data.expandDims(0);
|
|
|
|
instance.tf.dispose(data);
|
|
|
|
// log('Input:', expanded);
|
|
|
|
res = await instance.detect(expanded, instance.config);
|
|
|
|
instance.tf.dispose(expanded);
|
|
|
|
} else {
|
|
|
|
if (instance.config.debug) log('Warmup tfjs-node not loaded');
|
|
|
|
/*
|
|
|
|
const input = await canvasJS.loadImage(img);
|
|
|
|
const canvas = canvasJS.createCanvas(input.width, input.height);
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
ctx.drawImage(img, 0, 0, input.width, input.height);
|
|
|
|
res = await instance.detect(input, instance.config);
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2022-03-19 16:02:30 +01:00
|
|
|
async function runInference(instance: Human) {
|
|
|
|
let res: Result | undefined;
|
|
|
|
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);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Runs pre-compile on all loaded models */
|
|
|
|
export async function runCompile(allModels: Models) {
|
2022-08-04 15:15:13 +02:00
|
|
|
if (!tf.env().flagRegistry['ENGINE_COMPILE_ONLY']) return; // tfjs does not support compile-only inference
|
2022-03-19 16:02:30 +01:00
|
|
|
const backendType = tf.getBackend();
|
|
|
|
const webGLBackend = tf.backend();
|
|
|
|
if ((backendType !== 'webgl' && backendType !== 'humangl') || (!webGLBackend || !webGLBackend.checkCompileCompletion)) {
|
2022-04-15 13:54:27 +02:00
|
|
|
// log('compile pass: skip');
|
2022-03-19 16:02:30 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
tf.env().set('ENGINE_COMPILE_ONLY', true);
|
|
|
|
const numTensorsStart = tf.engine().state.numTensors;
|
2022-04-15 13:54:27 +02:00
|
|
|
const compiledModels: string[] = [];
|
|
|
|
for (const [modelName, model] of Object.entries(allModels).filter(([key, val]) => (key !== null && val !== null))) {
|
2022-03-19 16:02:30 +01:00
|
|
|
const shape = (model.inputs && model.inputs[0] && model.inputs[0].shape) ? [...model.inputs[0].shape] : [1, 64, 64, 3];
|
|
|
|
const dtype = (model.inputs && model.inputs[0] && model.inputs[0].dtype) ? model.inputs[0].dtype : 'float32';
|
|
|
|
for (let dim = 0; dim < shape.length; dim++) {
|
|
|
|
if (shape[dim] === -1) shape[dim] = dim === 0 ? 1 : 64; // override batch number and any dynamic dimensions
|
|
|
|
}
|
|
|
|
const tensor = tf.zeros(shape, dtype);
|
2022-04-15 13:54:27 +02:00
|
|
|
// const res = await model.executeAsync(tensor); // fails with current tfjs
|
|
|
|
try {
|
|
|
|
const res = model.execute(tensor);
|
|
|
|
compiledModels.push(modelName);
|
|
|
|
if (Array.isArray(res)) res.forEach((t) => tf.dispose(t));
|
|
|
|
else tf.dispose(res);
|
|
|
|
} catch {
|
|
|
|
log('compile fail model:', modelName);
|
|
|
|
}
|
2022-03-19 16:02:30 +01:00
|
|
|
tf.dispose(tensor);
|
|
|
|
}
|
|
|
|
const kernels = await webGLBackend.checkCompileCompletionAsync();
|
|
|
|
webGLBackend.getUniformLocations();
|
2022-04-15 13:54:27 +02:00
|
|
|
log('compile pass models:', compiledModels);
|
2022-03-19 16:02:30 +01:00
|
|
|
log('compile pass kernels:', kernels.length);
|
|
|
|
tf.env().set('ENGINE_COMPILE_ONLY', false);
|
|
|
|
const numTensorsEnd = tf.engine().state.numTensors;
|
|
|
|
if ((numTensorsEnd - numTensorsStart) > 0) log('tensor leak:', numTensorsEnd - numTensorsStart);
|
|
|
|
}
|
|
|
|
|
2021-09-13 00:37:06 +02:00
|
|
|
/** Warmup method pre-initializes all configured models for faster inference
|
|
|
|
* - can take significant time on startup
|
2022-04-15 13:54:27 +02:00
|
|
|
* - only used in browser environments for `webgl` and `humangl` backends
|
2021-09-13 00:37:06 +02:00
|
|
|
* @param userConfig?: Config
|
|
|
|
*/
|
2021-12-27 16:59:56 +01:00
|
|
|
export async function warmup(instance: Human, userConfig?: Partial<Config>): Promise<Result | undefined> {
|
2021-09-13 00:37:06 +02:00
|
|
|
const t0 = now();
|
2021-09-21 03:59:49 +02:00
|
|
|
instance.state = 'warmup';
|
2021-09-13 00:37:06 +02:00
|
|
|
if (userConfig) instance.config = mergeDeep(instance.config, userConfig) as Config;
|
2021-11-17 22:50:21 +01:00
|
|
|
if (!instance.config.warmup || instance.config.warmup.length === 0 || instance.config.warmup === 'none') {
|
|
|
|
return { face: [], body: [], hand: [], gesture: [], object: [], performance: instance.performance, timestamp: now(), persons: [], error: null };
|
|
|
|
}
|
2021-09-21 03:59:49 +02:00
|
|
|
return new Promise(async (resolve) => {
|
2022-04-15 13:54:27 +02:00
|
|
|
await runCompile(instance.models);
|
2022-03-19 16:02:30 +01:00
|
|
|
const res = await runInference(instance);
|
2021-09-21 03:59:49 +02:00
|
|
|
const t1 = now();
|
2022-03-19 16:02:30 +01:00
|
|
|
if (instance.config.debug) log('warmup', instance.config.warmup, Math.round(t1 - t0), 'ms');
|
2021-09-21 03:59:49 +02:00
|
|
|
instance.emit('warmup');
|
|
|
|
resolve(res);
|
|
|
|
});
|
2021-09-13 00:37:06 +02:00
|
|
|
}
|