Remove unnecessary dependency
This commit is contained in:
parent
8b46606a2e
commit
b37f67d6ed
13 changed files with 117 additions and 307 deletions
web/apps/photos
src
services
utils
thirdparty/face-api/classes
|
@ -1,5 +1,6 @@
|
|||
import { workerBridge } from "@/next/worker/worker-bridge";
|
||||
import { euclidean } from "hdbscan";
|
||||
import { Box, Point, boxFromBoundingBox } from "services/ml/geom";
|
||||
import {
|
||||
FaceDetection,
|
||||
FaceDetectionMethod,
|
||||
|
@ -20,7 +21,6 @@ import {
|
|||
normalizePixelBetween0And1,
|
||||
} from "utils/image";
|
||||
import { newBox } from "utils/machineLearning";
|
||||
import { Box, Point } from "../../../thirdparty/face-api/classes";
|
||||
|
||||
class YoloFaceDetectionService implements FaceDetectionService {
|
||||
public method: Versioned<FaceDetectionMethod>;
|
||||
|
@ -296,7 +296,7 @@ function getDetectionCenter(detection: FaceDetection) {
|
|||
center.y += p.y;
|
||||
});
|
||||
|
||||
return center.div({ x: 4, y: 4 });
|
||||
return center.div(new Point(4, 4));
|
||||
}
|
||||
|
||||
function computeTransformToBox(inBox: Box, toBox: Box): Matrix {
|
||||
|
@ -328,5 +328,5 @@ function newBoxFromPoints(
|
|||
right: number,
|
||||
bottom: number,
|
||||
) {
|
||||
return new Box({ left, top, right, bottom });
|
||||
return boxFromBoundingBox({ left, top, right, bottom });
|
||||
}
|
||||
|
|
108
web/apps/photos/src/services/ml/geom.ts
Normal file
108
web/apps/photos/src/services/ml/geom.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
export class Point {
|
||||
public x: number;
|
||||
public y: number;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public add(pt: Point): Point {
|
||||
return new Point(this.x + pt.x, this.y + pt.y);
|
||||
}
|
||||
|
||||
public sub(pt: Point): Point {
|
||||
return new Point(this.x - pt.x, this.y - pt.y);
|
||||
}
|
||||
|
||||
public div(pt: Point): Point {
|
||||
return new Point(this.x / pt.x, this.y / pt.y);
|
||||
}
|
||||
}
|
||||
|
||||
export interface IBoundingBox {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
}
|
||||
|
||||
export interface IRect {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export const boxFromBoundingBox = ({
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
}: IBoundingBox) => {
|
||||
return new Box({
|
||||
x: left,
|
||||
y: top,
|
||||
width: right - left,
|
||||
height: bottom - top,
|
||||
});
|
||||
};
|
||||
|
||||
export class Box implements IRect {
|
||||
public x: number;
|
||||
public y: number;
|
||||
public width: number;
|
||||
public height: number;
|
||||
|
||||
constructor({ x, y, width, height }: IRect) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public get left(): number {
|
||||
return this.x;
|
||||
}
|
||||
public get top(): number {
|
||||
return this.y;
|
||||
}
|
||||
public get right(): number {
|
||||
return this.x + this.width;
|
||||
}
|
||||
public get bottom(): number {
|
||||
return this.y + this.height;
|
||||
}
|
||||
public get area(): number {
|
||||
return this.width * this.height;
|
||||
}
|
||||
public get topLeft(): Point {
|
||||
return new Point(this.left, this.top);
|
||||
}
|
||||
public get topRight(): Point {
|
||||
return new Point(this.right, this.top);
|
||||
}
|
||||
public get bottomLeft(): Point {
|
||||
return new Point(this.left, this.bottom);
|
||||
}
|
||||
public get bottomRight(): Point {
|
||||
return new Point(this.right, this.bottom);
|
||||
}
|
||||
|
||||
public round(): Box {
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidNumber(num: any) {
|
||||
return (
|
||||
(!!num && num !== Infinity && num !== -Infinity && !isNaN(num)) ||
|
||||
num === 0
|
||||
);
|
||||
}
|
|
@ -2,7 +2,7 @@ import { DebugInfo } from "hdbscan";
|
|||
import PQueue from "p-queue";
|
||||
import { EnteFile } from "types/file";
|
||||
import { Dimensions } from "types/image";
|
||||
import { Box, Point } from "../../../thirdparty/face-api/classes";
|
||||
import { Box, Point } from "./geom";
|
||||
|
||||
export interface MLSyncResult {
|
||||
nOutOfSyncFiles: number;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Matrix, inverse } from "ml-matrix";
|
|||
import { FaceAlignment } from "services/ml/types";
|
||||
import { BlobOptions, Dimensions } from "types/image";
|
||||
import { enlargeBox } from "utils/machineLearning";
|
||||
import { Box } from "../../../thirdparty/face-api/classes";
|
||||
import { Box } from "services/ml/geom";
|
||||
|
||||
export function normalizePixelBetween0And1(pixelValue: number) {
|
||||
return pixelValue / 255.0;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Matrix } from "ml-matrix";
|
||||
import { FaceAlignment, FaceDetection } from "services/ml/types";
|
||||
import { getSimilarityTransformation } from "similarity-transformation";
|
||||
import { Point } from "../../../thirdparty/face-api/classes";
|
||||
import { Point } from "services/ml/geom";
|
||||
|
||||
const ARCFACE_LANDMARKS = [
|
||||
[38.2946, 51.6963],
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { FaceAlignment, FaceCrop, FaceCropConfig } from "services/ml/types";
|
||||
import { cropWithRotation } from "utils/image";
|
||||
import { enlargeBox } from ".";
|
||||
import { Box } from "../../../thirdparty/face-api/classes";
|
||||
import { Box } from "services/ml/geom";
|
||||
|
||||
export function getFaceCrop(
|
||||
imageBitmap: ImageBitmap,
|
||||
|
|
|
@ -4,6 +4,7 @@ import log from "@/next/log";
|
|||
import PQueue from "p-queue";
|
||||
import DownloadManager from "services/download";
|
||||
import { getLocalFiles } from "services/fileService";
|
||||
import { Box, Point, boxFromBoundingBox } from "services/ml/geom";
|
||||
import {
|
||||
DetectedFace,
|
||||
Face,
|
||||
|
@ -17,7 +18,6 @@ import { Dimensions } from "types/image";
|
|||
import { getRenderableImage } from "utils/file";
|
||||
import { clamp, warpAffineFloat32List } from "utils/image";
|
||||
import mlIDbStorage from "utils/storage/mlIDbStorage";
|
||||
import { Box, Point } from "../../../thirdparty/face-api/classes";
|
||||
|
||||
export function newBox(x: number, y: number, width: number, height: number) {
|
||||
return new Box({ x, y, width, height });
|
||||
|
@ -36,7 +36,7 @@ export function enlargeBox(box: Box, factor: number = 1.5) {
|
|||
const size = new Point(box.width, box.height);
|
||||
const newHalfSize = new Point((factor * size.x) / 2, (factor * size.y) / 2);
|
||||
|
||||
return new Box({
|
||||
return boxFromBoundingBox({
|
||||
left: center.x - newHalfSize.x,
|
||||
top: center.y - newHalfSize.y,
|
||||
right: center.x + newHalfSize.x,
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import { Box } from './Box';
|
||||
|
||||
export interface IBoundingBox {
|
||||
left: number
|
||||
top: number
|
||||
right: number
|
||||
bottom: number
|
||||
}
|
||||
|
||||
export class BoundingBox extends Box<BoundingBox> implements IBoundingBox {
|
||||
constructor(left: number, top: number, right: number, bottom: number, allowNegativeDimensions: boolean = false) {
|
||||
super({ left, top, right, bottom }, allowNegativeDimensions)
|
||||
}
|
||||
}
|
182
web/apps/photos/thirdparty/face-api/classes/Box.ts
vendored
182
web/apps/photos/thirdparty/face-api/classes/Box.ts
vendored
|
@ -1,182 +0,0 @@
|
|||
import { IBoundingBox } from './BoundingBox';
|
||||
import { IDimensions } from './Dimensions';
|
||||
import { Point } from './Point';
|
||||
import { IRect } from './Rect';
|
||||
|
||||
export class Box<BoxType = any> implements IBoundingBox, IRect {
|
||||
|
||||
public static isRect(rect: any): boolean {
|
||||
return !!rect && [rect.x, rect.y, rect.width, rect.height].every(isValidNumber)
|
||||
}
|
||||
|
||||
public static assertIsValidBox(box: any, callee: string, allowNegativeDimensions: boolean = 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`)
|
||||
}
|
||||
}
|
||||
|
||||
public x: number
|
||||
public y: number
|
||||
public width: number
|
||||
public height: number
|
||||
|
||||
constructor(_box: IBoundingBox | IRect, allowNegativeDimensions: boolean = true) {
|
||||
const box = (_box || {}) as any
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// public get x(): number { return this._x }
|
||||
// public get y(): number { return this._y }
|
||||
// public get width(): number { return this._width }
|
||||
// public get height(): number { return this._height }
|
||||
public get left(): number { return this.x }
|
||||
public get top(): number { return this.y }
|
||||
public get right(): number { return this.x + this.width }
|
||||
public get bottom(): number { return this.y + this.height }
|
||||
public get area(): number { return this.width * this.height }
|
||||
public get topLeft(): Point { return new Point(this.left, this.top) }
|
||||
public get topRight(): Point { return new Point(this.right, this.top) }
|
||||
public get bottomLeft(): Point { return new Point(this.left, this.bottom) }
|
||||
public get bottomRight(): Point { return new Point(this.right, this.bottom) }
|
||||
|
||||
public round(): Box<BoxType> {
|
||||
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 })
|
||||
}
|
||||
|
||||
public floor(): Box<BoxType> {
|
||||
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 })
|
||||
}
|
||||
|
||||
public toSquare(): Box<BoxType> {
|
||||
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 })
|
||||
}
|
||||
|
||||
public rescale(s: IDimensions | number): Box<BoxType> {
|
||||
const scaleX = isDimensions(s) ? (s as IDimensions).width : s as number
|
||||
const scaleY = isDimensions(s) ? (s as IDimensions).height : s as number
|
||||
return new Box({
|
||||
x: this.x * scaleX,
|
||||
y: this.y * scaleY,
|
||||
width: this.width * scaleX,
|
||||
height: this.height * scaleY
|
||||
})
|
||||
}
|
||||
|
||||
public pad(padX: number, padY: number): Box<BoxType> {
|
||||
let [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 })
|
||||
}
|
||||
|
||||
public clipAtImageBorders(imgWidth: number, imgHeight: number): Box<BoxType> {
|
||||
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()
|
||||
}
|
||||
|
||||
public shift(sx: number, sy: number): Box<BoxType> {
|
||||
const { width, height } = this
|
||||
const x = this.x + sx
|
||||
const y = this.y + sy
|
||||
|
||||
return new Box({ x, y, width, height })
|
||||
}
|
||||
|
||||
public padAtBorders(imageHeight: number, imageWidth: number) {
|
||||
const w = this.width + 1
|
||||
const h = this.height + 1
|
||||
|
||||
let dx = 1
|
||||
let 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 }
|
||||
}
|
||||
|
||||
public calibrate(region: Box) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
export function isValidNumber(num: any) {
|
||||
return !!num && num !== Infinity && num !== -Infinity && !isNaN(num) || num === 0
|
||||
}
|
||||
|
||||
export function isDimensions(obj: any): boolean {
|
||||
return obj && obj.width && obj.height
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { isValidNumber } from './Box';
|
||||
|
||||
export interface IDimensions {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export class Dimensions implements IDimensions {
|
||||
|
||||
private _width: number
|
||||
private _height: number
|
||||
|
||||
constructor(width: number, height: number) {
|
||||
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
|
||||
}
|
||||
|
||||
public get width(): number { return this._width }
|
||||
public get height(): number { return this._height }
|
||||
|
||||
public reverse(): Dimensions {
|
||||
return new Dimensions(1 / this.width, 1 / this.height)
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
export interface IPoint {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export class Point implements IPoint {
|
||||
public x: number
|
||||
public y: number
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
|
||||
// get x(): number { return this._x }
|
||||
// get y(): number { return this._y }
|
||||
|
||||
public add(pt: IPoint): Point {
|
||||
return new Point(this.x + pt.x, this.y + pt.y)
|
||||
}
|
||||
|
||||
public sub(pt: IPoint): Point {
|
||||
return new Point(this.x - pt.x, this.y - pt.y)
|
||||
}
|
||||
|
||||
public mul(pt: IPoint): Point {
|
||||
return new Point(this.x * pt.x, this.y * pt.y)
|
||||
}
|
||||
|
||||
public div(pt: IPoint): Point {
|
||||
return new Point(this.x / pt.x, this.y / pt.y)
|
||||
}
|
||||
|
||||
public abs(): Point {
|
||||
return new Point(Math.abs(this.x), Math.abs(this.y))
|
||||
}
|
||||
|
||||
public magnitude(): number {
|
||||
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2))
|
||||
}
|
||||
|
||||
public floor(): Point {
|
||||
return new Point(Math.floor(this.x), Math.floor(this.y))
|
||||
}
|
||||
|
||||
public round(): Point {
|
||||
return new Point(Math.round(this.x), Math.round(this.y))
|
||||
}
|
||||
|
||||
public bound(lower: number, higher: number): Point {
|
||||
const x = Math.max(lower, Math.min(higher, this.x));
|
||||
const y = Math.max(lower, Math.min(higher, this.y));
|
||||
return new Point(x, y);
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import { Box } from './Box';
|
||||
|
||||
export interface IRect {
|
||||
x: number
|
||||
y: number
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export class Rect extends Box<Rect> implements IRect {
|
||||
constructor(x: number, y: number, width: number, height: number, allowNegativeDimensions: boolean = false) {
|
||||
super({ x, y, width, height }, allowNegativeDimensions)
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
export * from './BoundingBox'
|
||||
export * from './Box'
|
||||
export * from './Dimensions'
|
||||
export * from './Point'
|
||||
export * from './Rect'
|
Loading…
Add table
Reference in a new issue