new serve module and demo structure

pull/94/head
Vladimir Mandic 2021-03-29 14:40:34 -04:00
parent fb41bb4544
commit fff34ca5b9
23 changed files with 121 additions and 5230 deletions

View File

@ -11,6 +11,7 @@ Repository: **<git+https://github.com/vladmandic/human.git>**
### **HEAD -> main** 2021/03/28 mandic00@live.com ### **HEAD -> main** 2021/03/28 mandic00@live.com
- minor rotation calculation fix
- remove debug output - remove debug output
- new face rotation calculations - new face rotation calculations
- cleanup - cleanup

View File

@ -43,7 +43,7 @@ function log(...msg) {
async function getFaceDB() { async function getFaceDB() {
// download db with known faces // download db with known faces
try { try {
const res = await fetch('/demo/faces.json'); const res = await fetch('/demo/facematch-faces.json');
db = (res && res.ok) ? await res.json() : []; db = (res && res.ok) ? await res.json() : [];
for (const rec of db) { for (const rec of db) {
rec.embedding = rec.embedding.map((a) => parseFloat(a.toFixed(4))); rec.embedding = rec.embedding.map((a) => parseFloat(a.toFixed(4)));

BIN
demo/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,3 +1,5 @@
// @ts-nocheck
let instance = 0; let instance = 0;
let CSScreated = false; let CSScreated = false;
@ -80,7 +82,7 @@ class Menu {
this.instance = instance; this.instance = instance;
instance++; instance++;
this._maxFPS = 0; this._maxFPS = 0;
this.hidden = 0; this.hidden = false;
} }
createMenu(parent, title = '', position = { top: null, left: null, bottom: null, right: null }) { createMenu(parent, title = '', position = { top: null, left: null, bottom: null, right: null }) {
@ -109,9 +111,11 @@ class Menu {
if (title) elTitle.innerHTML = `${title}${svg}`; if (title) elTitle.innerHTML = `${title}${svg}`;
this.menu.appendChild(elTitle); this.menu.appendChild(elTitle);
elTitle.addEventListener('click', () => { elTitle.addEventListener('click', () => {
this.container.classList.toggle('menu-container-fadeout'); if (this.container && this.menu) {
this.container.classList.toggle('menu-container-fadein'); this.container.classList.toggle('menu-container-fadeout');
this.menu.style.borderStyle = this.container.classList.contains('menu-container-fadeout') ? 'none' : 'solid'; this.container.classList.toggle('menu-container-fadein');
this.menu.style.borderStyle = this.container.classList.contains('menu-container-fadeout') ? 'none' : 'solid';
}
}); });
this.menu.appendChild(this.container); this.menu.appendChild(this.container);
@ -129,40 +133,42 @@ class Menu {
} }
get width() { get width() {
return this.menu.offsetWidth; return this.menu?.offsetWidth || 0;
} }
get height() { get height() {
return this.menu.offsetHeight; return this.menu?.offsetHeight || 0;
} }
hide() { hide() {
if (this.container.classList.contains('menu-container-fadein')) { if (this.container && this.container.classList.contains('menu-container-fadein')) {
this.container.classList.toggle('menu-container-fadeout'); this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein'); this.container.classList.toggle('menu-container-fadein');
} }
} }
visible() { visible() {
return (this.container.classList.contains('menu-container-fadein')); return (this.container ? this.container.classList.contains('menu-container-fadein') : false);
} }
toggle(evt) { toggle(evt) {
this.container.classList.toggle('menu-container-fadeout'); if (this.container && this.menu) {
this.container.classList.toggle('menu-container-fadein'); this.container.classList.toggle('menu-container-fadeout');
if (this.container.classList.contains('menu-container-fadein') && evt) { this.container.classList.toggle('menu-container-fadein');
const x = evt.x || (evt.touches && evt.touches[0] ? evt.touches[0].pageX : null); if (this.container.classList.contains('menu-container-fadein') && evt) {
// const y = evt.y || (evt.touches && evt.touches[0] ? evt.touches[0].pageY : null); const x = evt.x || (evt.touches && evt.touches[0] ? evt.touches[0].pageX : null);
if (x) this.menu.style.left = `${x - (this.menu.offsetWidth / 2)}px`; // const y = evt.y || (evt.touches && evt.touches[0] ? evt.touches[0].pageY : null);
// if (y) this.menu.style.top = '5.5rem'; // `${evt.y + 55}px`; if (x) this.menu.style.left = `${x - (this.menu.offsetWidth / 2)}px`;
if (this.menu.offsetLeft < 0) this.menu.style.left = 0; // if (y) this.menu.style.top = '5.5rem'; // `${evt.y + 55}px`;
if ((this.menu.offsetLeft + this.menu.offsetWidth) > window.innerWidth) { if (this.menu.offsetLeft < 0) this.menu.style.left = '0';
this.menu.style.left = null; if ((this.menu.offsetLeft + this.menu.offsetWidth) > window.innerWidth) {
this.menu.style.right = 0; this.menu.style.left = '';
this.menu.style.right = '0';
}
this.menu.style.borderStyle = 'solid';
} else {
this.menu.style.borderStyle = 'none';
} }
this.menu.style.borderStyle = 'solid';
} else {
this.menu.style.borderStyle = 'none';
} }
} }
@ -171,7 +177,7 @@ class Menu {
el.className = 'menu-title'; el.className = 'menu-title';
el.id = this.newID; el.id = this.newID;
el.innerHTML = title; el.innerHTML = title;
this.menu.appendChild(el); if (this.menu) this.menu.appendChild(el);
el.addEventListener('click', () => { el.addEventListener('click', () => {
this.hidden = !this.hidden; this.hidden = !this.hidden;
const all = document.getElementsByClassName('menu'); const all = document.getElementsByClassName('menu');
@ -187,7 +193,7 @@ class Menu {
el.className = 'menu-item menu-label'; el.className = 'menu-item menu-label';
el.id = this.newID; el.id = this.newID;
el.innerHTML = title; el.innerHTML = title;
this.container.appendChild(el); if (this.container) this.container.appendChild(el);
return el; return el;
} }
@ -195,10 +201,10 @@ class Menu {
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'menu-item'; el.className = 'menu-item';
el.innerHTML = `<div class="menu-checkbox"><input class="menu-checkbox" type="checkbox" id="${this.newID}" ${object[variable] ? 'checked' : ''}/><label class="menu-checkbox-label" for="${this.ID}"></label></div>${title}`; el.innerHTML = `<div class="menu-checkbox"><input class="menu-checkbox" type="checkbox" id="${this.newID}" ${object[variable] ? 'checked' : ''}/><label class="menu-checkbox-label" for="${this.ID}"></label></div>${title}`;
this.container.appendChild(el); if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => { el.addEventListener('change', (evt) => {
object[variable] = evt.target.checked; object[variable] = evt.target?.checked;
if (callback) callback(evt.target.checked); if (callback) callback(evt.target?.checked);
}); });
return el; return el;
} }
@ -215,9 +221,9 @@ class Menu {
el.style.fontFamily = document.body.style.fontFamily; el.style.fontFamily = document.body.style.fontFamily;
el.style.fontSize = document.body.style.fontSize; el.style.fontSize = document.body.style.fontSize;
el.style.fontVariant = document.body.style.fontVariant; el.style.fontVariant = document.body.style.fontVariant;
this.container.appendChild(el); if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => { el.addEventListener('change', (evt) => {
if (callback) callback(items[evt.target.selectedIndex]); if (callback) callback(items[evt.target?.selectedIndex]);
}); });
return el; return el;
} }
@ -226,11 +232,13 @@ class Menu {
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'menu-item'; el.className = 'menu-item';
el.innerHTML = `<input class="menu-range" type="range" id="${this.newID}" min="${min}" max="${max}" step="${step}" value="${object[variable]}">${title}`; el.innerHTML = `<input class="menu-range" type="range" id="${this.newID}" min="${min}" max="${max}" step="${step}" value="${object[variable]}">${title}`;
this.container.appendChild(el); if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => { el.addEventListener('change', (evt) => {
object[variable] = parseInt(evt.target.value) === parseFloat(evt.target.value) ? parseInt(evt.target.value) : parseFloat(evt.target.value); if (evt.target) {
evt.target.setAttribute('value', evt.target.value); object[variable] = parseInt(evt.target?.value) === parseFloat(evt.target?.value) ? parseInt(evt.target?.value) : parseFloat(evt.target?.value);
if (callback) callback(evt.target.value); evt.target.setAttribute('value', evt.target.value);
if (callback) callback(evt.target?.value);
}
}); });
el.input = el.children[0]; el.input = el.children[0];
return el; return el;
@ -241,7 +249,7 @@ class Menu {
el.className = 'menu-item'; el.className = 'menu-item';
el.id = this.newID; el.id = this.newID;
if (html) el.innerHTML = html; if (html) el.innerHTML = html;
this.container.appendChild(el); if (this.container) this.container.appendChild(el);
return el; return el;
} }
@ -254,7 +262,7 @@ class Menu {
el.type = 'button'; el.type = 'button';
el.id = this.newID; el.id = this.newID;
el.innerText = titleOn; el.innerText = titleOn;
this.container.appendChild(el); if (this.container) this.container.appendChild(el);
el.addEventListener('click', () => { el.addEventListener('click', () => {
if (el.innerText === titleOn) el.innerText = titleOff; if (el.innerText === titleOn) el.innerText = titleOff;
else el.innerText = titleOn; else el.innerText = titleOn;
@ -268,7 +276,7 @@ class Menu {
el.className = 'menu-item'; el.className = 'menu-item';
el.id = `menu-val-${title}`; el.id = `menu-val-${title}`;
el.innerText = `${title}: ${val}${suffix}`; el.innerText = `${title}: ${val}${suffix}`;
this.container.appendChild(el); if (this.container) this.container.appendChild(el);
return el; return el;
} }
@ -285,7 +293,7 @@ class Menu {
el.className = 'menu-item menu-chart-title'; el.className = 'menu-item menu-chart-title';
el.id = this.newID; el.id = this.newID;
el.innerHTML = `<font color=${theme.chartColor}>${title}</font><canvas id="menu-canvas-${id}" class="menu-chart-canvas" width="${width}px" height="${height}px"></canvas>`; el.innerHTML = `<font color=${theme.chartColor}>${title}</font><canvas id="menu-canvas-${id}" class="menu-chart-canvas" width="${width}px" height="${height}px"></canvas>`;
this.container.appendChild(el); if (this.container) this.container.appendChild(el);
return el; return el;
} }

View File

@ -14,9 +14,9 @@
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon"> <link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../assets/icon.png"> <link rel="apple-touch-icon" href="../assets/icon.png">
<!-- load compiled demo js --> <!-- load compiled demo js -->
<script src="../dist/demo-browser-index.js"></script> <!-- <script src="../dist/demo-browser-index.js"></script> -->
<!-- alternatively load demo sources directly --> <!-- alternatively load demo sources directly -->
<!-- <script src="./browser.js" type="module"></script> --> <script src="./index.js" type="module"></script>
<style> <style>
@font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../assets/lato-light.woff2') } @font-face { font-family: 'Lato'; font-display: swap; font-style: normal; font-weight: 100; src: local('Lato'), url('../assets/lato-light.woff2') }
@font-face { font-family: 'FA'; font-display: swap; font-style: normal; font-weight: 900; src: local('FA'), url('../assets/fa-solid-900.woff2'); } @font-face { font-family: 'FA'; font-display: swap; font-style: normal; font-weight: 900; src: local('FA'), url('../assets/fa-solid-900.woff2'); }

View File

@ -1,7 +1,7 @@
// import Human from '../dist/human.esm.js'; // equivalent of @vladmandic/human import Human from '../dist/human.esm.js'; // equivalent of @vladmandic/human
import Human from '../src/human'; // import Human from '../src/human'; // import sources directly
import Menu from './menu.js'; import Menu from './helpers/menu.js';
import GLBench from './gl-bench.js'; import GLBench from './helpers/gl-bench.js';
const userConfig = { backend: 'webgl' }; // add any user configuration overrides const userConfig = { backend: 'webgl' }; // add any user configuration overrides
@ -37,7 +37,7 @@ const ui = {
columns: 2, // when processing sample images create this many columns columns: 2, // when processing sample images create this many columns
facing: true, // camera facing front or back facing: true, // camera facing front or back
useWorker: false, // use web workers for processing useWorker: false, // use web workers for processing
worker: 'worker.js', worker: 'index-worker.js',
samples: ['../assets/sample6.jpg', '../assets/sample1.jpg', '../assets/sample4.jpg', '../assets/sample5.jpg', '../assets/sample3.jpg', '../assets/sample2.jpg'], samples: ['../assets/sample6.jpg', '../assets/sample1.jpg', '../assets/sample4.jpg', '../assets/sample5.jpg', '../assets/sample3.jpg', '../assets/sample2.jpg'],
compare: '../assets/sample-me.jpg', compare: '../assets/sample-me.jpg',
console: true, // log messages to browser console console: true, // log messages to browser console

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
dist/human.js.map vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -57,12 +57,12 @@
"@tensorflow/tfjs-node": "^3.3.0", "@tensorflow/tfjs-node": "^3.3.0",
"@tensorflow/tfjs-node-gpu": "^3.3.0", "@tensorflow/tfjs-node-gpu": "^3.3.0",
"@types/node": "^14.14.37", "@types/node": "^14.14.37",
"@typescript-eslint/eslint-plugin": "^4.19.0", "@typescript-eslint/eslint-plugin": "^4.20.0",
"@typescript-eslint/parser": "^4.19.0", "@typescript-eslint/parser": "^4.20.0",
"@vladmandic/pilogger": "^0.2.15", "@vladmandic/pilogger": "^0.2.15",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"dayjs": "^1.10.4", "dayjs": "^1.10.4",
"esbuild": "^0.10.2", "esbuild": "^0.11.0",
"eslint": "^7.23.0", "eslint": "^7.23.0",
"eslint-config-airbnb-base": "^14.2.1", "eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.22.1",

View File

@ -139,6 +139,7 @@ const targets = {
outfile: 'dist/human.esm.js', outfile: 'dist/human.esm.js',
external: ['fs', 'buffer', 'util', 'os'], external: ['fs', 'buffer', 'util', 'os'],
}, },
/*
demo: { demo: {
platform: 'browser', platform: 'browser',
format: 'esm', format: 'esm',
@ -146,6 +147,7 @@ const targets = {
outfile: 'dist/demo-browser-index.js', outfile: 'dist/demo-browser-index.js',
external: ['fs', 'buffer', 'util', 'os'], external: ['fs', 'buffer', 'util', 'os'],
}, },
*/
}, },
}; };
@ -221,7 +223,7 @@ async function build(f, msg, dev = false) {
for (const [targetGroupName, targetGroup] of Object.entries(targets)) { for (const [targetGroupName, targetGroup] of Object.entries(targets)) {
for (const [targetName, targetOptions] of Object.entries(targetGroup)) { for (const [targetName, targetOptions] of Object.entries(targetGroup)) {
// if triggered from watch mode, rebuild only browser bundle // if triggered from watch mode, rebuild only browser bundle
// if ((require.main !== module) && (targetGroupName !== 'browserBundle')) continue; if ((require.main !== module) && ((targetGroupName === 'browserNoBundle') || (targetGroupName === 'nodeGPU'))) continue;
const meta = dev const meta = dev
// @ts-ignore // @ts-ignore
? await esbuild.build({ ...config.common, ...config.debug, ...targetOptions }) ? await esbuild.build({ ...config.common, ...config.debug, ...targetOptions })

View File

@ -26,8 +26,8 @@ const build = require('./build.js');
const options = { const options = {
key: fs.readFileSync('server/https.key'), key: fs.readFileSync('server/https.key'),
cert: fs.readFileSync('server/https.crt'), cert: fs.readFileSync('server/https.crt'),
root: '..', defaultFolder: 'demo',
default: 'demo/index.html', defaultFile: 'index.html',
httpPort: 10030, httpPort: 10030,
httpsPort: 10031, httpsPort: 10031,
insecureHTTPParser: false, insecureHTTPParser: false,
@ -84,6 +84,7 @@ async function watch() {
} }
// get file content for a valid url request // get file content for a valid url request
/*
function handle(url) { function handle(url) {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
let obj = { ok: false, file: decodeURI(url) }; let obj = { ok: false, file: decodeURI(url) };
@ -93,20 +94,58 @@ function handle(url) {
obj.stat = fs.statSync(obj.file); obj.stat = fs.statSync(obj.file);
if (obj.stat.isFile()) obj.ok = true; if (obj.stat.isFile()) obj.ok = true;
if (!obj.ok && obj.stat.isDirectory()) { if (!obj.ok && obj.stat.isDirectory()) {
if (fs.existsSync(path.join(obj.file, options.default))) { if (fs.existsSync(path.join(obj.file, options.defaultFile))) {
obj = await handle(path.join(obj.file, options.default)); obj = await handle(path.join(obj.file, options.defaultFile));
} else if (fs.existsSync(path.join(obj.file, options.defaultFolder, options.defaultFile))) {
obj = await handle(path.join(obj.file, options.defaultFolder, options.defaultFile));
} else { } else {
obj.ok = obj.stat.isDirectory(); obj.ok = obj.stat.isDirectory();
} }
} }
resolve(obj); resolve(obj);
} }
}); });
} }
*/
function handle(url) {
const result = { ok: false, stat: {}, file: '' };
const checkFile = (f) => {
result.file = f;
if (fs.existsSync(f)) {
result.stat = fs.statSync(f);
if (result.stat.isFile()) {
result.ok = true;
return true;
}
}
return false;
}
const checkFolder = (f) => {
result.file = f;
if (fs.existsSync(f)) {
result.stat = fs.statSync(f);
if (result.stat.isDirectory()) {
result.ok = true;
return true;
}
}
return false;
}
return new Promise((resolve) => {
if (checkFile(path.join(process.cwd(), url))) resolve(result);
else if (checkFile(path.join(process.cwd(), url, options.defaultFile))) resolve(result);
else if (checkFile(path.join(process.cwd(), options.defaultFolder, url))) resolve(result);
else if (checkFile(path.join(process.cwd(), options.defaultFolder, url, options.defaultFile))) resolve(result);
else if (checkFolder(path.join(process.cwd(), url))) resolve(result);
else if (checkFolder(path.join(process.cwd(), options.defaultFolder, url))) resolve(result);
else resolve(result);
});
}
// process http requests // process http requests
async function httpRequest(req, res) { async function httpRequest(req, res) {
handle(path.join(__dirname, options.root, req.url)).then((result) => { handle(decodeURI(req.url)).then((result) => {
// get original ip of requestor, regardless if it's behind proxy or not // get original ip of requestor, regardless if it's behind proxy or not
const forwarded = (req.headers['forwarded'] || '').match(/for="\[(.*)\]:/); const forwarded = (req.headers['forwarded'] || '').match(/for="\[(.*)\]:/);
const ip = (Array.isArray(forwarded) ? forwarded[1] : null) || req.headers['x-forwarded-for'] || req.ip || req.socket.remoteAddress; const ip = (Array.isArray(forwarded) ? forwarded[1] : null) || req.headers['x-forwarded-for'] || req.ip || req.socket.remoteAddress;
@ -154,6 +193,7 @@ async function httpRequest(req, res) {
async function main() { async function main() {
log.header(); log.header();
await watch(); await watch();
process.chdir(path.join(__dirname, '..'));
if (options.httpPort && options.httpPort > 0) { if (options.httpPort && options.httpPort > 0) {
const server1 = http.createServer(options, httpRequest); const server1 = http.createServer(options, httpRequest);
server1.on('listening', () => log.state('HTTP server listening:', options.httpPort)); server1.on('listening', () => log.state('HTTP server listening:', options.httpPort));

View File

@ -255,7 +255,8 @@ const config: Config = {
body: { body: {
enabled: true, enabled: true,
modelPath: '../models/posenet.json', // can be 'posenet' or 'blazepose' modelPath: '../models/posenet.json', // can be 'posenet', 'blazepose' or 'efficientpose'
// 'blazepose' and 'efficientpose' are experimental
maxDetections: 10, // maximum number of people detected in the input maxDetections: 10, // maximum number of people detected in the input
// should be set to the minimum number for performance // should be set to the minimum number for performance
// only valid for posenet as blazepose only detects single pose // only valid for posenet as blazepose only detects single pose
@ -296,6 +297,7 @@ const config: Config = {
object: { object: {
enabled: false, enabled: false,
modelPath: '../models/nanodet.json', modelPath: '../models/nanodet.json',
// 'nanodet' is experimental
minConfidence: 0.20, // threshold for discarding a prediction minConfidence: 0.20, // threshold for discarding a prediction
iouThreshold: 0.40, // threshold for deciding whether boxes overlap too much iouThreshold: 0.40, // threshold for deciding whether boxes overlap too much
// in non-maximum suppression // in non-maximum suppression

View File

@ -21,7 +21,7 @@
"allowJs": true "allowJs": true
}, },
"formatCodeOptions": { "indentSize": 2, "tabSize": 2 }, "formatCodeOptions": { "indentSize": 2, "tabSize": 2 },
"include": ["src/*", "src/***/*", "demo/*"], "include": ["src/*", "src/***/*", "demo/*", "demo/helpers/gl-bench.js", "demo/helpers/menu.js"],
"typedocOptions": { "typedocOptions": {
"excludePrivate": true, "excludePrivate": true,
"excludeExternals": true, "excludeExternals": true,

2
wiki

@ -1 +1 @@
Subproject commit aae30ba3904eb7d76847895e11c55926e35ad00b Subproject commit df23c6e6fdd3aedd5da6ee08d90d61983a761e91