face-api/dist/face-api.node-gpu.js

4670 lines
169 KiB
JavaScript

/*
Face-API
homepage: <https://github.com/vladmandic/face-api>
author: <https://github.com/vladmandic>'
*/
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __markAsModule = (target) => __defProp(target, "__esModule", {value: true});
var __commonJS = (cb, mod) => () => (mod || cb((mod = {exports: {}}).exports, mod), mod.exports);
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, {get: all[name], enumerable: true});
};
var __reExport = (target, module2, desc) => {
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, {get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable});
}
return target;
};
var __toModule = (module2) => {
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? {get: () => module2.default, enumerable: true} : {value: module2, enumerable: true})), module2);
};
// dist/tfjs.esm.js
var require_tfjs_esm = __commonJS((exports) => {
var __create2 = Object.create;
var __defProp2 = Object.defineProperty;
var __getProtoOf2 = Object.getPrototypeOf;
var __hasOwnProp2 = Object.prototype.hasOwnProperty;
var __getOwnPropNames2 = Object.getOwnPropertyNames;
var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
var __markAsModule2 = (target) => __defProp2(target, "__esModule", {value: true});
var __reExport2 = (target, module22, desc) => {
if (module22 && typeof module22 === "object" || typeof module22 === "function") {
for (let key of __getOwnPropNames2(module22))
if (!__hasOwnProp2.call(target, key) && key !== "default")
__defProp2(target, key, {get: () => module22[key], enumerable: !(desc = __getOwnPropDesc2(module22, key)) || desc.enumerable});
}
return target;
};
var __toModule2 = (module22) => {
return __reExport2(__markAsModule2(__defProp2(module22 != null ? __create2(__getProtoOf2(module22)) : {}, "default", module22 && module22.__esModule && "default" in module22 ? {get: () => module22.default, enumerable: true} : {value: module22, enumerable: true})), module22);
};
__markAsModule2(exports);
__reExport2(exports, __toModule2(require("@tensorflow/tfjs-node-gpu")));
});
// src/index.ts
__markAsModule(exports);
__export(exports, {
AgeGenderNet: () => AgeGenderNet,
BoundingBox: () => BoundingBox,
Box: () => Box,
ComposableTask: () => ComposableTask,
ComputeAllFaceDescriptorsTask: () => ComputeAllFaceDescriptorsTask,
ComputeFaceDescriptorsTaskBase: () => ComputeFaceDescriptorsTaskBase,
ComputeSingleFaceDescriptorTask: () => ComputeSingleFaceDescriptorTask,
DetectAllFaceLandmarksTask: () => DetectAllFaceLandmarksTask,
DetectAllFacesTask: () => DetectAllFacesTask,
DetectFaceLandmarksTaskBase: () => DetectFaceLandmarksTaskBase,
DetectFacesTaskBase: () => DetectFacesTaskBase,
DetectSingleFaceLandmarksTask: () => DetectSingleFaceLandmarksTask,
DetectSingleFaceTask: () => DetectSingleFaceTask,
Dimensions: () => Dimensions,
FACE_EXPRESSION_LABELS: () => FACE_EXPRESSION_LABELS,
FaceDetection: () => FaceDetection,
FaceDetectionNet: () => FaceDetectionNet,
FaceExpressionNet: () => FaceExpressionNet,
FaceExpressions: () => FaceExpressions,
FaceLandmark68Net: () => FaceLandmark68Net,
FaceLandmark68TinyNet: () => FaceLandmark68TinyNet,
FaceLandmarkNet: () => FaceLandmarkNet,
FaceLandmarks: () => FaceLandmarks,
FaceLandmarks5: () => FaceLandmarks5,
FaceLandmarks68: () => FaceLandmarks68,
FaceMatch: () => FaceMatch,
FaceMatcher: () => FaceMatcher,
FaceRecognitionNet: () => FaceRecognitionNet,
Gender: () => Gender,
LabeledBox: () => LabeledBox,
LabeledFaceDescriptors: () => LabeledFaceDescriptors,
NetInput: () => NetInput,
NeuralNetwork: () => NeuralNetwork,
ObjectDetection: () => ObjectDetection,
Point: () => Point,
PredictedBox: () => PredictedBox,
Rect: () => Rect,
SsdMobilenetv1: () => SsdMobilenetv1,
SsdMobilenetv1Options: () => SsdMobilenetv1Options,
TinyFaceDetector: () => TinyFaceDetector,
TinyFaceDetectorOptions: () => TinyFaceDetectorOptions,
TinyYolov2: () => TinyYolov2,
TinyYolov2Options: () => TinyYolov2Options,
allFaces: () => allFaces,
allFacesSsdMobilenetv1: () => allFacesSsdMobilenetv1,
allFacesTinyYolov2: () => allFacesTinyYolov2,
awaitMediaLoaded: () => awaitMediaLoaded,
bufferToImage: () => bufferToImage,
computeFaceDescriptor: () => computeFaceDescriptor,
createCanvas: () => createCanvas,
createCanvasFromMedia: () => createCanvasFromMedia,
createFaceDetectionNet: () => createFaceDetectionNet,
createFaceRecognitionNet: () => createFaceRecognitionNet,
createSsdMobilenetv1: () => createSsdMobilenetv1,
createTinyFaceDetector: () => createTinyFaceDetector,
createTinyYolov2: () => createTinyYolov2,
detectAllFaces: () => detectAllFaces,
detectFaceLandmarks: () => detectFaceLandmarks,
detectFaceLandmarksTiny: () => detectFaceLandmarksTiny,
detectLandmarks: () => detectLandmarks,
detectSingleFace: () => detectSingleFace,
draw: () => draw_exports,
env: () => env,
euclideanDistance: () => euclideanDistance,
extendWithAge: () => extendWithAge,
extendWithFaceDescriptor: () => extendWithFaceDescriptor,
extendWithFaceDetection: () => extendWithFaceDetection,
extendWithFaceExpressions: () => extendWithFaceExpressions,
extendWithFaceLandmarks: () => extendWithFaceLandmarks,
extendWithGender: () => extendWithGender,
extractFaceTensors: () => extractFaceTensors,
extractFaces: () => extractFaces,
fetchImage: () => fetchImage,
fetchJson: () => fetchJson,
fetchNetWeights: () => fetchNetWeights,
fetchOrThrow: () => fetchOrThrow,
getContext2dOrThrow: () => getContext2dOrThrow,
getMediaDimensions: () => getMediaDimensions,
imageTensorToCanvas: () => imageTensorToCanvas,
imageToSquare: () => imageToSquare,
inverseSigmoid: () => inverseSigmoid,
iou: () => iou,
isMediaElement: () => isMediaElement,
isMediaLoaded: () => isMediaLoaded,
isWithAge: () => isWithAge,
isWithFaceDetection: () => isWithFaceDetection,
isWithFaceExpressions: () => isWithFaceExpressions,
isWithFaceLandmarks: () => isWithFaceLandmarks,
isWithGender: () => isWithGender,
loadAgeGenderModel: () => loadAgeGenderModel,
loadFaceDetectionModel: () => loadFaceDetectionModel,
loadFaceExpressionModel: () => loadFaceExpressionModel,
loadFaceLandmarkModel: () => loadFaceLandmarkModel,
loadFaceLandmarkTinyModel: () => loadFaceLandmarkTinyModel,
loadFaceRecognitionModel: () => loadFaceRecognitionModel,
loadSsdMobilenetv1Model: () => loadSsdMobilenetv1Model,
loadTinyFaceDetectorModel: () => loadTinyFaceDetectorModel,
loadTinyYolov2Model: () => loadTinyYolov2Model,
loadWeightMap: () => loadWeightMap,
locateFaces: () => locateFaces,
matchDimensions: () => matchDimensions,
minBbox: () => minBbox,
nets: () => nets,
nonMaxSuppression: () => nonMaxSuppression,
normalize: () => normalize,
padToSquare: () => padToSquare,
predictAgeAndGender: () => predictAgeAndGender,
recognizeFaceExpressions: () => recognizeFaceExpressions,
resizeResults: () => resizeResults,
resolveInput: () => resolveInput,
shuffleArray: () => shuffleArray,
sigmoid: () => sigmoid,
ssdMobilenetv1: () => ssdMobilenetv1,
tf: () => tf42,
tinyFaceDetector: () => tinyFaceDetector,
tinyYolov2: () => tinyYolov2,
toNetInput: () => toNetInput,
utils: () => utils_exports,
validateConfig: () => validateConfig,
version: () => version2
});
var tf42 = __toModule(require_tfjs_esm());
// src/draw/index.ts
var draw_exports = {};
__export(draw_exports, {
AnchorPosition: () => AnchorPosition,
DrawBox: () => DrawBox,
DrawBoxOptions: () => DrawBoxOptions,
DrawFaceLandmarks: () => DrawFaceLandmarks,
DrawFaceLandmarksOptions: () => DrawFaceLandmarksOptions,
DrawTextField: () => DrawTextField,
DrawTextFieldOptions: () => DrawTextFieldOptions,
drawContour: () => drawContour,
drawDetections: () => drawDetections,
drawFaceExpressions: () => drawFaceExpressions,
drawFaceLandmarks: () => drawFaceLandmarks
});
// src/draw/drawContour.ts
function drawContour(ctx, points, isClosed = false) {
ctx.beginPath();
points.slice(1).forEach(({x, y}, prevIdx) => {
const from = points[prevIdx];
ctx.moveTo(from.x, from.y);
ctx.lineTo(x, y);
});
if (isClosed) {
const from = points[points.length - 1];
const to = points[0];
if (!from || !to) {
return;
}
ctx.moveTo(from.x, from.y);
ctx.lineTo(to.x, to.y);
}
ctx.stroke();
}
// src/utils/index.ts
var utils_exports = {};
__export(utils_exports, {
computeReshapedDimensions: () => computeReshapedDimensions,
getCenterPoint: () => getCenterPoint,
isDimensions: () => isDimensions,
isEven: () => isEven,
isFloat: () => isFloat,
isTensor: () => isTensor,
isTensor1D: () => isTensor1D,
isTensor2D: () => isTensor2D,
isTensor3D: () => isTensor3D,
isTensor4D: () => isTensor4D,
isValidNumber: () => isValidNumber,
isValidProbablitiy: () => isValidProbablitiy,
range: () => range,
round: () => round
});
var tf = __toModule(require_tfjs_esm());
// src/classes/Dimensions.ts
var Dimensions = class {
constructor(width, height) {
if (!isValidNumber(width) || !isValidNumber(height)) {
throw new Error(`Dimensions.constructor - expected width and height to be valid numbers, instead have ${JSON.stringify({width, height})}`);
}
this._width = width;
this._height = height;
}
get width() {
return this._width;
}
get height() {
return this._height;
}
reverse() {
return new Dimensions(1 / this.width, 1 / this.height);
}
};
// src/utils/index.ts
function isTensor(tensor2, dim) {
return tensor2 instanceof tf.Tensor && tensor2.shape.length === dim;
}
function isTensor1D(tensor2) {
return isTensor(tensor2, 1);
}
function isTensor2D(tensor2) {
return isTensor(tensor2, 2);
}
function isTensor3D(tensor2) {
return isTensor(tensor2, 3);
}
function isTensor4D(tensor2) {
return isTensor(tensor2, 4);
}
function isFloat(num) {
return num % 1 !== 0;
}
function isEven(num) {
return num % 2 === 0;
}
function round(num, prec = 2) {
const f = 10 ** prec;
return Math.floor(num * f) / f;
}
function isDimensions(obj) {
return obj && obj.width && obj.height;
}
function computeReshapedDimensions({width, height}, inputSize) {
const scale2 = inputSize / Math.max(height, width);
return new Dimensions(Math.round(width * scale2), Math.round(height * scale2));
}
function getCenterPoint(pts) {
return pts.reduce((sum, pt) => sum.add(pt), new Point(0, 0)).div(new Point(pts.length, pts.length));
}
function range(num, start, step) {
return Array(num).fill(0).map((_, i) => start + i * step);
}
function isValidNumber(num) {
return !!num && num !== Infinity && num !== -Infinity && !Number.isNaN(num) || num === 0;
}
function isValidProbablitiy(num) {
return isValidNumber(num) && num >= 0 && num <= 1;
}
// src/classes/Point.ts
var Point = class {
constructor(x, y) {
this._x = x;
this._y = y;
}
get x() {
return this._x;
}
get y() {
return this._y;
}
add(pt) {
return new Point(this.x + pt.x, this.y + pt.y);
}
sub(pt) {
return new Point(this.x - pt.x, this.y - pt.y);
}
mul(pt) {
return new Point(this.x * pt.x, this.y * pt.y);
}
div(pt) {
return new Point(this.x / pt.x, this.y / pt.y);
}
abs() {
return new Point(Math.abs(this.x), Math.abs(this.y));
}
magnitude() {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
floor() {
return new Point(Math.floor(this.x), Math.floor(this.y));
}
};
// src/classes/Box.ts
var Box = class {
static isRect(rect) {
return !!rect && [rect.x, rect.y, rect.width, rect.height].every(isValidNumber);
}
static assertIsValidBox(box, callee, allowNegativeDimensions = false) {
if (!Box.isRect(box)) {
throw new Error(`${callee} - invalid box: ${JSON.stringify(box)}, expected object with properties x, y, width, height`);
}
if (!allowNegativeDimensions && (box.width < 0 || box.height < 0)) {
throw new Error(`${callee} - width (${box.width}) and height (${box.height}) must be positive numbers`);
}
}
constructor(_box, allowNegativeDimensions = true) {
const box = _box || {};
const isBbox = [box.left, box.top, box.right, box.bottom].every(isValidNumber);
const isRect = [box.x, box.y, box.width, box.height].every(isValidNumber);
if (!isRect && !isBbox) {
throw new Error(`Box.constructor - expected box to be IBoundingBox | IRect, instead have ${JSON.stringify(box)}`);
}
const [x, y, width, height] = isRect ? [box.x, box.y, box.width, box.height] : [box.left, box.top, box.right - box.left, box.bottom - box.top];
Box.assertIsValidBox({
x,
y,
width,
height
}, "Box.constructor", allowNegativeDimensions);
this._x = x;
this._y = y;
this._width = width;
this._height = height;
}
get x() {
return this._x;
}
get y() {
return this._y;
}
get width() {
return this._width;
}
get height() {
return this._height;
}
get left() {
return this.x;
}
get top() {
return this.y;
}
get right() {
return this.x + this.width;
}
get bottom() {
return this.y + this.height;
}
get area() {
return this.width * this.height;
}
get topLeft() {
return new Point(this.left, this.top);
}
get topRight() {
return new Point(this.right, this.top);
}
get bottomLeft() {
return new Point(this.left, this.bottom);
}
get bottomRight() {
return new Point(this.right, this.bottom);
}
round() {
const [x, y, width, height] = [this.x, this.y, this.width, this.height].map((val) => Math.round(val));
return new Box({
x,
y,
width,
height
});
}
floor() {
const [x, y, width, height] = [this.x, this.y, this.width, this.height].map((val) => Math.floor(val));
return new Box({
x,
y,
width,
height
});
}
toSquare() {
let {
x,
y,
width,
height
} = this;
const diff = Math.abs(width - height);
if (width < height) {
x -= diff / 2;
width += diff;
}
if (height < width) {
y -= diff / 2;
height += diff;
}
return new Box({x, y, width, height});
}
rescale(s) {
const scaleX = isDimensions(s) ? s.width : s;
const scaleY = isDimensions(s) ? s.height : s;
return new Box({
x: this.x * scaleX,
y: this.y * scaleY,
width: this.width * scaleX,
height: this.height * scaleY
});
}
pad(padX, padY) {
const [x, y, width, height] = [
this.x - padX / 2,
this.y - padY / 2,
this.width + padX,
this.height + padY
];
return new Box({
x,
y,
width,
height
});
}
clipAtImageBorders(imgWidth, imgHeight) {
const {x, y, right, bottom} = this;
const clippedX = Math.max(x, 0);
const clippedY = Math.max(y, 0);
const newWidth = right - clippedX;
const newHeight = bottom - clippedY;
const clippedWidth = Math.min(newWidth, imgWidth - clippedX);
const clippedHeight = Math.min(newHeight, imgHeight - clippedY);
return new Box({
x: clippedX,
y: clippedY,
width: clippedWidth,
height: clippedHeight
}).floor();
}
shift(sx, sy) {
const {width, height} = this;
const x = this.x + sx;
const y = this.y + sy;
return new Box({
x,
y,
width,
height
});
}
padAtBorders(imageHeight, imageWidth) {
const w = this.width + 1;
const h = this.height + 1;
const dx = 1;
const dy = 1;
let edx = w;
let edy = h;
let x = this.left;
let y = this.top;
let ex = this.right;
let ey = this.bottom;
if (ex > imageWidth) {
edx = -ex + imageWidth + w;
ex = imageWidth;
}
if (ey > imageHeight) {
edy = -ey + imageHeight + h;
ey = imageHeight;
}
if (x < 1) {
edy = 2 - x;
x = 1;
}
if (y < 1) {
edy = 2 - y;
y = 1;
}
return {
dy,
edy,
dx,
edx,
y,
ey,
x,
ex,
w,
h
};
}
calibrate(region) {
return new Box({
left: this.left + region.left * this.width,
top: this.top + region.top * this.height,
right: this.right + region.right * this.width,
bottom: this.bottom + region.bottom * this.height
}).toSquare().round();
}
};
// src/classes/BoundingBox.ts
var BoundingBox = class extends Box {
constructor(left, top, right, bottom, allowNegativeDimensions = false) {
super({
left,
top,
right,
bottom
}, allowNegativeDimensions);
}
};
// src/classes/ObjectDetection.ts
var ObjectDetection = class {
constructor(score, classScore, className, relativeBox, imageDims) {
this._imageDims = new Dimensions(imageDims.width, imageDims.height);
this._score = score;
this._classScore = classScore;
this._className = className;
this._box = new Box(relativeBox).rescale(this._imageDims);
}
get score() {
return this._score;
}
get classScore() {
return this._classScore;
}
get className() {
return this._className;
}
get box() {
return this._box;
}
get imageDims() {
return this._imageDims;
}
get imageWidth() {
return this.imageDims.width;
}
get imageHeight() {
return this.imageDims.height;
}
get relativeBox() {
return new Box(this._box).rescale(this.imageDims.reverse());
}
forSize(width, height) {
return new ObjectDetection(this.score, this.classScore, this.className, this.relativeBox, {width, height});
}
};
// src/classes/FaceDetection.ts
var FaceDetection = class extends ObjectDetection {
constructor(score, relativeBox, imageDims) {
super(score, score, "", relativeBox, imageDims);
}
forSize(width, height) {
const {score, relativeBox, imageDims} = super.forSize(width, height);
return new FaceDetection(score, relativeBox, imageDims);
}
};
// src/ops/iou.ts
function iou(box1, box2, isIOU = true) {
const width = Math.max(0, Math.min(box1.right, box2.right) - Math.max(box1.left, box2.left));
const height = Math.max(0, Math.min(box1.bottom, box2.bottom) - Math.max(box1.top, box2.top));
const interSection = width * height;
return isIOU ? interSection / (box1.area + box2.area - interSection) : interSection / Math.min(box1.area, box2.area);
}
// src/ops/minBbox.ts
function minBbox(pts) {
const xs = pts.map((pt) => pt.x);
const ys = pts.map((pt) => pt.y);
const minX = xs.reduce((min, x) => x < min ? x : min, Infinity);
const minY = ys.reduce((min, y) => y < min ? y : min, Infinity);
const maxX = xs.reduce((max, x) => max < x ? x : max, 0);
const maxY = ys.reduce((max, y) => max < y ? y : max, 0);
return new BoundingBox(minX, minY, maxX, maxY);
}
// src/ops/nonMaxSuppression.ts
function nonMaxSuppression(boxes, scores, iouThreshold, isIOU = true) {
let indicesSortedByScore = scores.map((score, boxIndex) => ({score, boxIndex})).sort((c1, c2) => c1.score - c2.score).map((c) => c.boxIndex);
const pick = [];
while (indicesSortedByScore.length > 0) {
const curr = indicesSortedByScore.pop();
pick.push(curr);
const indices = indicesSortedByScore;
const outputs = [];
for (let i = 0; i < indices.length; i++) {
const idx = indices[i];
const currBox = boxes[curr];
const idxBox = boxes[idx];
outputs.push(iou(currBox, idxBox, isIOU));
}
indicesSortedByScore = indicesSortedByScore.filter((_, j) => outputs[j] <= iouThreshold);
}
return pick;
}
// src/ops/normalize.ts
var tf2 = __toModule(require_tfjs_esm());
function normalize(x, meanRgb) {
return tf2.tidy(() => {
const [r, g, b] = meanRgb;
const avg_r = tf2.fill([...x.shape.slice(0, 3), 1], r, "float32");
const avg_g = tf2.fill([...x.shape.slice(0, 3), 1], g, "float32");
const avg_b = tf2.fill([...x.shape.slice(0, 3), 1], b, "float32");
const avg_rgb = tf2.concat([avg_r, avg_g, avg_b], 3);
return tf2.sub(x, avg_rgb);
});
}
// src/ops/padToSquare.ts
var tf3 = __toModule(require_tfjs_esm());
function padToSquare(imgTensor, isCenterImage = false) {
return tf3.tidy(() => {
const [height, width] = imgTensor.shape.slice(1);
if (height === width) {
return imgTensor;
}
const dimDiff = Math.abs(height - width);
const paddingAmount = Math.round(dimDiff * (isCenterImage ? 0.5 : 1));
const paddingAxis = height > width ? 2 : 1;
const createPaddingTensor = (paddingAmountLocal) => {
const paddingTensorShape = imgTensor.shape.slice();
paddingTensorShape[paddingAxis] = paddingAmountLocal;
return tf3.fill(paddingTensorShape, 0, "float32");
};
const paddingTensorAppend = createPaddingTensor(paddingAmount);
const remainingPaddingAmount = dimDiff - paddingTensorAppend.shape[paddingAxis];
const paddingTensorPrepend = isCenterImage && remainingPaddingAmount ? createPaddingTensor(remainingPaddingAmount) : null;
const tensorsToStack = [
paddingTensorPrepend,
imgTensor,
paddingTensorAppend
].filter((t) => !!t).map((t) => tf3.cast(t, "float32"));
return tf3.concat(tensorsToStack, paddingAxis);
});
}
// src/ops/shuffleArray.ts
function shuffleArray(inputArray) {
const array = inputArray.slice();
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const x = array[i];
array[i] = array[j];
array[j] = x;
}
return array;
}
// src/ops/index.ts
function sigmoid(x) {
return 1 / (1 + Math.exp(-x));
}
function inverseSigmoid(x) {
return Math.log(x / (1 - x));
}
// src/classes/Rect.ts
var Rect = class extends Box {
constructor(x, y, width, height, allowNegativeDimensions = false) {
super({
x,
y,
width,
height
}, allowNegativeDimensions);
}
};
// src/classes/FaceLandmarks.ts
var relX = 0.5;
var relY = 0.43;
var relScale = 0.45;
var FaceLandmarks = class {
constructor(relativeFaceLandmarkPositions, imgDims, shift = new Point(0, 0)) {
const {width, height} = imgDims;
this._imgDims = new Dimensions(width, height);
this._shift = shift;
this._positions = relativeFaceLandmarkPositions.map((pt) => pt.mul(new Point(width, height)).add(shift));
}
get shift() {
return new Point(this._shift.x, this._shift.y);
}
get imageWidth() {
return this._imgDims.width;
}
get imageHeight() {
return this._imgDims.height;
}
get positions() {
return this._positions;
}
get relativePositions() {
return this._positions.map((pt) => pt.sub(this._shift).div(new Point(this.imageWidth, this.imageHeight)));
}
forSize(width, height) {
return new this.constructor(this.relativePositions, {width, height});
}
shiftBy(x, y) {
return new this.constructor(this.relativePositions, this._imgDims, new Point(x, y));
}
shiftByPoint(pt) {
return this.shiftBy(pt.x, pt.y);
}
align(detection, options = {}) {
if (detection) {
const box = detection instanceof FaceDetection ? detection.box.floor() : new Box(detection);
return this.shiftBy(box.x, box.y).align(null, options);
}
const {useDlibAlignment, minBoxPadding} = {useDlibAlignment: false, minBoxPadding: 0.2, ...options};
if (useDlibAlignment) {
return this.alignDlib();
}
return this.alignMinBbox(minBoxPadding);
}
alignDlib() {
const centers = this.getRefPointsForAlignment();
const [leftEyeCenter, rightEyeCenter, mouthCenter] = centers;
const distToMouth = (pt) => mouthCenter.sub(pt).magnitude();
const eyeToMouthDist = (distToMouth(leftEyeCenter) + distToMouth(rightEyeCenter)) / 2;
const size = Math.floor(eyeToMouthDist / relScale);
const refPoint = getCenterPoint(centers);
const x = Math.floor(Math.max(0, refPoint.x - relX * size));
const y = Math.floor(Math.max(0, refPoint.y - relY * size));
return new Rect(x, y, Math.min(size, this.imageWidth + x), Math.min(size, this.imageHeight + y));
}
alignMinBbox(padding) {
const box = minBbox(this.positions);
return box.pad(box.width * padding, box.height * padding);
}
getRefPointsForAlignment() {
throw new Error("getRefPointsForAlignment not implemented by base class");
}
};
// src/classes/FaceLandmarks5.ts
var FaceLandmarks5 = class extends FaceLandmarks {
getRefPointsForAlignment() {
const pts = this.positions;
return [
pts[0],
pts[1],
getCenterPoint([pts[3], pts[4]])
];
}
};
// src/classes/FaceLandmarks68.ts
var FaceLandmarks68 = class extends FaceLandmarks {
getJawOutline() {
return this.positions.slice(0, 17);
}
getLeftEyeBrow() {
return this.positions.slice(17, 22);
}
getRightEyeBrow() {
return this.positions.slice(22, 27);
}
getNose() {
return this.positions.slice(27, 36);
}
getLeftEye() {
return this.positions.slice(36, 42);
}
getRightEye() {
return this.positions.slice(42, 48);
}
getMouth() {
return this.positions.slice(48, 68);
}
getRefPointsForAlignment() {
return [
this.getLeftEye(),
this.getRightEye(),
this.getMouth()
].map(getCenterPoint);
}
};
// src/classes/FaceMatch.ts
var FaceMatch = class {
constructor(label, distance) {
this._label = label;
this._distance = distance;
}
get label() {
return this._label;
}
get distance() {
return this._distance;
}
toString(withDistance = true) {
return `${this.label}${withDistance ? ` (${round(this.distance)})` : ""}`;
}
};
// src/classes/LabeledBox.ts
var LabeledBox = class extends Box {
static assertIsValidLabeledBox(box, callee) {
Box.assertIsValidBox(box, callee);
if (!isValidNumber(box.label)) {
throw new Error(`${callee} - expected property label (${box.label}) to be a number`);
}
}
constructor(box, label) {
super(box);
this._label = label;
}
get label() {
return this._label;
}
};
// src/classes/LabeledFaceDescriptors.ts
var LabeledFaceDescriptors = class {
constructor(label, descriptors) {
if (!(typeof label === "string")) {
throw new Error("LabeledFaceDescriptors - constructor expected label to be a string");
}
if (!Array.isArray(descriptors) || descriptors.some((desc) => !(desc instanceof Float32Array))) {
throw new Error("LabeledFaceDescriptors - constructor expected descriptors to be an array of Float32Array");
}
this._label = label;
this._descriptors = descriptors;
}
get label() {
return this._label;
}
get descriptors() {
return this._descriptors;
}
toJSON() {
return {
label: this.label,
descriptors: this.descriptors.map((d) => Array.from(d))
};
}
static fromJSON(json) {
const descriptors = json.descriptors.map((d) => new Float32Array(d));
return new LabeledFaceDescriptors(json.label, descriptors);
}
};
// src/classes/PredictedBox.ts
var PredictedBox = class extends LabeledBox {
static assertIsValidPredictedBox(box, callee) {
LabeledBox.assertIsValidLabeledBox(box, callee);
if (!isValidProbablitiy(box.score) || !isValidProbablitiy(box.classScore)) {
throw new Error(`${callee} - expected properties score (${box.score}) and (${box.classScore}) to be a number between [0, 1]`);
}
}
constructor(box, label, score, classScore) {
super(box, label);
this._score = score;
this._classScore = classScore;
}
get score() {
return this._score;
}
get classScore() {
return this._classScore;
}
};
// src/factories/WithFaceDetection.ts
function isWithFaceDetection(obj) {
return obj.detection instanceof FaceDetection;
}
function extendWithFaceDetection(sourceObj, detection) {
const extension = {detection};
return {...sourceObj, ...extension};
}
// src/env/createBrowserEnv.ts
function createBrowserEnv() {
const fetch = window.fetch;
if (!fetch)
throw new Error("fetch - missing fetch implementation for browser environment");
const readFile = () => {
throw new Error("readFile - filesystem not available for browser environment");
};
return {
Canvas: HTMLCanvasElement,
CanvasRenderingContext2D,
Image: HTMLImageElement,
ImageData,
Video: HTMLVideoElement,
createCanvasElement: () => document.createElement("canvas"),
createImageElement: () => document.createElement("img"),
fetch,
readFile
};
}
// src/env/createFileSystem.ts
function createFileSystem(fs) {
let requireFsError = "";
if (!fs) {
try {
fs = require("fs");
} catch (err) {
requireFsError = err.toString();
}
}
const readFile = fs ? (filePath) => new Promise((resolve, reject) => {
fs.readFile(filePath, (err, buffer) => err ? reject(err) : resolve(buffer));
}) : () => {
throw new Error(`readFile - failed to require fs in nodejs environment with error: ${requireFsError}`);
};
return {
readFile
};
}
// src/env/createNodejsEnv.ts
function createNodejsEnv() {
const Canvas = global["Canvas"] || global.HTMLCanvasElement;
const Image = global.Image || global.HTMLImageElement;
const createCanvasElement = () => {
if (Canvas)
return new Canvas();
throw new Error("createCanvasElement - missing Canvas implementation for nodejs environment");
};
const createImageElement = () => {
if (Image)
return new Image();
throw new Error("createImageElement - missing Image implementation for nodejs environment");
};
const fetch = global.fetch;
const fileSystem = createFileSystem();
return {
Canvas: Canvas || class {
},
CanvasRenderingContext2D: global.CanvasRenderingContext2D || class {
},
Image: Image || class {
},
ImageData: global.ImageData || class {
},
Video: global.HTMLVideoElement || class {
},
createCanvasElement,
createImageElement,
fetch,
...fileSystem
};
}
// src/env/isBrowser.ts
function isBrowser() {
return typeof window === "object" && typeof document !== "undefined" && typeof HTMLImageElement !== "undefined" && typeof HTMLCanvasElement !== "undefined" && typeof HTMLVideoElement !== "undefined" && typeof ImageData !== "undefined" && typeof CanvasRenderingContext2D !== "undefined";
}
// src/env/isNodejs.ts
function isNodejs() {
return typeof global === "object" && true && typeof module !== "undefined" && typeof process !== "undefined" && !!process.version;
}
// src/env/index.ts
var environment;
function getEnv() {
if (!environment) {
throw new Error("getEnv - environment is not defined, check isNodejs() and isBrowser()");
}
return environment;
}
function setEnv(env2) {
environment = env2;
}
function initialize() {
if (isBrowser())
return setEnv(createBrowserEnv());
if (isNodejs())
return setEnv(createNodejsEnv());
return null;
}
function monkeyPatch(env2) {
if (!environment) {
initialize();
}
if (!environment) {
throw new Error("monkeyPatch - environment is not defined, check isNodejs() and isBrowser()");
}
const {Canvas = environment.Canvas, Image = environment.Image} = env2;
environment.Canvas = Canvas;
environment.Image = Image;
environment.createCanvasElement = env2.createCanvasElement || (() => new Canvas());
environment.createImageElement = env2.createImageElement || (() => new Image());
environment.ImageData = env2.ImageData || environment.ImageData;
environment.Video = env2.Video || environment.Video;
environment.fetch = env2.fetch || environment.fetch;
environment.readFile = env2.readFile || environment.readFile;
}
var env = {
getEnv,
setEnv,
initialize,
createBrowserEnv,
createFileSystem,
createNodejsEnv,
monkeyPatch,
isBrowser,
isNodejs
};
initialize();
// src/dom/resolveInput.ts
function resolveInput(arg) {
if (!env.isNodejs() && typeof arg === "string") {
return document.getElementById(arg);
}
return arg;
}
// src/dom/getContext2dOrThrow.ts
function getContext2dOrThrow(canvasArg) {
const {Canvas, CanvasRenderingContext2D: CanvasRenderingContext2D2} = env.getEnv();
if (canvasArg instanceof CanvasRenderingContext2D2) {
return canvasArg;
}
const canvas = resolveInput(canvasArg);
if (!(canvas instanceof Canvas)) {
throw new Error("resolveContext2d - expected canvas to be of instance of Canvas");
}
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("resolveContext2d - canvas 2d context is null");
}
return ctx;
}
// src/draw/DrawTextField.ts
var AnchorPosition;
(function(AnchorPosition2) {
AnchorPosition2["TOP_LEFT"] = "TOP_LEFT";
AnchorPosition2["TOP_RIGHT"] = "TOP_RIGHT";
AnchorPosition2["BOTTOM_LEFT"] = "BOTTOM_LEFT";
AnchorPosition2["BOTTOM_RIGHT"] = "BOTTOM_RIGHT";
})(AnchorPosition || (AnchorPosition = {}));
var DrawTextFieldOptions = class {
constructor(options = {}) {
const {
anchorPosition,
backgroundColor,
fontColor,
fontSize,
fontStyle,
padding
} = options;
this.anchorPosition = anchorPosition || AnchorPosition.TOP_LEFT;
this.backgroundColor = backgroundColor || "rgba(0, 0, 0, 0.5)";
this.fontColor = fontColor || "rgba(255, 255, 255, 1)";
this.fontSize = fontSize || 14;
this.fontStyle = fontStyle || "Georgia";
this.padding = padding || 4;
}
};
var DrawTextField = class {
constructor(text, anchor, options = {}) {
this.text = typeof text === "string" ? [text] : text instanceof DrawTextField ? text.text : text;
this.anchor = anchor;
this.options = new DrawTextFieldOptions(options);
}
measureWidth(ctx) {
const {padding} = this.options;
return this.text.map((l) => ctx.measureText(l).width).reduce((w0, w1) => w0 < w1 ? w1 : w0, 0) + 2 * padding;
}
measureHeight() {
const {fontSize, padding} = this.options;
return this.text.length * fontSize + 2 * padding;
}
getUpperLeft(ctx, canvasDims) {
const {anchorPosition} = this.options;
const isShiftLeft = anchorPosition === AnchorPosition.BOTTOM_RIGHT || anchorPosition === AnchorPosition.TOP_RIGHT;
const isShiftTop = anchorPosition === AnchorPosition.BOTTOM_LEFT || anchorPosition === AnchorPosition.BOTTOM_RIGHT;
const textFieldWidth = this.measureWidth(ctx);
const textFieldHeight = this.measureHeight();
const x = isShiftLeft ? this.anchor.x - textFieldWidth : this.anchor.x;
const y = isShiftTop ? this.anchor.y - textFieldHeight : this.anchor.y;
if (canvasDims) {
const {width, height} = canvasDims;
const newX = Math.max(Math.min(x, width - textFieldWidth), 0);
const newY = Math.max(Math.min(y, height - textFieldHeight), 0);
return {x: newX, y: newY};
}
return {x, y};
}
draw(canvasArg) {
const canvas = resolveInput(canvasArg);
const ctx = getContext2dOrThrow(canvas);
const {
backgroundColor,
fontColor,
fontSize,
fontStyle,
padding
} = this.options;
ctx.font = `${fontSize}px ${fontStyle}`;
const maxTextWidth = this.measureWidth(ctx);
const textHeight = this.measureHeight();
ctx.fillStyle = backgroundColor;
const upperLeft = this.getUpperLeft(ctx, canvas);
ctx.fillRect(upperLeft.x, upperLeft.y, maxTextWidth, textHeight);
ctx.fillStyle = fontColor;
this.text.forEach((textLine, i) => {
const x = padding + upperLeft.x;
const y = padding + upperLeft.y + (i + 1) * fontSize;
ctx.fillText(textLine, x, y);
});
}
};
// src/draw/DrawBox.ts
var DrawBoxOptions = class {
constructor(options = {}) {
const {
boxColor,
lineWidth,
label,
drawLabelOptions
} = options;
this.boxColor = boxColor || "rgba(0, 0, 255, 1)";
this.lineWidth = lineWidth || 2;
this.label = label;
const defaultDrawLabelOptions = {
anchorPosition: AnchorPosition.BOTTOM_LEFT,
backgroundColor: this.boxColor
};
this.drawLabelOptions = new DrawTextFieldOptions({...defaultDrawLabelOptions, ...drawLabelOptions});
}
};
var DrawBox = class {
constructor(box, options = {}) {
this.box = new Box(box);
this.options = new DrawBoxOptions(options);
}
draw(canvasArg) {
const ctx = getContext2dOrThrow(canvasArg);
const {boxColor, lineWidth} = this.options;
const {
x,
y,
width,
height
} = this.box;
ctx.strokeStyle = boxColor;
ctx.lineWidth = lineWidth;
ctx.strokeRect(x, y, width, height);
const {label} = this.options;
if (label) {
new DrawTextField([label], {x: x - lineWidth / 2, y}, this.options.drawLabelOptions).draw(canvasArg);
}
}
};
// src/draw/drawDetections.ts
function drawDetections(canvasArg, detections) {
const detectionsArray = Array.isArray(detections) ? detections : [detections];
detectionsArray.forEach((det) => {
const score = det instanceof FaceDetection ? det.score : isWithFaceDetection(det) ? det.detection.score : void 0;
const box = det instanceof FaceDetection ? det.box : isWithFaceDetection(det) ? det.detection.box : new Box(det);
const label = score ? `${round(score)}` : void 0;
new DrawBox(box, {label}).draw(canvasArg);
});
}
// src/faceExpressionNet/FaceExpressionNet.ts
var tf18 = __toModule(require_tfjs_esm());
// src/dom/isMediaLoaded.ts
function isMediaLoaded(media) {
const {Image, Video} = env.getEnv();
return media instanceof Image && media.complete || media instanceof Video && media.readyState >= 3;
}
// src/dom/awaitMediaLoaded.ts
function awaitMediaLoaded(media) {
return new Promise((resolve, reject) => {
if (media instanceof env.getEnv().Canvas || isMediaLoaded(media))
return resolve(null);
function onError(e) {
if (!e.currentTarget)
return;
e.currentTarget.removeEventListener("load", onLoad);
e.currentTarget.removeEventListener("error", onError);
reject(e);
}
function onLoad(e) {
if (!e.currentTarget)
return;
e.currentTarget.removeEventListener("load", onLoad);
e.currentTarget.removeEventListener("error", onError);
resolve(e);
}
media.addEventListener("load", onLoad);
media.addEventListener("error", onError);
});
}
// src/dom/bufferToImage.ts
function bufferToImage(buf) {
return new Promise((resolve, reject) => {
if (!(buf instanceof Blob))
reject(new Error("bufferToImage - expected buf to be of type: Blob"));
const reader = new FileReader();
reader.onload = () => {
if (typeof reader.result !== "string")
reject(new Error("bufferToImage - expected reader.result to be a string, in onload"));
const img = env.getEnv().createImageElement();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = reader.result;
};
reader.onerror = reject;
reader.readAsDataURL(buf);
});
}
// src/dom/getMediaDimensions.ts
function getMediaDimensions(input) {
const {Image, Video} = env.getEnv();
if (input instanceof Image) {
return new Dimensions(input.naturalWidth, input.naturalHeight);
}
if (input instanceof Video) {
return new Dimensions(input.videoWidth, input.videoHeight);
}
return new Dimensions(input.width, input.height);
}
// src/dom/createCanvas.ts
function createCanvas({width, height}) {
const {createCanvasElement} = env.getEnv();
const canvas = createCanvasElement();
canvas.width = width;
canvas.height = height;
return canvas;
}
function createCanvasFromMedia(media, dims) {
const {ImageData: ImageData2} = env.getEnv();
if (!(media instanceof ImageData2) && !isMediaLoaded(media)) {
throw new Error("createCanvasFromMedia - media has not finished loading yet");
}
const {width, height} = dims || getMediaDimensions(media);
const canvas = createCanvas({width, height});
if (media instanceof ImageData2) {
getContext2dOrThrow(canvas).putImageData(media, 0, 0);
} else {
getContext2dOrThrow(canvas).drawImage(media, 0, 0, width, height);
}
return canvas;
}
// src/dom/imageTensorToCanvas.ts
var tf4 = __toModule(require_tfjs_esm());
async function imageTensorToCanvas(imgTensor, canvas) {
const targetCanvas = canvas || env.getEnv().createCanvasElement();
const [height, width, numChannels] = imgTensor.shape.slice(isTensor4D(imgTensor) ? 1 : 0);
const imgTensor3D = tf4.tidy(() => imgTensor.as3D(height, width, numChannels).toInt());
await tf4.browser.toPixels(imgTensor3D, targetCanvas);
imgTensor3D.dispose();
return targetCanvas;
}
// src/dom/isMediaElement.ts
function isMediaElement(input) {
const {Image, Canvas, Video} = env.getEnv();
return input instanceof Image || input instanceof Canvas || input instanceof Video;
}
// src/dom/NetInput.ts
var tf5 = __toModule(require_tfjs_esm());
// src/dom/imageToSquare.ts
function imageToSquare(input, inputSize, centerImage = false) {
const {Image, Canvas} = env.getEnv();
if (!(input instanceof Image || input instanceof Canvas)) {
throw new Error("imageToSquare - expected arg0 to be HTMLImageElement | HTMLCanvasElement");
}
if (inputSize <= 0)
return createCanvas({width: 1, height: 1});
const dims = getMediaDimensions(input);
const scale2 = inputSize / Math.max(dims.height, dims.width);
const width = scale2 * dims.width;
const height = scale2 * dims.height;
const targetCanvas = createCanvas({width: inputSize, height: inputSize});
const inputCanvas = input instanceof Canvas ? input : createCanvasFromMedia(input);
const offset = Math.abs(width - height) / 2;
const dx = centerImage && width < height ? offset : 0;
const dy = centerImage && height < width ? offset : 0;
if (inputCanvas.width > 0 && inputCanvas.height > 0)
getContext2dOrThrow(targetCanvas).drawImage(inputCanvas, dx, dy, width, height);
return targetCanvas;
}
// src/dom/NetInput.ts
var NetInput = class {
constructor(inputs, treatAsBatchInput = false) {
this._imageTensors = [];
this._canvases = [];
this._treatAsBatchInput = false;
this._inputDimensions = [];
if (!Array.isArray(inputs)) {
throw new Error(`NetInput.constructor - expected inputs to be an Array of TResolvedNetInput or to be instanceof tf.Tensor4D, instead have ${inputs}`);
}
this._treatAsBatchInput = treatAsBatchInput;
this._batchSize = inputs.length;
inputs.forEach((input, idx) => {
if (isTensor3D(input)) {
this._imageTensors[idx] = input;
this._inputDimensions[idx] = input.shape;
return;
}
if (isTensor4D(input)) {
const batchSize = input.shape[0];
if (batchSize !== 1) {
throw new Error(`NetInput - tf.Tensor4D with batchSize ${batchSize} passed, but not supported in input array`);
}
this._imageTensors[idx] = input;
this._inputDimensions[idx] = input.shape.slice(1);
return;
}
const canvas = input instanceof env.getEnv().Canvas ? input : createCanvasFromMedia(input);
this._canvases[idx] = canvas;
this._inputDimensions[idx] = [canvas.height, canvas.width, 3];
});
}
get imageTensors() {
return this._imageTensors;
}
get canvases() {
return this._canvases;
}
get isBatchInput() {
return this.batchSize > 1 || this._treatAsBatchInput;
}
get batchSize() {
return this._batchSize;
}
get inputDimensions() {
return this._inputDimensions;
}
get inputSize() {
return this._inputSize;
}
get reshapedInputDimensions() {
return range(this.batchSize, 0, 1).map((_, batchIdx) => this.getReshapedInputDimensions(batchIdx));
}
getInput(batchIdx) {
return this.canvases[batchIdx] || this.imageTensors[batchIdx];
}
getInputDimensions(batchIdx) {
return this._inputDimensions[batchIdx];
}
getInputHeight(batchIdx) {
return this._inputDimensions[batchIdx][0];
}
getInputWidth(batchIdx) {
return this._inputDimensions[batchIdx][1];
}
getReshapedInputDimensions(batchIdx) {
if (typeof this.inputSize !== "number") {
throw new Error("getReshapedInputDimensions - inputSize not set, toBatchTensor has not been called yet");
}
const width = this.getInputWidth(batchIdx);
const height = this.getInputHeight(batchIdx);
return computeReshapedDimensions({width, height}, this.inputSize);
}
toBatchTensor(inputSize, isCenterInputs = true) {
this._inputSize = inputSize;
return tf5.tidy(() => {
const inputTensors = range(this.batchSize, 0, 1).map((batchIdx) => {
const input = this.getInput(batchIdx);
if (input instanceof tf5.Tensor) {
let imgTensor = isTensor4D(input) ? input : tf5.expandDims(input);
imgTensor = padToSquare(imgTensor, isCenterInputs);
if (imgTensor.shape[1] !== inputSize || imgTensor.shape[2] !== inputSize) {
imgTensor = tf5.image.resizeBilinear(imgTensor, [inputSize, inputSize], false, false);
}
return imgTensor.as3D(inputSize, inputSize, 3);
}
if (input instanceof env.getEnv().Canvas) {
return tf5.browser.fromPixels(imageToSquare(input, inputSize, isCenterInputs));
}
throw new Error(`toBatchTensor - at batchIdx ${batchIdx}, expected input to be instanceof tf.Tensor or instanceof HTMLCanvasElement, instead have ${input}`);
});
const batchTensor = tf5.stack(inputTensors.map((t) => tf5.cast(t, "float32"))).as4D(this.batchSize, inputSize, inputSize, 3);
return batchTensor;
});
}
};
// src/dom/toNetInput.ts
async function toNetInput(inputs) {
if (inputs instanceof NetInput)
return inputs;
const inputArgArray = Array.isArray(inputs) ? inputs : [inputs];
if (!inputArgArray.length)
throw new Error("toNetInput - empty array passed as input");
const getIdxHint = (idx) => Array.isArray(inputs) ? ` at input index ${idx}:` : "";
const inputArray = inputArgArray.map(resolveInput);
inputArray.forEach((input, i) => {
if (!isMediaElement(input) && !isTensor3D(input) && !isTensor4D(input)) {
if (typeof inputArgArray[i] === "string")
throw new Error(`toNetInput -${getIdxHint(i)} string passed, but could not resolve HTMLElement for element id ${inputArgArray[i]}`);
throw new Error(`toNetInput -${getIdxHint(i)} expected media to be of type HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | tf.Tensor3D, or to be an element id`);
}
if (isTensor4D(input)) {
const batchSize = input.shape[0];
if (batchSize !== 1)
throw new Error(`toNetInput -${getIdxHint(i)} tf.Tensor4D with batchSize ${batchSize} passed, but not supported in input array`);
}
});
await Promise.all(inputArray.map((input) => isMediaElement(input) && awaitMediaLoaded(input)));
return new NetInput(inputArray, Array.isArray(inputs));
}
// src/dom/extractFaces.ts
async function extractFaces(input, detections) {
const {Canvas} = env.getEnv();
let canvas = input;
if (!(input instanceof Canvas)) {
const netInput = await toNetInput(input);
if (netInput.batchSize > 1)
throw new Error("extractFaces - batchSize > 1 not supported");
const tensorOrCanvas = netInput.getInput(0);
canvas = tensorOrCanvas instanceof Canvas ? tensorOrCanvas : await imageTensorToCanvas(tensorOrCanvas);
}
const ctx = getContext2dOrThrow(canvas);
const boxes = detections.map((det) => det instanceof FaceDetection ? det.forSize(canvas.width, canvas.height).box.floor() : det).map((box) => box.clipAtImageBorders(canvas.width, canvas.height));
return boxes.map(({x, y, width, height}) => {
const faceImg = createCanvas({width, height});
if (width > 0 && height > 0)
getContext2dOrThrow(faceImg).putImageData(ctx.getImageData(x, y, width, height), 0, 0);
return faceImg;
});
}
// src/dom/extractFaceTensors.ts
var tf6 = __toModule(require_tfjs_esm());
async function extractFaceTensors(imageTensor, detections) {
if (!isTensor3D(imageTensor) && !isTensor4D(imageTensor)) {
throw new Error("extractFaceTensors - expected image tensor to be 3D or 4D");
}
if (isTensor4D(imageTensor) && imageTensor.shape[0] > 1) {
throw new Error("extractFaceTensors - batchSize > 1 not supported");
}
return tf6.tidy(() => {
const [imgHeight, imgWidth, numChannels] = imageTensor.shape.slice(isTensor4D(imageTensor) ? 1 : 0);
const boxes = detections.map((det) => det instanceof FaceDetection ? det.forSize(imgWidth, imgHeight).box : det).map((box) => box.clipAtImageBorders(imgWidth, imgHeight));
const faceTensors = boxes.map(({
x,
y,
width,
height
}) => tf6.slice3d(imageTensor.as3D(imgHeight, imgWidth, numChannels), [y, x, 0], [height, width, numChannels]));
return faceTensors;
});
}
// src/dom/fetchOrThrow.ts
async function fetchOrThrow(url, init) {
const {fetch} = env.getEnv();
const res = await fetch(url, init);
if (!(res.status < 400)) {
throw new Error(`failed to fetch: (${res.status}) ${res.statusText}, from url: ${res.url}`);
}
return res;
}
// src/dom/fetchImage.ts
async function fetchImage(uri) {
const res = await fetchOrThrow(uri);
const blob = await res.blob();
if (!blob.type.startsWith("image/")) {
throw new Error(`fetchImage - expected blob type to be of type image/*, instead have: ${blob.type}, for url: ${res.url}`);
}
return bufferToImage(blob);
}
// src/dom/fetchJson.ts
async function fetchJson(uri) {
return (await fetchOrThrow(uri)).json();
}
// src/dom/fetchNetWeights.ts
async function fetchNetWeights(uri) {
return new Float32Array(await (await fetchOrThrow(uri)).arrayBuffer());
}
// src/dom/loadWeightMap.ts
var tf7 = __toModule(require_tfjs_esm());
// src/common/getModelUris.ts
function getModelUris(uri, defaultModelName) {
const defaultManifestFilename = `${defaultModelName}-weights_manifest.json`;
if (!uri) {
return {
modelBaseUri: "",
manifestUri: defaultManifestFilename
};
}
if (uri === "/") {
return {
modelBaseUri: "/",
manifestUri: `/${defaultManifestFilename}`
};
}
const protocol = uri.startsWith("http://") ? "http://" : uri.startsWith("https://") ? "https://" : "";
uri = uri.replace(protocol, "");
const parts = uri.split("/").filter((s) => s);
const manifestFile = uri.endsWith(".json") ? parts[parts.length - 1] : defaultManifestFilename;
let modelBaseUri = protocol + (uri.endsWith(".json") ? parts.slice(0, parts.length - 1) : parts).join("/");
modelBaseUri = uri.startsWith("/") ? `/${modelBaseUri}` : modelBaseUri;
return {
modelBaseUri,
manifestUri: modelBaseUri === "/" ? `/${manifestFile}` : `${modelBaseUri}/${manifestFile}`
};
}
// src/dom/loadWeightMap.ts
async function loadWeightMap(uri, defaultModelName) {
const {manifestUri, modelBaseUri} = getModelUris(uri, defaultModelName);
const manifest = await fetchJson(manifestUri);
return tf7.io.loadWeights(manifest, modelBaseUri);
}
// src/dom/matchDimensions.ts
function matchDimensions(input, reference, useMediaDimensions = false) {
const {width, height} = useMediaDimensions ? getMediaDimensions(reference) : reference;
input.width = width;
input.height = height;
return {width, height};
}
// src/faceFeatureExtractor/FaceFeatureExtractor.ts
var tf15 = __toModule(require_tfjs_esm());
// src/NeuralNetwork.ts
var tf8 = __toModule(require_tfjs_esm());
var NeuralNetwork = class {
constructor(name) {
this._params = void 0;
this._paramMappings = [];
this._name = name;
}
get params() {
return this._params;
}
get paramMappings() {
return this._paramMappings;
}
get isLoaded() {
return !!this.params;
}
getParamFromPath(paramPath) {
const {obj, objProp} = this.traversePropertyPath(paramPath);
return obj[objProp];
}
reassignParamFromPath(paramPath, tensor2) {
const {obj, objProp} = this.traversePropertyPath(paramPath);
obj[objProp].dispose();
obj[objProp] = tensor2;
}
getParamList() {
return this._paramMappings.map(({paramPath}) => ({
path: paramPath,
tensor: this.getParamFromPath(paramPath)
}));
}
getTrainableParams() {
return this.getParamList().filter((param) => param.tensor instanceof tf8.Variable);
}
getFrozenParams() {
return this.getParamList().filter((param) => !(param.tensor instanceof tf8.Variable));
}
variable() {
this.getFrozenParams().forEach(({path, tensor: tensor2}) => {
this.reassignParamFromPath(path, tensor2.variable());
});
}
freeze() {
this.getTrainableParams().forEach(({path, tensor: variable}) => {
const tensor2 = tf8.tensor(variable.dataSync());
variable.dispose();
this.reassignParamFromPath(path, tensor2);
});
}
dispose(throwOnRedispose = true) {
this.getParamList().forEach((param) => {
if (throwOnRedispose && param.tensor.isDisposed) {
throw new Error(`param tensor has already been disposed for path ${param.path}`);
}
param.tensor.dispose();
});
this._params = void 0;
}
serializeParams() {
return new Float32Array(this.getParamList().map(({tensor: tensor2}) => Array.from(tensor2.dataSync())).reduce((flat, arr) => flat.concat(arr)));
}
async load(weightsOrUrl) {
if (weightsOrUrl instanceof Float32Array) {
this.extractWeights(weightsOrUrl);
return;
}
await this.loadFromUri(weightsOrUrl);
}
async loadFromUri(uri) {
if (uri && typeof uri !== "string") {
throw new Error(`${this._name}.loadFromUri - expected model uri`);
}
const weightMap = await loadWeightMap(uri, this.getDefaultModelName());
this.loadFromWeightMap(weightMap);
}
async loadFromDisk(filePath) {
if (filePath && typeof filePath !== "string") {
throw new Error(`${this._name}.loadFromDisk - expected model file path`);
}
const {readFile} = env.getEnv();
const {manifestUri, modelBaseUri} = getModelUris(filePath, this.getDefaultModelName());
const fetchWeightsFromDisk = (filePaths) => Promise.all(filePaths.map((fp) => readFile(fp).then((buf) => buf.buffer)));
const loadWeights = tf8.io.weightsLoaderFactory(fetchWeightsFromDisk);
const manifest = JSON.parse((await readFile(manifestUri)).toString());
const weightMap = await loadWeights(manifest, modelBaseUri);
this.loadFromWeightMap(weightMap);
}
loadFromWeightMap(weightMap) {
const {paramMappings, params} = this.extractParamsFromWeightMap(weightMap);
this._paramMappings = paramMappings;
this._params = params;
}
extractWeights(weights) {
const {paramMappings, params} = this.extractParams(weights);
this._paramMappings = paramMappings;
this._params = params;
}
traversePropertyPath(paramPath) {
if (!this.params) {
throw new Error("traversePropertyPath - model has no loaded params");
}
const result = paramPath.split("/").reduce((res, objProp2) => {
if (!res.nextObj.hasOwnProperty(objProp2)) {
throw new Error(`traversePropertyPath - object does not have property ${objProp2}, for path ${paramPath}`);
}
return {obj: res.nextObj, objProp: objProp2, nextObj: res.nextObj[objProp2]};
}, {nextObj: this.params});
const {obj, objProp} = result;
if (!obj || !objProp || !(obj[objProp] instanceof tf8.Tensor)) {
throw new Error(`traversePropertyPath - parameter is not a tensor, for path ${paramPath}`);
}
return {obj, objProp};
}
};
// src/faceFeatureExtractor/denseBlock.ts
var tf10 = __toModule(require_tfjs_esm());
// src/common/depthwiseSeparableConv.ts
var tf9 = __toModule(require_tfjs_esm());
function depthwiseSeparableConv(x, params, stride) {
return tf9.tidy(() => {
let out = tf9.separableConv2d(x, params.depthwise_filter, params.pointwise_filter, stride, "same");
out = tf9.add(out, params.bias);
return out;
});
}
// src/faceFeatureExtractor/denseBlock.ts
function denseBlock3(x, denseBlockParams, isFirstLayer = false) {
return tf10.tidy(() => {
const out1 = tf10.relu(isFirstLayer ? tf10.add(tf10.conv2d(x, denseBlockParams.conv0.filters, [2, 2], "same"), denseBlockParams.conv0.bias) : depthwiseSeparableConv(x, denseBlockParams.conv0, [2, 2]));
const out2 = depthwiseSeparableConv(out1, denseBlockParams.conv1, [1, 1]);
const in3 = tf10.relu(tf10.add(out1, out2));
const out3 = depthwiseSeparableConv(in3, denseBlockParams.conv2, [1, 1]);
return tf10.relu(tf10.add(out1, tf10.add(out2, out3)));
});
}
function denseBlock4(x, denseBlockParams, isFirstLayer = false, isScaleDown = true) {
return tf10.tidy(() => {
const out1 = tf10.relu(isFirstLayer ? tf10.add(tf10.conv2d(x, denseBlockParams.conv0.filters, isScaleDown ? [2, 2] : [1, 1], "same"), denseBlockParams.conv0.bias) : depthwiseSeparableConv(x, denseBlockParams.conv0, isScaleDown ? [2, 2] : [1, 1]));
const out2 = depthwiseSeparableConv(out1, denseBlockParams.conv1, [1, 1]);
const in3 = tf10.relu(tf10.add(out1, out2));
const out3 = depthwiseSeparableConv(in3, denseBlockParams.conv2, [1, 1]);
const in4 = tf10.relu(tf10.add(out1, tf10.add(out2, out3)));
const out4 = depthwiseSeparableConv(in4, denseBlockParams.conv3, [1, 1]);
return tf10.relu(tf10.add(out1, tf10.add(out2, tf10.add(out3, out4))));
});
}
// src/common/convLayer.ts
var tf11 = __toModule(require_tfjs_esm());
function convLayer(x, params, padding = "same", withRelu = false) {
return tf11.tidy(() => {
const out = tf11.add(tf11.conv2d(x, params.filters, [1, 1], padding), params.bias);
return withRelu ? tf11.relu(out) : out;
});
}
// src/common/disposeUnusedWeightTensors.ts
function disposeUnusedWeightTensors(weightMap, paramMappings) {
Object.keys(weightMap).forEach((path) => {
if (!paramMappings.some((pm) => pm.originalPath === path)) {
weightMap[path].dispose();
}
});
}
// src/common/extractConvParamsFactory.ts
var tf12 = __toModule(require_tfjs_esm());
function extractConvParamsFactory(extractWeights, paramMappings) {
return (channelsIn, channelsOut, filterSize, mappedPrefix) => {
const filters = tf12.tensor4d(extractWeights(channelsIn * channelsOut * filterSize * filterSize), [filterSize, filterSize, channelsIn, channelsOut]);
const bias = tf12.tensor1d(extractWeights(channelsOut));
paramMappings.push({paramPath: `${mappedPrefix}/filters`}, {paramPath: `${mappedPrefix}/bias`});
return {filters, bias};
};
}
// src/common/extractFCParamsFactory.ts
var tf13 = __toModule(require_tfjs_esm());
function extractFCParamsFactory(extractWeights, paramMappings) {
return (channelsIn, channelsOut, mappedPrefix) => {
const fc_weights = tf13.tensor2d(extractWeights(channelsIn * channelsOut), [channelsIn, channelsOut]);
const fc_bias = tf13.tensor1d(extractWeights(channelsOut));
paramMappings.push({paramPath: `${mappedPrefix}/weights`}, {paramPath: `${mappedPrefix}/bias`});
return {
weights: fc_weights,
bias: fc_bias
};
};
}
// src/common/extractSeparableConvParamsFactory.ts
var tf14 = __toModule(require_tfjs_esm());
// src/common/types.ts
var SeparableConvParams = class {
constructor(depthwise_filter, pointwise_filter, bias) {
this.depthwise_filter = depthwise_filter;
this.pointwise_filter = pointwise_filter;
this.bias = bias;
}
};
// src/common/extractSeparableConvParamsFactory.ts
function extractSeparableConvParamsFactory(extractWeights, paramMappings) {
return (channelsIn, channelsOut, mappedPrefix) => {
const depthwise_filter = tf14.tensor4d(extractWeights(3 * 3 * channelsIn), [3, 3, channelsIn, 1]);
const pointwise_filter = tf14.tensor4d(extractWeights(channelsIn * channelsOut), [1, 1, channelsIn, channelsOut]);
const bias = tf14.tensor1d(extractWeights(channelsOut));
paramMappings.push({paramPath: `${mappedPrefix}/depthwise_filter`}, {paramPath: `${mappedPrefix}/pointwise_filter`}, {paramPath: `${mappedPrefix}/bias`});
return new SeparableConvParams(depthwise_filter, pointwise_filter, bias);
};
}
function loadSeparableConvParamsFactory(extractWeightEntry) {
return (prefix) => {
const depthwise_filter = extractWeightEntry(`${prefix}/depthwise_filter`, 4);
const pointwise_filter = extractWeightEntry(`${prefix}/pointwise_filter`, 4);
const bias = extractWeightEntry(`${prefix}/bias`, 1);
return new SeparableConvParams(depthwise_filter, pointwise_filter, bias);
};
}
// src/common/extractWeightEntryFactory.ts
function extractWeightEntryFactory(weightMap, paramMappings) {
return (originalPath, paramRank, mappedPath) => {
const tensor2 = weightMap[originalPath];
if (!isTensor(tensor2, paramRank)) {
throw new Error(`expected weightMap[${originalPath}] to be a Tensor${paramRank}D, instead have ${tensor2}`);
}
paramMappings.push({originalPath, paramPath: mappedPath || originalPath});
return tensor2;
};
}
// src/common/extractWeightsFactory.ts
function extractWeightsFactory(weights) {
let remainingWeights = weights;
function extractWeights(numWeights) {
const ret = remainingWeights.slice(0, numWeights);
remainingWeights = remainingWeights.slice(numWeights);
return ret;
}
function getRemainingWeights() {
return remainingWeights;
}
return {
extractWeights,
getRemainingWeights
};
}
// src/faceFeatureExtractor/extractorsFactory.ts
function extractorsFactory(extractWeights, paramMappings) {
const extractConvParams = extractConvParamsFactory(extractWeights, paramMappings);
const extractSeparableConvParams = extractSeparableConvParamsFactory(extractWeights, paramMappings);
function extractDenseBlock3Params(channelsIn, channelsOut, mappedPrefix, isFirstLayer = false) {
const conv0 = isFirstLayer ? extractConvParams(channelsIn, channelsOut, 3, `${mappedPrefix}/conv0`) : extractSeparableConvParams(channelsIn, channelsOut, `${mappedPrefix}/conv0`);
const conv1 = extractSeparableConvParams(channelsOut, channelsOut, `${mappedPrefix}/conv1`);
const conv22 = extractSeparableConvParams(channelsOut, channelsOut, `${mappedPrefix}/conv2`);
return {conv0, conv1, conv2: conv22};
}
function extractDenseBlock4Params(channelsIn, channelsOut, mappedPrefix, isFirstLayer = false) {
const {conv0, conv1, conv2: conv22} = extractDenseBlock3Params(channelsIn, channelsOut, mappedPrefix, isFirstLayer);
const conv3 = extractSeparableConvParams(channelsOut, channelsOut, `${mappedPrefix}/conv3`);
return {
conv0,
conv1,
conv2: conv22,
conv3
};
}
return {
extractDenseBlock3Params,
extractDenseBlock4Params
};
}
// src/faceFeatureExtractor/extractParams.ts
function extractParams(weights) {
const paramMappings = [];
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights);
const {
extractDenseBlock4Params
} = extractorsFactory(extractWeights, paramMappings);
const dense0 = extractDenseBlock4Params(3, 32, "dense0", true);
const dense1 = extractDenseBlock4Params(32, 64, "dense1");
const dense2 = extractDenseBlock4Params(64, 128, "dense2");
const dense3 = extractDenseBlock4Params(128, 256, "dense3");
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`);
}
return {
paramMappings,
params: {
dense0,
dense1,
dense2,
dense3
}
};
}
// src/common/loadConvParamsFactory.ts
function loadConvParamsFactory(extractWeightEntry) {
return (prefix) => {
const filters = extractWeightEntry(`${prefix}/filters`, 4);
const bias = extractWeightEntry(`${prefix}/bias`, 1);
return {filters, bias};
};
}
// src/faceFeatureExtractor/loadParamsFactory.ts
function loadParamsFactory(weightMap, paramMappings) {
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings);
const extractConvParams = loadConvParamsFactory(extractWeightEntry);
const extractSeparableConvParams = loadSeparableConvParamsFactory(extractWeightEntry);
function extractDenseBlock3Params(prefix, isFirstLayer = false) {
const conv0 = isFirstLayer ? extractConvParams(`${prefix}/conv0`) : extractSeparableConvParams(`${prefix}/conv0`);
const conv1 = extractSeparableConvParams(`${prefix}/conv1`);
const conv22 = extractSeparableConvParams(`${prefix}/conv2`);
return {conv0, conv1, conv2: conv22};
}
function extractDenseBlock4Params(prefix, isFirstLayer = false) {
const conv0 = isFirstLayer ? extractConvParams(`${prefix}/conv0`) : extractSeparableConvParams(`${prefix}/conv0`);
const conv1 = extractSeparableConvParams(`${prefix}/conv1`);
const conv22 = extractSeparableConvParams(`${prefix}/conv2`);
const conv3 = extractSeparableConvParams(`${prefix}/conv3`);
return {
conv0,
conv1,
conv2: conv22,
conv3
};
}
return {
extractDenseBlock3Params,
extractDenseBlock4Params
};
}
// src/faceFeatureExtractor/extractParamsFromWeightMap.ts
function extractParamsFromWeightMap(weightMap) {
const paramMappings = [];
const {
extractDenseBlock4Params
} = loadParamsFactory(weightMap, paramMappings);
const params = {
dense0: extractDenseBlock4Params("dense0", true),
dense1: extractDenseBlock4Params("dense1"),
dense2: extractDenseBlock4Params("dense2"),
dense3: extractDenseBlock4Params("dense3")
};
disposeUnusedWeightTensors(weightMap, paramMappings);
return {params, paramMappings};
}
// src/faceFeatureExtractor/FaceFeatureExtractor.ts
var FaceFeatureExtractor = class extends NeuralNetwork {
constructor() {
super("FaceFeatureExtractor");
}
forwardInput(input) {
const {params} = this;
if (!params) {
throw new Error("FaceFeatureExtractor - load model before inference");
}
return tf15.tidy(() => {
const batchTensor = tf15.cast(input.toBatchTensor(112, true), "float32");
const meanRgb = [122.782, 117.001, 104.298];
const normalized = normalize(batchTensor, meanRgb).div(255);
let out = denseBlock4(normalized, params.dense0, true);
out = denseBlock4(out, params.dense1);
out = denseBlock4(out, params.dense2);
out = denseBlock4(out, params.dense3);
out = tf15.avgPool(out, [7, 7], [2, 2], "valid");
return out;
});
}
async forward(input) {
return this.forwardInput(await toNetInput(input));
}
getDefaultModelName() {
return "face_feature_extractor_model";
}
extractParamsFromWeightMap(weightMap) {
return extractParamsFromWeightMap(weightMap);
}
extractParams(weights) {
return extractParams(weights);
}
};
// src/faceProcessor/FaceProcessor.ts
var tf17 = __toModule(require_tfjs_esm());
// src/common/fullyConnectedLayer.ts
var tf16 = __toModule(require_tfjs_esm());
function fullyConnectedLayer(x, params) {
return tf16.tidy(() => tf16.add(tf16.matMul(x, params.weights), params.bias));
}
// src/faceProcessor/extractParams.ts
function extractParams2(weights, channelsIn, channelsOut) {
const paramMappings = [];
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights);
const extractFCParams = extractFCParamsFactory(extractWeights, paramMappings);
const fc = extractFCParams(channelsIn, channelsOut, "fc");
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`);
}
return {
paramMappings,
params: {fc}
};
}
// src/faceProcessor/extractParamsFromWeightMap.ts
function extractParamsFromWeightMap2(weightMap) {
const paramMappings = [];
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings);
function extractFcParams(prefix) {
const weights = extractWeightEntry(`${prefix}/weights`, 2);
const bias = extractWeightEntry(`${prefix}/bias`, 1);
return {weights, bias};
}
const params = {
fc: extractFcParams("fc")
};
disposeUnusedWeightTensors(weightMap, paramMappings);
return {params, paramMappings};
}
// src/faceProcessor/util.ts
function seperateWeightMaps(weightMap) {
const featureExtractorMap = {};
const classifierMap = {};
Object.keys(weightMap).forEach((key) => {
const map = key.startsWith("fc") ? classifierMap : featureExtractorMap;
map[key] = weightMap[key];
});
return {featureExtractorMap, classifierMap};
}
// src/faceProcessor/FaceProcessor.ts
var FaceProcessor = class extends NeuralNetwork {
constructor(_name, faceFeatureExtractor) {
super(_name);
this._faceFeatureExtractor = faceFeatureExtractor;
}
get faceFeatureExtractor() {
return this._faceFeatureExtractor;
}
runNet(input) {
const {params} = this;
if (!params) {
throw new Error(`${this._name} - load model before inference`);
}
return tf17.tidy(() => {
const bottleneckFeatures = input instanceof NetInput ? this.faceFeatureExtractor.forwardInput(input) : input;
return fullyConnectedLayer(bottleneckFeatures.as2D(bottleneckFeatures.shape[0], -1), params.fc);
});
}
dispose(throwOnRedispose = true) {
this.faceFeatureExtractor.dispose(throwOnRedispose);
super.dispose(throwOnRedispose);
}
loadClassifierParams(weights) {
const {params, paramMappings} = this.extractClassifierParams(weights);
this._params = params;
this._paramMappings = paramMappings;
}
extractClassifierParams(weights) {
return extractParams2(weights, this.getClassifierChannelsIn(), this.getClassifierChannelsOut());
}
extractParamsFromWeightMap(weightMap) {
const {featureExtractorMap, classifierMap} = seperateWeightMaps(weightMap);
this.faceFeatureExtractor.loadFromWeightMap(featureExtractorMap);
return extractParamsFromWeightMap2(classifierMap);
}
extractParams(weights) {
const cIn = this.getClassifierChannelsIn();
const cOut = this.getClassifierChannelsOut();
const classifierWeightSize = cOut * cIn + cOut;
const featureExtractorWeights = weights.slice(0, weights.length - classifierWeightSize);
const classifierWeights = weights.slice(weights.length - classifierWeightSize);
this.faceFeatureExtractor.extractWeights(featureExtractorWeights);
return this.extractClassifierParams(classifierWeights);
}
};
// src/faceExpressionNet/FaceExpressions.ts
var FACE_EXPRESSION_LABELS = ["neutral", "happy", "sad", "angry", "fearful", "disgusted", "surprised"];
var FaceExpressions = class {
constructor(probabilities) {
if (probabilities.length !== 7) {
throw new Error(`FaceExpressions.constructor - expected probabilities.length to be 7, have: ${probabilities.length}`);
}
FACE_EXPRESSION_LABELS.forEach((expression, idx) => {
this[expression] = probabilities[idx];
});
}
asSortedArray() {
return FACE_EXPRESSION_LABELS.map((expression) => ({expression, probability: this[expression]})).sort((e0, e1) => e1.probability - e0.probability);
}
};
// src/faceExpressionNet/FaceExpressionNet.ts
var FaceExpressionNet = class extends FaceProcessor {
constructor(faceFeatureExtractor = new FaceFeatureExtractor()) {
super("FaceExpressionNet", faceFeatureExtractor);
}
forwardInput(input) {
return tf18.tidy(() => tf18.softmax(this.runNet(input)));
}
async forward(input) {
return this.forwardInput(await toNetInput(input));
}
async predictExpressions(input) {
const netInput = await toNetInput(input);
const out = await this.forwardInput(netInput);
const probabilitesByBatch = await Promise.all(tf18.unstack(out).map(async (t) => {
const data = t.dataSync();
t.dispose();
return data;
}));
out.dispose();
const predictionsByBatch = probabilitesByBatch.map((probabilites) => new FaceExpressions(probabilites));
return netInput.isBatchInput ? predictionsByBatch : predictionsByBatch[0];
}
getDefaultModelName() {
return "face_expression_model";
}
getClassifierChannelsIn() {
return 256;
}
getClassifierChannelsOut() {
return 7;
}
};
// src/factories/WithFaceExpressions.ts
function isWithFaceExpressions(obj) {
return obj.expressions instanceof FaceExpressions;
}
function extendWithFaceExpressions(sourceObj, expressions) {
const extension = {expressions};
return {...sourceObj, ...extension};
}
// src/draw/drawFaceExpressions.ts
function drawFaceExpressions(canvasArg, faceExpressions, minConfidence = 0.1, textFieldAnchor) {
const faceExpressionsArray = Array.isArray(faceExpressions) ? faceExpressions : [faceExpressions];
faceExpressionsArray.forEach((e) => {
const expr = e instanceof FaceExpressions ? e : isWithFaceExpressions(e) ? e.expressions : void 0;
if (!expr) {
throw new Error("drawFaceExpressions - expected faceExpressions to be FaceExpressions | WithFaceExpressions<{}> or array thereof");
}
const sorted = expr.asSortedArray();
const resultsToDisplay = sorted.filter((exprLocal) => exprLocal.probability > minConfidence);
const anchor = isWithFaceDetection(e) ? e.detection.box.bottomLeft : textFieldAnchor || new Point(0, 0);
const drawTextField = new DrawTextField(resultsToDisplay.map((exprLocal) => `${exprLocal.expression} (${round(exprLocal.probability)})`), anchor);
drawTextField.draw(canvasArg);
});
}
// src/factories/WithFaceLandmarks.ts
function isWithFaceLandmarks(obj) {
return isWithFaceDetection(obj) && obj["landmarks"] instanceof FaceLandmarks && obj["unshiftedLandmarks"] instanceof FaceLandmarks && obj["alignedRect"] instanceof FaceDetection;
}
function calculateFaceAngle(mesh) {
const radians = (a1, a2, b1, b2) => Math.atan2(b2 - a2, b1 - a1) % Math.PI;
const degrees = (theta) => theta * 180 / Math.PI;
const angle = {roll: void 0, pitch: void 0, yaw: void 0};
if (!mesh || !mesh._positions || mesh._positions.length !== 68)
return angle;
const pt = mesh._positions;
angle.roll = -radians(pt[36]._x, pt[36]._y, pt[45]._x, pt[45]._y);
angle.pitch = radians(0, Math.abs(pt[0]._x - pt[30]._x) / pt[30]._x, Math.PI, Math.abs(pt[16]._x - pt[30]._x) / pt[30]._x);
const bottom = pt.reduce((prev, cur) => prev < cur._y ? prev : cur._y, Infinity);
const top = pt.reduce((prev, cur) => prev > cur._y ? prev : cur._y, -Infinity);
angle.yaw = Math.PI * (mesh._imgDims._height / (top - bottom) / 1.4 - 1);
return angle;
}
function extendWithFaceLandmarks(sourceObj, unshiftedLandmarks) {
const {box: shift} = sourceObj.detection;
const landmarks = unshiftedLandmarks.shiftBy(shift.x, shift.y);
const rect = landmarks.align();
const {imageDims} = sourceObj.detection;
const alignedRect = new FaceDetection(sourceObj.detection.score, rect.rescale(imageDims.reverse()), imageDims);
const angle = calculateFaceAngle(unshiftedLandmarks);
const extension = {
landmarks,
unshiftedLandmarks,
alignedRect,
angle
};
return {...sourceObj, ...extension};
}
// src/draw/DrawFaceLandmarks.ts
var DrawFaceLandmarksOptions = class {
constructor(options = {}) {
const {
drawLines = true,
drawPoints = true,
lineWidth,
lineColor,
pointSize,
pointColor
} = options;
this.drawLines = drawLines;
this.drawPoints = drawPoints;
this.lineWidth = lineWidth || 1;
this.pointSize = pointSize || 2;
this.lineColor = lineColor || "rgba(0, 255, 255, 1)";
this.pointColor = pointColor || "rgba(255, 0, 255, 1)";
}
};
var DrawFaceLandmarks = class {
constructor(faceLandmarks, options = {}) {
this.faceLandmarks = faceLandmarks;
this.options = new DrawFaceLandmarksOptions(options);
}
draw(canvasArg) {
const ctx = getContext2dOrThrow(canvasArg);
const {
drawLines,
drawPoints,
lineWidth,
lineColor,
pointSize,
pointColor
} = this.options;
if (drawLines && this.faceLandmarks instanceof FaceLandmarks68) {
ctx.strokeStyle = lineColor;
ctx.lineWidth = lineWidth;
drawContour(ctx, this.faceLandmarks.getJawOutline());
drawContour(ctx, this.faceLandmarks.getLeftEyeBrow());
drawContour(ctx, this.faceLandmarks.getRightEyeBrow());
drawContour(ctx, this.faceLandmarks.getNose());
drawContour(ctx, this.faceLandmarks.getLeftEye(), true);
drawContour(ctx, this.faceLandmarks.getRightEye(), true);
drawContour(ctx, this.faceLandmarks.getMouth(), true);
}
if (drawPoints) {
ctx.strokeStyle = pointColor;
ctx.fillStyle = pointColor;
const drawPoint = (pt) => {
ctx.beginPath();
ctx.arc(pt.x, pt.y, pointSize, 0, 2 * Math.PI);
ctx.fill();
};
this.faceLandmarks.positions.forEach(drawPoint);
}
}
};
function drawFaceLandmarks(canvasArg, faceLandmarks) {
const faceLandmarksArray = Array.isArray(faceLandmarks) ? faceLandmarks : [faceLandmarks];
faceLandmarksArray.forEach((f) => {
const landmarks = f instanceof FaceLandmarks ? f : isWithFaceLandmarks(f) ? f.landmarks : void 0;
if (!landmarks) {
throw new Error("drawFaceLandmarks - expected faceExpressions to be FaceLandmarks | WithFaceLandmarks<WithFaceDetection<{}>> or array thereof");
}
new DrawFaceLandmarks(landmarks).draw(canvasArg);
});
}
// package.json
var version = "1.1.9";
// src/ageGenderNet/AgeGenderNet.ts
var tf20 = __toModule(require_tfjs_esm());
// src/xception/TinyXception.ts
var tf19 = __toModule(require_tfjs_esm());
// src/xception/extractParams.ts
function extractorsFactory2(extractWeights, paramMappings) {
const extractConvParams = extractConvParamsFactory(extractWeights, paramMappings);
const extractSeparableConvParams = extractSeparableConvParamsFactory(extractWeights, paramMappings);
function extractReductionBlockParams(channelsIn, channelsOut, mappedPrefix) {
const separable_conv0 = extractSeparableConvParams(channelsIn, channelsOut, `${mappedPrefix}/separable_conv0`);
const separable_conv1 = extractSeparableConvParams(channelsOut, channelsOut, `${mappedPrefix}/separable_conv1`);
const expansion_conv = extractConvParams(channelsIn, channelsOut, 1, `${mappedPrefix}/expansion_conv`);
return {separable_conv0, separable_conv1, expansion_conv};
}
function extractMainBlockParams(channels, mappedPrefix) {
const separable_conv0 = extractSeparableConvParams(channels, channels, `${mappedPrefix}/separable_conv0`);
const separable_conv1 = extractSeparableConvParams(channels, channels, `${mappedPrefix}/separable_conv1`);
const separable_conv2 = extractSeparableConvParams(channels, channels, `${mappedPrefix}/separable_conv2`);
return {separable_conv0, separable_conv1, separable_conv2};
}
return {
extractConvParams,
extractSeparableConvParams,
extractReductionBlockParams,
extractMainBlockParams
};
}
function extractParams3(weights, numMainBlocks) {
const paramMappings = [];
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights);
const {
extractConvParams,
extractSeparableConvParams,
extractReductionBlockParams,
extractMainBlockParams
} = extractorsFactory2(extractWeights, paramMappings);
const entry_flow_conv_in = extractConvParams(3, 32, 3, "entry_flow/conv_in");
const entry_flow_reduction_block_0 = extractReductionBlockParams(32, 64, "entry_flow/reduction_block_0");
const entry_flow_reduction_block_1 = extractReductionBlockParams(64, 128, "entry_flow/reduction_block_1");
const entry_flow = {
conv_in: entry_flow_conv_in,
reduction_block_0: entry_flow_reduction_block_0,
reduction_block_1: entry_flow_reduction_block_1
};
const middle_flow = {};
range(numMainBlocks, 0, 1).forEach((idx) => {
middle_flow[`main_block_${idx}`] = extractMainBlockParams(128, `middle_flow/main_block_${idx}`);
});
const exit_flow_reduction_block = extractReductionBlockParams(128, 256, "exit_flow/reduction_block");
const exit_flow_separable_conv = extractSeparableConvParams(256, 512, "exit_flow/separable_conv");
const exit_flow = {
reduction_block: exit_flow_reduction_block,
separable_conv: exit_flow_separable_conv
};
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`);
}
return {
paramMappings,
params: {entry_flow, middle_flow, exit_flow}
};
}
// src/xception/extractParamsFromWeightMap.ts
function loadParamsFactory2(weightMap, paramMappings) {
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings);
const extractConvParams = loadConvParamsFactory(extractWeightEntry);
const extractSeparableConvParams = loadSeparableConvParamsFactory(extractWeightEntry);
function extractReductionBlockParams(mappedPrefix) {
const separable_conv0 = extractSeparableConvParams(`${mappedPrefix}/separable_conv0`);
const separable_conv1 = extractSeparableConvParams(`${mappedPrefix}/separable_conv1`);
const expansion_conv = extractConvParams(`${mappedPrefix}/expansion_conv`);
return {separable_conv0, separable_conv1, expansion_conv};
}
function extractMainBlockParams(mappedPrefix) {
const separable_conv0 = extractSeparableConvParams(`${mappedPrefix}/separable_conv0`);
const separable_conv1 = extractSeparableConvParams(`${mappedPrefix}/separable_conv1`);
const separable_conv2 = extractSeparableConvParams(`${mappedPrefix}/separable_conv2`);
return {separable_conv0, separable_conv1, separable_conv2};
}
return {
extractConvParams,
extractSeparableConvParams,
extractReductionBlockParams,
extractMainBlockParams
};
}
function extractParamsFromWeightMap3(weightMap, numMainBlocks) {
const paramMappings = [];
const {
extractConvParams,
extractSeparableConvParams,
extractReductionBlockParams,
extractMainBlockParams
} = loadParamsFactory2(weightMap, paramMappings);
const entry_flow_conv_in = extractConvParams("entry_flow/conv_in");
const entry_flow_reduction_block_0 = extractReductionBlockParams("entry_flow/reduction_block_0");
const entry_flow_reduction_block_1 = extractReductionBlockParams("entry_flow/reduction_block_1");
const entry_flow = {
conv_in: entry_flow_conv_in,
reduction_block_0: entry_flow_reduction_block_0,
reduction_block_1: entry_flow_reduction_block_1
};
const middle_flow = {};
range(numMainBlocks, 0, 1).forEach((idx) => {
middle_flow[`main_block_${idx}`] = extractMainBlockParams(`middle_flow/main_block_${idx}`);
});
const exit_flow_reduction_block = extractReductionBlockParams("exit_flow/reduction_block");
const exit_flow_separable_conv = extractSeparableConvParams("exit_flow/separable_conv");
const exit_flow = {
reduction_block: exit_flow_reduction_block,
separable_conv: exit_flow_separable_conv
};
disposeUnusedWeightTensors(weightMap, paramMappings);
return {params: {entry_flow, middle_flow, exit_flow}, paramMappings};
}
// src/xception/TinyXception.ts
function conv(x, params, stride) {
return tf19.add(tf19.conv2d(x, params.filters, stride, "same"), params.bias);
}
function reductionBlock(x, params, isActivateInput = true) {
let out = isActivateInput ? tf19.relu(x) : x;
out = depthwiseSeparableConv(out, params.separable_conv0, [1, 1]);
out = depthwiseSeparableConv(tf19.relu(out), params.separable_conv1, [1, 1]);
out = tf19.maxPool(out, [3, 3], [2, 2], "same");
out = tf19.add(out, conv(x, params.expansion_conv, [2, 2]));
return out;
}
function mainBlock(x, params) {
let out = depthwiseSeparableConv(tf19.relu(x), params.separable_conv0, [1, 1]);
out = depthwiseSeparableConv(tf19.relu(out), params.separable_conv1, [1, 1]);
out = depthwiseSeparableConv(tf19.relu(out), params.separable_conv2, [1, 1]);
out = tf19.add(out, x);
return out;
}
var TinyXception = class extends NeuralNetwork {
constructor(numMainBlocks) {
super("TinyXception");
this._numMainBlocks = numMainBlocks;
}
forwardInput(input) {
const {params} = this;
if (!params) {
throw new Error("TinyXception - load model before inference");
}
return tf19.tidy(() => {
const batchTensor = tf19.cast(input.toBatchTensor(112, true), "float32");
const meanRgb = [122.782, 117.001, 104.298];
const normalized = normalize(batchTensor, meanRgb).div(255);
let out = tf19.relu(conv(normalized, params.entry_flow.conv_in, [2, 2]));
out = reductionBlock(out, params.entry_flow.reduction_block_0, false);
out = reductionBlock(out, params.entry_flow.reduction_block_1);
range(this._numMainBlocks, 0, 1).forEach((idx) => {
out = mainBlock(out, params.middle_flow[`main_block_${idx}`]);
});
out = reductionBlock(out, params.exit_flow.reduction_block);
out = tf19.relu(depthwiseSeparableConv(out, params.exit_flow.separable_conv, [1, 1]));
return out;
});
}
async forward(input) {
return this.forwardInput(await toNetInput(input));
}
getDefaultModelName() {
return "tiny_xception_model";
}
extractParamsFromWeightMap(weightMap) {
return extractParamsFromWeightMap3(weightMap, this._numMainBlocks);
}
extractParams(weights) {
return extractParams3(weights, this._numMainBlocks);
}
};
// src/ageGenderNet/extractParams.ts
function extractParams4(weights) {
const paramMappings = [];
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights);
const extractFCParams = extractFCParamsFactory(extractWeights, paramMappings);
const age = extractFCParams(512, 1, "fc/age");
const gender = extractFCParams(512, 2, "fc/gender");
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`);
}
return {
paramMappings,
params: {fc: {age, gender}}
};
}
// src/ageGenderNet/extractParamsFromWeightMap.ts
function extractParamsFromWeightMap4(weightMap) {
const paramMappings = [];
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings);
function extractFcParams(prefix) {
const weights = extractWeightEntry(`${prefix}/weights`, 2);
const bias = extractWeightEntry(`${prefix}/bias`, 1);
return {weights, bias};
}
const params = {
fc: {
age: extractFcParams("fc/age"),
gender: extractFcParams("fc/gender")
}
};
disposeUnusedWeightTensors(weightMap, paramMappings);
return {params, paramMappings};
}
// src/ageGenderNet/types.ts
var Gender;
(function(Gender2) {
Gender2["FEMALE"] = "female";
Gender2["MALE"] = "male";
})(Gender || (Gender = {}));
// src/ageGenderNet/AgeGenderNet.ts
var AgeGenderNet = class extends NeuralNetwork {
constructor(faceFeatureExtractor = new TinyXception(2)) {
super("AgeGenderNet");
this._faceFeatureExtractor = faceFeatureExtractor;
}
get faceFeatureExtractor() {
return this._faceFeatureExtractor;
}
runNet(input) {
const {params} = this;
if (!params) {
throw new Error(`${this._name} - load model before inference`);
}
return tf20.tidy(() => {
const bottleneckFeatures = input instanceof NetInput ? this.faceFeatureExtractor.forwardInput(input) : input;
const pooled = tf20.avgPool(bottleneckFeatures, [7, 7], [2, 2], "valid").as2D(bottleneckFeatures.shape[0], -1);
const age = fullyConnectedLayer(pooled, params.fc.age).as1D();
const gender = fullyConnectedLayer(pooled, params.fc.gender);
return {age, gender};
});
}
forwardInput(input) {
return tf20.tidy(() => {
const {age, gender} = this.runNet(input);
return {age, gender: tf20.softmax(gender)};
});
}
async forward(input) {
return this.forwardInput(await toNetInput(input));
}
async predictAgeAndGender(input) {
const netInput = await toNetInput(input);
const out = await this.forwardInput(netInput);
const ages = tf20.unstack(out.age);
const genders = tf20.unstack(out.gender);
const ageAndGenderTensors = ages.map((ageTensor, i) => ({
ageTensor,
genderTensor: genders[i]
}));
const predictionsByBatch = await Promise.all(ageAndGenderTensors.map(async ({ageTensor, genderTensor}) => {
const age = ageTensor.dataSync()[0];
const probMale = genderTensor.dataSync()[0];
const isMale = probMale > 0.5;
const gender = isMale ? Gender.MALE : Gender.FEMALE;
const genderProbability = isMale ? probMale : 1 - probMale;
ageTensor.dispose();
genderTensor.dispose();
return {age, gender, genderProbability};
}));
out.age.dispose();
out.gender.dispose();
return netInput.isBatchInput ? predictionsByBatch : predictionsByBatch[0];
}
getDefaultModelName() {
return "age_gender_model";
}
dispose(throwOnRedispose = true) {
this.faceFeatureExtractor.dispose(throwOnRedispose);
super.dispose(throwOnRedispose);
}
loadClassifierParams(weights) {
const {params, paramMappings} = this.extractClassifierParams(weights);
this._params = params;
this._paramMappings = paramMappings;
}
extractClassifierParams(weights) {
return extractParams4(weights);
}
extractParamsFromWeightMap(weightMap) {
const {featureExtractorMap, classifierMap} = seperateWeightMaps(weightMap);
this.faceFeatureExtractor.loadFromWeightMap(featureExtractorMap);
return extractParamsFromWeightMap4(classifierMap);
}
extractParams(weights) {
const classifierWeightSize = 512 * 1 + 1 + (512 * 2 + 2);
const featureExtractorWeights = weights.slice(0, weights.length - classifierWeightSize);
const classifierWeights = weights.slice(weights.length - classifierWeightSize);
this.faceFeatureExtractor.extractWeights(featureExtractorWeights);
return this.extractClassifierParams(classifierWeights);
}
};
// src/faceLandmarkNet/FaceLandmark68NetBase.ts
var tf21 = __toModule(require_tfjs_esm());
var FaceLandmark68NetBase = class extends FaceProcessor {
postProcess(output, inputSize, originalDimensions) {
const inputDimensions = originalDimensions.map(({width, height}) => {
const scale2 = inputSize / Math.max(height, width);
return {
width: width * scale2,
height: height * scale2
};
});
const batchSize = inputDimensions.length;
return tf21.tidy(() => {
const createInterleavedTensor = (fillX, fillY) => tf21.stack([tf21.fill([68], fillX, "float32"), tf21.fill([68], fillY, "float32")], 1).as2D(1, 136).as1D();
const getPadding = (batchIdx, cond) => {
const {width, height} = inputDimensions[batchIdx];
return cond(width, height) ? Math.abs(width - height) / 2 : 0;
};
const getPaddingX = (batchIdx) => getPadding(batchIdx, (w, h) => w < h);
const getPaddingY = (batchIdx) => getPadding(batchIdx, (w, h) => h < w);
const landmarkTensors = output.mul(tf21.fill([batchSize, 136], inputSize, "float32")).sub(tf21.stack(Array.from(Array(batchSize), (_, batchIdx) => createInterleavedTensor(getPaddingX(batchIdx), getPaddingY(batchIdx))))).div(tf21.stack(Array.from(Array(batchSize), (_, batchIdx) => createInterleavedTensor(inputDimensions[batchIdx].width, inputDimensions[batchIdx].height))));
return landmarkTensors;
});
}
forwardInput(input) {
return tf21.tidy(() => {
const out = this.runNet(input);
return this.postProcess(out, input.inputSize, input.inputDimensions.map(([height, width]) => ({height, width})));
});
}
async forward(input) {
return this.forwardInput(await toNetInput(input));
}
async detectLandmarks(input) {
const netInput = await toNetInput(input);
const landmarkTensors = tf21.tidy(() => tf21.unstack(this.forwardInput(netInput)));
const landmarksForBatch = await Promise.all(landmarkTensors.map(async (landmarkTensor, batchIdx) => {
const landmarksArray = Array.from(landmarkTensor.dataSync());
const xCoords = landmarksArray.filter((_, i) => isEven(i));
const yCoords = landmarksArray.filter((_, i) => !isEven(i));
return new FaceLandmarks68(Array(68).fill(0).map((_, i) => new Point(xCoords[i], yCoords[i])), {
height: netInput.getInputHeight(batchIdx),
width: netInput.getInputWidth(batchIdx)
});
}));
landmarkTensors.forEach((t) => t.dispose());
return netInput.isBatchInput ? landmarksForBatch : landmarksForBatch[0];
}
getClassifierChannelsOut() {
return 136;
}
};
// src/faceLandmarkNet/FaceLandmark68Net.ts
var FaceLandmark68Net = class extends FaceLandmark68NetBase {
constructor(faceFeatureExtractor = new FaceFeatureExtractor()) {
super("FaceLandmark68Net", faceFeatureExtractor);
}
getDefaultModelName() {
return "face_landmark_68_model";
}
getClassifierChannelsIn() {
return 256;
}
};
// src/faceFeatureExtractor/TinyFaceFeatureExtractor.ts
var tf22 = __toModule(require_tfjs_esm());
// src/faceFeatureExtractor/extractParamsFromWeightMapTiny.ts
function extractParamsFromWeightMapTiny(weightMap) {
const paramMappings = [];
const {
extractDenseBlock3Params
} = loadParamsFactory(weightMap, paramMappings);
const params = {
dense0: extractDenseBlock3Params("dense0", true),
dense1: extractDenseBlock3Params("dense1"),
dense2: extractDenseBlock3Params("dense2")
};
disposeUnusedWeightTensors(weightMap, paramMappings);
return {params, paramMappings};
}
// src/faceFeatureExtractor/extractParamsTiny.ts
function extractParamsTiny(weights) {
const paramMappings = [];
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights);
const {
extractDenseBlock3Params
} = extractorsFactory(extractWeights, paramMappings);
const dense0 = extractDenseBlock3Params(3, 32, "dense0", true);
const dense1 = extractDenseBlock3Params(32, 64, "dense1");
const dense2 = extractDenseBlock3Params(64, 128, "dense2");
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`);
}
return {
paramMappings,
params: {dense0, dense1, dense2}
};
}
// src/faceFeatureExtractor/TinyFaceFeatureExtractor.ts
var TinyFaceFeatureExtractor = class extends NeuralNetwork {
constructor() {
super("TinyFaceFeatureExtractor");
}
forwardInput(input) {
const {params} = this;
if (!params) {
throw new Error("TinyFaceFeatureExtractor - load model before inference");
}
return tf22.tidy(() => {
const batchTensor = tf22.cast(input.toBatchTensor(112, true), "float32");
const meanRgb = [122.782, 117.001, 104.298];
const normalized = normalize(batchTensor, meanRgb).div(255);
let out = denseBlock3(normalized, params.dense0, true);
out = denseBlock3(out, params.dense1);
out = denseBlock3(out, params.dense2);
out = tf22.avgPool(out, [14, 14], [2, 2], "valid");
return out;
});
}
async forward(input) {
return this.forwardInput(await toNetInput(input));
}
getDefaultModelName() {
return "face_feature_extractor_tiny_model";
}
extractParamsFromWeightMap(weightMap) {
return extractParamsFromWeightMapTiny(weightMap);
}
extractParams(weights) {
return extractParamsTiny(weights);
}
};
// src/faceLandmarkNet/FaceLandmark68TinyNet.ts
var FaceLandmark68TinyNet = class extends FaceLandmark68NetBase {
constructor(faceFeatureExtractor = new TinyFaceFeatureExtractor()) {
super("FaceLandmark68TinyNet", faceFeatureExtractor);
}
getDefaultModelName() {
return "face_landmark_68_tiny_model";
}
getClassifierChannelsIn() {
return 128;
}
};
// src/faceLandmarkNet/index.ts
var FaceLandmarkNet = class extends FaceLandmark68Net {
};
// src/faceRecognitionNet/FaceRecognitionNet.ts
var tf27 = __toModule(require_tfjs_esm());
// src/faceRecognitionNet/convLayer.ts
var tf24 = __toModule(require_tfjs_esm());
// src/faceRecognitionNet/scaleLayer.ts
var tf23 = __toModule(require_tfjs_esm());
function scale(x, params) {
return tf23.add(tf23.mul(x, params.weights), params.biases);
}
// src/faceRecognitionNet/convLayer.ts
function convLayer2(x, params, strides, withRelu, padding = "same") {
const {filters, bias} = params.conv;
let out = tf24.conv2d(x, filters, strides, padding);
out = tf24.add(out, bias);
out = scale(out, params.scale);
return withRelu ? tf24.relu(out) : out;
}
function conv2(x, params) {
return convLayer2(x, params, [1, 1], true);
}
function convNoRelu(x, params) {
return convLayer2(x, params, [1, 1], false);
}
function convDown(x, params) {
return convLayer2(x, params, [2, 2], true, "valid");
}
// src/faceRecognitionNet/extractParams.ts
var tf25 = __toModule(require_tfjs_esm());
function extractorsFactory3(extractWeights, paramMappings) {
function extractFilterValues(numFilterValues, numFilters, filterSize) {
const weights = extractWeights(numFilterValues);
const depth = weights.length / (numFilters * filterSize * filterSize);
if (isFloat(depth)) {
throw new Error(`depth has to be an integer: ${depth}, weights.length: ${weights.length}, numFilters: ${numFilters}, filterSize: ${filterSize}`);
}
return tf25.tidy(() => tf25.transpose(tf25.tensor4d(weights, [numFilters, depth, filterSize, filterSize]), [2, 3, 1, 0]));
}
function extractConvParams(numFilterValues, numFilters, filterSize, mappedPrefix) {
const filters = extractFilterValues(numFilterValues, numFilters, filterSize);
const bias = tf25.tensor1d(extractWeights(numFilters));
paramMappings.push({paramPath: `${mappedPrefix}/filters`}, {paramPath: `${mappedPrefix}/bias`});
return {filters, bias};
}
function extractScaleLayerParams(numWeights, mappedPrefix) {
const weights = tf25.tensor1d(extractWeights(numWeights));
const biases = tf25.tensor1d(extractWeights(numWeights));
paramMappings.push({paramPath: `${mappedPrefix}/weights`}, {paramPath: `${mappedPrefix}/biases`});
return {
weights,
biases
};
}
function extractConvLayerParams(numFilterValues, numFilters, filterSize, mappedPrefix) {
const conv3 = extractConvParams(numFilterValues, numFilters, filterSize, `${mappedPrefix}/conv`);
const scale2 = extractScaleLayerParams(numFilters, `${mappedPrefix}/scale`);
return {conv: conv3, scale: scale2};
}
function extractResidualLayerParams(numFilterValues, numFilters, filterSize, mappedPrefix, isDown = false) {
const conv1 = extractConvLayerParams((isDown ? 0.5 : 1) * numFilterValues, numFilters, filterSize, `${mappedPrefix}/conv1`);
const conv22 = extractConvLayerParams(numFilterValues, numFilters, filterSize, `${mappedPrefix}/conv2`);
return {conv1, conv2: conv22};
}
return {
extractConvLayerParams,
extractResidualLayerParams
};
}
function extractParams5(weights) {
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights);
const paramMappings = [];
const {
extractConvLayerParams,
extractResidualLayerParams
} = extractorsFactory3(extractWeights, paramMappings);
const conv32_down = extractConvLayerParams(4704, 32, 7, "conv32_down");
const conv32_1 = extractResidualLayerParams(9216, 32, 3, "conv32_1");
const conv32_2 = extractResidualLayerParams(9216, 32, 3, "conv32_2");
const conv32_3 = extractResidualLayerParams(9216, 32, 3, "conv32_3");
const conv64_down = extractResidualLayerParams(36864, 64, 3, "conv64_down", true);
const conv64_1 = extractResidualLayerParams(36864, 64, 3, "conv64_1");
const conv64_2 = extractResidualLayerParams(36864, 64, 3, "conv64_2");
const conv64_3 = extractResidualLayerParams(36864, 64, 3, "conv64_3");
const conv128_down = extractResidualLayerParams(147456, 128, 3, "conv128_down", true);
const conv128_1 = extractResidualLayerParams(147456, 128, 3, "conv128_1");
const conv128_2 = extractResidualLayerParams(147456, 128, 3, "conv128_2");
const conv256_down = extractResidualLayerParams(589824, 256, 3, "conv256_down", true);
const conv256_1 = extractResidualLayerParams(589824, 256, 3, "conv256_1");
const conv256_2 = extractResidualLayerParams(589824, 256, 3, "conv256_2");
const conv256_down_out = extractResidualLayerParams(589824, 256, 3, "conv256_down_out");
const fc = tf25.tidy(() => tf25.transpose(tf25.tensor2d(extractWeights(256 * 128), [128, 256]), [1, 0]));
paramMappings.push({paramPath: "fc"});
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`);
}
const params = {
conv32_down,
conv32_1,
conv32_2,
conv32_3,
conv64_down,
conv64_1,
conv64_2,
conv64_3,
conv128_down,
conv128_1,
conv128_2,
conv256_down,
conv256_1,
conv256_2,
conv256_down_out,
fc
};
return {params, paramMappings};
}
// src/faceRecognitionNet/extractParamsFromWeightMap.ts
function extractorsFactory4(weightMap, paramMappings) {
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings);
function extractScaleLayerParams(prefix) {
const weights = extractWeightEntry(`${prefix}/scale/weights`, 1);
const biases = extractWeightEntry(`${prefix}/scale/biases`, 1);
return {weights, biases};
}
function extractConvLayerParams(prefix) {
const filters = extractWeightEntry(`${prefix}/conv/filters`, 4);
const bias = extractWeightEntry(`${prefix}/conv/bias`, 1);
const scale2 = extractScaleLayerParams(prefix);
return {conv: {filters, bias}, scale: scale2};
}
function extractResidualLayerParams(prefix) {
return {
conv1: extractConvLayerParams(`${prefix}/conv1`),
conv2: extractConvLayerParams(`${prefix}/conv2`)
};
}
return {
extractConvLayerParams,
extractResidualLayerParams
};
}
function extractParamsFromWeightMap5(weightMap) {
const paramMappings = [];
const {
extractConvLayerParams,
extractResidualLayerParams
} = extractorsFactory4(weightMap, paramMappings);
const conv32_down = extractConvLayerParams("conv32_down");
const conv32_1 = extractResidualLayerParams("conv32_1");
const conv32_2 = extractResidualLayerParams("conv32_2");
const conv32_3 = extractResidualLayerParams("conv32_3");
const conv64_down = extractResidualLayerParams("conv64_down");
const conv64_1 = extractResidualLayerParams("conv64_1");
const conv64_2 = extractResidualLayerParams("conv64_2");
const conv64_3 = extractResidualLayerParams("conv64_3");
const conv128_down = extractResidualLayerParams("conv128_down");
const conv128_1 = extractResidualLayerParams("conv128_1");
const conv128_2 = extractResidualLayerParams("conv128_2");
const conv256_down = extractResidualLayerParams("conv256_down");
const conv256_1 = extractResidualLayerParams("conv256_1");
const conv256_2 = extractResidualLayerParams("conv256_2");
const conv256_down_out = extractResidualLayerParams("conv256_down_out");
const {fc} = weightMap;
paramMappings.push({originalPath: "fc", paramPath: "fc"});
if (!isTensor2D(fc)) {
throw new Error(`expected weightMap[fc] to be a Tensor2D, instead have ${fc}`);
}
const params = {
conv32_down,
conv32_1,
conv32_2,
conv32_3,
conv64_down,
conv64_1,
conv64_2,
conv64_3,
conv128_down,
conv128_1,
conv128_2,
conv256_down,
conv256_1,
conv256_2,
conv256_down_out,
fc
};
disposeUnusedWeightTensors(weightMap, paramMappings);
return {params, paramMappings};
}
// src/faceRecognitionNet/residualLayer.ts
var tf26 = __toModule(require_tfjs_esm());
function residual(x, params) {
let out = conv2(x, params.conv1);
out = convNoRelu(out, params.conv2);
out = tf26.add(out, x);
out = tf26.relu(out);
return out;
}
function residualDown(x, params) {
let out = convDown(x, params.conv1);
out = convNoRelu(out, params.conv2);
let pooled = tf26.avgPool(x, 2, 2, "valid");
const zeros2 = tf26.zeros(pooled.shape);
const isPad = pooled.shape[3] !== out.shape[3];
const isAdjustShape = pooled.shape[1] !== out.shape[1] || pooled.shape[2] !== out.shape[2];
if (isAdjustShape) {
const padShapeX = [...out.shape];
padShapeX[1] = 1;
const zerosW = tf26.zeros(padShapeX);
out = tf26.concat([out, zerosW], 1);
const padShapeY = [...out.shape];
padShapeY[2] = 1;
const zerosH = tf26.zeros(padShapeY);
out = tf26.concat([out, zerosH], 2);
}
pooled = isPad ? tf26.concat([pooled, zeros2], 3) : pooled;
out = tf26.add(pooled, out);
out = tf26.relu(out);
return out;
}
// src/faceRecognitionNet/FaceRecognitionNet.ts
var FaceRecognitionNet = class extends NeuralNetwork {
constructor() {
super("FaceRecognitionNet");
}
forwardInput(input) {
const {params} = this;
if (!params) {
throw new Error("FaceRecognitionNet - load model before inference");
}
return tf27.tidy(() => {
const batchTensor = tf27.cast(input.toBatchTensor(150, true), "float32");
const meanRgb = [122.782, 117.001, 104.298];
const normalized = normalize(batchTensor, meanRgb).div(255);
let out = convDown(normalized, params.conv32_down);
out = tf27.maxPool(out, 3, 2, "valid");
out = residual(out, params.conv32_1);
out = residual(out, params.conv32_2);
out = residual(out, params.conv32_3);
out = residualDown(out, params.conv64_down);
out = residual(out, params.conv64_1);
out = residual(out, params.conv64_2);
out = residual(out, params.conv64_3);
out = residualDown(out, params.conv128_down);
out = residual(out, params.conv128_1);
out = residual(out, params.conv128_2);
out = residualDown(out, params.conv256_down);
out = residual(out, params.conv256_1);
out = residual(out, params.conv256_2);
out = residualDown(out, params.conv256_down_out);
const globalAvg = out.mean([1, 2]);
const fullyConnected = tf27.matMul(globalAvg, params.fc);
return fullyConnected;
});
}
async forward(input) {
return this.forwardInput(await toNetInput(input));
}
async computeFaceDescriptor(input) {
var _a;
if ((_a = input == null ? void 0 : input.shape) == null ? void 0 : _a.some((dim) => dim <= 0))
return new Float32Array(128);
const netInput = await toNetInput(input);
const faceDescriptorTensors = tf27.tidy(() => tf27.unstack(this.forwardInput(netInput)));
const faceDescriptorsForBatch = await Promise.all(faceDescriptorTensors.map((t) => t.data()));
faceDescriptorTensors.forEach((t) => t.dispose());
return netInput.isBatchInput ? faceDescriptorsForBatch : faceDescriptorsForBatch[0];
}
getDefaultModelName() {
return "face_recognition_model";
}
extractParamsFromWeightMap(weightMap) {
return extractParamsFromWeightMap5(weightMap);
}
extractParams(weights) {
return extractParams5(weights);
}
};
// src/faceRecognitionNet/index.ts
function createFaceRecognitionNet(weights) {
const net = new FaceRecognitionNet();
net.extractWeights(weights);
return net;
}
// src/factories/WithFaceDescriptor.ts
function extendWithFaceDescriptor(sourceObj, descriptor) {
const extension = {descriptor};
return {...sourceObj, ...extension};
}
// src/factories/WithAge.ts
function isWithAge(obj) {
return typeof obj.age === "number";
}
function extendWithAge(sourceObj, age) {
const extension = {age};
return {...sourceObj, ...extension};
}
// src/factories/WithGender.ts
function isWithGender(obj) {
return (obj.gender === Gender.MALE || obj.gender === Gender.FEMALE) && isValidProbablitiy(obj.genderProbability);
}
function extendWithGender(sourceObj, gender, genderProbability) {
const extension = {gender, genderProbability};
return {...sourceObj, ...extension};
}
// src/ssdMobilenetv1/SsdMobilenetv1.ts
var tf34 = __toModule(require_tfjs_esm());
// src/ssdMobilenetv1/extractParams.ts
var tf28 = __toModule(require_tfjs_esm());
function extractorsFactory5(extractWeights, paramMappings) {
function extractDepthwiseConvParams(numChannels, mappedPrefix) {
const filters = tf28.tensor4d(extractWeights(3 * 3 * numChannels), [3, 3, numChannels, 1]);
const batch_norm_scale = tf28.tensor1d(extractWeights(numChannels));
const batch_norm_offset = tf28.tensor1d(extractWeights(numChannels));
const batch_norm_mean = tf28.tensor1d(extractWeights(numChannels));
const batch_norm_variance = tf28.tensor1d(extractWeights(numChannels));
paramMappings.push({paramPath: `${mappedPrefix}/filters`}, {paramPath: `${mappedPrefix}/batch_norm_scale`}, {paramPath: `${mappedPrefix}/batch_norm_offset`}, {paramPath: `${mappedPrefix}/batch_norm_mean`}, {paramPath: `${mappedPrefix}/batch_norm_variance`});
return {
filters,
batch_norm_scale,
batch_norm_offset,
batch_norm_mean,
batch_norm_variance
};
}
function extractConvParams(channelsIn, channelsOut, filterSize, mappedPrefix, isPointwiseConv) {
const filters = tf28.tensor4d(extractWeights(channelsIn * channelsOut * filterSize * filterSize), [filterSize, filterSize, channelsIn, channelsOut]);
const bias = tf28.tensor1d(extractWeights(channelsOut));
paramMappings.push({paramPath: `${mappedPrefix}/filters`}, {paramPath: `${mappedPrefix}/${isPointwiseConv ? "batch_norm_offset" : "bias"}`});
return {filters, bias};
}
function extractPointwiseConvParams(channelsIn, channelsOut, filterSize, mappedPrefix) {
const {
filters,
bias
} = extractConvParams(channelsIn, channelsOut, filterSize, mappedPrefix, true);
return {
filters,
batch_norm_offset: bias
};
}
function extractConvPairParams(channelsIn, channelsOut, mappedPrefix) {
const depthwise_conv = extractDepthwiseConvParams(channelsIn, `${mappedPrefix}/depthwise_conv`);
const pointwise_conv = extractPointwiseConvParams(channelsIn, channelsOut, 1, `${mappedPrefix}/pointwise_conv`);
return {depthwise_conv, pointwise_conv};
}
function extractMobilenetV1Params() {
const conv_0 = extractPointwiseConvParams(3, 32, 3, "mobilenetv1/conv_0");
const conv_1 = extractConvPairParams(32, 64, "mobilenetv1/conv_1");
const conv_2 = extractConvPairParams(64, 128, "mobilenetv1/conv_2");
const conv_3 = extractConvPairParams(128, 128, "mobilenetv1/conv_3");
const conv_4 = extractConvPairParams(128, 256, "mobilenetv1/conv_4");
const conv_5 = extractConvPairParams(256, 256, "mobilenetv1/conv_5");
const conv_6 = extractConvPairParams(256, 512, "mobilenetv1/conv_6");
const conv_7 = extractConvPairParams(512, 512, "mobilenetv1/conv_7");
const conv_8 = extractConvPairParams(512, 512, "mobilenetv1/conv_8");
const conv_9 = extractConvPairParams(512, 512, "mobilenetv1/conv_9");
const conv_10 = extractConvPairParams(512, 512, "mobilenetv1/conv_10");
const conv_11 = extractConvPairParams(512, 512, "mobilenetv1/conv_11");
const conv_12 = extractConvPairParams(512, 1024, "mobilenetv1/conv_12");
const conv_13 = extractConvPairParams(1024, 1024, "mobilenetv1/conv_13");
return {
conv_0,
conv_1,
conv_2,
conv_3,
conv_4,
conv_5,
conv_6,
conv_7,
conv_8,
conv_9,
conv_10,
conv_11,
conv_12,
conv_13
};
}
function extractPredictionLayerParams() {
const conv_0 = extractPointwiseConvParams(1024, 256, 1, "prediction_layer/conv_0");
const conv_1 = extractPointwiseConvParams(256, 512, 3, "prediction_layer/conv_1");
const conv_2 = extractPointwiseConvParams(512, 128, 1, "prediction_layer/conv_2");
const conv_3 = extractPointwiseConvParams(128, 256, 3, "prediction_layer/conv_3");
const conv_4 = extractPointwiseConvParams(256, 128, 1, "prediction_layer/conv_4");
const conv_5 = extractPointwiseConvParams(128, 256, 3, "prediction_layer/conv_5");
const conv_6 = extractPointwiseConvParams(256, 64, 1, "prediction_layer/conv_6");
const conv_7 = extractPointwiseConvParams(64, 128, 3, "prediction_layer/conv_7");
const box_encoding_0_predictor = extractConvParams(512, 12, 1, "prediction_layer/box_predictor_0/box_encoding_predictor");
const class_predictor_0 = extractConvParams(512, 9, 1, "prediction_layer/box_predictor_0/class_predictor");
const box_encoding_1_predictor = extractConvParams(1024, 24, 1, "prediction_layer/box_predictor_1/box_encoding_predictor");
const class_predictor_1 = extractConvParams(1024, 18, 1, "prediction_layer/box_predictor_1/class_predictor");
const box_encoding_2_predictor = extractConvParams(512, 24, 1, "prediction_layer/box_predictor_2/box_encoding_predictor");
const class_predictor_2 = extractConvParams(512, 18, 1, "prediction_layer/box_predictor_2/class_predictor");
const box_encoding_3_predictor = extractConvParams(256, 24, 1, "prediction_layer/box_predictor_3/box_encoding_predictor");
const class_predictor_3 = extractConvParams(256, 18, 1, "prediction_layer/box_predictor_3/class_predictor");
const box_encoding_4_predictor = extractConvParams(256, 24, 1, "prediction_layer/box_predictor_4/box_encoding_predictor");
const class_predictor_4 = extractConvParams(256, 18, 1, "prediction_layer/box_predictor_4/class_predictor");
const box_encoding_5_predictor = extractConvParams(128, 24, 1, "prediction_layer/box_predictor_5/box_encoding_predictor");
const class_predictor_5 = extractConvParams(128, 18, 1, "prediction_layer/box_predictor_5/class_predictor");
const box_predictor_0 = {
box_encoding_predictor: box_encoding_0_predictor,
class_predictor: class_predictor_0
};
const box_predictor_1 = {
box_encoding_predictor: box_encoding_1_predictor,
class_predictor: class_predictor_1
};
const box_predictor_2 = {
box_encoding_predictor: box_encoding_2_predictor,
class_predictor: class_predictor_2
};
const box_predictor_3 = {
box_encoding_predictor: box_encoding_3_predictor,
class_predictor: class_predictor_3
};
const box_predictor_4 = {
box_encoding_predictor: box_encoding_4_predictor,
class_predictor: class_predictor_4
};
const box_predictor_5 = {
box_encoding_predictor: box_encoding_5_predictor,
class_predictor: class_predictor_5
};
return {
conv_0,
conv_1,
conv_2,
conv_3,
conv_4,
conv_5,
conv_6,
conv_7,
box_predictor_0,
box_predictor_1,
box_predictor_2,
box_predictor_3,
box_predictor_4,
box_predictor_5
};
}
return {
extractMobilenetV1Params,
extractPredictionLayerParams
};
}
function extractParams6(weights) {
const paramMappings = [];
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights);
const {
extractMobilenetV1Params,
extractPredictionLayerParams
} = extractorsFactory5(extractWeights, paramMappings);
const mobilenetv1 = extractMobilenetV1Params();
const prediction_layer = extractPredictionLayerParams();
const extra_dim = tf28.tensor3d(extractWeights(5118 * 4), [1, 5118, 4]);
const output_layer = {
extra_dim
};
paramMappings.push({paramPath: "output_layer/extra_dim"});
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`);
}
return {
params: {
mobilenetv1,
prediction_layer,
output_layer
},
paramMappings
};
}
// src/ssdMobilenetv1/extractParamsFromWeightMap.ts
function extractorsFactory6(weightMap, paramMappings) {
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings);
function extractPointwiseConvParams(prefix, idx, mappedPrefix) {
const filters = extractWeightEntry(`${prefix}/Conv2d_${idx}_pointwise/weights`, 4, `${mappedPrefix}/filters`);
const batch_norm_offset = extractWeightEntry(`${prefix}/Conv2d_${idx}_pointwise/convolution_bn_offset`, 1, `${mappedPrefix}/batch_norm_offset`);
return {filters, batch_norm_offset};
}
function extractConvPairParams(idx) {
const mappedPrefix = `mobilenetv1/conv_${idx}`;
const prefixDepthwiseConv = `MobilenetV1/Conv2d_${idx}_depthwise`;
const mappedPrefixDepthwiseConv = `${mappedPrefix}/depthwise_conv`;
const mappedPrefixPointwiseConv = `${mappedPrefix}/pointwise_conv`;
const filters = extractWeightEntry(`${prefixDepthwiseConv}/depthwise_weights`, 4, `${mappedPrefixDepthwiseConv}/filters`);
const batch_norm_scale = extractWeightEntry(`${prefixDepthwiseConv}/BatchNorm/gamma`, 1, `${mappedPrefixDepthwiseConv}/batch_norm_scale`);
const batch_norm_offset = extractWeightEntry(`${prefixDepthwiseConv}/BatchNorm/beta`, 1, `${mappedPrefixDepthwiseConv}/batch_norm_offset`);
const batch_norm_mean = extractWeightEntry(`${prefixDepthwiseConv}/BatchNorm/moving_mean`, 1, `${mappedPrefixDepthwiseConv}/batch_norm_mean`);
const batch_norm_variance = extractWeightEntry(`${prefixDepthwiseConv}/BatchNorm/moving_variance`, 1, `${mappedPrefixDepthwiseConv}/batch_norm_variance`);
return {
depthwise_conv: {
filters,
batch_norm_scale,
batch_norm_offset,
batch_norm_mean,
batch_norm_variance
},
pointwise_conv: extractPointwiseConvParams("MobilenetV1", idx, mappedPrefixPointwiseConv)
};
}
function extractMobilenetV1Params() {
return {
conv_0: extractPointwiseConvParams("MobilenetV1", 0, "mobilenetv1/conv_0"),
conv_1: extractConvPairParams(1),
conv_2: extractConvPairParams(2),
conv_3: extractConvPairParams(3),
conv_4: extractConvPairParams(4),
conv_5: extractConvPairParams(5),
conv_6: extractConvPairParams(6),
conv_7: extractConvPairParams(7),
conv_8: extractConvPairParams(8),
conv_9: extractConvPairParams(9),
conv_10: extractConvPairParams(10),
conv_11: extractConvPairParams(11),
conv_12: extractConvPairParams(12),
conv_13: extractConvPairParams(13)
};
}
function extractConvParams(prefix, mappedPrefix) {
const filters = extractWeightEntry(`${prefix}/weights`, 4, `${mappedPrefix}/filters`);
const bias = extractWeightEntry(`${prefix}/biases`, 1, `${mappedPrefix}/bias`);
return {filters, bias};
}
function extractBoxPredictorParams(idx) {
const box_encoding_predictor = extractConvParams(`Prediction/BoxPredictor_${idx}/BoxEncodingPredictor`, `prediction_layer/box_predictor_${idx}/box_encoding_predictor`);
const class_predictor = extractConvParams(`Prediction/BoxPredictor_${idx}/ClassPredictor`, `prediction_layer/box_predictor_${idx}/class_predictor`);
return {box_encoding_predictor, class_predictor};
}
function extractPredictionLayerParams() {
return {
conv_0: extractPointwiseConvParams("Prediction", 0, "prediction_layer/conv_0"),
conv_1: extractPointwiseConvParams("Prediction", 1, "prediction_layer/conv_1"),
conv_2: extractPointwiseConvParams("Prediction", 2, "prediction_layer/conv_2"),
conv_3: extractPointwiseConvParams("Prediction", 3, "prediction_layer/conv_3"),
conv_4: extractPointwiseConvParams("Prediction", 4, "prediction_layer/conv_4"),
conv_5: extractPointwiseConvParams("Prediction", 5, "prediction_layer/conv_5"),
conv_6: extractPointwiseConvParams("Prediction", 6, "prediction_layer/conv_6"),
conv_7: extractPointwiseConvParams("Prediction", 7, "prediction_layer/conv_7"),
box_predictor_0: extractBoxPredictorParams(0),
box_predictor_1: extractBoxPredictorParams(1),
box_predictor_2: extractBoxPredictorParams(2),
box_predictor_3: extractBoxPredictorParams(3),
box_predictor_4: extractBoxPredictorParams(4),
box_predictor_5: extractBoxPredictorParams(5)
};
}
return {
extractMobilenetV1Params,
extractPredictionLayerParams
};
}
function extractParamsFromWeightMap6(weightMap) {
const paramMappings = [];
const {
extractMobilenetV1Params,
extractPredictionLayerParams
} = extractorsFactory6(weightMap, paramMappings);
const extra_dim = weightMap["Output/extra_dim"];
paramMappings.push({originalPath: "Output/extra_dim", paramPath: "output_layer/extra_dim"});
if (!isTensor3D(extra_dim)) {
throw new Error(`expected weightMap['Output/extra_dim'] to be a Tensor3D, instead have ${extra_dim}`);
}
const params = {
mobilenetv1: extractMobilenetV1Params(),
prediction_layer: extractPredictionLayerParams(),
output_layer: {
extra_dim
}
};
disposeUnusedWeightTensors(weightMap, paramMappings);
return {params, paramMappings};
}
// src/ssdMobilenetv1/mobileNetV1.ts
var tf30 = __toModule(require_tfjs_esm());
// src/ssdMobilenetv1/pointwiseConvLayer.ts
var tf29 = __toModule(require_tfjs_esm());
function pointwiseConvLayer(x, params, strides) {
return tf29.tidy(() => {
let out = tf29.conv2d(x, params.filters, strides, "same");
out = tf29.add(out, params.batch_norm_offset);
return tf29.clipByValue(out, 0, 6);
});
}
// src/ssdMobilenetv1/mobileNetV1.ts
var epsilon = 0.0010000000474974513;
function depthwiseConvLayer(x, params, strides) {
return tf30.tidy(() => {
let out = tf30.depthwiseConv2d(x, params.filters, strides, "same");
out = tf30.batchNorm(out, params.batch_norm_mean, params.batch_norm_variance, params.batch_norm_offset, params.batch_norm_scale, epsilon);
return tf30.clipByValue(out, 0, 6);
});
}
function getStridesForLayerIdx(layerIdx) {
return [2, 4, 6, 12].some((idx) => idx === layerIdx) ? [2, 2] : [1, 1];
}
function mobileNetV1(x, params) {
return tf30.tidy(() => {
let conv11;
let out = pointwiseConvLayer(x, params.conv_0, [2, 2]);
const convPairParams = [
params.conv_1,
params.conv_2,
params.conv_3,
params.conv_4,
params.conv_5,
params.conv_6,
params.conv_7,
params.conv_8,
params.conv_9,
params.conv_10,
params.conv_11,
params.conv_12,
params.conv_13
];
convPairParams.forEach((param, i) => {
const layerIdx = i + 1;
const depthwiseConvStrides = getStridesForLayerIdx(layerIdx);
out = depthwiseConvLayer(out, param.depthwise_conv, depthwiseConvStrides);
out = pointwiseConvLayer(out, param.pointwise_conv, [1, 1]);
if (layerIdx === 11)
conv11 = out;
});
if (conv11 === null) {
throw new Error("mobileNetV1 - output of conv layer 11 is null");
}
return {
out,
conv11
};
});
}
// src/ssdMobilenetv1/nonMaxSuppression.ts
function IOU(boxes, i, j) {
const boxesData = boxes.arraySync();
const yminI = Math.min(boxesData[i][0], boxesData[i][2]);
const xminI = Math.min(boxesData[i][1], boxesData[i][3]);
const ymaxI = Math.max(boxesData[i][0], boxesData[i][2]);
const xmaxI = Math.max(boxesData[i][1], boxesData[i][3]);
const yminJ = Math.min(boxesData[j][0], boxesData[j][2]);
const xminJ = Math.min(boxesData[j][1], boxesData[j][3]);
const ymaxJ = Math.max(boxesData[j][0], boxesData[j][2]);
const xmaxJ = Math.max(boxesData[j][1], boxesData[j][3]);
const areaI = (ymaxI - yminI) * (xmaxI - xminI);
const areaJ = (ymaxJ - yminJ) * (xmaxJ - xminJ);
if (areaI <= 0 || areaJ <= 0)
return 0;
const intersectionYmin = Math.max(yminI, yminJ);
const intersectionXmin = Math.max(xminI, xminJ);
const intersectionYmax = Math.min(ymaxI, ymaxJ);
const intersectionXmax = Math.min(xmaxI, xmaxJ);
const intersectionArea = Math.max(intersectionYmax - intersectionYmin, 0) * Math.max(intersectionXmax - intersectionXmin, 0);
return intersectionArea / (areaI + areaJ - intersectionArea);
}
function nonMaxSuppression2(boxes, scores, maxOutputSize, iouThreshold, scoreThreshold) {
const numBoxes = boxes.shape[0];
const outputSize = Math.min(maxOutputSize, numBoxes);
const candidates = scores.map((score, boxIndex) => ({score, boxIndex})).filter((c) => c.score > scoreThreshold).sort((c1, c2) => c2.score - c1.score);
const suppressFunc = (x) => x <= iouThreshold ? 1 : 0;
const selected = [];
candidates.forEach((c) => {
if (selected.length >= outputSize)
return;
const originalScore = c.score;
for (let j = selected.length - 1; j >= 0; --j) {
const iou2 = IOU(boxes, c.boxIndex, selected[j]);
if (iou2 === 0)
continue;
c.score *= suppressFunc(iou2);
if (c.score <= scoreThreshold)
break;
}
if (originalScore === c.score) {
selected.push(c.boxIndex);
}
});
return selected;
}
// src/ssdMobilenetv1/outputLayer.ts
var tf31 = __toModule(require_tfjs_esm());
function getCenterCoordinatesAndSizesLayer(x) {
const vec = tf31.unstack(tf31.transpose(x, [1, 0]));
const sizes = [
tf31.sub(vec[2], vec[0]),
tf31.sub(vec[3], vec[1])
];
const centers = [
tf31.add(vec[0], tf31.div(sizes[0], 2)),
tf31.add(vec[1], tf31.div(sizes[1], 2))
];
return {sizes, centers};
}
function decodeBoxesLayer(x0, x1) {
const {sizes, centers} = getCenterCoordinatesAndSizesLayer(x0);
const vec = tf31.unstack(tf31.transpose(x1, [1, 0]));
const div0_out = tf31.div(tf31.mul(tf31.exp(tf31.div(vec[2], 5)), sizes[0]), 2);
const add0_out = tf31.add(tf31.mul(tf31.div(vec[0], 10), sizes[0]), centers[0]);
const div1_out = tf31.div(tf31.mul(tf31.exp(tf31.div(vec[3], 5)), sizes[1]), 2);
const add1_out = tf31.add(tf31.mul(tf31.div(vec[1], 10), sizes[1]), centers[1]);
return tf31.transpose(tf31.stack([
tf31.sub(add0_out, div0_out),
tf31.sub(add1_out, div1_out),
tf31.add(add0_out, div0_out),
tf31.add(add1_out, div1_out)
]), [1, 0]);
}
function outputLayer(boxPredictions, classPredictions, params) {
return tf31.tidy(() => {
const batchSize = boxPredictions.shape[0];
let boxes = decodeBoxesLayer(tf31.reshape(tf31.tile(params.extra_dim, [batchSize, 1, 1]), [-1, 4]), tf31.reshape(boxPredictions, [-1, 4]));
boxes = tf31.reshape(boxes, [batchSize, boxes.shape[0] / batchSize, 4]);
const scoresAndClasses = tf31.sigmoid(tf31.slice(classPredictions, [0, 0, 1], [-1, -1, -1]));
let scores = tf31.slice(scoresAndClasses, [0, 0, 0], [-1, -1, 1]);
scores = tf31.reshape(scores, [batchSize, scores.shape[1]]);
const boxesByBatch = tf31.unstack(boxes);
const scoresByBatch = tf31.unstack(scores);
return {boxes: boxesByBatch, scores: scoresByBatch};
});
}
// src/ssdMobilenetv1/predictionLayer.ts
var tf33 = __toModule(require_tfjs_esm());
// src/ssdMobilenetv1/boxPredictionLayer.ts
var tf32 = __toModule(require_tfjs_esm());
function boxPredictionLayer(x, params) {
return tf32.tidy(() => {
const batchSize = x.shape[0];
const boxPredictionEncoding = tf32.reshape(convLayer(x, params.box_encoding_predictor), [batchSize, -1, 1, 4]);
const classPrediction = tf32.reshape(convLayer(x, params.class_predictor), [batchSize, -1, 3]);
return {boxPredictionEncoding, classPrediction};
});
}
// src/ssdMobilenetv1/predictionLayer.ts
function predictionLayer(x, conv11, params) {
return tf33.tidy(() => {
const conv0 = pointwiseConvLayer(x, params.conv_0, [1, 1]);
const conv1 = pointwiseConvLayer(conv0, params.conv_1, [2, 2]);
const conv22 = pointwiseConvLayer(conv1, params.conv_2, [1, 1]);
const conv3 = pointwiseConvLayer(conv22, params.conv_3, [2, 2]);
const conv4 = pointwiseConvLayer(conv3, params.conv_4, [1, 1]);
const conv5 = pointwiseConvLayer(conv4, params.conv_5, [2, 2]);
const conv6 = pointwiseConvLayer(conv5, params.conv_6, [1, 1]);
const conv7 = pointwiseConvLayer(conv6, params.conv_7, [2, 2]);
const boxPrediction0 = boxPredictionLayer(conv11, params.box_predictor_0);
const boxPrediction1 = boxPredictionLayer(x, params.box_predictor_1);
const boxPrediction2 = boxPredictionLayer(conv1, params.box_predictor_2);
const boxPrediction3 = boxPredictionLayer(conv3, params.box_predictor_3);
const boxPrediction4 = boxPredictionLayer(conv5, params.box_predictor_4);
const boxPrediction5 = boxPredictionLayer(conv7, params.box_predictor_5);
const boxPredictions = tf33.concat([
boxPrediction0.boxPredictionEncoding,
boxPrediction1.boxPredictionEncoding,
boxPrediction2.boxPredictionEncoding,
boxPrediction3.boxPredictionEncoding,
boxPrediction4.boxPredictionEncoding,
boxPrediction5.boxPredictionEncoding
], 1);
const classPredictions = tf33.concat([
boxPrediction0.classPrediction,
boxPrediction1.classPrediction,
boxPrediction2.classPrediction,
boxPrediction3.classPrediction,
boxPrediction4.classPrediction,
boxPrediction5.classPrediction
], 1);
return {
boxPredictions,
classPredictions
};
});
}
// src/ssdMobilenetv1/SsdMobilenetv1Options.ts
var SsdMobilenetv1Options = class {
constructor({minConfidence, maxResults} = {}) {
this._name = "SsdMobilenetv1Options";
this._minConfidence = minConfidence || 0.5;
this._maxResults = maxResults || 100;
if (typeof this._minConfidence !== "number" || this._minConfidence <= 0 || this._minConfidence >= 1) {
throw new Error(`${this._name} - expected minConfidence to be a number between 0 and 1`);
}
if (typeof this._maxResults !== "number") {
throw new Error(`${this._name} - expected maxResults to be a number`);
}
}
get minConfidence() {
return this._minConfidence;
}
get maxResults() {
return this._maxResults;
}
};
// src/ssdMobilenetv1/SsdMobilenetv1.ts
var SsdMobilenetv1 = class extends NeuralNetwork {
constructor() {
super("SsdMobilenetv1");
}
forwardInput(input) {
const {params} = this;
if (!params)
throw new Error("SsdMobilenetv1 - load model before inference");
return tf34.tidy(() => {
const batchTensor = tf34.cast(input.toBatchTensor(512, false), "float32");
const x = tf34.sub(tf34.div(batchTensor, 127.5), 1);
const features = mobileNetV1(x, params.mobilenetv1);
const {boxPredictions, classPredictions} = predictionLayer(features.out, features.conv11, params.prediction_layer);
return outputLayer(boxPredictions, classPredictions, params.output_layer);
});
}
async forward(input) {
return this.forwardInput(await toNetInput(input));
}
async locateFaces(input, options = {}) {
const {maxResults, minConfidence} = new SsdMobilenetv1Options(options);
const netInput = await toNetInput(input);
const {boxes: _boxes, scores: _scores} = this.forwardInput(netInput);
const boxes = _boxes[0];
const scores = _scores[0];
for (let i = 1; i < _boxes.length; i++) {
_boxes[i].dispose();
_scores[i].dispose();
}
const scoresData = Array.from(scores.dataSync());
const iouThreshold = 0.5;
const indices = nonMaxSuppression2(boxes, scoresData, maxResults, iouThreshold, minConfidence);
const reshapedDims = netInput.getReshapedInputDimensions(0);
const inputSize = netInput.inputSize;
const padX = inputSize / reshapedDims.width;
const padY = inputSize / reshapedDims.height;
const boxesData = boxes.arraySync();
const results = indices.map((idx) => {
const [top, bottom] = [
Math.max(0, boxesData[idx][0]),
Math.min(1, boxesData[idx][2])
].map((val) => val * padY);
const [left, right] = [
Math.max(0, boxesData[idx][1]),
Math.min(1, boxesData[idx][3])
].map((val) => val * padX);
return new FaceDetection(scoresData[idx], new Rect(left, top, right - left, bottom - top), {height: netInput.getInputHeight(0), width: netInput.getInputWidth(0)});
});
boxes.dispose();
scores.dispose();
return results;
}
getDefaultModelName() {
return "ssd_mobilenetv1_model";
}
extractParamsFromWeightMap(weightMap) {
return extractParamsFromWeightMap6(weightMap);
}
extractParams(weights) {
return extractParams6(weights);
}
};
// src/ssdMobilenetv1/index.ts
function createSsdMobilenetv1(weights) {
const net = new SsdMobilenetv1();
net.extractWeights(weights);
return net;
}
function createFaceDetectionNet(weights) {
return createSsdMobilenetv1(weights);
}
var FaceDetectionNet = class extends SsdMobilenetv1 {
};
// src/tinyYolov2/const.ts
var IOU_THRESHOLD = 0.4;
var BOX_ANCHORS = [
new Point(0.738768, 0.874946),
new Point(2.42204, 2.65704),
new Point(4.30971, 7.04493),
new Point(10.246, 4.59428),
new Point(12.6868, 11.8741)
];
var BOX_ANCHORS_SEPARABLE = [
new Point(1.603231, 2.094468),
new Point(6.041143, 7.080126),
new Point(2.882459, 3.518061),
new Point(4.266906, 5.178857),
new Point(9.041765, 10.66308)
];
var MEAN_RGB_SEPARABLE = [117.001, 114.697, 97.404];
var DEFAULT_MODEL_NAME = "tiny_yolov2_model";
var DEFAULT_MODEL_NAME_SEPARABLE_CONV = "tiny_yolov2_separable_conv_model";
// src/tinyYolov2/TinyYolov2Base.ts
var tf39 = __toModule(require_tfjs_esm());
// src/tinyYolov2/config.ts
var isNumber = (arg) => typeof arg === "number";
function validateConfig(config) {
if (!config) {
throw new Error(`invalid config: ${config}`);
}
if (typeof config.withSeparableConvs !== "boolean") {
throw new Error(`config.withSeparableConvs has to be a boolean, have: ${config.withSeparableConvs}`);
}
if (!isNumber(config.iouThreshold) || config.iouThreshold < 0 || config.iouThreshold > 1) {
throw new Error(`config.iouThreshold has to be a number between [0, 1], have: ${config.iouThreshold}`);
}
if (!Array.isArray(config.classes) || !config.classes.length || !config.classes.every((c) => typeof c === "string")) {
throw new Error(`config.classes has to be an array class names: string[], have: ${JSON.stringify(config.classes)}`);
}
if (!Array.isArray(config.anchors) || !config.anchors.length || !config.anchors.map((a) => a || {}).every((a) => isNumber(a.x) && isNumber(a.y))) {
throw new Error(`config.anchors has to be an array of { x: number, y: number }, have: ${JSON.stringify(config.anchors)}`);
}
if (config.meanRgb && (!Array.isArray(config.meanRgb) || config.meanRgb.length !== 3 || !config.meanRgb.every(isNumber))) {
throw new Error(`config.meanRgb has to be an array of shape [number, number, number], have: ${JSON.stringify(config.meanRgb)}`);
}
}
// src/tinyYolov2/convWithBatchNorm.ts
var tf36 = __toModule(require_tfjs_esm());
// src/tinyYolov2/leaky.ts
var tf35 = __toModule(require_tfjs_esm());
function leaky(x) {
return tf35.tidy(() => {
const min = tf35.mul(x, tf35.scalar(0.10000000149011612));
return tf35.add(tf35.relu(tf35.sub(x, min)), min);
});
}
// src/tinyYolov2/convWithBatchNorm.ts
function convWithBatchNorm(x, params) {
return tf36.tidy(() => {
let out = tf36.pad(x, [[0, 0], [1, 1], [1, 1], [0, 0]]);
out = tf36.conv2d(out, params.conv.filters, [1, 1], "valid");
out = tf36.sub(out, params.bn.sub);
out = tf36.mul(out, params.bn.truediv);
out = tf36.add(out, params.conv.bias);
return leaky(out);
});
}
// src/tinyYolov2/depthwiseSeparableConv.ts
var tf37 = __toModule(require_tfjs_esm());
function depthwiseSeparableConv2(x, params) {
return tf37.tidy(() => {
let out = tf37.pad(x, [[0, 0], [1, 1], [1, 1], [0, 0]]);
out = tf37.separableConv2d(out, params.depthwise_filter, params.pointwise_filter, [1, 1], "valid");
out = tf37.add(out, params.bias);
return leaky(out);
});
}
// src/tinyYolov2/extractParams.ts
var tf38 = __toModule(require_tfjs_esm());
function extractorsFactory7(extractWeights, paramMappings) {
const extractConvParams = extractConvParamsFactory(extractWeights, paramMappings);
function extractBatchNormParams(size, mappedPrefix) {
const sub6 = tf38.tensor1d(extractWeights(size));
const truediv = tf38.tensor1d(extractWeights(size));
paramMappings.push({paramPath: `${mappedPrefix}/sub`}, {paramPath: `${mappedPrefix}/truediv`});
return {sub: sub6, truediv};
}
function extractConvWithBatchNormParams(channelsIn, channelsOut, mappedPrefix) {
const conv3 = extractConvParams(channelsIn, channelsOut, 3, `${mappedPrefix}/conv`);
const bn = extractBatchNormParams(channelsOut, `${mappedPrefix}/bn`);
return {conv: conv3, bn};
}
const extractSeparableConvParams = extractSeparableConvParamsFactory(extractWeights, paramMappings);
return {
extractConvParams,
extractConvWithBatchNormParams,
extractSeparableConvParams
};
}
function extractParams7(weights, config, boxEncodingSize, filterSizes) {
const {
extractWeights,
getRemainingWeights
} = extractWeightsFactory(weights);
const paramMappings = [];
const {
extractConvParams,
extractConvWithBatchNormParams,
extractSeparableConvParams
} = extractorsFactory7(extractWeights, paramMappings);
let params;
if (config.withSeparableConvs) {
const [s0, s1, s2, s3, s4, s5, s6, s7, s8] = filterSizes;
const conv0 = config.isFirstLayerConv2d ? extractConvParams(s0, s1, 3, "conv0") : extractSeparableConvParams(s0, s1, "conv0");
const conv1 = extractSeparableConvParams(s1, s2, "conv1");
const conv22 = extractSeparableConvParams(s2, s3, "conv2");
const conv3 = extractSeparableConvParams(s3, s4, "conv3");
const conv4 = extractSeparableConvParams(s4, s5, "conv4");
const conv5 = extractSeparableConvParams(s5, s6, "conv5");
const conv6 = s7 ? extractSeparableConvParams(s6, s7, "conv6") : void 0;
const conv7 = s8 ? extractSeparableConvParams(s7, s8, "conv7") : void 0;
const conv8 = extractConvParams(s8 || s7 || s6, 5 * boxEncodingSize, 1, "conv8");
params = {
conv0,
conv1,
conv2: conv22,
conv3,
conv4,
conv5,
conv6,
conv7,
conv8
};
} else {
const [s0, s1, s2, s3, s4, s5, s6, s7, s8] = filterSizes;
const conv0 = extractConvWithBatchNormParams(s0, s1, "conv0");
const conv1 = extractConvWithBatchNormParams(s1, s2, "conv1");
const conv22 = extractConvWithBatchNormParams(s2, s3, "conv2");
const conv3 = extractConvWithBatchNormParams(s3, s4, "conv3");
const conv4 = extractConvWithBatchNormParams(s4, s5, "conv4");
const conv5 = extractConvWithBatchNormParams(s5, s6, "conv5");
const conv6 = extractConvWithBatchNormParams(s6, s7, "conv6");
const conv7 = extractConvWithBatchNormParams(s7, s8, "conv7");
const conv8 = extractConvParams(s8, 5 * boxEncodingSize, 1, "conv8");
params = {
conv0,
conv1,
conv2: conv22,
conv3,
conv4,
conv5,
conv6,
conv7,
conv8
};
}
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`);
}
return {params, paramMappings};
}
// src/tinyYolov2/extractParamsFromWeightMap.ts
function extractorsFactory8(weightMap, paramMappings) {
const extractWeightEntry = extractWeightEntryFactory(weightMap, paramMappings);
function extractBatchNormParams(prefix) {
const sub6 = extractWeightEntry(`${prefix}/sub`, 1);
const truediv = extractWeightEntry(`${prefix}/truediv`, 1);
return {sub: sub6, truediv};
}
function extractConvParams(prefix) {
const filters = extractWeightEntry(`${prefix}/filters`, 4);
const bias = extractWeightEntry(`${prefix}/bias`, 1);
return {filters, bias};
}
function extractConvWithBatchNormParams(prefix) {
const conv3 = extractConvParams(`${prefix}/conv`);
const bn = extractBatchNormParams(`${prefix}/bn`);
return {conv: conv3, bn};
}
const extractSeparableConvParams = loadSeparableConvParamsFactory(extractWeightEntry);
return {
extractConvParams,
extractConvWithBatchNormParams,
extractSeparableConvParams
};
}
function extractParamsFromWeightMap7(weightMap, config) {
const paramMappings = [];
const {
extractConvParams,
extractConvWithBatchNormParams,
extractSeparableConvParams
} = extractorsFactory8(weightMap, paramMappings);
let params;
if (config.withSeparableConvs) {
const numFilters = config.filterSizes && config.filterSizes.length || 9;
params = {
conv0: config.isFirstLayerConv2d ? extractConvParams("conv0") : extractSeparableConvParams("conv0"),
conv1: extractSeparableConvParams("conv1"),
conv2: extractSeparableConvParams("conv2"),
conv3: extractSeparableConvParams("conv3"),
conv4: extractSeparableConvParams("conv4"),
conv5: extractSeparableConvParams("conv5"),
conv6: numFilters > 7 ? extractSeparableConvParams("conv6") : void 0,
conv7: numFilters > 8 ? extractSeparableConvParams("conv7") : void 0,
conv8: extractConvParams("conv8")
};
} else {
params = {
conv0: extractConvWithBatchNormParams("conv0"),
conv1: extractConvWithBatchNormParams("conv1"),
conv2: extractConvWithBatchNormParams("conv2"),
conv3: extractConvWithBatchNormParams("conv3"),
conv4: extractConvWithBatchNormParams("conv4"),
conv5: extractConvWithBatchNormParams("conv5"),
conv6: extractConvWithBatchNormParams("conv6"),
conv7: extractConvWithBatchNormParams("conv7"),
conv8: extractConvParams("conv8")
};
}
disposeUnusedWeightTensors(weightMap, paramMappings);
return {params, paramMappings};
}
// src/tinyYolov2/TinyYolov2Options.ts
var TinyYolov2Options = class {
constructor({inputSize, scoreThreshold} = {}) {
this._name = "TinyYolov2Options";
this._inputSize = inputSize || 416;
this._scoreThreshold = scoreThreshold || 0.5;
if (typeof this._inputSize !== "number" || this._inputSize % 32 !== 0) {
throw new Error(`${this._name} - expected inputSize to be a number divisible by 32`);
}
if (typeof this._scoreThreshold !== "number" || this._scoreThreshold <= 0 || this._scoreThreshold >= 1) {
throw new Error(`${this._name} - expected scoreThreshold to be a number between 0 and 1`);
}
}
get inputSize() {
return this._inputSize;
}
get scoreThreshold() {
return this._scoreThreshold;
}
};
// src/tinyYolov2/TinyYolov2Base.ts
var _TinyYolov2Base = class extends NeuralNetwork {
constructor(config) {
super("TinyYolov2");
validateConfig(config);
this._config = config;
}
get config() {
return this._config;
}
get withClassScores() {
return this.config.withClassScores || this.config.classes.length > 1;
}
get boxEncodingSize() {
return 5 + (this.withClassScores ? this.config.classes.length : 0);
}
runTinyYolov2(x, params) {
let out = convWithBatchNorm(x, params.conv0);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = convWithBatchNorm(out, params.conv1);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = convWithBatchNorm(out, params.conv2);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = convWithBatchNorm(out, params.conv3);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = convWithBatchNorm(out, params.conv4);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = convWithBatchNorm(out, params.conv5);
out = tf39.maxPool(out, [2, 2], [1, 1], "same");
out = convWithBatchNorm(out, params.conv6);
out = convWithBatchNorm(out, params.conv7);
return convLayer(out, params.conv8, "valid", false);
}
runMobilenet(x, params) {
let out = this.config.isFirstLayerConv2d ? leaky(convLayer(x, params.conv0, "valid", false)) : depthwiseSeparableConv2(x, params.conv0);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = depthwiseSeparableConv2(out, params.conv1);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = depthwiseSeparableConv2(out, params.conv2);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = depthwiseSeparableConv2(out, params.conv3);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = depthwiseSeparableConv2(out, params.conv4);
out = tf39.maxPool(out, [2, 2], [2, 2], "same");
out = depthwiseSeparableConv2(out, params.conv5);
out = tf39.maxPool(out, [2, 2], [1, 1], "same");
out = params.conv6 ? depthwiseSeparableConv2(out, params.conv6) : out;
out = params.conv7 ? depthwiseSeparableConv2(out, params.conv7) : out;
return convLayer(out, params.conv8, "valid", false);
}
forwardInput(input, inputSize) {
const {params} = this;
if (!params) {
throw new Error("TinyYolov2 - load model before inference");
}
return tf39.tidy(() => {
let batchTensor = tf39.cast(input.toBatchTensor(inputSize, false), "float32");
batchTensor = this.config.meanRgb ? normalize(batchTensor, this.config.meanRgb) : batchTensor;
batchTensor = batchTensor.div(255);
return this.config.withSeparableConvs ? this.runMobilenet(batchTensor, params) : this.runTinyYolov2(batchTensor, params);
});
}
async forward(input, inputSize) {
return this.forwardInput(await toNetInput(input), inputSize);
}
async detect(input, forwardParams = {}) {
const {inputSize, scoreThreshold} = new TinyYolov2Options(forwardParams);
const netInput = await toNetInput(input);
const out = await this.forwardInput(netInput, inputSize);
const out0 = tf39.tidy(() => tf39.unstack(out)[0].expandDims());
const inputDimensions = {
width: netInput.getInputWidth(0),
height: netInput.getInputHeight(0)
};
const results = await this.extractBoxes(out0, netInput.getReshapedInputDimensions(0), scoreThreshold);
out.dispose();
out0.dispose();
const boxes = results.map((res) => res.box);
const scores = results.map((res) => res.score);
const classScores = results.map((res) => res.classScore);
const classNames = results.map((res) => this.config.classes[res.label]);
const indices = nonMaxSuppression(boxes.map((box) => box.rescale(inputSize)), scores, this.config.iouThreshold, true);
const detections = indices.map((idx) => new ObjectDetection(scores[idx], classScores[idx], classNames[idx], boxes[idx], inputDimensions));
return detections;
}
getDefaultModelName() {
return "";
}
extractParamsFromWeightMap(weightMap) {
return extractParamsFromWeightMap7(weightMap, this.config);
}
extractParams(weights) {
const filterSizes = this.config.filterSizes || _TinyYolov2Base.DEFAULT_FILTER_SIZES;
const numFilters = filterSizes ? filterSizes.length : void 0;
if (numFilters !== 7 && numFilters !== 8 && numFilters !== 9) {
throw new Error(`TinyYolov2 - expected 7 | 8 | 9 convolutional filters, but found ${numFilters} filterSizes in config`);
}
return extractParams7(weights, this.config, this.boxEncodingSize, filterSizes);
}
async extractBoxes(outputTensor, inputBlobDimensions, scoreThreshold) {
const {width, height} = inputBlobDimensions;
const inputSize = Math.max(width, height);
const correctionFactorX = inputSize / width;
const correctionFactorY = inputSize / height;
const numCells = outputTensor.shape[1];
const numBoxes = this.config.anchors.length;
const [boxesTensor, scoresTensor, classScoresTensor] = tf39.tidy(() => {
const reshaped = outputTensor.reshape([numCells, numCells, numBoxes, this.boxEncodingSize]);
const boxes = reshaped.slice([0, 0, 0, 0], [numCells, numCells, numBoxes, 4]);
const scores = reshaped.slice([0, 0, 0, 4], [numCells, numCells, numBoxes, 1]);
const classScores = this.withClassScores ? tf39.softmax(reshaped.slice([0, 0, 0, 5], [numCells, numCells, numBoxes, this.config.classes.length]), 3) : tf39.scalar(0);
return [boxes, scores, classScores];
});
const results = [];
const scoresData = await scoresTensor.array();
const boxesData = await boxesTensor.array();
for (let row = 0; row < numCells; row++) {
for (let col = 0; col < numCells; col++) {
for (let anchor = 0; anchor < numBoxes; anchor++) {
const score = sigmoid(scoresData[row][col][anchor][0]);
if (!scoreThreshold || score > scoreThreshold) {
const ctX = (col + sigmoid(boxesData[row][col][anchor][0])) / numCells * correctionFactorX;
const ctY = (row + sigmoid(boxesData[row][col][anchor][1])) / numCells * correctionFactorY;
const widthLocal = Math.exp(boxesData[row][col][anchor][2]) * this.config.anchors[anchor].x / numCells * correctionFactorX;
const heightLocal = Math.exp(boxesData[row][col][anchor][3]) * this.config.anchors[anchor].y / numCells * correctionFactorY;
const x = ctX - widthLocal / 2;
const y = ctY - heightLocal / 2;
const pos = {row, col, anchor};
const {classScore, label} = this.withClassScores ? await this.extractPredictedClass(classScoresTensor, pos) : {classScore: 1, label: 0};
results.push({
box: new BoundingBox(x, y, x + widthLocal, y + heightLocal),
score,
classScore: score * classScore,
label,
...pos
});
}
}
}
}
boxesTensor.dispose();
scoresTensor.dispose();
classScoresTensor.dispose();
return results;
}
async extractPredictedClass(classesTensor, pos) {
const {row, col, anchor} = pos;
const classesData = await classesTensor.array();
return Array(this.config.classes.length).fill(0).map((_, i) => classesData[row][col][anchor][i]).map((classScore, label) => ({
classScore,
label
})).reduce((max, curr) => max.classScore > curr.classScore ? max : curr);
}
};
var TinyYolov2Base = _TinyYolov2Base;
TinyYolov2Base.DEFAULT_FILTER_SIZES = [3, 16, 32, 64, 128, 256, 512, 1024, 1024];
// src/tinyYolov2/TinyYolov2.ts
var TinyYolov2 = class extends TinyYolov2Base {
constructor(withSeparableConvs = true) {
const config = {
withSeparableConvs,
iouThreshold: IOU_THRESHOLD,
classes: ["face"],
...withSeparableConvs ? {
anchors: BOX_ANCHORS_SEPARABLE,
meanRgb: MEAN_RGB_SEPARABLE
} : {
anchors: BOX_ANCHORS,
withClassScores: true
}
};
super(config);
}
get withSeparableConvs() {
return this.config.withSeparableConvs;
}
get anchors() {
return this.config.anchors;
}
async locateFaces(input, forwardParams) {
const objectDetections = await this.detect(input, forwardParams);
return objectDetections.map((det) => new FaceDetection(det.score, det.relativeBox, {width: det.imageWidth, height: det.imageHeight}));
}
getDefaultModelName() {
return this.withSeparableConvs ? DEFAULT_MODEL_NAME_SEPARABLE_CONV : DEFAULT_MODEL_NAME;
}
extractParamsFromWeightMap(weightMap) {
return super.extractParamsFromWeightMap(weightMap);
}
};
// src/tinyYolov2/index.ts
function createTinyYolov2(weights, withSeparableConvs = true) {
const net = new TinyYolov2(withSeparableConvs);
net.extractWeights(weights);
return net;
}
// src/tinyFaceDetector/TinyFaceDetectorOptions.ts
var TinyFaceDetectorOptions = class extends TinyYolov2Options {
constructor() {
super(...arguments);
this._name = "TinyFaceDetectorOptions";
}
};
// src/globalApi/ComposableTask.ts
var ComposableTask = class {
async then(onfulfilled) {
return onfulfilled(await this.run());
}
async run() {
throw new Error("ComposableTask - run is not implemented");
}
};
// src/globalApi/DetectFaceLandmarksTasks.ts
var tf41 = __toModule(require_tfjs_esm());
// src/globalApi/extractFacesAndComputeResults.ts
var tf40 = __toModule(require_tfjs_esm());
async function extractAllFacesAndComputeResults(parentResults, input, computeResults, extractedFaces, getRectForAlignment = ({alignedRect}) => alignedRect) {
const faceBoxes = parentResults.map((parentResult) => isWithFaceLandmarks(parentResult) ? getRectForAlignment(parentResult) : parentResult.detection);
const faces = extractedFaces || (input instanceof tf40.Tensor ? await extractFaceTensors(input, faceBoxes) : await extractFaces(input, faceBoxes));
const results = await computeResults(faces);
faces.forEach((f) => f instanceof tf40.Tensor && f.dispose());
return results;
}
async function extractSingleFaceAndComputeResult(parentResult, input, computeResult, extractedFaces, getRectForAlignment) {
return extractAllFacesAndComputeResults([parentResult], input, async (faces) => computeResult(faces[0]), extractedFaces, getRectForAlignment);
}
// src/tinyFaceDetector/const.ts
var IOU_THRESHOLD2 = 0.4;
var BOX_ANCHORS2 = [
new Point(1.603231, 2.094468),
new Point(6.041143, 7.080126),
new Point(2.882459, 3.518061),
new Point(4.266906, 5.178857),
new Point(9.041765, 10.66308)
];
var MEAN_RGB = [117.001, 114.697, 97.404];
// src/tinyFaceDetector/TinyFaceDetector.ts
var TinyFaceDetector = class extends TinyYolov2Base {
constructor() {
const config = {
withSeparableConvs: true,
iouThreshold: IOU_THRESHOLD2,
classes: ["face"],
anchors: BOX_ANCHORS2,
meanRgb: MEAN_RGB,
isFirstLayerConv2d: true,
filterSizes: [3, 16, 32, 64, 128, 256, 512]
};
super(config);
}
get anchors() {
return this.config.anchors;
}
async locateFaces(input, forwardParams) {
const objectDetections = await this.detect(input, forwardParams);
return objectDetections.map((det) => new FaceDetection(det.score, det.relativeBox, {width: det.imageWidth, height: det.imageHeight}));
}
getDefaultModelName() {
return "tiny_face_detector_model";
}
extractParamsFromWeightMap(weightMap) {
return super.extractParamsFromWeightMap(weightMap);
}
};
// src/globalApi/nets.ts
var nets = {
ssdMobilenetv1: new SsdMobilenetv1(),
tinyFaceDetector: new TinyFaceDetector(),
tinyYolov2: new TinyYolov2(),
faceLandmark68Net: new FaceLandmark68Net(),
faceLandmark68TinyNet: new FaceLandmark68TinyNet(),
faceRecognitionNet: new FaceRecognitionNet(),
faceExpressionNet: new FaceExpressionNet(),
ageGenderNet: new AgeGenderNet()
};
var ssdMobilenetv1 = (input, options) => nets.ssdMobilenetv1.locateFaces(input, options);
var tinyFaceDetector = (input, options) => nets.tinyFaceDetector.locateFaces(input, options);
var tinyYolov2 = (input, options) => nets.tinyYolov2.locateFaces(input, options);
var detectFaceLandmarks = (input) => nets.faceLandmark68Net.detectLandmarks(input);
var detectFaceLandmarksTiny = (input) => nets.faceLandmark68TinyNet.detectLandmarks(input);
var computeFaceDescriptor = (input) => nets.faceRecognitionNet.computeFaceDescriptor(input);
var recognizeFaceExpressions = (input) => nets.faceExpressionNet.predictExpressions(input);
var predictAgeAndGender = (input) => nets.ageGenderNet.predictAgeAndGender(input);
var loadSsdMobilenetv1Model = (url) => nets.ssdMobilenetv1.load(url);
var loadTinyFaceDetectorModel = (url) => nets.tinyFaceDetector.load(url);
var loadTinyYolov2Model = (url) => nets.tinyYolov2.load(url);
var loadFaceLandmarkModel = (url) => nets.faceLandmark68Net.load(url);
var loadFaceLandmarkTinyModel = (url) => nets.faceLandmark68TinyNet.load(url);
var loadFaceRecognitionModel = (url) => nets.faceRecognitionNet.load(url);
var loadFaceExpressionModel = (url) => nets.faceExpressionNet.load(url);
var loadAgeGenderModel = (url) => nets.ageGenderNet.load(url);
var loadFaceDetectionModel = loadSsdMobilenetv1Model;
var locateFaces = ssdMobilenetv1;
var detectLandmarks = detectFaceLandmarks;
// src/globalApi/PredictFaceExpressionsTask.ts
var PredictFaceExpressionsTaskBase = class extends ComposableTask {
constructor(parentTask, input, extractedFaces) {
super();
this.parentTask = parentTask;
this.input = input;
this.extractedFaces = extractedFaces;
}
};
var PredictAllFaceExpressionsTask = class extends PredictFaceExpressionsTaskBase {
async run() {
const parentResults = await this.parentTask;
const faceExpressionsByFace = await extractAllFacesAndComputeResults(parentResults, this.input, async (faces) => Promise.all(faces.map((face) => nets.faceExpressionNet.predictExpressions(face))), this.extractedFaces);
return parentResults.map((parentResult, i) => extendWithFaceExpressions(parentResult, faceExpressionsByFace[i]));
}
withAgeAndGender() {
return new PredictAllAgeAndGenderTask(this, this.input);
}
};
var PredictSingleFaceExpressionsTask = class extends PredictFaceExpressionsTaskBase {
async run() {
const parentResult = await this.parentTask;
if (!parentResult) {
return void 0;
}
const faceExpressions = await extractSingleFaceAndComputeResult(parentResult, this.input, (face) => nets.faceExpressionNet.predictExpressions(face), this.extractedFaces);
return extendWithFaceExpressions(parentResult, faceExpressions);
}
withAgeAndGender() {
return new PredictSingleAgeAndGenderTask(this, this.input);
}
};
var PredictAllFaceExpressionsWithFaceAlignmentTask = class extends PredictAllFaceExpressionsTask {
withAgeAndGender() {
return new PredictAllAgeAndGenderWithFaceAlignmentTask(this, this.input);
}
withFaceDescriptors() {
return new ComputeAllFaceDescriptorsTask(this, this.input);
}
};
var PredictSingleFaceExpressionsWithFaceAlignmentTask = class extends PredictSingleFaceExpressionsTask {
withAgeAndGender() {
return new PredictSingleAgeAndGenderWithFaceAlignmentTask(this, this.input);
}
withFaceDescriptor() {
return new ComputeSingleFaceDescriptorTask(this, this.input);
}
};
// src/globalApi/PredictAgeAndGenderTask.ts
var PredictAgeAndGenderTaskBase = class extends ComposableTask {
constructor(parentTask, input, extractedFaces) {
super();
this.parentTask = parentTask;
this.input = input;
this.extractedFaces = extractedFaces;
}
};
var PredictAllAgeAndGenderTask = class extends PredictAgeAndGenderTaskBase {
async run() {
const parentResults = await this.parentTask;
const ageAndGenderByFace = await extractAllFacesAndComputeResults(parentResults, this.input, async (faces) => Promise.all(faces.map((face) => nets.ageGenderNet.predictAgeAndGender(face))), this.extractedFaces);
return parentResults.map((parentResult, i) => {
const {age, gender, genderProbability} = ageAndGenderByFace[i];
return extendWithAge(extendWithGender(parentResult, gender, genderProbability), age);
});
}
withFaceExpressions() {
return new PredictAllFaceExpressionsTask(this, this.input);
}
};
var PredictSingleAgeAndGenderTask = class extends PredictAgeAndGenderTaskBase {
async run() {
const parentResult = await this.parentTask;
if (!parentResult)
return void 0;
const {age, gender, genderProbability} = await extractSingleFaceAndComputeResult(parentResult, this.input, (face) => nets.ageGenderNet.predictAgeAndGender(face), this.extractedFaces);
return extendWithAge(extendWithGender(parentResult, gender, genderProbability), age);
}
withFaceExpressions() {
return new PredictSingleFaceExpressionsTask(this, this.input);
}
};
var PredictAllAgeAndGenderWithFaceAlignmentTask = class extends PredictAllAgeAndGenderTask {
withFaceExpressions() {
return new PredictAllFaceExpressionsWithFaceAlignmentTask(this, this.input);
}
withFaceDescriptors() {
return new ComputeAllFaceDescriptorsTask(this, this.input);
}
};
var PredictSingleAgeAndGenderWithFaceAlignmentTask = class extends PredictSingleAgeAndGenderTask {
withFaceExpressions() {
return new PredictSingleFaceExpressionsWithFaceAlignmentTask(this, this.input);
}
withFaceDescriptor() {
return new ComputeSingleFaceDescriptorTask(this, this.input);
}
};
// src/globalApi/ComputeFaceDescriptorsTasks.ts
var ComputeFaceDescriptorsTaskBase = class extends ComposableTask {
constructor(parentTask, input) {
super();
this.parentTask = parentTask;
this.input = input;
}
};
var ComputeAllFaceDescriptorsTask = class extends ComputeFaceDescriptorsTaskBase {
async run() {
const parentResults = await this.parentTask;
const descriptors = await extractAllFacesAndComputeResults(parentResults, this.input, (faces) => Promise.all(faces.map((face) => nets.faceRecognitionNet.computeFaceDescriptor(face))), null, (parentResult) => parentResult.landmarks.align(null, {useDlibAlignment: true}));
return descriptors.map((descriptor, i) => extendWithFaceDescriptor(parentResults[i], descriptor));
}
withFaceExpressions() {
return new PredictAllFaceExpressionsWithFaceAlignmentTask(this, this.input);
}
withAgeAndGender() {
return new PredictAllAgeAndGenderWithFaceAlignmentTask(this, this.input);
}
};
var ComputeSingleFaceDescriptorTask = class extends ComputeFaceDescriptorsTaskBase {
async run() {
const parentResult = await this.parentTask;
if (!parentResult) {
return void 0;
}
const descriptor = await extractSingleFaceAndComputeResult(parentResult, this.input, (face) => nets.faceRecognitionNet.computeFaceDescriptor(face), null, (parentResult2) => parentResult2.landmarks.align(null, {useDlibAlignment: true}));
return extendWithFaceDescriptor(parentResult, descriptor);
}
withFaceExpressions() {
return new PredictSingleFaceExpressionsWithFaceAlignmentTask(this, this.input);
}
withAgeAndGender() {
return new PredictSingleAgeAndGenderWithFaceAlignmentTask(this, this.input);
}
};
// src/globalApi/DetectFaceLandmarksTasks.ts
var DetectFaceLandmarksTaskBase = class extends ComposableTask {
constructor(parentTask, input, useTinyLandmarkNet) {
super();
this.parentTask = parentTask;
this.input = input;
this.useTinyLandmarkNet = useTinyLandmarkNet;
}
get landmarkNet() {
return this.useTinyLandmarkNet ? nets.faceLandmark68TinyNet : nets.faceLandmark68Net;
}
};
var DetectAllFaceLandmarksTask = class extends DetectFaceLandmarksTaskBase {
async run() {
const parentResults = await this.parentTask;
const detections = parentResults.map((res) => res.detection);
const faces = this.input instanceof tf41.Tensor ? await extractFaceTensors(this.input, detections) : await extractFaces(this.input, detections);
const faceLandmarksByFace = await Promise.all(faces.map((face) => this.landmarkNet.detectLandmarks(face)));
faces.forEach((f) => f instanceof tf41.Tensor && f.dispose());
return parentResults.map((parentResult, i) => extendWithFaceLandmarks(parentResult, faceLandmarksByFace[i]));
}
withFaceExpressions() {
return new PredictAllFaceExpressionsWithFaceAlignmentTask(this, this.input);
}
withAgeAndGender() {
return new PredictAllAgeAndGenderWithFaceAlignmentTask(this, this.input);
}
withFaceDescriptors() {
return new ComputeAllFaceDescriptorsTask(this, this.input);
}
};
var DetectSingleFaceLandmarksTask = class extends DetectFaceLandmarksTaskBase {
async run() {
const parentResult = await this.parentTask;
if (!parentResult) {
return void 0;
}
const {detection} = parentResult;
const faces = this.input instanceof tf41.Tensor ? await extractFaceTensors(this.input, [detection]) : await extractFaces(this.input, [detection]);
const landmarks = await this.landmarkNet.detectLandmarks(faces[0]);
faces.forEach((f) => f instanceof tf41.Tensor && f.dispose());
return extendWithFaceLandmarks(parentResult, landmarks);
}
withFaceExpressions() {
return new PredictSingleFaceExpressionsWithFaceAlignmentTask(this, this.input);
}
withAgeAndGender() {
return new PredictSingleAgeAndGenderWithFaceAlignmentTask(this, this.input);
}
withFaceDescriptor() {
return new ComputeSingleFaceDescriptorTask(this, this.input);
}
};
// src/globalApi/DetectFacesTasks.ts
var DetectFacesTaskBase = class extends ComposableTask {
constructor(input, options = new SsdMobilenetv1Options()) {
super();
this.input = input;
this.options = options;
}
};
var DetectAllFacesTask = class extends DetectFacesTaskBase {
async run() {
const {input, options} = this;
let result;
if (options instanceof TinyFaceDetectorOptions)
result = nets.tinyFaceDetector.locateFaces(input, options);
else if (options instanceof SsdMobilenetv1Options)
result = nets.ssdMobilenetv1.locateFaces(input, options);
else if (options instanceof TinyYolov2Options)
result = nets.tinyYolov2.locateFaces(input, options);
else
throw new Error("detectFaces - expected options to be instance of TinyFaceDetectorOptions | SsdMobilenetv1Options | TinyYolov2Options");
return result;
}
runAndExtendWithFaceDetections() {
return new Promise((resolve, reject) => {
this.run().then((detections) => resolve(detections.map((detection) => extendWithFaceDetection({}, detection)))).catch((err) => reject(err));
});
}
withFaceLandmarks(useTinyLandmarkNet = false) {
return new DetectAllFaceLandmarksTask(this.runAndExtendWithFaceDetections(), this.input, useTinyLandmarkNet);
}
withFaceExpressions() {
return new PredictAllFaceExpressionsTask(this.runAndExtendWithFaceDetections(), this.input);
}
withAgeAndGender() {
return new PredictAllAgeAndGenderTask(this.runAndExtendWithFaceDetections(), this.input);
}
};
var DetectSingleFaceTask = class extends DetectFacesTaskBase {
async run() {
const faceDetections = await new DetectAllFacesTask(this.input, this.options);
let faceDetectionWithHighestScore = faceDetections[0];
faceDetections.forEach((faceDetection) => {
if (faceDetection.score > faceDetectionWithHighestScore.score)
faceDetectionWithHighestScore = faceDetection;
});
return faceDetectionWithHighestScore;
}
runAndExtendWithFaceDetection() {
return new Promise(async (resolve) => {
const detection = await this.run();
resolve(detection ? extendWithFaceDetection({}, detection) : void 0);
});
}
withFaceLandmarks(useTinyLandmarkNet = false) {
return new DetectSingleFaceLandmarksTask(this.runAndExtendWithFaceDetection(), this.input, useTinyLandmarkNet);
}
withFaceExpressions() {
return new PredictSingleFaceExpressionsTask(this.runAndExtendWithFaceDetection(), this.input);
}
withAgeAndGender() {
return new PredictSingleAgeAndGenderTask(this.runAndExtendWithFaceDetection(), this.input);
}
};
// src/globalApi/detectFaces.ts
function detectSingleFace(input, options = new SsdMobilenetv1Options()) {
return new DetectSingleFaceTask(input, options);
}
function detectAllFaces(input, options = new SsdMobilenetv1Options()) {
return new DetectAllFacesTask(input, options);
}
// src/globalApi/allFaces.ts
async function allFacesSsdMobilenetv1(input, minConfidence) {
return detectAllFaces(input, new SsdMobilenetv1Options(minConfidence ? {minConfidence} : {})).withFaceLandmarks().withFaceDescriptors();
}
async function allFacesTinyYolov2(input, forwardParams = {}) {
return detectAllFaces(input, new TinyYolov2Options(forwardParams)).withFaceLandmarks().withFaceDescriptors();
}
var allFaces = allFacesSsdMobilenetv1;
// src/euclideanDistance.ts
function euclideanDistance(arr1, arr2) {
if (arr1.length !== arr2.length)
throw new Error("euclideanDistance: arr1.length !== arr2.length");
const desc1 = Array.from(arr1);
const desc2 = Array.from(arr2);
return Math.sqrt(desc1.map((val, i) => val - desc2[i]).reduce((res, diff) => res + diff ** 2, 0));
}
// src/globalApi/FaceMatcher.ts
var FaceMatcher = class {
constructor(inputs, distanceThreshold = 0.6) {
this._distanceThreshold = distanceThreshold;
const inputArray = Array.isArray(inputs) ? inputs : [inputs];
if (!inputArray.length) {
throw new Error("FaceRecognizer.constructor - expected atleast one input");
}
let count = 1;
const createUniqueLabel = () => `person ${count++}`;
this._labeledDescriptors = inputArray.map((desc) => {
if (desc instanceof LabeledFaceDescriptors) {
return desc;
}
if (desc instanceof Float32Array) {
return new LabeledFaceDescriptors(createUniqueLabel(), [desc]);
}
if (desc.descriptor && desc.descriptor instanceof Float32Array) {
return new LabeledFaceDescriptors(createUniqueLabel(), [desc.descriptor]);
}
throw new Error("FaceRecognizer.constructor - expected inputs to be of type LabeledFaceDescriptors | WithFaceDescriptor<any> | Float32Array | Array<LabeledFaceDescriptors | WithFaceDescriptor<any> | Float32Array>");
});
}
get labeledDescriptors() {
return this._labeledDescriptors;
}
get distanceThreshold() {
return this._distanceThreshold;
}
computeMeanDistance(queryDescriptor, descriptors) {
return descriptors.map((d) => euclideanDistance(d, queryDescriptor)).reduce((d1, d2) => d1 + d2, 0) / (descriptors.length || 1);
}
matchDescriptor(queryDescriptor) {
return this.labeledDescriptors.map(({descriptors, label}) => new FaceMatch(label, this.computeMeanDistance(queryDescriptor, descriptors))).reduce((best, curr) => best.distance < curr.distance ? best : curr);
}
findBestMatch(queryDescriptor) {
const bestMatch = this.matchDescriptor(queryDescriptor);
return bestMatch.distance < this.distanceThreshold ? bestMatch : new FaceMatch("unknown", bestMatch.distance);
}
toJSON() {
return {
distanceThreshold: this.distanceThreshold,
labeledDescriptors: this.labeledDescriptors.map((ld) => ld.toJSON())
};
}
static fromJSON(json) {
const labeledDescriptors = json.labeledDescriptors.map((ld) => LabeledFaceDescriptors.fromJSON(ld));
return new FaceMatcher(labeledDescriptors, json.distanceThreshold);
}
};
// src/tinyFaceDetector/index.ts
function createTinyFaceDetector(weights) {
const net = new TinyFaceDetector();
net.extractWeights(weights);
return net;
}
// src/resizeResults.ts
function resizeResults(results, dimensions) {
const {width, height} = new Dimensions(dimensions.width, dimensions.height);
if (width <= 0 || height <= 0) {
throw new Error(`resizeResults - invalid dimensions: ${JSON.stringify({width, height})}`);
}
if (Array.isArray(results)) {
return results.map((obj) => resizeResults(obj, {width, height}));
}
if (isWithFaceLandmarks(results)) {
const resizedDetection = results.detection.forSize(width, height);
const resizedLandmarks = results.unshiftedLandmarks.forSize(resizedDetection.box.width, resizedDetection.box.height);
return extendWithFaceLandmarks(extendWithFaceDetection(results, resizedDetection), resizedLandmarks);
}
if (isWithFaceDetection(results)) {
return extendWithFaceDetection(results, results.detection.forSize(width, height));
}
if (results instanceof FaceLandmarks || results instanceof FaceDetection) {
return results.forSize(width, height);
}
return results;
}
// src/index.ts
var node = typeof process !== "undefined";
var browser3 = typeof navigator !== "undefined" && typeof navigator.userAgent !== "undefined";
var version2 = {faceapi: version, node, browser: browser3};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AgeGenderNet,
BoundingBox,
Box,
ComposableTask,
ComputeAllFaceDescriptorsTask,
ComputeFaceDescriptorsTaskBase,
ComputeSingleFaceDescriptorTask,
DetectAllFaceLandmarksTask,
DetectAllFacesTask,
DetectFaceLandmarksTaskBase,
DetectFacesTaskBase,
DetectSingleFaceLandmarksTask,
DetectSingleFaceTask,
Dimensions,
FACE_EXPRESSION_LABELS,
FaceDetection,
FaceDetectionNet,
FaceExpressionNet,
FaceExpressions,
FaceLandmark68Net,
FaceLandmark68TinyNet,
FaceLandmarkNet,
FaceLandmarks,
FaceLandmarks5,
FaceLandmarks68,
FaceMatch,
FaceMatcher,
FaceRecognitionNet,
Gender,
LabeledBox,
LabeledFaceDescriptors,
NetInput,
NeuralNetwork,
ObjectDetection,
Point,
PredictedBox,
Rect,
SsdMobilenetv1,
SsdMobilenetv1Options,
TinyFaceDetector,
TinyFaceDetectorOptions,
TinyYolov2,
TinyYolov2Options,
allFaces,
allFacesSsdMobilenetv1,
allFacesTinyYolov2,
awaitMediaLoaded,
bufferToImage,
computeFaceDescriptor,
createCanvas,
createCanvasFromMedia,
createFaceDetectionNet,
createFaceRecognitionNet,
createSsdMobilenetv1,
createTinyFaceDetector,
createTinyYolov2,
detectAllFaces,
detectFaceLandmarks,
detectFaceLandmarksTiny,
detectLandmarks,
detectSingleFace,
draw,
env,
euclideanDistance,
extendWithAge,
extendWithFaceDescriptor,
extendWithFaceDetection,
extendWithFaceExpressions,
extendWithFaceLandmarks,
extendWithGender,
extractFaceTensors,
extractFaces,
fetchImage,
fetchJson,
fetchNetWeights,
fetchOrThrow,
getContext2dOrThrow,
getMediaDimensions,
imageTensorToCanvas,
imageToSquare,
inverseSigmoid,
iou,
isMediaElement,
isMediaLoaded,
isWithAge,
isWithFaceDetection,
isWithFaceExpressions,
isWithFaceLandmarks,
isWithGender,
loadAgeGenderModel,
loadFaceDetectionModel,
loadFaceExpressionModel,
loadFaceLandmarkModel,
loadFaceLandmarkTinyModel,
loadFaceRecognitionModel,
loadSsdMobilenetv1Model,
loadTinyFaceDetectorModel,
loadTinyYolov2Model,
loadWeightMap,
locateFaces,
matchDimensions,
minBbox,
nets,
nonMaxSuppression,
normalize,
padToSquare,
predictAgeAndGender,
recognizeFaceExpressions,
resizeResults,
resolveInput,
shuffleArray,
sigmoid,
ssdMobilenetv1,
tf,
tinyFaceDetector,
tinyYolov2,
toNetInput,
utils,
validateConfig,
version
});
//# sourceMappingURL=face-api.node-gpu.js.map