add typedocs and types

pull/293/head
Vladimir Mandic 2021-03-13 22:31:09 -05:00
parent a08c0c0061
commit f65bae05e4
9 changed files with 134 additions and 100 deletions

View File

@ -38,6 +38,7 @@
"import/extensions": "off", "import/extensions": "off",
"import/no-absolute-path": "off", "import/no-absolute-path": "off",
"import/no-extraneous-dependencies": "off", "import/no-extraneous-dependencies": "off",
"import/no-named-as-default": "off",
"import/no-unresolved": "off", "import/no-unresolved": "off",
"import/prefer-default-export": "off", "import/prefer-default-export": "off",
"lines-between-class-members": "off", "lines-between-class-members": "off",

View File

@ -9,6 +9,12 @@ Repository: **<git+https://github.com/vladmandic/human.git>**
## Changelog ## Changelog
### **HEAD -> main** 2021/03/13 mandic00@live.com
### **origin/main** 2021/03/13 mandic00@live.com
### **1.1.2** 2021/03/12 mandic00@live.com ### **1.1.2** 2021/03/12 mandic00@live.com
- distance based on minkowski space and limited euclidean space - distance based on minkowski space and limited euclidean space

View File

@ -23,7 +23,6 @@
## WiP Items ## WiP Items
- face.tensor should return image in correct aspect ratio
- box sizing on mobile - box sizing on mobile
## Issues ## Issues

View File

@ -6,7 +6,7 @@
"main": "dist/human.node.js", "main": "dist/human.node.js",
"module": "dist/human.esm.js", "module": "dist/human.esm.js",
"browser": "dist/human.esm.js", "browser": "dist/human.esm.js",
"types": "types/human.d.ts", "types": "types/src/human.d.ts",
"author": "Vladimir Mandic <mandic00@live.com>", "author": "Vladimir Mandic <mandic00@live.com>",
"bugs": { "bugs": {
"url": "https://github.com/vladmandic/human/issues" "url": "https://github.com/vladmandic/human/issues"
@ -23,7 +23,7 @@
"scripts": { "scripts": {
"start": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught --no-deprecation demo/node.js", "start": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught --no-deprecation demo/node.js",
"dev": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught server/serve.js", "dev": "node --trace-warnings --unhandled-rejections=strict --trace-uncaught server/serve.js",
"build": "rimraf dist/* && rimraf types/* && node --trace-warnings --unhandled-rejections=strict --trace-uncaught server/build.js && node server/changelog.js", "build": "rimraf dist/* types/* typedoc/* && node --trace-warnings --unhandled-rejections=strict --trace-uncaught server/build.js",
"lint": "eslint src server demo", "lint": "eslint src server demo",
"test": "npm run lint && npm run start" "test": "npm run lint && npm run start"
}, },
@ -71,6 +71,7 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"simple-git": "^2.36.2", "simple-git": "^2.36.2",
"tslib": "^2.1.0", "tslib": "^2.1.0",
"typedoc": "^0.20.30",
"typescript": "^4.2.3" "typescript": "^4.2.3"
} }
} }

View File

@ -2,22 +2,22 @@ import config from '../config';
import { TRI468 as triangulation } from './blazeface/coords'; import { TRI468 as triangulation } from './blazeface/coords';
export const options = { export const options = {
color: 'rgba(173, 216, 230, 0.3)', // 'lightblue' with light alpha channel color: <string>'rgba(173, 216, 230, 0.3)', // 'lightblue' with light alpha channel
labelColor: 'rgba(173, 216, 230, 1)', // 'lightblue' with dark alpha channel labelColor: <string>'rgba(173, 216, 230, 1)', // 'lightblue' with dark alpha channel
shadowColor: 'black', shadowColor: <string>'black',
font: 'small-caps 16px "Segoe UI"', font: <string>'small-caps 16px "Segoe UI"',
lineHeight: 20, lineHeight: <number>20,
lineWidth: 6, lineWidth: <number>6,
pointSize: 2, pointSize: <number>2,
roundRect: 28, roundRect: <number>28,
drawPoints: false, drawPoints: <Boolean>false,
drawLabels: true, drawLabels: <Boolean>true,
drawBoxes: true, drawBoxes: <Boolean>true,
drawPolygons: true, drawPolygons: <Boolean>true,
fillPolygons: false, fillPolygons: <Boolean>false,
useDepth: true, useDepth: <Boolean>true,
useCurves: false, useCurves: <Boolean>false,
bufferedOutput: false, bufferedOutput: <Boolean>false,
}; };
function point(ctx, x, y, z = null) { function point(ctx, x, y, z = null) {

View File

@ -24,6 +24,48 @@ const now = () => {
return parseInt((Number(process.hrtime.bigint()) / 1000 / 1000).toString()); return parseInt((Number(process.hrtime.bigint()) / 1000 / 1000).toString());
}; };
type Tensor = {};
type Model = {};
export type Result = {
face: Array<{
confidence: Number,
boxConfidence: Number,
faceConfidence: Number,
box: [Number, Number, Number, Number],
mesh: Array<[Number, Number, Number]>
meshRaw: Array<[Number, Number, Number]>
boxRaw: [Number, Number, Number, Number],
annotations: any,
age: Number,
gender: String,
genderConfidence: Number,
emotion: String,
embedding: any,
iris: Number,
angle: { roll: Number | null, yaw: Number | null, pitch: Number | null },
}>,
body: Array<{
id: Number,
part: String,
position: { x: Number, y: Number, z: Number },
score: Number,
presence: Number }>,
hand: Array<{
confidence: Number,
box: any,
landmarks: any,
annotations: any,
}>,
gesture: Array<{
part: String,
gesture: String,
}>,
performance: { any },
canvas: OffscreenCanvas | HTMLCanvasElement,
}
export type { default as Config } from '../config';
// helper function: perform deep merge of multiple objects so it allows full inheriance with overrides // helper function: perform deep merge of multiple objects so it allows full inheriance with overrides
function mergeDeep(...objects) { function mergeDeep(...objects) {
const isObject = (obj) => obj && typeof obj === 'object'; const isObject = (obj) => obj && typeof obj === 'object';
@ -39,25 +81,25 @@ function mergeDeep(...objects) {
}, {}); }, {});
} }
class Human { export class Human {
version: string; version: String;
config: typeof config.default; config: typeof config.default;
state: string; state: String;
image: { tensor: typeof tf.Tensor, canvas: OffscreenCanvas | HTMLCanvasElement }; image: { tensor: Tensor, canvas: OffscreenCanvas | HTMLCanvasElement };
// classes // classes
tf: typeof tf; tf: typeof tf;
draw: typeof draw; draw: { options?: typeof draw.options, gesture: Function, face: Function, body: Function, hand: Function, canvas: Function, all: Function };
// models // models
models: { models: {
face, face: facemesh.MediaPipeFaceMesh | null,
posenet, posenet: posenet.PoseNet | null,
blazepose, blazepose: Model | null,
handpose, handpose: handpose.HandPose | null,
iris, iris: Model | null,
age, age: Model | null,
gender, gender: Model | null,
emotion, emotion: Model | null,
embedding, embedding: Model | null,
}; };
classes: { classes: {
facemesh: typeof facemesh; facemesh: typeof facemesh;
@ -67,13 +109,13 @@ class Human {
body: typeof posenet | typeof blazepose; body: typeof posenet | typeof blazepose;
hand: typeof handpose; hand: typeof handpose;
}; };
sysinfo: { platform: string, agent: string }; sysinfo: { platform: String, agent: String };
#package: any; #package: any;
#perf: any; #perf: any;
#numTensors: number; #numTensors: number;
#analyzeMemoryLeaks: boolean; #analyzeMemoryLeaks: Boolean;
#checkSanity: boolean; #checkSanity: Boolean;
#firstRun: boolean; #firstRun: Boolean;
// definition end // definition end
constructor(userConfig = {}) { constructor(userConfig = {}) {
@ -102,7 +144,7 @@ class Human {
}; };
// export access to image processing // export access to image processing
// @ts-ignore // @ts-ignore
this.image = (input: tf.Tensor | ImageData | HTMLCanvasElement | HTMLVideoElement | OffscreenCanvas) => image.process(input, this.config); this.image = (input: Tensor | ImageData | HTMLCanvasElement | HTMLVideoElement | OffscreenCanvas) => image.process(input, this.config);
// export raw access to underlying models // export raw access to underlying models
this.classes = { this.classes = {
facemesh, facemesh,
@ -122,6 +164,7 @@ class Human {
} }
// helper function: measure tensor leak // helper function: measure tensor leak
/** @hidden */
#analyze = (...msg) => { #analyze = (...msg) => {
if (!this.#analyzeMemoryLeaks) return; if (!this.#analyzeMemoryLeaks) return;
const current = this.tf.engine().state.numTensors; const current = this.tf.engine().state.numTensors;
@ -132,12 +175,11 @@ class Human {
} }
// quick sanity check on inputs // quick sanity check on inputs
#sanity = (input): null | string => { /** @hidden */
#sanity = (input): null | String => {
if (!this.#checkSanity) return null; if (!this.#checkSanity) return null;
if (!input) return 'input is not defined'; if (!input) return 'input is not defined';
if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf.Tensor)) { if (this.tf.ENV.flags.IS_NODE && !(input instanceof tf.Tensor)) return 'input must be a tensor';
return 'input must be a tensor';
}
try { try {
this.tf.getBackend(); this.tf.getBackend();
} catch { } catch {
@ -146,18 +188,18 @@ class Human {
return null; return null;
} }
simmilarity(embedding1: Array<number>, embedding2: Array<number>): number { simmilarity(embedding1: Array<Number>, embedding2: Array<Number>): Number {
if (this.config.face.embedding.enabled) return embedding.simmilarity(embedding1, embedding2); if (this.config.face.embedding.enabled) return embedding.simmilarity(embedding1, embedding2);
return 0; return 0;
} }
enhance(input: typeof tf.Tensor): typeof tf.Tensor | null { enhance(input: Tensor): Tensor | null {
if (this.config.face.embedding.enabled) return embedding.enhance(input); if (this.config.face.embedding.enabled) return embedding.enhance(input);
return null; return null;
} }
// preload models, not explicitly required as it's done automatically on first use // preload models, not explicitly required as it's done automatically on first use
async load(userConfig = null) { async load(userConfig: Object = {}) {
this.state = 'load'; this.state = 'load';
const timeStamp = now(); const timeStamp = now();
if (userConfig) this.config = mergeDeep(this.config, userConfig); if (userConfig) this.config = mergeDeep(this.config, userConfig);
@ -215,6 +257,7 @@ class Human {
} }
// check if backend needs initialization if it changed // check if backend needs initialization if it changed
/** @hidden */
#checkBackend = async (force = false) => { #checkBackend = async (force = false) => {
if (this.config.backend && (this.config.backend !== '') && force || (this.tf.getBackend() !== this.config.backend)) { if (this.config.backend && (this.config.backend !== '') && force || (this.tf.getBackend() !== this.config.backend)) {
const timeStamp = now(); const timeStamp = now();
@ -267,7 +310,8 @@ class Human {
} }
} }
#calculateFaceAngle = (mesh): { roll: number | null, yaw: number | null, pitch: number | null } => { /** @hidden */
#calculateFaceAngle = (mesh): { roll: Number | null, yaw: Number | null, pitch: Number | null } => {
if (!mesh || mesh.length < 300) return { roll: null, yaw: null, pitch: null }; if (!mesh || mesh.length < 300) return { roll: null, yaw: null, pitch: null };
const radians = (a1, a2, b1, b2) => Math.atan2(b2 - a2, b1 - a1); const radians = (a1, a2, b1, b2) => Math.atan2(b2 - a2, b1 - a1);
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
@ -285,6 +329,7 @@ class Human {
return angle; return angle;
} }
/** @hidden */
#detectFace = async (input): Promise<any> => { #detectFace = async (input): Promise<any> => {
// run facemesh, includes blazeface and iris // run facemesh, includes blazeface and iris
// eslint-disable-next-line no-async-promise-executor // eslint-disable-next-line no-async-promise-executor
@ -294,27 +339,29 @@ class Human {
let emotionRes; let emotionRes;
let embeddingRes; let embeddingRes;
const faceRes: Array<{ const faceRes: Array<{
confidence: number, confidence: Number,
boxConfidence: number, boxConfidence: Number,
faceConfidence: number, faceConfidence: Number,
box: [number, number, number, number], box: [Number, Number, Number, Number],
mesh: Array<[number, number, number]> mesh: Array<[Number, Number, Number]>
meshRaw: Array<[number, number, number]> meshRaw: Array<[Number, Number, Number]>
boxRaw: [number, number, number, number], boxRaw: [Number, Number, Number, Number],
annotations: any, annotations: any,
age: number, age: Number,
gender: string, gender: String,
genderConfidence: number, genderConfidence: Number,
emotion: string, emotion: String,
embedding: any, embedding: any,
iris: number, iris: Number,
angle: { roll: number | null, yaw: number | null, pitch: number | null }, angle: { roll: Number | null, yaw: Number | null, pitch: Number | null },
tensor: Tensor,
}> = []; }> = [];
this.state = 'run:face'; this.state = 'run:face';
timeStamp = now(); timeStamp = now();
const faces = await this.models.face?.estimateFaces(input, this.config); const faces = await this.models.face?.estimateFaces(input, this.config);
this.#perf.face = Math.trunc(now() - timeStamp); this.#perf.face = Math.trunc(now() - timeStamp);
if (!faces) return [];
for (const face of faces) { for (const face of faces) {
this.#analyze('Get Face'); this.#analyze('Get Face');
@ -418,45 +465,7 @@ class Human {
} }
// main detect function // main detect function
async detect(input, userConfig = {}): Promise<{ async detect(input: Tensor | ImageData | HTMLCanvasElement | HTMLVideoElement | OffscreenCanvas, userConfig: Object = {}): Promise<Result | { error: String }> {
face: Array<{
confidence: number,
boxConfidence: number,
faceConfidence: number,
box: [number, number, number, number],
mesh: Array<[number, number, number]>
meshRaw: Array<[number, number, number]>
boxRaw: [number, number, number, number],
annotations: any,
age: number,
gender: string,
genderConfidence: number,
emotion: string,
embedding: any,
iris: number,
angle: { roll: number | null, yaw: number | null, pitch: number | null },
}>,
body: Array<{
id: number,
part: string,
position: { x: number, y: number, z: number },
score: number,
presence: number }>,
hand: Array<{
confidence: number,
box: any,
landmarks: any,
annotations: any,
}>,
gesture: Array<{
part: string,
gesture: string,
}>,
performance: { any },
canvas: OffscreenCanvas | HTMLCanvasElement
} | { error: string }> {
// end definition
// detection happens inside a promise // detection happens inside a promise
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
this.state = 'config'; this.state = 'config';
@ -562,6 +571,7 @@ class Human {
}); });
} }
/** @hidden */
#warmupBitmap = async () => { #warmupBitmap = async () => {
const b64toBlob = (base64, type = 'application/octet-stream') => fetch(`data:${type};base64,${base64}`).then((res) => res.blob()); const b64toBlob = (base64, type = 'application/octet-stream') => fetch(`data:${type};base64,${base64}`).then((res) => res.blob());
let blob; let blob;
@ -579,6 +589,7 @@ class Human {
return res; return res;
} }
/** @hidden */
#warmupCanvas = async () => new Promise((resolve) => { #warmupCanvas = async () => new Promise((resolve) => {
let src; let src;
let size = 0; let size = 0;
@ -611,6 +622,7 @@ class Human {
else resolve(null); else resolve(null);
}); });
/** @hidden */
#warmupNode = async () => { #warmupNode = async () => {
const atob = (str) => Buffer.from(str, 'base64'); const atob = (str) => Buffer.from(str, 'base64');
const img = this.config.warmup === 'face' ? atob(sample.face) : atob(sample.body); const img = this.config.warmup === 'face' ? atob(sample.face) : atob(sample.body);
@ -624,7 +636,7 @@ class Human {
return res; return res;
} }
async warmup(userConfig): Promise<{ face, body, hand, gesture, performance, canvas } | { error }> { async warmup(userConfig: Object = {}): Promise<Result | { error }> {
const t0 = now(); const t0 = now();
if (userConfig) this.config = mergeDeep(this.config, userConfig); if (userConfig) this.config = mergeDeep(this.config, userConfig);
const video = this.config.videoOptimized; const video = this.config.videoOptimized;

View File

@ -22,4 +22,19 @@
}, },
"formatCodeOptions": { "indentSize": 2, "tabSize": 2 }, "formatCodeOptions": { "indentSize": 2, "tabSize": 2 },
"include": ["src/*", "src/***/*", "demo/*"], "include": ["src/*", "src/***/*", "demo/*"],
"typedocOptions": {
"excludePrivate": true,
"excludeExternals": true,
"excludeProtected": true,
"excludeInternal": true,
"disableSources": true,
"gitRevision": "main",
"hideGenerator": "true",
"theme": "default",
"readme": "none",
"out": "typedoc",
"entryPoints": "src/human.ts",
"logLevel": "Error",
"logger": "none"
}
} }

2
wiki

@ -1 +1 @@
Subproject commit 4c5d355a1d413c54f7f8ff2fa8bb39c535cc58b4 Subproject commit 17f65ba8169f07140d23ea7b31f9c22b13b83642