mirror of https://github.com/vladmandic/human
460 lines
23 KiB
JavaScript
460 lines
23 KiB
JavaScript
const fs = require('fs');
|
|
const process = require('process');
|
|
const canvasJS = require('canvas');
|
|
|
|
let fetch; // fetch is dynamically imported later
|
|
let config;
|
|
|
|
const log = (status, ...data) => {
|
|
if (typeof process.send !== 'undefined') process.send([status, data]); // send to parent process over ipc
|
|
// eslint-disable-next-line no-console
|
|
else console.log(status, ...data); // write to console if no parent process
|
|
};
|
|
|
|
async function testHTTP() {
|
|
if (config.modelBasePath.startsWith('file:')) return true;
|
|
return new Promise((resolve) => {
|
|
fetch(config.modelBasePath)
|
|
.then((res) => {
|
|
if (res && res.ok) log('state', 'passed: model server:', config.modelBasePath);
|
|
else log('error', 'failed: model server:', config.modelBasePath);
|
|
resolve(res && res.ok);
|
|
})
|
|
.catch((err) => {
|
|
log('error', 'failed: model server:', err.message);
|
|
resolve(false);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getImage(human, input, options = { channels: 3, expand: true, cast: true }) {
|
|
let img;
|
|
try {
|
|
img = await canvasJS.loadImage(input);
|
|
} catch (err) {
|
|
log('error', 'failed: load image', input, err.message);
|
|
return img;
|
|
}
|
|
const canvas = canvasJS.createCanvas(img.width, img.height);
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.drawImage(img, 0, 0, img.width, img.height);
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
|
|
const data = human.tf.tensor(Array.from(imageData.data), [canvas.height, canvas.width, 4], options.cast ? 'float32' : 'int32'); // create rgba image tensor from flat array
|
|
const channels = options.channels === 3 ? human.tf.slice3d(data, [0, 0, 0], [-1, -1, 3]) : human.tf.clone(data); // optionally strip alpha channel
|
|
const tensor = options.expand ? human.tf.expandDims(channels, 0) : human.tf.clone(channels); // optionally add batch num dimension
|
|
human.tf.dispose([data, channels]);
|
|
const sum = human.tf.sum(tensor);
|
|
log('state', 'passed: load image:', input, tensor.shape, { checksum: sum.dataSync()[0] });
|
|
human.tf.dispose(sum);
|
|
return tensor;
|
|
}
|
|
|
|
function printResults(detect) {
|
|
const person = (detect.face && detect.face[0]) ? { score: detect.face[0].score, age: detect.face[0].age, gender: detect.face[0].gender } : {};
|
|
const object = (detect.object && detect.object[0]) ? { score: detect.object[0].score, class: detect.object[0].label } : {};
|
|
const body = (detect.body && detect.body[0]) ? { score: detect.body[0].score, keypoints: detect.body[0].keypoints.length } : {};
|
|
const persons = detect.persons;
|
|
if (detect.face) log('data', ' result: face:', detect.face?.length, 'body:', detect.body?.length, 'hand:', detect.hand?.length, 'gesture:', detect.gesture?.length, 'object:', detect.object?.length, 'person:', persons.length, person, object, body);
|
|
if (detect.performance) log('data', ' result: performance:', 'load:', detect?.performance.load, 'total:', detect.performance?.total);
|
|
}
|
|
|
|
async function testInstance(human) {
|
|
if (human) log('state', 'passed: create human');
|
|
else log('error', 'failed: create human');
|
|
|
|
// if (!human.tf) human.tf = tf;
|
|
log('info', 'human version:', human.version);
|
|
log('info', 'platform:', human.env.platform, 'agent:', human.env.agent);
|
|
log('info', 'tfjs version:', human.tf.version.tfjs);
|
|
|
|
await human.load();
|
|
if (config.backend === human.tf.getBackend()) log('state', 'passed: set backend:', config.backend);
|
|
else log('error', 'failed: set backend:', config.backend);
|
|
log('state', 'tensors', human.tf.memory().numTensors);
|
|
|
|
if (human.models) {
|
|
log('state', 'passed: load models');
|
|
const keys = Object.keys(human.models);
|
|
const loaded = keys.filter((model) => human.models[model]);
|
|
log('state', ' result: defined models:', keys.length, 'loaded models:', loaded.length);
|
|
return true;
|
|
}
|
|
log('error', 'failed: load models');
|
|
return false;
|
|
}
|
|
|
|
async function testWarmup(human, title) {
|
|
let warmup;
|
|
try {
|
|
warmup = await human.warmup(config);
|
|
} catch (err) {
|
|
log('error', 'error warmup');
|
|
}
|
|
if (warmup) {
|
|
log('state', 'passed: warmup:', config.warmup, title);
|
|
printResults(warmup);
|
|
} else {
|
|
log('error', 'failed: warmup:', config.warmup, title);
|
|
}
|
|
return warmup;
|
|
}
|
|
|
|
async function testDetect(human, input, title, checkLeak = true) {
|
|
await human.load(config);
|
|
const tensors = human.tf.engine().state.numTensors;
|
|
const image = input ? await getImage(human, input) : human.tf.randomNormal([1, 1024, 1024, 3]);
|
|
if (!image) {
|
|
log('error', 'failed: detect: input is null');
|
|
return false;
|
|
}
|
|
let detect;
|
|
try {
|
|
detect = await human.detect(image, config);
|
|
} catch (err) {
|
|
log('error', 'error: detect', err);
|
|
}
|
|
if (image instanceof human.tf.Tensor) human.tf.dispose(image);
|
|
if (detect) {
|
|
log('state', 'passed: detect:', input || 'random', title);
|
|
// const count = human.tf.engine().state.numTensors;
|
|
// if (count - tensors > 0) log('warn', 'failed: memory', config.warmup, title, 'tensors:', count - tensors);
|
|
printResults(detect);
|
|
} else {
|
|
log('error', 'failed: detect', input || 'random', title);
|
|
}
|
|
// check tensor leaks
|
|
if (checkLeak) {
|
|
const leak = human.tf.engine().state.numTensors - tensors;
|
|
if (leak !== 0) log('error', 'failed: memory leak', leak);
|
|
}
|
|
return detect;
|
|
}
|
|
|
|
const evt = { image: 0, detect: 0, warmup: 0 };
|
|
async function events(event) {
|
|
log('state', 'event:', event);
|
|
evt[event]++;
|
|
}
|
|
|
|
const verify = (state, ...messages) => {
|
|
if (state) log('state', 'passed:', ...messages);
|
|
else log('error', 'failed:', ...messages);
|
|
};
|
|
|
|
async function verifyDetails(human) {
|
|
log('info', 'test: details verification');
|
|
const res = await testDetect(human, 'samples/in/ai-body.jpg', 'default');
|
|
verify(res.face.length === 1, 'details face length', res.face.length);
|
|
for (const face of res.face) {
|
|
verify(face.score > 0.9 && face.boxScore > 0.9 && face.faceScore > 0.9, 'details face score', face.score, face.boxScore, face.faceScore);
|
|
verify(face.age > 23 && face.age < 24 && face.gender === 'female' && face.genderScore > 0.9 && face.iris > 70 && face.iris < 80, 'details face age/gender', face.age, face.gender, face.genderScore, face.iris);
|
|
verify(face.box.length === 4 && face.boxRaw.length === 4 && face.mesh.length === 478 && face.meshRaw.length === 478 && face.embedding.length === 1024, 'details face arrays', face.box.length, face.mesh.length, face.embedding.length);
|
|
verify(face.emotion.length === 3 && face.emotion[0].score > 0.45 && face.emotion[0].emotion === 'neutral', 'details face emotion', face.emotion.length, face.emotion[0]);
|
|
verify(face.real > 0.8, 'details face anti-spoofing', face.real);
|
|
verify(face.live > 0.8, 'details face liveness', face.live);
|
|
}
|
|
verify(res.body.length === 1, 'details body length', res.body.length);
|
|
for (const body of res.body) {
|
|
verify(body.score > 0.9 && body.box.length === 4 && body.boxRaw.length === 4 && body.keypoints.length === 17 && Object.keys(body.annotations).length === 6, 'details body', body.score, body.keypoints.length, Object.keys(body.annotations).length);
|
|
}
|
|
verify(res.hand.length === 1, 'details hand length', res.hand.length);
|
|
for (const hand of res.hand) {
|
|
verify(hand.score > 0.5 && hand.boxScore > 0.5 && hand.fingerScore > 0.5 && hand.box.length === 4 && hand.boxRaw.length === 4 && hand.label === 'point', 'details hand', hand.boxScore, hand.fingerScore, hand.label);
|
|
verify(hand.keypoints.length === 21 && Object.keys(hand.landmarks).length === 5 && Object.keys(hand.annotations).length === 6, 'details hand arrays', hand.keypoints.length, Object.keys(hand.landmarks).length, Object.keys(hand.annotations).length);
|
|
}
|
|
verify(res.gesture.length === 6, 'details gesture length', res.gesture.length);
|
|
verify(res.gesture[0].gesture === 'facing right', 'details gesture first', res.gesture[0]);
|
|
verify(res.object.length === 1, 'details object length', res.object.length);
|
|
for (const obj of res.object) {
|
|
verify(obj.score > 0.7 && obj.label === 'person' && obj.box.length === 4 && obj.boxRaw.length === 4, 'details object', obj.score, obj.label);
|
|
}
|
|
}
|
|
|
|
async function testTensorShapes(human, input) {
|
|
await human.load(config);
|
|
const numTensors = human.tf.engine().state.numTensors;
|
|
let res;
|
|
let tensor;
|
|
|
|
tensor = await getImage(human, input, { channels: 4, expand: true, cast: true });
|
|
res = await human.detect(tensor, config);
|
|
verify(res.face.length === 1 && res.face[0].gender === 'female', 'tensor shape:', tensor.shape, 'dtype:', tensor.dtype);
|
|
human.tf.dispose(tensor);
|
|
|
|
tensor = await getImage(human, input, { channels: 4, expand: false, cast: true });
|
|
res = await human.detect(tensor, config);
|
|
verify(res.face.length === 1 && res.face[0].gender === 'female', 'tensor shape:', tensor.shape, 'dtype:', tensor.dtype);
|
|
human.tf.dispose(tensor);
|
|
|
|
tensor = await getImage(human, input, { channels: 3, expand: true, cast: true });
|
|
res = await human.detect(tensor, config);
|
|
verify(res.face.length === 1 && res.face[0].gender === 'female', 'tensor shape:', tensor.shape, 'dtype:', tensor.dtype);
|
|
human.tf.dispose(tensor);
|
|
|
|
tensor = await getImage(human, input, { channels: 3, expand: false, cast: true });
|
|
res = await human.detect(tensor, config);
|
|
verify(res.face.length === 1 && res.face[0].gender === 'female', 'tensor shape:', tensor.shape, 'dtype:', tensor.dtype);
|
|
human.tf.dispose(tensor);
|
|
|
|
tensor = await getImage(human, input, { channels: 4, expand: true, cast: false });
|
|
res = await human.detect(tensor, config);
|
|
verify(res.face.length === 1 && res.face[0].gender === 'female', 'tensor shape:', tensor.shape, 'dtype:', tensor.dtype);
|
|
human.tf.dispose(tensor);
|
|
|
|
const leak = human.tf.engine().state.numTensors - numTensors;
|
|
if (leak !== 0) log('error', 'failed: memory leak', leak);
|
|
}
|
|
|
|
async function verifyCompare(human) {
|
|
log('info', 'test: input compare');
|
|
const t1 = await getImage(human, 'samples/in/ai-face.jpg');
|
|
const t2 = await getImage(human, 'samples/in/ai-body.jpg');
|
|
const n1 = await human.compare(t1, t1);
|
|
const n2 = await human.compare(t1, t2);
|
|
const n3 = await human.compare(t2, t1);
|
|
const n4 = await human.compare(t2, t2);
|
|
verify(n1 === 0 && n4 === 0 && Math.round(n2) === Math.round(n3) && n2 > 20 && n2 < 30, 'image compare', n1, n2);
|
|
human.tf.dispose([t1, t2]);
|
|
}
|
|
|
|
async function test(Human, inputConfig) {
|
|
config = inputConfig;
|
|
fetch = (await import('node-fetch')).default;
|
|
const ok = await testHTTP();
|
|
if (!ok) {
|
|
log('error', 'aborting test');
|
|
return;
|
|
}
|
|
const t0 = process.hrtime.bigint();
|
|
let res;
|
|
|
|
// test event emitter
|
|
const human = new Human(config);
|
|
|
|
human.events.addEventListener('warmup', () => events('warmup'));
|
|
human.events.addEventListener('image', () => events('image'));
|
|
human.events.addEventListener('detect', () => events('detect'));
|
|
|
|
// test configuration validation
|
|
log('info', 'test: configuration validation');
|
|
let invalid = human.validate();
|
|
if (invalid.length === 0) log('state', 'passed: configuration default validation', invalid);
|
|
else log('error', 'failed: configuration default validation', invalid);
|
|
config.invalid = true;
|
|
invalid = human.validate(config);
|
|
if (invalid.length === 1) log('state', 'passed: configuration invalid validation', invalid);
|
|
else log('error', 'failed: configuration default validation', invalid);
|
|
delete config.invalid;
|
|
|
|
// test model loading
|
|
log('info', 'test: model load');
|
|
await human.load();
|
|
const models = Object.keys(human.models).map((model) => ({ name: model, loaded: (human.models[model] !== null) }));
|
|
const loaded = models.filter((model) => model.loaded);
|
|
if (models.length === 22 && loaded.length === 12) log('state', 'passed: models loaded', models.length, loaded.length, models);
|
|
else log('error', 'failed: models loaded', models.length, loaded.length, models);
|
|
|
|
// increase defaults
|
|
config.face = { detector: { maxDetected: 20 } };
|
|
|
|
// test warmup sequences
|
|
log('info', 'test: warmup');
|
|
await testInstance(human);
|
|
config.cacheSensitivity = 0;
|
|
config.warmup = 'none';
|
|
res = await testWarmup(human, 'default');
|
|
if (res.error !== 'null') log('error', 'failed: warmup none result mismatch');
|
|
else log('state', 'passed: warmup none result match');
|
|
config.warmup = 'face';
|
|
res = await testWarmup(human, 'default');
|
|
if (!res || res?.face?.length !== 1 || res?.body?.length !== 1 || res?.hand?.length !== 1 || res?.gesture?.length < 7) log('error', 'failed: warmup face result mismatch', res?.face?.length, res?.body?.length, res?.hand?.length, res?.gesture?.length);
|
|
else log('state', 'passed: warmup face result match');
|
|
config.warmup = 'body';
|
|
res = await testWarmup(human, 'default');
|
|
if (!res || res?.face?.length !== 1 || res?.body?.length !== 1 || res?.hand?.length !== 1 || res?.gesture?.length !== 6) log('error', 'failed: warmup body result mismatch', res?.face?.length, res?.body?.length, res?.hand?.length, res?.gesture?.length);
|
|
else log('state', 'passed: warmup body result match');
|
|
log('state', 'details:', {
|
|
face: { boxScore: res.face[0].boxScore, faceScore: res.face[0].faceScore, age: res.face[0].age, gender: res.face[0].gender, genderScore: res.face[0].genderScore },
|
|
emotion: res.face[0].emotion,
|
|
body: { score: res.body[0].score, keypoints: res.body[0].keypoints.length },
|
|
hand: { boxScore: res.hand[0].boxScore, fingerScore: res.hand[0].fingerScore, keypoints: res.hand[0].keypoints.length },
|
|
gestures: res.gesture,
|
|
});
|
|
|
|
await verifyDetails(human);
|
|
await testTensorShapes(human, 'samples/in/ai-body.jpg');
|
|
|
|
// test default config async
|
|
log('info', 'test default');
|
|
human.reset();
|
|
config.async = true;
|
|
config.cacheSensitivity = 0;
|
|
res = await testDetect(human, 'samples/in/ai-body.jpg', 'default');
|
|
if (!res || res?.face?.length !== 1 || res?.face[0].gender !== 'female') log('error', 'failed: default result face mismatch', res?.face?.length, res?.face[0].gender, res?.face[0].genderScore);
|
|
else log('state', 'passed: default result face match', res?.face?.length, res?.face[0].gender, res?.face[0].genderScore);
|
|
|
|
// test default config sync
|
|
log('info', 'test sync');
|
|
human.reset();
|
|
config.async = false;
|
|
config.cacheSensitivity = 0;
|
|
res = await testDetect(human, 'samples/in/ai-body.jpg', 'default');
|
|
if (!res || res?.face?.length !== 1 || res?.face[0].gender !== 'female') log('error', 'failed: default sync', res?.face?.length, res?.face[0].gender, res?.face[0].genderScore);
|
|
else log('state', 'passed: default sync', res?.face?.length, res?.face[0].gender, res?.face[0].genderScore);
|
|
|
|
// test image processing
|
|
log('info', 'test: image process');
|
|
const img1 = await human.image(null);
|
|
const img2 = await human.image(await getImage(human, 'samples/in/ai-face.jpg'));
|
|
if (!img1 || !img2 || img1.tensor !== null || img2.tensor?.shape?.length !== 4) log('error', 'failed: image input', img1?.tensor?.shape, img2?.tensor?.shape);
|
|
else log('state', 'passed: image input', img1?.tensor?.shape, img2?.tensor?.shape);
|
|
|
|
// test null input
|
|
log('info', 'test: image null');
|
|
res = await human.detect(null);
|
|
if (!res || !res.error) log('error', 'failed: invalid input', res);
|
|
else log('state', 'passed: invalid input', res);
|
|
|
|
// test face similarity
|
|
log('info', 'test face similarity');
|
|
human.reset();
|
|
config.async = false;
|
|
config.cacheSensitivity = 0;
|
|
let res1 = await testDetect(human, 'samples/in/ai-face.jpg', 'default');
|
|
let res2 = await testDetect(human, 'samples/in/ai-body.jpg', 'default');
|
|
let res3 = await testDetect(human, 'samples/in/ai-upper.jpg', 'default');
|
|
const desc1 = res1 && res1.face && res1.face[0] && res1.face[0].embedding ? [...res1.face[0].embedding] : null;
|
|
const desc2 = res2 && res2.face && res2.face[0] && res2.face[0].embedding ? [...res2.face[0].embedding] : null;
|
|
const desc3 = res3 && res3.face && res3.face[0] && res3.face[0].embedding ? [...res3.face[0].embedding] : null;
|
|
if (!desc1 || !desc2 || !desc3 || desc1.length !== 1024 || desc2.length !== 1024 || desc3.length !== 1024) log('error', 'failed: face descriptor', desc1?.length, desc2?.length, desc3?.length);
|
|
else log('state', 'passed: face descriptor');
|
|
res1 = human.similarity(desc1, desc1);
|
|
res2 = human.similarity(desc1, desc2);
|
|
res3 = human.similarity(desc1, desc3);
|
|
if (res1 < 1 || res2 < 0.50 || res3 < 0.50) log('error', 'failed: face similarity', { similarity: [res1, res2, res3], descriptors: [desc1?.length, desc2?.length, desc3?.length] });
|
|
else log('state', 'passed: face similarity', { similarity: [res1, res2, res3], descriptors: [desc1?.length, desc2?.length, desc3?.length] });
|
|
|
|
// test face matching
|
|
log('info', 'test face matching');
|
|
const db = JSON.parse(fs.readFileSync('demo/facematch/faces.json').toString());
|
|
const arr = db.map((rec) => rec.embedding);
|
|
if (db.length < 20) log('error', 'failed: face database ', db.length);
|
|
else log('state', 'passed: face database', db.length);
|
|
res1 = human.match(desc1, arr);
|
|
res2 = human.match(desc2, arr);
|
|
res3 = human.match(desc3, arr);
|
|
if (res1.index !== 4 || res2.index !== 4 || res3.index !== 4) log('error', 'failed: face match', res1, res2, res3);
|
|
else log('state', 'passed: face match', { first: { index: res1.index, similarity: res1.similarity } }, { second: { index: res2.index, similarity: res2.similarity } }, { third: { index: res3.index, similarity: res3.similarity } });
|
|
|
|
// test object detection
|
|
log('info', 'test object');
|
|
human.reset();
|
|
config.object = { enabled: true };
|
|
res = await testDetect(human, 'samples/in/ai-body.jpg', 'default');
|
|
if (!res || res?.object?.length !== 1 || res?.object[0]?.label !== 'person') log('error', 'failed: object result mismatch', res?.object?.length);
|
|
else log('state', 'passed: object result match');
|
|
|
|
// test sensitive config
|
|
log('info', 'test sensitive');
|
|
human.reset();
|
|
config.cacheSensitivity = 0;
|
|
config.face = { detector: { minConfidence: 0.0001, maxDetected: 1 } };
|
|
config.body = { minConfidence: 0.0001 };
|
|
config.hand = { minConfidence: 0.0001 };
|
|
res = await testDetect(human, 'samples/in/ai-body.jpg', 'default');
|
|
if (!res || res?.face?.length !== 1 || res?.body?.length !== 1 || res?.hand?.length !== 2 || res?.gesture?.length !== 8) log('error', 'failed: sensitive result mismatch', res?.face?.length, res?.body?.length, res?.hand?.length, res?.gesture?.length);
|
|
else log('state', 'passed: sensitive result match');
|
|
|
|
// test sensitive details face
|
|
const face = res && res.face ? res.face[0] : null;
|
|
if (!face || face?.box?.length !== 4 || face?.mesh?.length !== 478 || face?.embedding?.length !== 1024 || face?.rotation?.matrix?.length !== 9) {
|
|
log('error', 'failed: sensitive face result mismatch', res?.face?.length, face?.box?.length, face?.mesh?.length, face?.embedding?.length, face?.rotation?.matrix?.length);
|
|
} else log('state', 'passed: sensitive face result match');
|
|
if (!face || face?.emotion?.length < 3) log('error', 'failed: sensitive face emotion result mismatch', face?.emotion.length);
|
|
else log('state', 'passed: sensitive face emotion result', face?.emotion.length);
|
|
|
|
// test sensitive details body
|
|
const body = res && res.body ? res.body[0] : null;
|
|
if (!body || body?.box?.length !== 4 || body?.keypoints?.length !== 17) log('error', 'failed: sensitive body result mismatch', body);
|
|
else log('state', 'passed: sensitive body result match');
|
|
|
|
// test sensitive details hand
|
|
const hand = res && res.hand ? res.hand[0] : null;
|
|
if (!hand || hand?.box?.length !== 4 || hand?.keypoints?.length !== 21) log('error', 'failed: sensitive hand result mismatch', hand?.keypoints?.length);
|
|
else log('state', 'passed: sensitive hand result match');
|
|
|
|
// test detectors only
|
|
log('info', 'test detectors');
|
|
human.reset();
|
|
config.face = { mesh: { enabled: false }, iris: { enabled: false }, description: { enabled: false }, emotion: { enabled: false } };
|
|
config.hand = { landmarks: false };
|
|
res = await testDetect(human, 'samples/in/ai-body.jpg', 'default');
|
|
if (!res || res?.face?.length !== 1 || res?.face[0]?.gender || res?.face[0]?.age || res?.face[0]?.embedding) log('error', 'failed: detectors result face mismatch', res?.face);
|
|
else log('state', 'passed: detector result face match');
|
|
if (!res || res?.hand?.length !== 1 || res?.hand[0]?.landmarks?.length > 0) log('error', 'failed: detectors result hand mismatch', res?.hand?.length);
|
|
else log('state', 'passed: detector result hand match');
|
|
|
|
// test multiple instances
|
|
log('info', 'test: multi-instance');
|
|
const first = new Human(config);
|
|
const second = new Human(config);
|
|
await testDetect(human, null, 'default');
|
|
log('info', 'test: first instance');
|
|
await testDetect(first, 'samples/in/ai-upper.jpg', 'default');
|
|
log('info', 'test: second instance');
|
|
await testDetect(second, 'samples/in/ai-upper.jpg', 'default');
|
|
|
|
// test async multiple instances
|
|
log('info', 'test: concurrent');
|
|
await Promise.all([
|
|
testDetect(human, 'samples/in/ai-face.jpg', 'default', false),
|
|
testDetect(first, 'samples/in/ai-face.jpg', 'default', false),
|
|
testDetect(second, 'samples/in/ai-face.jpg', 'default', false),
|
|
testDetect(human, 'samples/in/ai-body.jpg', 'default', false),
|
|
testDetect(first, 'samples/in/ai-body.jpg', 'default', false),
|
|
testDetect(second, 'samples/in/ai-body.jpg', 'default', false),
|
|
testDetect(human, 'samples/in/ai-upper.jpg', 'default', false),
|
|
testDetect(first, 'samples/in/ai-upper.jpg', 'default', false),
|
|
testDetect(second, 'samples/in/ai-upper.jpg', 'default', false),
|
|
]);
|
|
|
|
// test monkey-patch
|
|
log('info', 'test: monkey-patch');
|
|
globalThis.Canvas = canvasJS.Canvas; // monkey-patch to use external canvas library
|
|
globalThis.ImageData = canvasJS.ImageData; // monkey-patch to use external canvas library
|
|
const inputImage = await canvasJS.loadImage('samples/in/ai-face.jpg'); // load image using canvas library
|
|
const inputCanvas = new canvasJS.Canvas(inputImage.width, inputImage.height); // create canvas
|
|
const ctx = inputCanvas.getContext('2d');
|
|
ctx.drawImage(inputImage, 0, 0); // draw input image onto canvas
|
|
res = await human.detect(inputCanvas);
|
|
if (!res || res?.face?.length !== 1) log('error', 'failed: monkey patch');
|
|
else log('state', 'passed: monkey patch');
|
|
|
|
// test segmentation
|
|
res = await human.segmentation(inputCanvas, inputCanvas);
|
|
if (!res || !res.data || !res.canvas) log('error', 'failed: segmentation');
|
|
else log('state', 'passed: segmentation', [res.data.length]);
|
|
human.env.Canvas = undefined;
|
|
|
|
// check if all instances reported same
|
|
const tensors1 = human.tf.engine().state.numTensors;
|
|
const tensors2 = first.tf.engine().state.numTensors;
|
|
const tensors3 = second.tf.engine().state.numTensors;
|
|
if (tensors1 === tensors2 && tensors1 === tensors3 && tensors2 === tensors3) log('state', 'passeed: equal usage');
|
|
else log('error', 'failed: equal usage', tensors1, tensors2, tensors3);
|
|
|
|
// validate cache compare algorithm
|
|
await verifyCompare(human);
|
|
|
|
// tests end
|
|
const t1 = process.hrtime.bigint();
|
|
|
|
// report end
|
|
log('info', 'events:', evt);
|
|
log('info', 'tensors', human.tf.memory().numTensors);
|
|
log('info', 'test complete:', Math.trunc(Number(t1 - t0) / 1000 / 1000), 'ms');
|
|
}
|
|
|
|
exports.test = test;
|