mirror of https://github.com/vladmandic/human
new serve module and demo structure
parent
fb41bb4544
commit
fff34ca5b9
|
@ -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
|
||||
|
|
|
@ -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)));
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
|
@ -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;
|
||||
}
|
||||
|
|
@ -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'); }
|
||||
|
|
|
@ -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
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
|
@ -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",
|
||||
|
|
|
@ -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 })
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
2
wiki
|
@ -1 +1 @@
|
|||
Subproject commit aae30ba3904eb7d76847895e11c55926e35ad00b
|
||||
Subproject commit df23c6e6fdd3aedd5da6ee08d90d61983a761e91
|
Loading…
Reference in New Issue