added wasm and webgpu backends

pull/50/head
Vladimir Mandic 2020-10-30 10:23:49 -04:00
parent 5ae0cce05a
commit 254deb9c59
27 changed files with 89542 additions and 160 deletions

View File

@ -9,7 +9,8 @@
- [**Change Log**](./CHANGELOG.md) - [**Change Log**](./CHANGELOG.md)
- [**Live Demo**](https://vladmandic.github.io/human/demo/index.html) - [**Live Demo**](https://vladmandic.github.io/human/demo/index.html)
Compatible with Browser, WebWorker and NodeJS execution! Compatible with *Browser*, *WebWorker* and *NodeJS* execution
Compatible with *CPU*, *WebGL*, *WASM* and *WebGPU* backends
(and maybe with React-Native as it doesn't use any DOM objects) (and maybe with React-Native as it doesn't use any DOM objects)
*This is a pre-release project, see [issues](https://github.com/vladmandic/human/issues) for list of known limitations and planned enhancements* *This is a pre-release project, see [issues](https://github.com/vladmandic/human/issues) for list of known limitations and planned enhancements*
@ -159,10 +160,12 @@ If your application resides in a different folder, modify `modelPath` property i
Demos are included in `/demo`: Demos are included in `/demo`:
Browser: **Browser**:
- `index.html`, `browser.js`, `worker.js`: Full demo using Browser with ESM module, includes selectable backends and webworkers - `index.html`, `browser.js`, `worker.js`: Full demo using Browser with ESM module, includes selectable backends and webworkers
NodeJS: *If you want to test `wasm` or `webgpu` backends, enable loading in `index.html`*
**NodeJS**:
- `node.js`: Demo using NodeJS with CommonJS module - `node.js`: Demo using NodeJS with CommonJS module
This is a very simple demo as althought `Human` library is compatible with NodeJS execution This is a very simple demo as althought `Human` library is compatible with NodeJS execution
and is able to load images and models from local filesystem, and is able to load images and models from local filesystem,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2229
assets/tf-backend-webgpu.js Normal file

File diff suppressed because one or more lines are too long

82910
assets/tf.es2017.js Normal file

File diff suppressed because it is too large Load Diff

18
assets/tf.min.js vendored

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

View File

@ -29,7 +29,8 @@ const ui = {
// configuration overrides // configuration overrides
const config = { const config = {
backend: 'webgl', // if you want to use 'wasm' backend, enable script load of tf and tf-backend-wasm in index.html backend: 'webgl',
wasm: { path: '../assets' },
filter: { filter: {
enabled: true, enabled: true,
width: 0, width: 0,
@ -108,7 +109,7 @@ function drawResults(input, result, canvas) {
// update log // update log
const engine = human.tf.engine(); const engine = human.tf.engine();
const memory = `${engine.state.numBytes.toLocaleString()} bytes ${engine.state.numDataBuffers.toLocaleString()} buffers ${engine.state.numTensors.toLocaleString()} tensors`; const memory = `${engine.state.numBytes.toLocaleString()} bytes ${engine.state.numDataBuffers.toLocaleString()} buffers ${engine.state.numTensors.toLocaleString()} tensors`;
const gpu = engine.backendInstance ? `GPU: ${engine.backendInstance.numBytesInGPU.toLocaleString()} bytes` : ''; const gpu = engine.backendInstance ? `GPU: ${(engine.backendInstance.numBytesInGPU ? engine.backendInstance.numBytesInGPU : 0).toLocaleString()} bytes` : '';
document.getElementById('log').innerText = ` document.getElementById('log').innerText = `
TFJS Version: ${human.tf.version_core} | Backend: ${human.tf.getBackend()} | Memory: ${memory} ${gpu} TFJS Version: ${human.tf.version_core} | Backend: ${human.tf.getBackend()} | Memory: ${memory} ${gpu}
Performance: ${str(result.performance)} | Object size: ${(str(result)).length.toLocaleString()} bytes Performance: ${str(result.performance)} | Object size: ${(str(result)).length.toLocaleString()} bytes
@ -267,6 +268,7 @@ function setupMenu() {
menu.addButton('Process Images', 'Process Images', () => detectSampleImages()); menu.addButton('Process Images', 'Process Images', () => detectSampleImages());
menu.addHTML('<hr style="min-width: 200px; border-style: inset; border-color: dimgray">'); menu.addHTML('<hr style="min-width: 200px; border-style: inset; border-color: dimgray">');
menu.addList('Backend', ['cpu', 'webgl', 'wasm', 'webgpu'], config.backend, (val) => config.backend = val);
menu.addBool('Use Web Worker', ui, 'useWorker'); menu.addBool('Use Web Worker', ui, 'useWorker');
menu.addHTML('<hr style="min-width: 200px; border-style: inset; border-color: dimgray">'); menu.addHTML('<hr style="min-width: 200px; border-style: inset; border-color: dimgray">');
menu.addLabel('Enabled Models'); menu.addLabel('Enabled Models');

View File

@ -12,8 +12,9 @@
<meta name="msapplication-tooltip" content="Human: AI-powered 3D Human Detection"> <meta name="msapplication-tooltip" content="Human: AI-powered 3D Human Detection">
<link rel="manifest" href="../dist/human.esm.json"> <link rel="manifest" href="../dist/human.esm.json">
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
<!-- <script src="../assets/tf.min.js"></script> --> <!-- <script src="../assets/tf.es2017.js"></script> -->
<!-- <script src="../assets/tf-backend-wasm.min.js"></script> --> <!-- <script src="../assets/tf-backend-wasm.es2017.js"></script> -->
<!-- <script src="../assets/tf-backend-webgpu.js"></script> -->
<script src="./browser.js" type="module"></script> <script src="./browser.js" type="module"></script>
</head> </head>
<body style="margin: 0; background: black; color: white; font-family: 'Segoe UI'; font-size: 16px; font-variant: small-caps; overflow-x: hidden"> <body style="margin: 0; background: black; color: white; font-family: 'Segoe UI'; font-size: 16px; font-variant: small-caps; overflow-x: hidden">

View File

@ -8,9 +8,13 @@ const css = `
.menu-container-fadein { max-height: 100vh; overflow: hidden; transition: max-height, 0.5s ease; } .menu-container-fadein { max-height: 100vh; overflow: hidden; transition: max-height, 0.5s ease; }
.menu-item { display: flex; white-space: nowrap; background: darkslategray; padding: 0.2rem; width: max-content; } .menu-item { display: flex; white-space: nowrap; background: darkslategray; padding: 0.2rem; width: max-content; }
.menu-title { text-align: right; cursor: pointer; } .menu-title { text-align: right; cursor: pointer; }
.menu-hr { margin: 0.2rem; border: 1px solid rgba(0, 0, 0, 0.5) } .menu-hr { margin: 0.2rem; border: 1px solid rgba(0, 0, 0, 0.5); }
.menu-label { padding: 0; } .menu-label { padding: 0; }
.menu-list { margin-right: 0.8rem; }
select:focus { outline: none; }
.menu-list-item { background: black; color: white; border: none; padding: 0.2rem; font-family: inherit; font-variant: inherit; border-radius: 1rem; }
.menu-chart-title { align-items: center; } .menu-chart-title { align-items: center; }
.menu-chart-canvas { background: transparent; height: 40px; width: 180px; margin: 0.2rem 0.2rem 0.2rem 1rem; } .menu-chart-canvas { background: transparent; height: 40px; width: 180px; margin: 0.2rem 0.2rem 0.2rem 1rem; }
@ -130,6 +134,24 @@ class Menu {
}); });
} }
async addList(title, items, selected, callback) {
const el = document.createElement('div');
el.className = 'menu-item';
let options = '';
for (const item of items) {
const def = item === selected ? 'selected' : '';
options += `<option value="${item}" ${def}>${item}</option>`;
}
el.innerHTML = `<div class="menu-list"><select name="${this.ID}" class="menu-list-item">${options}</select><label for="${this.ID}"></label></div>${title}`;
el.style.fontFamily = document.body.style.fontFamily;
el.style.fontSize = document.body.style.fontSize;
el.style.fontVariant = document.body.style.fontVariant;
this.container.appendChild(el);
el.addEventListener('change', (evt) => {
if (callback) callback(items[evt.target.selectedIndex]);
});
}
async addRange(title, object, variable, min, max, step, callback) { async addRange(title, object, variable, min, max, step, callback) {
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'menu-item'; el.className = 'menu-item';

View File

@ -5777,7 +5777,7 @@ var require_config = __commonJS((exports) => {
var require_package = __commonJS((exports, module) => { var require_package = __commonJS((exports, module) => {
module.exports = { module.exports = {
name: "@vladmandic/human", name: "@vladmandic/human",
version: "0.4.10", version: "0.5.1",
description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction", description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction",
sideEffects: false, sideEffects: false,
main: "dist/human.node.js", main: "dist/human.node.js",
@ -5799,10 +5799,9 @@ var require_package = __commonJS((exports, module) => {
dependencies: {}, dependencies: {},
peerDependencies: {}, peerDependencies: {},
devDependencies: { devDependencies: {
seedrandom: "^3.0.5",
"@tensorflow/tfjs": "^2.7.0", "@tensorflow/tfjs": "^2.7.0",
"@tensorflow/tfjs-node": "^2.7.0", "@tensorflow/tfjs-node": "^2.7.0",
"@vladmandic/pilogger": "^0.2.6", "@vladmandic/pilogger": "^0.2.7",
dayjs: "^1.9.4", dayjs: "^1.9.4",
esbuild: "^0.7.22", esbuild: "^0.7.22",
eslint: "^7.12.1", eslint: "^7.12.1",
@ -5812,6 +5811,7 @@ var require_package = __commonJS((exports, module) => {
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
rimraf: "^3.0.2", rimraf: "^3.0.2",
seedrandom: "^3.0.5",
"simple-git": "^2.21.0" "simple-git": "^2.21.0"
}, },
scripts: { scripts: {
@ -5958,17 +5958,33 @@ class Human {
this.models.emotion = await emotion.load(this.config); this.models.emotion = await emotion.load(this.config);
} }
} }
async resetBackend(backendName) {
if (tf.getBackend() !== this.config.backend) {
this.state = "backend";
if (backendName in tf.engine().registry) {
this.log("Human library setting backend:", this.config.backend);
this.config.backend = backendName;
const backendFactory = tf.findBackendFactory(backendName);
tf.removeBackend(backendName);
tf.registerBackend(backendName, backendFactory);
await tf.setBackend(backendName);
await tf.ready();
} else {
this.log("Human library backend not registred:", backendName);
}
}
}
tfImage(input) { tfImage(input) {
let filtered; let filtered;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0; const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0;
const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0; const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0;
let targetWidth = originalWidth; let targetWidth = originalWidth;
let targetHeight = originalHeight;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
if (this.config.filter.width > 0) if (this.config.filter.width > 0)
targetWidth = this.config.filter.width; targetWidth = this.config.filter.width;
else if (this.config.filter.height > 0) else if (this.config.filter.height > 0)
targetWidth = originalWidth * (this.config.filter.height / originalHeight); targetWidth = originalWidth * (this.config.filter.height / originalHeight);
let targetHeight = originalHeight;
if (this.config.filter.height > 0) if (this.config.filter.height > 0)
targetHeight = this.config.filter.height; targetHeight = this.config.filter.height;
else if (this.config.filter.width > 0) else if (this.config.filter.width > 0)
@ -6015,7 +6031,19 @@ class Human {
if (input instanceof tf.Tensor) { if (input instanceof tf.Tensor) {
tensor = tf.clone(input); tensor = tf.clone(input);
} else { } else {
const pixels = tf.browser.fromPixels(filtered || input); const canvas = filtered || input;
let pixels;
if (this.config.backend === "webgl" || canvas instanceof ImageData)
pixels = tf.browser.fromPixels(canvas);
else {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(canvas, 0, 0);
const data = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf.browser.fromPixels(data);
}
const casted = pixels.toFloat(); const casted = pixels.toFloat();
tensor = casted.expandDims(0); tensor = casted.expandDims(0);
pixels.dispose(); pixels.dispose();
@ -6039,12 +6067,7 @@ class Human {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const timeStart = now(); const timeStart = now();
timeStamp = now(); timeStamp = now();
if (tf.getBackend() !== this.config.backend) { await this.resetBackend(this.config.backend);
this.state = "backend";
this.log("Human library setting backend:", this.config.backend);
await tf.setBackend(this.config.backend);
await tf.ready();
}
perf.backend = Math.trunc(now() - timeStamp); perf.backend = Math.trunc(now() - timeStamp);
if (first) { if (first) {
this.log("Human library starting"); this.log("Human library starting");

File diff suppressed because one or more lines are too long

View File

@ -5,7 +5,7 @@
"imports": [] "imports": []
}, },
"package.json": { "package.json": {
"bytes": 2871, "bytes": 2870,
"imports": [] "imports": []
}, },
"src/emotion/emotion.js": { "src/emotion/emotion.js": {
@ -116,7 +116,7 @@
"imports": [] "imports": []
}, },
"src/human.js": { "src/human.js": {
"bytes": 11908, "bytes": 13144,
"imports": [ "imports": [
{ {
"path": "src/facemesh/facemesh.js" "path": "src/facemesh/facemesh.js"
@ -260,7 +260,7 @@
"dist/human.esm-nobundle.js.map": { "dist/human.esm-nobundle.js.map": {
"imports": [], "imports": [],
"inputs": {}, "inputs": {},
"bytes": 248630 "bytes": 250346
}, },
"dist/human.esm-nobundle.js": { "dist/human.esm-nobundle.js": {
"imports": [], "imports": [],
@ -353,16 +353,16 @@
"bytesInOutput": 2230 "bytesInOutput": 2230
}, },
"package.json": { "package.json": {
"bytesInOutput": 3013 "bytesInOutput": 3012
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 10775 "bytesInOutput": 11781
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 0 "bytesInOutput": 0
} }
}, },
"bytes": 155239 "bytes": 156244
} }
} }
} }

47
dist/human.esm.js vendored
View File

@ -72826,7 +72826,7 @@ var require_config = __commonJS((exports) => {
var require_package = __commonJS((exports, module) => { var require_package = __commonJS((exports, module) => {
module.exports = { module.exports = {
name: "@vladmandic/human", name: "@vladmandic/human",
version: "0.4.10", version: "0.5.1",
description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction", description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction",
sideEffects: false, sideEffects: false,
main: "dist/human.node.js", main: "dist/human.node.js",
@ -72848,10 +72848,9 @@ var require_package = __commonJS((exports, module) => {
dependencies: {}, dependencies: {},
peerDependencies: {}, peerDependencies: {},
devDependencies: { devDependencies: {
seedrandom: "^3.0.5",
"@tensorflow/tfjs": "^2.7.0", "@tensorflow/tfjs": "^2.7.0",
"@tensorflow/tfjs-node": "^2.7.0", "@tensorflow/tfjs-node": "^2.7.0",
"@vladmandic/pilogger": "^0.2.6", "@vladmandic/pilogger": "^0.2.7",
dayjs: "^1.9.4", dayjs: "^1.9.4",
esbuild: "^0.7.22", esbuild: "^0.7.22",
eslint: "^7.12.1", eslint: "^7.12.1",
@ -72861,6 +72860,7 @@ var require_package = __commonJS((exports, module) => {
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
rimraf: "^3.0.2", rimraf: "^3.0.2",
seedrandom: "^3.0.5",
"simple-git": "^2.21.0" "simple-git": "^2.21.0"
}, },
scripts: { scripts: {
@ -73007,17 +73007,33 @@ class Human {
this.models.emotion = await emotion.load(this.config); this.models.emotion = await emotion.load(this.config);
} }
} }
async resetBackend(backendName) {
if (tf.getBackend() !== this.config.backend) {
this.state = "backend";
if (backendName in tf.engine().registry) {
this.log("Human library setting backend:", this.config.backend);
this.config.backend = backendName;
const backendFactory = tf.findBackendFactory(backendName);
tf.removeBackend(backendName);
tf.registerBackend(backendName, backendFactory);
await tf.setBackend(backendName);
await tf.ready();
} else {
this.log("Human library backend not registred:", backendName);
}
}
}
tfImage(input) { tfImage(input) {
let filtered; let filtered;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0; const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0;
const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0; const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0;
let targetWidth = originalWidth; let targetWidth = originalWidth;
let targetHeight = originalHeight;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
if (this.config.filter.width > 0) if (this.config.filter.width > 0)
targetWidth = this.config.filter.width; targetWidth = this.config.filter.width;
else if (this.config.filter.height > 0) else if (this.config.filter.height > 0)
targetWidth = originalWidth * (this.config.filter.height / originalHeight); targetWidth = originalWidth * (this.config.filter.height / originalHeight);
let targetHeight = originalHeight;
if (this.config.filter.height > 0) if (this.config.filter.height > 0)
targetHeight = this.config.filter.height; targetHeight = this.config.filter.height;
else if (this.config.filter.width > 0) else if (this.config.filter.width > 0)
@ -73064,7 +73080,19 @@ class Human {
if (input instanceof tf.Tensor) { if (input instanceof tf.Tensor) {
tensor = tf.clone(input); tensor = tf.clone(input);
} else { } else {
const pixels = tf.browser.fromPixels(filtered || input); const canvas = filtered || input;
let pixels;
if (this.config.backend === "webgl" || canvas instanceof ImageData)
pixels = tf.browser.fromPixels(canvas);
else {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(canvas, 0, 0);
const data = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf.browser.fromPixels(data);
}
const casted = pixels.toFloat(); const casted = pixels.toFloat();
tensor = casted.expandDims(0); tensor = casted.expandDims(0);
pixels.dispose(); pixels.dispose();
@ -73088,12 +73116,7 @@ class Human {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const timeStart = now(); const timeStart = now();
timeStamp = now(); timeStamp = now();
if (tf.getBackend() !== this.config.backend) { await this.resetBackend(this.config.backend);
this.state = "backend";
this.log("Human library setting backend:", this.config.backend);
await tf.setBackend(this.config.backend);
await tf.ready();
}
perf.backend = Math.trunc(now() - timeStamp); perf.backend = Math.trunc(now() - timeStamp);
if (first) { if (first) {
this.log("Human library starting"); this.log("Human library starting");

File diff suppressed because one or more lines are too long

12
dist/human.esm.json vendored
View File

@ -149,7 +149,7 @@
] ]
}, },
"package.json": { "package.json": {
"bytes": 2871, "bytes": 2870,
"imports": [] "imports": []
}, },
"src/emotion/emotion.js": { "src/emotion/emotion.js": {
@ -291,7 +291,7 @@
"imports": [] "imports": []
}, },
"src/human.js": { "src/human.js": {
"bytes": 11908, "bytes": 13144,
"imports": [ "imports": [
{ {
"path": "node_modules/@tensorflow/tfjs/dist/tf.node.js" "path": "node_modules/@tensorflow/tfjs/dist/tf.node.js"
@ -468,7 +468,7 @@
"dist/human.esm.js.map": { "dist/human.esm.js.map": {
"imports": [], "imports": [],
"inputs": {}, "inputs": {},
"bytes": 5121936 "bytes": 5123652
}, },
"dist/human.esm.js": { "dist/human.esm.js": {
"imports": [], "imports": [],
@ -618,16 +618,16 @@
"bytesInOutput": 2230 "bytesInOutput": 2230
}, },
"package.json": { "package.json": {
"bytesInOutput": 3013 "bytesInOutput": 3012
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 10765 "bytesInOutput": 11771
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 0 "bytesInOutput": 0
} }
}, },
"bytes": 2924203 "bytes": 2925208
} }
} }
} }

47
dist/human.js vendored
View File

@ -72827,7 +72827,7 @@ var Human = (() => {
var require_package = __commonJS((exports, module) => { var require_package = __commonJS((exports, module) => {
module.exports = { module.exports = {
name: "@vladmandic/human", name: "@vladmandic/human",
version: "0.4.10", version: "0.5.1",
description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction", description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction",
sideEffects: false, sideEffects: false,
main: "dist/human.node.js", main: "dist/human.node.js",
@ -72849,10 +72849,9 @@ var Human = (() => {
dependencies: {}, dependencies: {},
peerDependencies: {}, peerDependencies: {},
devDependencies: { devDependencies: {
seedrandom: "^3.0.5",
"@tensorflow/tfjs": "^2.7.0", "@tensorflow/tfjs": "^2.7.0",
"@tensorflow/tfjs-node": "^2.7.0", "@tensorflow/tfjs-node": "^2.7.0",
"@vladmandic/pilogger": "^0.2.6", "@vladmandic/pilogger": "^0.2.7",
dayjs: "^1.9.4", dayjs: "^1.9.4",
esbuild: "^0.7.22", esbuild: "^0.7.22",
eslint: "^7.12.1", eslint: "^7.12.1",
@ -72862,6 +72861,7 @@ var Human = (() => {
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
rimraf: "^3.0.2", rimraf: "^3.0.2",
seedrandom: "^3.0.5",
"simple-git": "^2.21.0" "simple-git": "^2.21.0"
}, },
scripts: { scripts: {
@ -73012,17 +73012,33 @@ var Human = (() => {
this.models.emotion = await emotion.load(this.config); this.models.emotion = await emotion.load(this.config);
} }
} }
async resetBackend(backendName) {
if (tf.getBackend() !== this.config.backend) {
this.state = "backend";
if (backendName in tf.engine().registry) {
this.log("Human library setting backend:", this.config.backend);
this.config.backend = backendName;
const backendFactory = tf.findBackendFactory(backendName);
tf.removeBackend(backendName);
tf.registerBackend(backendName, backendFactory);
await tf.setBackend(backendName);
await tf.ready();
} else {
this.log("Human library backend not registred:", backendName);
}
}
}
tfImage(input) { tfImage(input) {
let filtered; let filtered;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0; const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0;
const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0; const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0;
let targetWidth = originalWidth; let targetWidth = originalWidth;
let targetHeight = originalHeight;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
if (this.config.filter.width > 0) if (this.config.filter.width > 0)
targetWidth = this.config.filter.width; targetWidth = this.config.filter.width;
else if (this.config.filter.height > 0) else if (this.config.filter.height > 0)
targetWidth = originalWidth * (this.config.filter.height / originalHeight); targetWidth = originalWidth * (this.config.filter.height / originalHeight);
let targetHeight = originalHeight;
if (this.config.filter.height > 0) if (this.config.filter.height > 0)
targetHeight = this.config.filter.height; targetHeight = this.config.filter.height;
else if (this.config.filter.width > 0) else if (this.config.filter.width > 0)
@ -73069,7 +73085,19 @@ var Human = (() => {
if (input instanceof tf.Tensor) { if (input instanceof tf.Tensor) {
tensor = tf.clone(input); tensor = tf.clone(input);
} else { } else {
const pixels = tf.browser.fromPixels(filtered || input); const canvas = filtered || input;
let pixels;
if (this.config.backend === "webgl" || canvas instanceof ImageData)
pixels = tf.browser.fromPixels(canvas);
else {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(canvas, 0, 0);
const data = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf.browser.fromPixels(data);
}
const casted = pixels.toFloat(); const casted = pixels.toFloat();
tensor = casted.expandDims(0); tensor = casted.expandDims(0);
pixels.dispose(); pixels.dispose();
@ -73093,12 +73121,7 @@ var Human = (() => {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const timeStart = now(); const timeStart = now();
timeStamp = now(); timeStamp = now();
if (tf.getBackend() !== this.config.backend) { await this.resetBackend(this.config.backend);
this.state = "backend";
this.log("Human library setting backend:", this.config.backend);
await tf.setBackend(this.config.backend);
await tf.ready();
}
perf.backend = Math.trunc(now() - timeStamp); perf.backend = Math.trunc(now() - timeStamp);
if (first) { if (first) {
this.log("Human library starting"); this.log("Human library starting");

4
dist/human.js.map vendored

File diff suppressed because one or more lines are too long

12
dist/human.json vendored
View File

@ -149,7 +149,7 @@
] ]
}, },
"package.json": { "package.json": {
"bytes": 2871, "bytes": 2870,
"imports": [] "imports": []
}, },
"src/emotion/emotion.js": { "src/emotion/emotion.js": {
@ -291,7 +291,7 @@
"imports": [] "imports": []
}, },
"src/human.js": { "src/human.js": {
"bytes": 11908, "bytes": 13144,
"imports": [ "imports": [
{ {
"path": "node_modules/@tensorflow/tfjs/dist/tf.node.js" "path": "node_modules/@tensorflow/tfjs/dist/tf.node.js"
@ -468,7 +468,7 @@
"dist/human.js.map": { "dist/human.js.map": {
"imports": [], "imports": [],
"inputs": {}, "inputs": {},
"bytes": 5125792 "bytes": 5127510
}, },
"dist/human.js": { "dist/human.js": {
"imports": [], "imports": [],
@ -618,13 +618,13 @@
"bytesInOutput": 2424 "bytesInOutput": 2424
}, },
"package.json": { "package.json": {
"bytesInOutput": 3145 "bytesInOutput": 3144
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 11994 "bytesInOutput": 13092
} }
}, },
"bytes": 3070080 "bytes": 3071177
} }
} }
} }

View File

@ -5777,7 +5777,7 @@ var require_config = __commonJS((exports2) => {
var require_package = __commonJS((exports2, module2) => { var require_package = __commonJS((exports2, module2) => {
module2.exports = { module2.exports = {
name: "@vladmandic/human", name: "@vladmandic/human",
version: "0.4.10", version: "0.5.1",
description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction", description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction",
sideEffects: false, sideEffects: false,
main: "dist/human.node.js", main: "dist/human.node.js",
@ -5799,10 +5799,9 @@ var require_package = __commonJS((exports2, module2) => {
dependencies: {}, dependencies: {},
peerDependencies: {}, peerDependencies: {},
devDependencies: { devDependencies: {
seedrandom: "^3.0.5",
"@tensorflow/tfjs": "^2.7.0", "@tensorflow/tfjs": "^2.7.0",
"@tensorflow/tfjs-node": "^2.7.0", "@tensorflow/tfjs-node": "^2.7.0",
"@vladmandic/pilogger": "^0.2.6", "@vladmandic/pilogger": "^0.2.7",
dayjs: "^1.9.4", dayjs: "^1.9.4",
esbuild: "^0.7.22", esbuild: "^0.7.22",
eslint: "^7.12.1", eslint: "^7.12.1",
@ -5812,6 +5811,7 @@ var require_package = __commonJS((exports2, module2) => {
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
rimraf: "^3.0.2", rimraf: "^3.0.2",
seedrandom: "^3.0.5",
"simple-git": "^2.21.0" "simple-git": "^2.21.0"
}, },
scripts: { scripts: {
@ -5961,17 +5961,33 @@ class Human {
this.models.emotion = await emotion.load(this.config); this.models.emotion = await emotion.load(this.config);
} }
} }
async resetBackend(backendName) {
if (tf.getBackend() !== this.config.backend) {
this.state = "backend";
if (backendName in tf.engine().registry) {
this.log("Human library setting backend:", this.config.backend);
this.config.backend = backendName;
const backendFactory = tf.findBackendFactory(backendName);
tf.removeBackend(backendName);
tf.registerBackend(backendName, backendFactory);
await tf.setBackend(backendName);
await tf.ready();
} else {
this.log("Human library backend not registred:", backendName);
}
}
}
tfImage(input) { tfImage(input) {
let filtered; let filtered;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0; const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0;
const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0; const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0;
let targetWidth = originalWidth; let targetWidth = originalWidth;
let targetHeight = originalHeight;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
if (this.config.filter.width > 0) if (this.config.filter.width > 0)
targetWidth = this.config.filter.width; targetWidth = this.config.filter.width;
else if (this.config.filter.height > 0) else if (this.config.filter.height > 0)
targetWidth = originalWidth * (this.config.filter.height / originalHeight); targetWidth = originalWidth * (this.config.filter.height / originalHeight);
let targetHeight = originalHeight;
if (this.config.filter.height > 0) if (this.config.filter.height > 0)
targetHeight = this.config.filter.height; targetHeight = this.config.filter.height;
else if (this.config.filter.width > 0) else if (this.config.filter.width > 0)
@ -6018,7 +6034,19 @@ class Human {
if (input instanceof tf.Tensor) { if (input instanceof tf.Tensor) {
tensor = tf.clone(input); tensor = tf.clone(input);
} else { } else {
const pixels = tf.browser.fromPixels(filtered || input); const canvas = filtered || input;
let pixels;
if (this.config.backend === "webgl" || canvas instanceof ImageData)
pixels = tf.browser.fromPixels(canvas);
else {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(canvas, 0, 0);
const data = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf.browser.fromPixels(data);
}
const casted = pixels.toFloat(); const casted = pixels.toFloat();
tensor = casted.expandDims(0); tensor = casted.expandDims(0);
pixels.dispose(); pixels.dispose();
@ -6042,12 +6070,7 @@ class Human {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const timeStart = now(); const timeStart = now();
timeStamp = now(); timeStamp = now();
if (tf.getBackend() !== this.config.backend) { await this.resetBackend(this.config.backend);
this.state = "backend";
this.log("Human library setting backend:", this.config.backend);
await tf.setBackend(this.config.backend);
await tf.ready();
}
perf.backend = Math.trunc(now() - timeStamp); perf.backend = Math.trunc(now() - timeStamp);
if (first) { if (first) {
this.log("Human library starting"); this.log("Human library starting");

File diff suppressed because one or more lines are too long

47
dist/human.node.js vendored
View File

@ -73868,7 +73868,7 @@ var require_config = __commonJS((exports2) => {
var require_package = __commonJS((exports2, module2) => { var require_package = __commonJS((exports2, module2) => {
module2.exports = { module2.exports = {
name: "@vladmandic/human", name: "@vladmandic/human",
version: "0.4.10", version: "0.5.1",
description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction", description: "human: 3D Face Detection, Iris Tracking and Age & Gender Prediction",
sideEffects: false, sideEffects: false,
main: "dist/human.node.js", main: "dist/human.node.js",
@ -73890,10 +73890,9 @@ var require_package = __commonJS((exports2, module2) => {
dependencies: {}, dependencies: {},
peerDependencies: {}, peerDependencies: {},
devDependencies: { devDependencies: {
seedrandom: "^3.0.5",
"@tensorflow/tfjs": "^2.7.0", "@tensorflow/tfjs": "^2.7.0",
"@tensorflow/tfjs-node": "^2.7.0", "@tensorflow/tfjs-node": "^2.7.0",
"@vladmandic/pilogger": "^0.2.6", "@vladmandic/pilogger": "^0.2.7",
dayjs: "^1.9.4", dayjs: "^1.9.4",
esbuild: "^0.7.22", esbuild: "^0.7.22",
eslint: "^7.12.1", eslint: "^7.12.1",
@ -73903,6 +73902,7 @@ var require_package = __commonJS((exports2, module2) => {
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
rimraf: "^3.0.2", rimraf: "^3.0.2",
seedrandom: "^3.0.5",
"simple-git": "^2.21.0" "simple-git": "^2.21.0"
}, },
scripts: { scripts: {
@ -74052,17 +74052,33 @@ class Human {
this.models.emotion = await emotion.load(this.config); this.models.emotion = await emotion.load(this.config);
} }
} }
async resetBackend(backendName) {
if (tf.getBackend() !== this.config.backend) {
this.state = "backend";
if (backendName in tf.engine().registry) {
this.log("Human library setting backend:", this.config.backend);
this.config.backend = backendName;
const backendFactory = tf.findBackendFactory(backendName);
tf.removeBackend(backendName);
tf.registerBackend(backendName, backendFactory);
await tf.setBackend(backendName);
await tf.ready();
} else {
this.log("Human library backend not registred:", backendName);
}
}
}
tfImage(input) { tfImage(input) {
let filtered; let filtered;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0; const originalWidth = input.naturalWidth || input.videoWidth || input.width || input.shape && input.shape[1] > 0;
const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0; const originalHeight = input.naturalHeight || input.videoHeight || input.height || input.shape && input.shape[2] > 0;
let targetWidth = originalWidth; let targetWidth = originalWidth;
let targetHeight = originalHeight;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
if (this.config.filter.width > 0) if (this.config.filter.width > 0)
targetWidth = this.config.filter.width; targetWidth = this.config.filter.width;
else if (this.config.filter.height > 0) else if (this.config.filter.height > 0)
targetWidth = originalWidth * (this.config.filter.height / originalHeight); targetWidth = originalWidth * (this.config.filter.height / originalHeight);
let targetHeight = originalHeight;
if (this.config.filter.height > 0) if (this.config.filter.height > 0)
targetHeight = this.config.filter.height; targetHeight = this.config.filter.height;
else if (this.config.filter.width > 0) else if (this.config.filter.width > 0)
@ -74109,7 +74125,19 @@ class Human {
if (input instanceof tf.Tensor) { if (input instanceof tf.Tensor) {
tensor = tf.clone(input); tensor = tf.clone(input);
} else { } else {
const pixels = tf.browser.fromPixels(filtered || input); const canvas = filtered || input;
let pixels;
if (this.config.backend === "webgl" || canvas instanceof ImageData)
pixels = tf.browser.fromPixels(canvas);
else {
const tempCanvas = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement("canvas");
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(canvas, 0, 0);
const data = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf.browser.fromPixels(data);
}
const casted = pixels.toFloat(); const casted = pixels.toFloat();
tensor = casted.expandDims(0); tensor = casted.expandDims(0);
pixels.dispose(); pixels.dispose();
@ -74133,12 +74161,7 @@ class Human {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
const timeStart = now(); const timeStart = now();
timeStamp = now(); timeStamp = now();
if (tf.getBackend() !== this.config.backend) { await this.resetBackend(this.config.backend);
this.state = "backend";
this.log("Human library setting backend:", this.config.backend);
await tf.setBackend(this.config.backend);
await tf.ready();
}
perf.backend = Math.trunc(now() - timeStamp); perf.backend = Math.trunc(now() - timeStamp);
if (first) { if (first) {
this.log("Human library starting"); this.log("Human library starting");

File diff suppressed because one or more lines are too long

12
dist/human.node.json vendored
View File

@ -5,7 +5,7 @@
"imports": [] "imports": []
}, },
"package.json": { "package.json": {
"bytes": 2871, "bytes": 2870,
"imports": [] "imports": []
}, },
"src/emotion/emotion.js": { "src/emotion/emotion.js": {
@ -116,7 +116,7 @@
"imports": [] "imports": []
}, },
"src/human.js": { "src/human.js": {
"bytes": 11908, "bytes": 13144,
"imports": [ "imports": [
{ {
"path": "src/facemesh/facemesh.js" "path": "src/facemesh/facemesh.js"
@ -260,7 +260,7 @@
"dist/human.node-nobundle.js.map": { "dist/human.node-nobundle.js.map": {
"imports": [], "imports": [],
"inputs": {}, "inputs": {},
"bytes": 260882 "bytes": 263858
}, },
"dist/human.node-nobundle.js": { "dist/human.node-nobundle.js": {
"imports": [], "imports": [],
@ -353,16 +353,16 @@
"bytesInOutput": 2232 "bytesInOutput": 2232
}, },
"package.json": { "package.json": {
"bytesInOutput": 3016 "bytesInOutput": 3015
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 47 "bytesInOutput": 47
}, },
"src/human.js": { "src/human.js": {
"bytesInOutput": 10775 "bytesInOutput": 11781
} }
}, },
"bytes": 155393 "bytes": 156398
} }
} }
} }

View File

@ -126,17 +126,33 @@ class Human {
} }
} }
async resetBackend(backendName) {
if (tf.getBackend() !== this.config.backend) {
this.state = 'backend';
if (backendName in tf.engine().registry) {
this.log('Human library setting backend:', this.config.backend);
this.config.backend = backendName;
const backendFactory = tf.findBackendFactory(backendName);
tf.removeBackend(backendName);
tf.registerBackend(backendName, backendFactory);
await tf.setBackend(backendName);
await tf.ready();
} else {
this.log('Human library backend not registred:', backendName);
}
}
}
tfImage(input) { tfImage(input) {
// let imageData; // let imageData;
let filtered; let filtered;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
const originalWidth = input.naturalWidth || input.videoWidth || input.width || (input.shape && (input.shape[1] > 0)); const originalWidth = input.naturalWidth || input.videoWidth || input.width || (input.shape && (input.shape[1] > 0));
const originalHeight = input.naturalHeight || input.videoHeight || input.height || (input.shape && (input.shape[2] > 0)); const originalHeight = input.naturalHeight || input.videoHeight || input.height || (input.shape && (input.shape[2] > 0));
let targetWidth = originalWidth; let targetWidth = originalWidth;
let targetHeight = originalHeight;
if (this.fx && this.config.filter.enabled && !(input instanceof tf.Tensor)) {
if (this.config.filter.width > 0) targetWidth = this.config.filter.width; if (this.config.filter.width > 0) targetWidth = this.config.filter.width;
else if (this.config.filter.height > 0) targetWidth = originalWidth * (this.config.filter.height / originalHeight); else if (this.config.filter.height > 0) targetWidth = originalWidth * (this.config.filter.height / originalHeight);
let targetHeight = originalHeight;
if (this.config.filter.height > 0) targetHeight = this.config.filter.height; if (this.config.filter.height > 0) targetHeight = this.config.filter.height;
else if (this.config.filter.width > 0) targetHeight = originalHeight * (this.config.filter.width / originalWidth); else if (this.config.filter.width > 0) targetHeight = originalHeight * (this.config.filter.width / originalWidth);
const offscreenCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas'); const offscreenCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas');
@ -166,7 +182,20 @@ class Human {
if (input instanceof tf.Tensor) { if (input instanceof tf.Tensor) {
tensor = tf.clone(input); tensor = tf.clone(input);
} else { } else {
const pixels = tf.browser.fromPixels(filtered || input); const canvas = filtered || input;
let pixels;
// tf kernel-optimized method to get imagedata, also if input is imagedata, just use it
if ((this.config.backend === 'webgl') || (canvas instanceof ImageData)) pixels = tf.browser.fromPixels(canvas);
// cpu and wasm kernel does not implement efficient fromPixels method nor we can use canvas as-is, so we do a silly one more canvas
else {
const tempCanvas = (typeof OffscreenCanvas !== 'undefined') ? new OffscreenCanvas(targetWidth, targetHeight) : document.createElement('canvas');
tempCanvas.width = targetWidth;
tempCanvas.height = targetHeight;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(canvas, 0, 0);
const data = tempCtx.getImageData(0, 0, targetWidth, targetHeight);
pixels = tf.browser.fromPixels(data);
}
const casted = pixels.toFloat(); const casted = pixels.toFloat();
tensor = casted.expandDims(0); tensor = casted.expandDims(0);
pixels.dispose(); pixels.dispose();
@ -197,12 +226,7 @@ class Human {
// configure backend // configure backend
timeStamp = now(); timeStamp = now();
if (tf.getBackend() !== this.config.backend) { await this.resetBackend(this.config.backend);
this.state = 'backend';
this.log('Human library setting backend:', this.config.backend);
await tf.setBackend(this.config.backend);
await tf.ready();
}
perf.backend = Math.trunc(now() - timeStamp); perf.backend = Math.trunc(now() - timeStamp);
// check number of loaded models // check number of loaded models