/* Face-API homepage: author: ' */ 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 = (callback, module) => () => { if (!module) { module = {exports: {}}; callback(module.exports, module); } return module.exports; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, {get: all[name], enumerable: true}); }; var __exportStar = (target, module, desc) => { if (module && typeof module === "object" || typeof module === "function") { for (let key of __getOwnPropNames(module)) if (!__hasOwnProp.call(target, key) && key !== "default") __defProp(target, key, {get: () => module[key], enumerable: !(desc = __getOwnPropDesc(module, key)) || desc.enumerable}); } return target; }; var __toModule = (module) => { return __exportStar(__markAsModule(__defProp(module != null ? __create(__getProtoOf(module)) : {}, "default", module && module.__esModule && "default" in module ? {get: () => module.default, enumerable: true} : {value: module, enumerable: true})), module); }; // dist/tfjs.esm.js import * as dist_star from "@tensorflow/tfjs/dist/index.js"; import * as tfjs_backend_wasm_star from "@tensorflow/tfjs-backend-wasm"; var require_tfjs_esm = __commonJS((exports) => { __markAsModule(exports); __exportStar(exports, dist_star); __exportStar(exports, tfjs_backend_wasm_star); }); // src/env/isNodejs.ts var require_isNodejs = __commonJS((exports, module) => { __markAsModule(exports); __export(exports, { isNodejs: () => isNodejs2 }); function isNodejs2() { return typeof global === "object" && true && typeof module !== "undefined" && typeof process !== "undefined" && !!process.version; } }); // src/index.ts var tf42 = 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 = 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 = 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 = 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/index.ts var import_isNodejs = __toModule(require_isNodejs()); 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 ((0, import_isNodejs.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: import_isNodejs.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 = 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 = 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 = 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 = 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 = 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 = require_tfjs_esm(); // src/NeuralNetwork.ts var tf8 = 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 = require_tfjs_esm(); // src/common/depthwiseSeparableConv.ts var tf9 = 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 = 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 = 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 = 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 = 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 = require_tfjs_esm(); // src/common/fullyConnectedLayer.ts var tf16 = 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> or array thereof"); } new DrawFaceLandmarks(landmarks).draw(canvasArg); }); } // package.json var version = "1.1.5"; // src/ageGenderNet/AgeGenderNet.ts var tf20 = require_tfjs_esm(); // src/xception/TinyXception.ts var tf19 = 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 = 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 = 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 = require_tfjs_esm(); // src/faceRecognitionNet/convLayer.ts var tf24 = require_tfjs_esm(); // src/faceRecognitionNet/scaleLayer.ts var tf23 = 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 = 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 = 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 = require_tfjs_esm(); // src/ssdMobilenetv1/extractParams.ts var tf28 = 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 = require_tfjs_esm(); // src/ssdMobilenetv1/pointwiseConvLayer.ts var tf29 = 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 = 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 = require_tfjs_esm(); // src/ssdMobilenetv1/boxPredictionLayer.ts var tf32 = 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 = 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 = require_tfjs_esm(); // src/tinyYolov2/leaky.ts var tf35 = 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 = 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 = 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 = require_tfjs_esm(); // src/globalApi/extractFacesAndComputeResults.ts var tf40 = 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(async (resolve) => { const detections = await this.run(); resolve(detections.map((detection) => extendWithFaceDetection({}, detection))); }); } 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 | Float32Array | Array | 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}; export { 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_exports as 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, tf42 as tf, tinyFaceDetector, tinyYolov2, toNetInput, utils_exports as utils, validateConfig, version2 as version }; //# sourceMappingURL=face-api.esm-nobundle.js.map