diff --git a/CHANGELOG.md b/CHANGELOG.md index 07700343..094fa8d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### **HEAD -> main** 2021/09/14 mandic00@live.com +- experimental custom tfjs bundle - disabled - add platform and backend capabilities detection - enhanced automated tests - enable canvas patching for nodejs diff --git a/README.md b/README.md index 6ca0bdb7..80175ee3 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,12 @@ All options as presented in the demo application...
+**Results Browser:** +[ *Demo -> Display -> Show Results* ]
+![Results](assets/screenshot-results.png) + +
+ **Face Similarity Matching:** Extracts all faces from provided input images, sorts them by similarity to selected face diff --git a/assets/screenshot-results.png b/assets/screenshot-results.png new file mode 100644 index 00000000..47f68880 Binary files /dev/null and b/assets/screenshot-results.png differ diff --git a/demo/helpers/jsonview.js b/demo/helpers/jsonview.js new file mode 100644 index 00000000..08dc1d9c --- /dev/null +++ b/demo/helpers/jsonview.js @@ -0,0 +1,161 @@ +let callbackFunction = null; + +function createElement(type, config) { + const htmlElement = document.createElement(type); + if (config === undefined) return htmlElement; + if (config.className) htmlElement.className = config.className; + if (config.content) htmlElement.textContent = config.content; + if (config.style) htmlElement.style = config.style; + if (config.children) config.children.forEach((el) => !el || htmlElement.appendChild(el)); + return htmlElement; +} + +function createExpandedElement(node) { + const iElem = createElement('i'); + if (node.expanded) { iElem.className = 'fas fa-caret-down'; } else { iElem.className = 'fas fa-caret-right'; } + const caretElem = createElement('div', { style: 'width: 18px; text-align: center; cursor: pointer', children: [iElem] }); + const handleClick = node.toggle.bind(node); + caretElem.addEventListener('click', handleClick); + const indexElem = createElement('div', { className: 'json json-index', content: node.key }); + indexElem.addEventListener('click', handleClick); + const typeElem = createElement('div', { className: 'json json-type', content: node.type }); + const keyElem = createElement('div', { className: 'json json-key', content: node.key }); + keyElem.addEventListener('click', handleClick); + const sizeElem = createElement('div', { className: 'json json-size' }); + sizeElem.addEventListener('click', handleClick); + if (node.type === 'array') { + sizeElem.innerText = `[${node.children.length} items]`; + } else if (node.type === 'object') { + const size = node.children.find((item) => item.key === 'size'); + sizeElem.innerText = size ? `{${size.value.toLocaleString()} bytes}` : `{${node.children.length} properties}`; + } + let lineChildren; + if (node.key === null) lineChildren = [caretElem, typeElem, sizeElem]; + else if (node.parent.type === 'array') lineChildren = [caretElem, indexElem, sizeElem]; + else lineChildren = [caretElem, keyElem, sizeElem]; + const lineElem = createElement('div', { className: 'json-line', children: lineChildren }); + if (node.depth > 0) lineElem.style = `margin-left: ${node.depth * 24}px;`; + return lineElem; +} + +function createNotExpandedElement(node) { + const caretElem = createElement('div', { style: 'width: 18px' }); + const keyElem = createElement('div', { className: 'json json-key', content: node.key }); + const separatorElement = createElement('div', { className: 'json-separator', content: ':' }); + const valueType = ` json-${typeof node.value}`; + const valueContent = node.value.toLocaleString(); + const valueElement = createElement('div', { className: `json json-value${valueType}`, content: valueContent }); + const lineElem = createElement('div', { className: 'json-line', children: [caretElem, keyElem, separatorElement, valueElement] }); + if (node.depth > 0) lineElem.style = `margin-left: ${node.depth * 24}px;`; + return lineElem; +} + +function createNode() { + return { + key: '', + parent: {}, + value: null, + expanded: false, + type: '', + children: [], + elem: {}, + depth: 0, + + hideChildren() { + if (Array.isArray(this.children)) { + this.children.forEach((item) => { + // @ts-ignore + item['elem']['classList'].add('hide'); + // @ts-ignore + if (item['expanded']) item.hideChildren(); + }); + } + }, + showChildren() { + if (Array.isArray(this.children)) { + this.children.forEach((item) => { + // @ts-ignore + item['elem']['classList'].remove('hide'); + // @ts-ignore + if (item['expanded']) item.showChildren(); + }); + } + }, + toggle() { + if (this.expanded) { + this.hideChildren(); + const icon = this.elem?.querySelector('.fas'); + icon.classList.replace('fa-caret-down', 'fa-caret-right'); + if (callbackFunction !== null) callbackFunction(null); + } else { + this.showChildren(); + const icon = this.elem?.querySelector('.fas'); + icon.classList.replace('fa-caret-right', 'fa-caret-down'); + if (this.type === 'object') { + if (callbackFunction !== null) callbackFunction(`${this.parent?.key}/${this.key}`); + } + } + this.expanded = !this.expanded; + }, + }; +} + +function getType(val) { + let type + if (Array.isArray(val)) type = 'array'; + else if (val === null) type = 'null'; + else type = typeof val; + return type; +} + +function traverseObject(obj, parent, filter) { + for (const key in obj) { + const child = createNode(); + child.parent = parent; + child.key = key; + child.type = getType(obj[key]); + child.depth = parent.depth + 1; + child.expanded = false; + if (Array.isArray(filter)) { + for (const filtered of filter) { + if (key === filtered) return; + } + } + if (typeof obj[key] === 'object') { + child.children = []; + parent.children.push(child); + traverseObject(obj[key], child, filter); + child.elem = createExpandedElement(child); + } else { + child.value = obj[key]; + child.elem = createNotExpandedElement(child); + parent.children.push(child); + } + } +} + +function createTree(obj, title, filter) { + const tree = createNode(); + tree.type = title; + tree.key = title; + tree.children = []; + tree.expanded = true; + traverseObject(obj, tree, filter); + tree.elem = createExpandedElement(tree); + return tree; +} + +function traverseTree(node, callback) { + callback(node); + if (node.children !== null) node.children.forEach((item) => traverseTree(item, callback)); +} + +async function jsonView(json, element, title = '', filter = []) { + const tree = createTree(json, title, filter); + traverseTree(tree, (node) => { + if (!node.expanded) node.hideChildren(); + element.appendChild(node.elem); + }); +} + +export default jsonView; diff --git a/demo/index.html b/demo/index.html index baaeb0bf..597b460a 100644 --- a/demo/index.html +++ b/demo/index.html @@ -66,6 +66,24 @@ .icon:hover { background: #505050; filter: grayscale(0); } .hint { opacity: 0; transition-duration: 0.5s; transition-property: opacity; font-style: italic; position: fixed; top: 5rem; padding: 8px; margin: 8px; box-shadow: 0 0 2px 2px #303030; } .input-file { align-self: center; width: 5rem; } + + .results { position: absolute; left: 0; top: 6rem; background: #303030; width: 20rem; height: 90%; font-size: 0.8rem; overflow-y: auto; display: none } + .results::-webkit-scrollbar { background-color: #303030; } + .results::-webkit-scrollbar-thumb { background: black; border-radius: 10px; } + .json-line { margin: 4px 0; display: flex; justify-content: flex-start; } + .json { margin-right: 8px; margin-left: 8px; } + .json-type { color: lightyellow; } + .json-key { color: white; } + .json-index { color: lightcoral; } + .json-value { margin-left: 20px; } + .json-number { color: lightgreen; } + .json-boolean { color: lightyellow; } + .json-string { color: lightblue; } + .json-size { color: gray; } + .hide { display: none; } + .fas { display: inline-block; width: 0; height: 0; border-style: solid; } + .fa-caret-down { border-width: 10px 8px 0 8px; border-color: white transparent } + .fa-caret-right { border-width: 10px 0 8px 10px; border-color: transparent transparent transparent white; } @@ -95,5 +113,6 @@
+
diff --git a/demo/index.js b/demo/index.js index 79c3bb56..5f068ec2 100644 --- a/demo/index.js +++ b/demo/index.js @@ -26,6 +26,7 @@ import Human from '../dist/human.esm.js'; // equivalent of @vladmandic/human import Menu from './helpers/menu.js'; import GLBench from './helpers/gl-bench.js'; import webRTC from './helpers/webrtc.js'; +import jsonView from './helpers/jsonview.js'; let human; @@ -99,6 +100,7 @@ const ui = { framesDraw: 0, // internal, statistics on frames drawn framesDetect: 0, // internal, statistics on frames detected bench: true, // show gl fps benchmark window + results: false, // show results tree lastFrame: 0, // time of last frame processing viewportSet: false, // internal, has custom viewport been set background: null, // holds instance of segmentation background image @@ -250,6 +252,14 @@ async function drawResults(input) { } else { human.draw.all(canvas, result, drawOptions); } + + // show tree with results + if (ui.results) { + const div = document.getElementById('results'); + div.innerHTML = ''; + jsonView(result, div, 'Results', ['canvas', 'timestamp']); + } + /* alternatively use individual functions human.draw.face(canvas, result.face); human.draw.body(canvas, result.body); @@ -631,6 +641,10 @@ function setupMenu() { const top = `${document.getElementById('menubar').clientHeight}px`; menu.display = new Menu(document.body, '', { top, left: x[0] }); + menu.display.addBool('results tree', ui, 'results', (val) => { + ui.results = val; + document.getElementById('results').style.display = ui.results ? 'block' : 'none'; + }); menu.display.addBool('perf monitor', ui, 'bench', (val) => ui.bench = val); menu.display.addBool('buffer output', ui, 'buffered', (val) => ui.buffered = val); menu.display.addBool('crop & scale', ui, 'crop', (val) => { @@ -988,6 +1002,7 @@ async function main() { status('human: ready'); document.getElementById('loader').style.display = 'none'; document.getElementById('play').style.display = 'block'; + document.getElementById('results').style.display = 'none'; // init drag & drop await dragAndDrop(); diff --git a/package.json b/package.json index ca684264..a3c07ce5 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/parser": "^4.31.1", "@vladmandic/build": "^0.4.1", - "@vladmandic/pilogger": "^0.3.1", + "@vladmandic/pilogger": "^0.3.2", "canvas": "^2.8.0", "dayjs": "^1.10.7", "esbuild": "^0.12.28",