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
- minor rotation calculation fix
- remove debug output
- new face rotation calculations
- cleanup

View File

@ -43,7 +43,7 @@ function log(...msg) {
async function getFaceDB() {
// download db with known faces
try {
const res = await fetch('/demo/faces.json');
const res = await fetch('/demo/facematch-faces.json');
db = (res && res.ok) ? await res.json() : [];
for (const rec of db) {
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 CSScreated = false;
@ -80,7 +82,7 @@ class Menu {
this.instance = instance;
instance++;
this._maxFPS = 0;
this.hidden = 0;
this.hidden = false;
}
createMenu(parent, title = '', position = { top: null, left: null, bottom: null, right: null }) {
@ -109,9 +111,11 @@ class Menu {
if (title) elTitle.innerHTML = `${title}${svg}`;
this.menu.appendChild(elTitle);
elTitle.addEventListener('click', () => {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
this.menu.style.borderStyle = this.container.classList.contains('menu-container-fadeout') ? 'none' : 'solid';
if (this.container && this.menu) {
this.container.classList.toggle('menu-container-fadeout');
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);
@ -129,40 +133,42 @@ class Menu {
}
get width() {
return this.menu.offsetWidth;
return this.menu?.offsetWidth || 0;
}
get height() {
return this.menu.offsetHeight;
return this.menu?.offsetHeight || 0;
}
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-fadein');
}
}
visible() {
return (this.container.classList.contains('menu-container-fadein'));
return (this.container ? this.container.classList.contains('menu-container-fadein') : false);
}
toggle(evt) {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
if (this.container.classList.contains('menu-container-fadein') && evt) {
const x = evt.x || (evt.touches && evt.touches[0] ? evt.touches[0].pageX : null);
// const y = evt.y || (evt.touches && evt.touches[0] ? evt.touches[0].pageY : null);
if (x) this.menu.style.left = `${x - (this.menu.offsetWidth / 2)}px`;
// if (y) this.menu.style.top = '5.5rem'; // `${evt.y + 55}px`;
if (this.menu.offsetLeft < 0) this.menu.style.left = 0;
if ((this.menu.offsetLeft + this.menu.offsetWidth) > window.innerWidth) {
this.menu.style.left = null;
this.menu.style.right = 0;
if (this.container && this.menu) {
this.container.classList.toggle('menu-container-fadeout');
this.container.classList.toggle('menu-container-fadein');
if (this.container.classList.contains('menu-container-fadein') && evt) {
const x = evt.x || (evt.touches && evt.touches[0] ? evt.touches[0].pageX : null);
// const y = evt.y || (evt.touches && evt.touches[0] ? evt.touches[0].pageY : null);
if (x) this.menu.style.left = `${x - (this.menu.offsetWidth / 2)}px`;
// if (y) this.menu.style.top = '5.5rem'; // `${evt.y + 55}px`;
if (this.menu.offsetLeft < 0) this.menu.style.left = '0';
if ((this.menu.offsetLeft + this.menu.offsetWidth) > window.innerWidth) {
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.id = this.newID;
el.innerHTML = title;
this.menu.appendChild(el);
if (this.menu) this.menu.appendChild(el);
el.addEventListener('click', () => {
this.hidden = !this.hidden;
const all = document.getElementsByClassName('menu');
@ -187,7 +193,7 @@ class Menu {
el.className = 'menu-item menu-label';
el.id = this.newID;
el.innerHTML = title;
this.container.appendChild(el);
if (this.container) this.container.appendChild(el);
return el;
}
@ -195,10 +201,10 @@ class Menu {
const el = document.createElement('div');
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}`;
this.container.appendChild(el);
if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => {
object[variable] = evt.target.checked;
if (callback) callback(evt.target.checked);
object[variable] = evt.target?.checked;
if (callback) callback(evt.target?.checked);
});
return el;
}
@ -215,9 +221,9 @@ class Menu {
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);
if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => {
if (callback) callback(items[evt.target.selectedIndex]);
if (callback) callback(items[evt.target?.selectedIndex]);
});
return el;
}
@ -226,11 +232,13 @@ class Menu {
const el = document.createElement('div');
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}`;
this.container.appendChild(el);
if (this.container) this.container.appendChild(el);
el.addEventListener('change', (evt) => {
object[variable] = parseInt(evt.target.value) === parseFloat(evt.target.value) ? parseInt(evt.target.value) : parseFloat(evt.target.value);
evt.target.setAttribute('value', evt.target.value);
if (callback) callback(evt.target.value);
if (evt.target) {
object[variable] = parseInt(evt.target?.value) === parseFloat(evt.target?.value) ? parseInt(evt.target?.value) : parseFloat(evt.target?.value);
evt.target.setAttribute('value', evt.target.value);
if (callback) callback(evt.target?.value);
}
});
el.input = el.children[0];
return el;
@ -241,7 +249,7 @@ class Menu {
el.className = 'menu-item';
el.id = this.newID;
if (html) el.innerHTML = html;
this.container.appendChild(el);
if (this.container) this.container.appendChild(el);
return el;
}
@ -254,7 +262,7 @@ class Menu {
el.type = 'button';
el.id = this.newID;
el.innerText = titleOn;
this.container.appendChild(el);
if (this.container) this.container.appendChild(el);
el.addEventListener('click', () => {
if (el.innerText === titleOn) el.innerText = titleOff;
else el.innerText = titleOn;
@ -268,7 +276,7 @@ class Menu {
el.className = 'menu-item';
el.id = `menu-val-${title}`;
el.innerText = `${title}: ${val}${suffix}`;
this.container.appendChild(el);
if (this.container) this.container.appendChild(el);
return el;
}
@ -285,7 +293,7 @@ class Menu {
el.className = 'menu-item menu-chart-title';
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>`;
this.container.appendChild(el);
if (this.container) this.container.appendChild(el);
return el;
}

View File

@ -14,9 +14,9 @@
<link rel="shortcut icon" href="../favicon.ico" type="image/x-icon">
<link rel="apple-touch-icon" href="../assets/icon.png">
<!-- 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 -->
<!-- <script src="./browser.js" type="module"></script> -->
<script src="./index.js" type="module"></script>
<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: '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 '../src/human';
import Menu from './menu.js';
import GLBench from './gl-bench.js';
import Human from '../dist/human.esm.js'; // equivalent of @vladmandic/human
// import Human from '../src/human'; // import sources directly
import Menu from './helpers/menu.js';
import GLBench from './helpers/gl-bench.js';
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
facing: true, // camera facing front or back
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'],
compare: '../assets/sample-me.jpg',
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-gpu": "^3.3.0",
"@types/node": "^14.14.37",
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"@typescript-eslint/eslint-plugin": "^4.20.0",
"@typescript-eslint/parser": "^4.20.0",
"@vladmandic/pilogger": "^0.2.15",
"chokidar": "^3.5.1",
"dayjs": "^1.10.4",
"esbuild": "^0.10.2",
"esbuild": "^0.11.0",
"eslint": "^7.23.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-plugin-import": "^2.22.1",

View File

@ -139,6 +139,7 @@ const targets = {
outfile: 'dist/human.esm.js',
external: ['fs', 'buffer', 'util', 'os'],
},
/*
demo: {
platform: 'browser',
format: 'esm',
@ -146,6 +147,7 @@ const targets = {
outfile: 'dist/demo-browser-index.js',
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 [targetName, targetOptions] of Object.entries(targetGroup)) {
// 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
// @ts-ignore
? await esbuild.build({ ...config.common, ...config.debug, ...targetOptions })

View File

@ -26,8 +26,8 @@ const build = require('./build.js');
const options = {
key: fs.readFileSync('server/https.key'),
cert: fs.readFileSync('server/https.crt'),
root: '..',
default: 'demo/index.html',
defaultFolder: 'demo',
defaultFile: 'index.html',
httpPort: 10030,
httpsPort: 10031,
insecureHTTPParser: false,
@ -84,6 +84,7 @@ async function watch() {
}
// get file content for a valid url request
/*
function handle(url) {
return new Promise(async (resolve) => {
let obj = { ok: false, file: decodeURI(url) };
@ -93,20 +94,58 @@ function handle(url) {
obj.stat = fs.statSync(obj.file);
if (obj.stat.isFile()) obj.ok = true;
if (!obj.ok && obj.stat.isDirectory()) {
if (fs.existsSync(path.join(obj.file, options.default))) {
obj = await handle(path.join(obj.file, options.default));
if (fs.existsSync(path.join(obj.file, options.defaultFile))) {
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 {
obj.ok = obj.stat.isDirectory();
obj.ok = obj.stat.isDirectory();
}
}
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
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
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;
@ -154,6 +193,7 @@ async function httpRequest(req, res) {
async function main() {
log.header();
await watch();
process.chdir(path.join(__dirname, '..'));
if (options.httpPort && options.httpPort > 0) {
const server1 = http.createServer(options, httpRequest);
server1.on('listening', () => log.state('HTTP server listening:', options.httpPort));

View File

@ -255,7 +255,8 @@ const config: Config = {
body: {
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
// should be set to the minimum number for performance
// only valid for posenet as blazepose only detects single pose
@ -296,6 +297,7 @@ const config: Config = {
object: {
enabled: false,
modelPath: '../models/nanodet.json',
// 'nanodet' is experimental
minConfidence: 0.20, // threshold for discarding a prediction
iouThreshold: 0.40, // threshold for deciding whether boxes overlap too much
// in non-maximum suppression

View File

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

2
wiki

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