@visutry/tryon-core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -0
- package/dist/__fixtures__/faceFixtures.d.ts +13 -0
- package/dist/__fixtures__/faceFixtures.d.ts.map +1 -0
- package/dist/__fixtures__/faceFixtures.js +206 -0
- package/dist/__fixtures__/faceFixtures.js.map +1 -0
- package/dist/adapter/visutry-site.d.ts +76 -0
- package/dist/adapter/visutry-site.d.ts.map +1 -0
- package/dist/adapter/visutry-site.js +172 -0
- package/dist/adapter/visutry-site.js.map +1 -0
- package/dist/analytics.d.ts +89 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +49 -0
- package/dist/analytics.js.map +1 -0
- package/dist/coordinate/CoordinateSystem.d.ts +46 -0
- package/dist/coordinate/CoordinateSystem.d.ts.map +1 -0
- package/dist/coordinate/CoordinateSystem.js +88 -0
- package/dist/coordinate/CoordinateSystem.js.map +1 -0
- package/dist/face/FaceMetricsCalculator.d.ts +52 -0
- package/dist/face/FaceMetricsCalculator.d.ts.map +1 -0
- package/dist/face/FaceMetricsCalculator.js +375 -0
- package/dist/face/FaceMetricsCalculator.js.map +1 -0
- package/dist/face/FaceSemanticMapper.d.ts +54 -0
- package/dist/face/FaceSemanticMapper.d.ts.map +1 -0
- package/dist/face/FaceSemanticMapper.js +129 -0
- package/dist/face/FaceSemanticMapper.js.map +1 -0
- package/dist/face/FaceShapeScorer.d.ts +35 -0
- package/dist/face/FaceShapeScorer.d.ts.map +1 -0
- package/dist/face/FaceShapeScorer.js +265 -0
- package/dist/face/FaceShapeScorer.js.map +1 -0
- package/dist/face/feature-extractor.d.ts +46 -0
- package/dist/face/feature-extractor.d.ts.map +1 -0
- package/dist/face/feature-extractor.js +106 -0
- package/dist/face/feature-extractor.js.map +1 -0
- package/dist/face/logreg-classifier.d.ts +117 -0
- package/dist/face/logreg-classifier.d.ts.map +1 -0
- package/dist/face/logreg-classifier.js +304 -0
- package/dist/face/logreg-classifier.js.map +1 -0
- package/dist/feature-flag.d.ts +43 -0
- package/dist/feature-flag.d.ts.map +1 -0
- package/dist/feature-flag.js +58 -0
- package/dist/feature-flag.js.map +1 -0
- package/dist/i18n/index.d.ts +15 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +91 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest/ManifestValidator.d.ts +23 -0
- package/dist/manifest/ManifestValidator.d.ts.map +1 -0
- package/dist/manifest/ManifestValidator.js +129 -0
- package/dist/manifest/ManifestValidator.js.map +1 -0
- package/dist/pose/GlassesPoseSolver.d.ts +64 -0
- package/dist/pose/GlassesPoseSolver.d.ts.map +1 -0
- package/dist/pose/GlassesPoseSolver.js +299 -0
- package/dist/pose/GlassesPoseSolver.js.map +1 -0
- package/dist/privacy/PrivacyGuard.d.ts +44 -0
- package/dist/privacy/PrivacyGuard.d.ts.map +1 -0
- package/dist/privacy/PrivacyGuard.js +105 -0
- package/dist/privacy/PrivacyGuard.js.map +1 -0
- package/dist/quality/QualityGate.d.ts +22 -0
- package/dist/quality/QualityGate.d.ts.map +1 -0
- package/dist/quality/QualityGate.js +161 -0
- package/dist/quality/QualityGate.js.map +1 -0
- package/dist/smoothing/PoseSmoothing.d.ts +37 -0
- package/dist/smoothing/PoseSmoothing.d.ts.map +1 -0
- package/dist/smoothing/PoseSmoothing.js +146 -0
- package/dist/smoothing/PoseSmoothing.js.map +1 -0
- package/dist/types/index.d.ts +444 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/errors.d.ts +7 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +20 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/math.d.ts +40 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.js +149 -0
- package/dist/utils/math.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { FaceResultSource, FaceSemanticPoints, Point3D } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Mapping from a semantic point name to the index in the raw landmark array.
|
|
4
|
+
* Adapters supply the index map appropriate for their tracker; the core ships a
|
|
5
|
+
* default MediaPipe Face Landmarker map.
|
|
6
|
+
*/
|
|
7
|
+
export type SemanticIndexMap = Partial<Record<keyof FaceSemanticPoints, number>>;
|
|
8
|
+
/**
|
|
9
|
+
* Default MediaPipe Face Landmarker (468/478 point topology) index map, as
|
|
10
|
+
* specified in the SDK spec §10.1.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MEDIAPIPE_SEMANTIC_INDEX_MAP: SemanticIndexMap;
|
|
13
|
+
export interface FaceSemanticMapperOptions {
|
|
14
|
+
indexMap?: SemanticIndexMap;
|
|
15
|
+
/** When true, derive eye centers / eyes center from outer+inner corners. Default true. */
|
|
16
|
+
deriveCenters?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Maps raw tracker landmarks onto the stable `FaceSemanticPoints` contract.
|
|
20
|
+
*
|
|
21
|
+
* This class is intentionally side-effect free and tracker-agnostic: it only
|
|
22
|
+
* needs an index map describing where each semantic point lives in the raw
|
|
23
|
+
* array. The web adapter passes the MediaPipe map; the WeChat adapter passes a
|
|
24
|
+
* custom map or relies on direct construction.
|
|
25
|
+
*/
|
|
26
|
+
export declare class FaceSemanticMapper {
|
|
27
|
+
private readonly indexMap;
|
|
28
|
+
private readonly deriveCenters;
|
|
29
|
+
constructor(options?: FaceSemanticMapperOptions);
|
|
30
|
+
/**
|
|
31
|
+
* Build a `FaceSemanticPoints` from a raw normalized landmark array.
|
|
32
|
+
* Missing indices or undefined landmarks are silently skipped — downstream
|
|
33
|
+
* consumers must tolerate optional points.
|
|
34
|
+
*/
|
|
35
|
+
map(landmarks: Point3D[]): FaceSemanticPoints;
|
|
36
|
+
/**
|
|
37
|
+
* Derive leftEyeCenter, rightEyeCenter and eyesCenter from the outer/inner
|
|
38
|
+
* eye corners when they are available. These derived points are the backbone
|
|
39
|
+
* of the glasses pose solver and face metrics.
|
|
40
|
+
*/
|
|
41
|
+
private deriveEyeCenters;
|
|
42
|
+
/**
|
|
43
|
+
* Count how many of the *required* semantic points (for analysis) are present.
|
|
44
|
+
* Used by the quality gate to emit `MISSING_KEY_POINTS`.
|
|
45
|
+
*/
|
|
46
|
+
static countMissing(semantic: FaceSemanticPoints, required?: (keyof FaceSemanticPoints)[]): {
|
|
47
|
+
missing: string[];
|
|
48
|
+
present: number;
|
|
49
|
+
total: number;
|
|
50
|
+
};
|
|
51
|
+
/** Convenience factory bound to a specific source's default map. */
|
|
52
|
+
static forSource(source: FaceResultSource): FaceSemanticMapper;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=FaceSemanticMapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaceSemanticMapper.d.ts","sourceRoot":"","sources":["../../src/face/FaceSemanticMapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGvF;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;AAEjF;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,gBAsB1C,CAAC;AAEF,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,0FAA0F;IAC1F,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;gBAE5B,OAAO,GAAE,yBAA8B;IAKnD;;;;OAIG;IACH,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,kBAAkB;IAuB7C;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;;OAGG;IACH,MAAM,CAAC,YAAY,CACjB,QAAQ,EAAE,kBAAkB,EAC5B,QAAQ,GAAE,CAAC,MAAM,kBAAkB,CAAC,EASnC,GACA;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE;IAYxD,oEAAoE;IACpE,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,kBAAkB;CAO/D"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { midpoint } from "../utils/math.js";
|
|
2
|
+
/**
|
|
3
|
+
* Default MediaPipe Face Landmarker (468/478 point topology) index map, as
|
|
4
|
+
* specified in the SDK spec §10.1.
|
|
5
|
+
*/
|
|
6
|
+
export const MEDIAPIPE_SEMANTIC_INDEX_MAP = {
|
|
7
|
+
leftEyeOuter: 33,
|
|
8
|
+
leftEyeInner: 133,
|
|
9
|
+
rightEyeInner: 362,
|
|
10
|
+
rightEyeOuter: 263,
|
|
11
|
+
noseBridge: 168,
|
|
12
|
+
noseTip: 1,
|
|
13
|
+
leftBrowCenter: 105,
|
|
14
|
+
rightBrowCenter: 334,
|
|
15
|
+
foreheadCenter: 10,
|
|
16
|
+
chin: 152,
|
|
17
|
+
leftCheek: 123,
|
|
18
|
+
rightCheek: 352,
|
|
19
|
+
leftJaw: 172,
|
|
20
|
+
rightJaw: 397,
|
|
21
|
+
// visutry additions — used for richer face shape classification
|
|
22
|
+
leftFace: 234,
|
|
23
|
+
rightFace: 454,
|
|
24
|
+
leftForehead: 103,
|
|
25
|
+
rightForehead: 332,
|
|
26
|
+
noseLeft: 98,
|
|
27
|
+
noseRight: 327,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Maps raw tracker landmarks onto the stable `FaceSemanticPoints` contract.
|
|
31
|
+
*
|
|
32
|
+
* This class is intentionally side-effect free and tracker-agnostic: it only
|
|
33
|
+
* needs an index map describing where each semantic point lives in the raw
|
|
34
|
+
* array. The web adapter passes the MediaPipe map; the WeChat adapter passes a
|
|
35
|
+
* custom map or relies on direct construction.
|
|
36
|
+
*/
|
|
37
|
+
export class FaceSemanticMapper {
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
Object.defineProperty(this, "indexMap", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
configurable: true,
|
|
42
|
+
writable: true,
|
|
43
|
+
value: void 0
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(this, "deriveCenters", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: void 0
|
|
50
|
+
});
|
|
51
|
+
this.indexMap = options.indexMap ?? MEDIAPIPE_SEMANTIC_INDEX_MAP;
|
|
52
|
+
this.deriveCenters = options.deriveCenters ?? true;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build a `FaceSemanticPoints` from a raw normalized landmark array.
|
|
56
|
+
* Missing indices or undefined landmarks are silently skipped — downstream
|
|
57
|
+
* consumers must tolerate optional points.
|
|
58
|
+
*/
|
|
59
|
+
map(landmarks) {
|
|
60
|
+
const semantic = {};
|
|
61
|
+
for (const key of Object.keys(this.indexMap)) {
|
|
62
|
+
const idx = this.indexMap[key];
|
|
63
|
+
if (idx === undefined)
|
|
64
|
+
continue;
|
|
65
|
+
const pt = landmarks[idx];
|
|
66
|
+
if (pt && typeof pt.x === "number" && typeof pt.y === "number") {
|
|
67
|
+
semantic[key] = {
|
|
68
|
+
x: pt.x,
|
|
69
|
+
y: pt.y,
|
|
70
|
+
z: pt.z ?? 0,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (this.deriveCenters) {
|
|
75
|
+
this.deriveEyeCenters(semantic);
|
|
76
|
+
}
|
|
77
|
+
return semantic;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Derive leftEyeCenter, rightEyeCenter and eyesCenter from the outer/inner
|
|
81
|
+
* eye corners when they are available. These derived points are the backbone
|
|
82
|
+
* of the glasses pose solver and face metrics.
|
|
83
|
+
*/
|
|
84
|
+
deriveEyeCenters(semantic) {
|
|
85
|
+
if (!semantic.leftEyeCenter && semantic.leftEyeOuter && semantic.leftEyeInner) {
|
|
86
|
+
semantic.leftEyeCenter = midpoint(semantic.leftEyeOuter, semantic.leftEyeInner);
|
|
87
|
+
}
|
|
88
|
+
if (!semantic.rightEyeCenter && semantic.rightEyeInner && semantic.rightEyeOuter) {
|
|
89
|
+
semantic.rightEyeCenter = midpoint(semantic.rightEyeInner, semantic.rightEyeOuter);
|
|
90
|
+
}
|
|
91
|
+
if (!semantic.eyesCenter && semantic.leftEyeCenter && semantic.rightEyeCenter) {
|
|
92
|
+
semantic.eyesCenter = midpoint(semantic.leftEyeCenter, semantic.rightEyeCenter);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Count how many of the *required* semantic points (for analysis) are present.
|
|
97
|
+
* Used by the quality gate to emit `MISSING_KEY_POINTS`.
|
|
98
|
+
*/
|
|
99
|
+
static countMissing(semantic, required = [
|
|
100
|
+
"leftEyeCenter",
|
|
101
|
+
"rightEyeCenter",
|
|
102
|
+
"noseBridge",
|
|
103
|
+
"chin",
|
|
104
|
+
"leftCheek",
|
|
105
|
+
"rightCheek",
|
|
106
|
+
"leftJaw",
|
|
107
|
+
"rightJaw",
|
|
108
|
+
]) {
|
|
109
|
+
const missing = [];
|
|
110
|
+
for (const key of required) {
|
|
111
|
+
if (!semantic[key])
|
|
112
|
+
missing.push(key);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
missing,
|
|
116
|
+
present: required.length - missing.length,
|
|
117
|
+
total: required.length,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Convenience factory bound to a specific source's default map. */
|
|
121
|
+
static forSource(source) {
|
|
122
|
+
if (source === "mediapipe") {
|
|
123
|
+
return new FaceSemanticMapper({ indexMap: MEDIAPIPE_SEMANTIC_INDEX_MAP });
|
|
124
|
+
}
|
|
125
|
+
// wechat-vk and custom callers must supply their own map at construction.
|
|
126
|
+
return new FaceSemanticMapper({ indexMap: {} });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=FaceSemanticMapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaceSemanticMapper.js","sourceRoot":"","sources":["../../src/face/FaceSemanticMapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FaceMetrics, FaceQualityWarning, FaceShapeResult, NormalizedFaceResult } from "../types/index.js";
|
|
2
|
+
import { FaceMetricsCalculator } from "./FaceMetricsCalculator.js";
|
|
3
|
+
export declare const FACE_SHAPE_SCORER_VERSION = "0.2.0";
|
|
4
|
+
/**
|
|
5
|
+
* Scores face shapes from geometric metrics.
|
|
6
|
+
*
|
|
7
|
+
* v0.2.0: Exact port of visutry's classifyFaceGeometry algorithm.
|
|
8
|
+
* Uses if/else integer scoring on 2D ratios — not bell/softmax.
|
|
9
|
+
* This ensures numerical equivalence with visutry's main site.
|
|
10
|
+
*
|
|
11
|
+
* Future enhancements (bell functions, softmax, chinType, multi-frame)
|
|
12
|
+
* can be layered on top of this known-good baseline.
|
|
13
|
+
*/
|
|
14
|
+
export declare class FaceShapeScorer {
|
|
15
|
+
private readonly metricsCalculator;
|
|
16
|
+
constructor(metricsCalculator?: FaceMetricsCalculator);
|
|
17
|
+
/**
|
|
18
|
+
* Score a single face result.
|
|
19
|
+
*/
|
|
20
|
+
score(face: NormalizedFaceResult): FaceShapeResult;
|
|
21
|
+
/**
|
|
22
|
+
* Score from pre-aggregated metrics.
|
|
23
|
+
*/
|
|
24
|
+
scoreFromMetrics(metrics: FaceMetrics, warnings?: FaceQualityWarning[]): FaceShapeResult;
|
|
25
|
+
/**
|
|
26
|
+
* Multi-frame scoring: aggregate metrics first, then score.
|
|
27
|
+
*/
|
|
28
|
+
scoreFrames(frames: NormalizedFaceResult[]): FaceShapeResult;
|
|
29
|
+
/**
|
|
30
|
+
* Get raw integer scores for all 7 shapes — same as visutry's scoring.
|
|
31
|
+
*/
|
|
32
|
+
private getAllScores;
|
|
33
|
+
private unknownResult;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=FaceShapeScorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaceShapeScorer.d.ts","sourceRoot":"","sources":["../../src/face/FaceShapeScorer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAGlB,eAAe,EACf,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAyHjD;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwB;gBAE9C,iBAAiB,CAAC,EAAE,qBAAqB;IAIrD;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,oBAAoB,GAAG,eAAe;IAKlD;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,GAAE,kBAAkB,EAAO,GAAG,eAAe;IA6E5F;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,oBAAoB,EAAE,GAAG,eAAe;IAa5D;;OAEG;IACH,OAAO,CAAC,YAAY;IAyBpB,OAAO,CAAC,aAAa;CA0BtB"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { FaceMetricsCalculator } from "./FaceMetricsCalculator.js";
|
|
2
|
+
export const FACE_SHAPE_SCORER_VERSION = "0.2.0";
|
|
3
|
+
/**
|
|
4
|
+
* All canonical face shapes, in the same order as visutry's CANONICAL_FACE_SHAPES.
|
|
5
|
+
*/
|
|
6
|
+
const CANONICAL_SHAPES = [
|
|
7
|
+
"oval",
|
|
8
|
+
"round",
|
|
9
|
+
"square",
|
|
10
|
+
"heart",
|
|
11
|
+
"diamond",
|
|
12
|
+
"oblong",
|
|
13
|
+
"triangle",
|
|
14
|
+
];
|
|
15
|
+
function clamp(value, min, max) {
|
|
16
|
+
return Math.min(max, Math.max(min, value));
|
|
17
|
+
}
|
|
18
|
+
function round(value, digits = 3) {
|
|
19
|
+
const factor = 10 ** digits;
|
|
20
|
+
return Math.round(value * factor) / factor;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build geometry signals — exact port of visutry's buildGeometrySignals().
|
|
24
|
+
*/
|
|
25
|
+
function buildGeometrySignals(shape, ratios) {
|
|
26
|
+
const lengthSignal = ratios.faceAspectRatio >= 1.42
|
|
27
|
+
? "Longer vertical face proportion"
|
|
28
|
+
: ratios.faceAspectRatio < 1.2
|
|
29
|
+
? "Compact face length relative to width"
|
|
30
|
+
: "Balanced face length-to-width ratio";
|
|
31
|
+
const jawSignal = ratios.jawToCheekWidth >= 0.94
|
|
32
|
+
? "Jaw width is close to cheekbone width"
|
|
33
|
+
: ratios.jawToCheekWidth <= 0.78
|
|
34
|
+
? "Jawline tapers below the cheekbones"
|
|
35
|
+
: "Jawline has moderate taper";
|
|
36
|
+
const upperSignal = ratios.foreheadToCheekWidth >= 0.9
|
|
37
|
+
? "Forehead width is close to cheekbone width"
|
|
38
|
+
: "Cheekbones read wider than the upper face";
|
|
39
|
+
const shapeSignal = shape.charAt(0).toUpperCase() + shape.slice(1) + " shape supported by measured proportions";
|
|
40
|
+
return [shapeSignal, lengthSignal, jawSignal, upperSignal];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* classifyFaceGeometry — exact port of visutry's classifyFaceGeometry().
|
|
44
|
+
*
|
|
45
|
+
* Uses integer if/else scoring on three key ratios:
|
|
46
|
+
* - faceAspectRatio (H/W, 2D)
|
|
47
|
+
* - jawToCheekWidth
|
|
48
|
+
* - foreheadToCheekWidth
|
|
49
|
+
*
|
|
50
|
+
* Confidence: clamp(0.56 + best.score * 0.065 + margin * 0.035, 0.58, 0.93)
|
|
51
|
+
*/
|
|
52
|
+
function classifyFaceGeometry(ratios) {
|
|
53
|
+
const scores = {
|
|
54
|
+
round: 0,
|
|
55
|
+
square: 0,
|
|
56
|
+
oval: 0,
|
|
57
|
+
heart: 0,
|
|
58
|
+
diamond: 0,
|
|
59
|
+
oblong: 0,
|
|
60
|
+
triangle: 0,
|
|
61
|
+
};
|
|
62
|
+
const { faceAspectRatio, jawToCheekWidth, foreheadToCheekWidth } = ratios;
|
|
63
|
+
// --- Exact replication of visutry's scoring rules ---
|
|
64
|
+
if (faceAspectRatio >= 1.42)
|
|
65
|
+
scores.oblong += 4;
|
|
66
|
+
if (faceAspectRatio >= 1.27 && faceAspectRatio < 1.42)
|
|
67
|
+
scores.oval += 3;
|
|
68
|
+
if (faceAspectRatio < 1.2)
|
|
69
|
+
scores.round += 2;
|
|
70
|
+
if (faceAspectRatio < 1.18 && jawToCheekWidth >= 0.86)
|
|
71
|
+
scores.square += 3;
|
|
72
|
+
if (jawToCheekWidth >= 0.92 && foreheadToCheekWidth >= 0.9)
|
|
73
|
+
scores.square += 3;
|
|
74
|
+
if (jawToCheekWidth < 0.76 && foreheadToCheekWidth >= 0.84)
|
|
75
|
+
scores.heart += 4;
|
|
76
|
+
if (jawToCheekWidth < 0.78 && foreheadToCheekWidth < 0.84)
|
|
77
|
+
scores.diamond += 4;
|
|
78
|
+
if (jawToCheekWidth > 0.98 && foreheadToCheekWidth < 0.88)
|
|
79
|
+
scores.triangle += 4;
|
|
80
|
+
if (jawToCheekWidth >= 0.78 && jawToCheekWidth <= 0.9 && faceAspectRatio >= 1.2) {
|
|
81
|
+
scores.oval += 2;
|
|
82
|
+
}
|
|
83
|
+
if (jawToCheekWidth >= 0.82 && jawToCheekWidth <= 0.94 && faceAspectRatio < 1.22) {
|
|
84
|
+
scores.round += 2;
|
|
85
|
+
}
|
|
86
|
+
// --- Rank candidates ---
|
|
87
|
+
const ranked = CANONICAL_SHAPES.map((shape) => ({ shape, score: scores[shape] })).sort((a, b) => b.score - a.score);
|
|
88
|
+
const best = ranked[0];
|
|
89
|
+
const second = ranked[1];
|
|
90
|
+
const confidence = clamp(0.56 + best.score * 0.065 + (best.score - second.score) * 0.035, 0.58, 0.93);
|
|
91
|
+
const alternatives = ranked
|
|
92
|
+
.slice(1, 3)
|
|
93
|
+
.filter((candidate) => candidate.score > 0)
|
|
94
|
+
.map((candidate) => candidate.shape);
|
|
95
|
+
return {
|
|
96
|
+
shape: best.shape,
|
|
97
|
+
alternatives,
|
|
98
|
+
confidence: round(confidence, 2),
|
|
99
|
+
signals: buildGeometrySignals(best.shape, ratios),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Scores face shapes from geometric metrics.
|
|
104
|
+
*
|
|
105
|
+
* v0.2.0: Exact port of visutry's classifyFaceGeometry algorithm.
|
|
106
|
+
* Uses if/else integer scoring on 2D ratios — not bell/softmax.
|
|
107
|
+
* This ensures numerical equivalence with visutry's main site.
|
|
108
|
+
*
|
|
109
|
+
* Future enhancements (bell functions, softmax, chinType, multi-frame)
|
|
110
|
+
* can be layered on top of this known-good baseline.
|
|
111
|
+
*/
|
|
112
|
+
export class FaceShapeScorer {
|
|
113
|
+
constructor(metricsCalculator) {
|
|
114
|
+
Object.defineProperty(this, "metricsCalculator", {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
configurable: true,
|
|
117
|
+
writable: true,
|
|
118
|
+
value: void 0
|
|
119
|
+
});
|
|
120
|
+
this.metricsCalculator = metricsCalculator ?? new FaceMetricsCalculator();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Score a single face result.
|
|
124
|
+
*/
|
|
125
|
+
score(face) {
|
|
126
|
+
const metrics = this.metricsCalculator.compute(face);
|
|
127
|
+
return this.scoreFromMetrics(metrics, face.quality.warnings);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Score from pre-aggregated metrics.
|
|
131
|
+
*/
|
|
132
|
+
scoreFromMetrics(metrics, warnings = []) {
|
|
133
|
+
// Require visutry-compatible ratios for classification.
|
|
134
|
+
if (!metrics.visutry) {
|
|
135
|
+
return this.unknownResult(metrics, [...warnings, "MISSING_KEY_POINTS"]);
|
|
136
|
+
}
|
|
137
|
+
const v = metrics.visutry;
|
|
138
|
+
// --- Quality gates (exact match to visutry's analyzeFaceLandmarks) ---
|
|
139
|
+
const MAX_TILT = 15;
|
|
140
|
+
const MAX_SYMMETRY = 0.14;
|
|
141
|
+
const MIN_SPAN = 0.16;
|
|
142
|
+
const allWarnings = [...warnings];
|
|
143
|
+
// Face span check
|
|
144
|
+
if (metrics.faceSpan !== undefined && metrics.faceSpan < MIN_SPAN) {
|
|
145
|
+
allWarnings.push("FACE_TOO_SMALL");
|
|
146
|
+
}
|
|
147
|
+
// Tilt check — visutry rejects > 15° as unavailable
|
|
148
|
+
if (Math.abs(v.eyeLineTiltDeg) > MAX_TILT) {
|
|
149
|
+
allWarnings.push("EXCESSIVE_TILT");
|
|
150
|
+
}
|
|
151
|
+
// Symmetry check — visutry rejects > 0.14 as unavailable
|
|
152
|
+
if (v.symmetryOffset > MAX_SYMMETRY) {
|
|
153
|
+
allWarnings.push("ASYMMETRIC_FACE");
|
|
154
|
+
}
|
|
155
|
+
// If quality gates failed, return unknown
|
|
156
|
+
if (allWarnings.some((w) => w === "EXCESSIVE_TILT" || w === "ASYMMETRIC_FACE" || w === "FACE_TOO_SMALL")) {
|
|
157
|
+
return this.unknownResult(metrics, allWarnings);
|
|
158
|
+
}
|
|
159
|
+
// --- Classify using visutry's exact algorithm ---
|
|
160
|
+
const result = classifyFaceGeometry(v);
|
|
161
|
+
// --- Build candidates list ---
|
|
162
|
+
// visutry returns shape + alternatives; we also include all shapes with
|
|
163
|
+
// their integer scores as candidates for SDK consumers.
|
|
164
|
+
const scores = this.getAllScores(v);
|
|
165
|
+
const ranked = CANONICAL_SHAPES.map((shape) => ({
|
|
166
|
+
shape,
|
|
167
|
+
score: scores[shape],
|
|
168
|
+
})).sort((a, b) => b.score - a.score);
|
|
169
|
+
const maxScore = Math.max(...ranked.map((r) => r.score), 1);
|
|
170
|
+
const candidates = ranked.map((r) => ({
|
|
171
|
+
shape: r.shape,
|
|
172
|
+
score: round(r.score / maxScore, 3),
|
|
173
|
+
reasons: buildGeometrySignals(r.shape, v),
|
|
174
|
+
}));
|
|
175
|
+
// --- Soft warnings for borderline quality ---
|
|
176
|
+
if (Math.abs(v.eyeLineTiltDeg) > 8) {
|
|
177
|
+
if (!allWarnings.includes("EXCESSIVE_TILT")) {
|
|
178
|
+
allWarnings.push("EXCESSIVE_TILT");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (v.symmetryOffset > 0.08) {
|
|
182
|
+
if (!allWarnings.includes("ASYMMETRIC_FACE")) {
|
|
183
|
+
allWarnings.push("ASYMMETRIC_FACE");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
primary: result.shape,
|
|
188
|
+
candidates,
|
|
189
|
+
confidence: result.confidence,
|
|
190
|
+
metrics,
|
|
191
|
+
warnings: allWarnings,
|
|
192
|
+
version: FACE_SHAPE_SCORER_VERSION,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Multi-frame scoring: aggregate metrics first, then score.
|
|
197
|
+
*/
|
|
198
|
+
scoreFrames(frames) {
|
|
199
|
+
if (frames.length === 0) {
|
|
200
|
+
return this.unknownResult();
|
|
201
|
+
}
|
|
202
|
+
const metrics = this.metricsCalculator.aggregate(frames);
|
|
203
|
+
const warnings = frames[0].quality.warnings;
|
|
204
|
+
return this.scoreFromMetrics(metrics, warnings);
|
|
205
|
+
}
|
|
206
|
+
// -----------------------------------------------------------------------
|
|
207
|
+
// Internal helpers
|
|
208
|
+
// -----------------------------------------------------------------------
|
|
209
|
+
/**
|
|
210
|
+
* Get raw integer scores for all 7 shapes — same as visutry's scoring.
|
|
211
|
+
*/
|
|
212
|
+
getAllScores(v) {
|
|
213
|
+
const scores = {
|
|
214
|
+
round: 0, square: 0, oval: 0, heart: 0, diamond: 0, oblong: 0, triangle: 0,
|
|
215
|
+
};
|
|
216
|
+
const { faceAspectRatio, jawToCheekWidth, foreheadToCheekWidth } = v;
|
|
217
|
+
if (faceAspectRatio >= 1.42)
|
|
218
|
+
scores.oblong += 4;
|
|
219
|
+
if (faceAspectRatio >= 1.27 && faceAspectRatio < 1.42)
|
|
220
|
+
scores.oval += 3;
|
|
221
|
+
if (faceAspectRatio < 1.2)
|
|
222
|
+
scores.round += 2;
|
|
223
|
+
if (faceAspectRatio < 1.18 && jawToCheekWidth >= 0.86)
|
|
224
|
+
scores.square += 3;
|
|
225
|
+
if (jawToCheekWidth >= 0.92 && foreheadToCheekWidth >= 0.9)
|
|
226
|
+
scores.square += 3;
|
|
227
|
+
if (jawToCheekWidth < 0.76 && foreheadToCheekWidth >= 0.84)
|
|
228
|
+
scores.heart += 4;
|
|
229
|
+
if (jawToCheekWidth < 0.78 && foreheadToCheekWidth < 0.84)
|
|
230
|
+
scores.diamond += 4;
|
|
231
|
+
if (jawToCheekWidth > 0.98 && foreheadToCheekWidth < 0.88)
|
|
232
|
+
scores.triangle += 4;
|
|
233
|
+
if (jawToCheekWidth >= 0.78 && jawToCheekWidth <= 0.9 && faceAspectRatio >= 1.2) {
|
|
234
|
+
scores.oval += 2;
|
|
235
|
+
}
|
|
236
|
+
if (jawToCheekWidth >= 0.82 && jawToCheekWidth <= 0.94 && faceAspectRatio < 1.22) {
|
|
237
|
+
scores.round += 2;
|
|
238
|
+
}
|
|
239
|
+
return scores;
|
|
240
|
+
}
|
|
241
|
+
unknownResult(metrics, warnings = ["LOW_CONFIDENCE"]) {
|
|
242
|
+
return {
|
|
243
|
+
primary: "unknown",
|
|
244
|
+
candidates: [],
|
|
245
|
+
confidence: 0,
|
|
246
|
+
metrics: metrics ?? {
|
|
247
|
+
faceWidth: 0,
|
|
248
|
+
faceHeight: 0,
|
|
249
|
+
cheekboneWidth: 0,
|
|
250
|
+
jawWidth: 0,
|
|
251
|
+
eyeOuterDistance: 0,
|
|
252
|
+
eyeInnerDistance: 0,
|
|
253
|
+
eyeCenterDistance: 0,
|
|
254
|
+
noseBridgeToEyeLine: 0,
|
|
255
|
+
widthHeightRatio: 0,
|
|
256
|
+
jawCheekRatio: 0,
|
|
257
|
+
chinType: "unknown",
|
|
258
|
+
measurementQuality: 0,
|
|
259
|
+
},
|
|
260
|
+
warnings,
|
|
261
|
+
version: FACE_SHAPE_SCORER_VERSION,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=FaceShapeScorer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaceShapeScorer.js","sourceRoot":"","sources":["../../src/face/FaceShapeScorer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature extraction for the logistic-regression classifier (spec §9.3).
|
|
3
|
+
*
|
|
4
|
+
* The feature vector is derived from `FaceMetrics` and must use exactly the
|
|
5
|
+
* same canonical order, missing-value strategy and preprocessing in both the
|
|
6
|
+
* Python training script and the TypeScript inference module. Any change to
|
|
7
|
+
* this file MUST bump `LOGREG_FEATURE_SCHEMA_VERSION` and trigger retraining.
|
|
8
|
+
*/
|
|
9
|
+
import type { FaceMetrics } from "../types/index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Feature schema version — must match the version baked into every exported
|
|
12
|
+
* model JSON. When the feature set changes (add/remove/reorder), bump this and
|
|
13
|
+
* retrain. See spec §9.3.
|
|
14
|
+
*/
|
|
15
|
+
export declare const LOGREG_FEATURE_SCHEMA_VERSION = "2.1.0";
|
|
16
|
+
/**
|
|
17
|
+
* Canonical feature names in fixed order. The index in this array is the
|
|
18
|
+
* column index used in `weights`, `mean` and `std` arrays of the model JSON.
|
|
19
|
+
*
|
|
20
|
+
* Design rationale:
|
|
21
|
+
* - Only scale-invariant, pose-robust ratios and curvatures are used.
|
|
22
|
+
* Raw pixel widths/heights depend on image resolution and camera distance.
|
|
23
|
+
* - `chinType` is ordinal-encoded: pointed (0) < rounded (1) < square (2).
|
|
24
|
+
* `unknown` maps to the midpoint (0.5) so it does not bias the model.
|
|
25
|
+
* - Missing optionals default to 0 (ratio neutral) — see `missingValueStrategy`
|
|
26
|
+
* in the model JSON.
|
|
27
|
+
*/
|
|
28
|
+
export declare const LOGREG_FEATURE_NAMES: readonly ["faceWidthToHeight", "jawCheekRatio", "foreheadCheekRatio", "jawToEyeOuter", "eyeOuterToCheek", "noseBridgeRatio", "jawCurvature", "foreheadCurvature", "symmetryOffset", "smileIntensity", "chinTypeEncoded"];
|
|
29
|
+
/** Number of features — convenience constant. */
|
|
30
|
+
export declare const LOGREG_NUM_FEATURES: 11;
|
|
31
|
+
/** Ordinal encoding for `chinType`. */
|
|
32
|
+
export declare function encodeChinType(chinType: FaceMetrics["chinType"]): number;
|
|
33
|
+
/**
|
|
34
|
+
* Extract the canonical feature vector from `FaceMetrics`.
|
|
35
|
+
*
|
|
36
|
+
* The returned array is in the exact order defined by `LOGREG_FEATURE_NAMES`.
|
|
37
|
+
* Missing optional fields default to 0 (ratios/curvatures) or 0.5 (chinType
|
|
38
|
+
* unknown). This matches the `missingValueStrategy: "zero"` declared in the
|
|
39
|
+
* model JSON.
|
|
40
|
+
*
|
|
41
|
+
* **This function is the single source of truth for feature order.** Both the
|
|
42
|
+
* TS inference module and the Python training script must replicate this
|
|
43
|
+
* exact extraction logic.
|
|
44
|
+
*/
|
|
45
|
+
export declare function extractFeatures(metrics: FaceMetrics): number[];
|
|
46
|
+
//# sourceMappingURL=feature-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-extractor.d.ts","sourceRoot":"","sources":["../../src/face/feature-extractor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMrD;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,UAAU,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,0NAYvB,CAAC;AAEX,iDAAiD;AACjD,eAAO,MAAM,mBAAmB,IAA8B,CAAC;AAM/D,uCAAuC;AACvC,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,CAAC,UAAU,CAAC,GAAG,MAAM,CAaxE;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,EAAE,CA4B9D"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature extraction for the logistic-regression classifier (spec §9.3).
|
|
3
|
+
*
|
|
4
|
+
* The feature vector is derived from `FaceMetrics` and must use exactly the
|
|
5
|
+
* same canonical order, missing-value strategy and preprocessing in both the
|
|
6
|
+
* Python training script and the TypeScript inference module. Any change to
|
|
7
|
+
* this file MUST bump `LOGREG_FEATURE_SCHEMA_VERSION` and trigger retraining.
|
|
8
|
+
*/
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Feature schema
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/**
|
|
13
|
+
* Feature schema version — must match the version baked into every exported
|
|
14
|
+
* model JSON. When the feature set changes (add/remove/reorder), bump this and
|
|
15
|
+
* retrain. See spec §9.3.
|
|
16
|
+
*/
|
|
17
|
+
export const LOGREG_FEATURE_SCHEMA_VERSION = "2.1.0";
|
|
18
|
+
/**
|
|
19
|
+
* Canonical feature names in fixed order. The index in this array is the
|
|
20
|
+
* column index used in `weights`, `mean` and `std` arrays of the model JSON.
|
|
21
|
+
*
|
|
22
|
+
* Design rationale:
|
|
23
|
+
* - Only scale-invariant, pose-robust ratios and curvatures are used.
|
|
24
|
+
* Raw pixel widths/heights depend on image resolution and camera distance.
|
|
25
|
+
* - `chinType` is ordinal-encoded: pointed (0) < rounded (1) < square (2).
|
|
26
|
+
* `unknown` maps to the midpoint (0.5) so it does not bias the model.
|
|
27
|
+
* - Missing optionals default to 0 (ratio neutral) — see `missingValueStrategy`
|
|
28
|
+
* in the model JSON.
|
|
29
|
+
*/
|
|
30
|
+
export const LOGREG_FEATURE_NAMES = [
|
|
31
|
+
"faceWidthToHeight",
|
|
32
|
+
"jawCheekRatio",
|
|
33
|
+
"foreheadCheekRatio",
|
|
34
|
+
"jawToEyeOuter",
|
|
35
|
+
"eyeOuterToCheek",
|
|
36
|
+
"noseBridgeRatio",
|
|
37
|
+
"jawCurvature",
|
|
38
|
+
"foreheadCurvature",
|
|
39
|
+
"symmetryOffset",
|
|
40
|
+
"smileIntensity",
|
|
41
|
+
"chinTypeEncoded",
|
|
42
|
+
];
|
|
43
|
+
/** Number of features — convenience constant. */
|
|
44
|
+
export const LOGREG_NUM_FEATURES = LOGREG_FEATURE_NAMES.length;
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// chinType encoding
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/** Ordinal encoding for `chinType`. */
|
|
49
|
+
export function encodeChinType(chinType) {
|
|
50
|
+
switch (chinType) {
|
|
51
|
+
case "pointed":
|
|
52
|
+
return 0;
|
|
53
|
+
case "rounded":
|
|
54
|
+
return 1;
|
|
55
|
+
case "square":
|
|
56
|
+
return 2;
|
|
57
|
+
case "unknown":
|
|
58
|
+
return 0.5;
|
|
59
|
+
default:
|
|
60
|
+
return 0.5;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Feature extraction
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
/**
|
|
67
|
+
* Extract the canonical feature vector from `FaceMetrics`.
|
|
68
|
+
*
|
|
69
|
+
* The returned array is in the exact order defined by `LOGREG_FEATURE_NAMES`.
|
|
70
|
+
* Missing optional fields default to 0 (ratios/curvatures) or 0.5 (chinType
|
|
71
|
+
* unknown). This matches the `missingValueStrategy: "zero"` declared in the
|
|
72
|
+
* model JSON.
|
|
73
|
+
*
|
|
74
|
+
* **This function is the single source of truth for feature order.** Both the
|
|
75
|
+
* TS inference module and the Python training script must replicate this
|
|
76
|
+
* exact extraction logic.
|
|
77
|
+
*/
|
|
78
|
+
export function extractFeatures(metrics) {
|
|
79
|
+
const eyeOuter = metrics.eyeOuterDistance || 1e-6;
|
|
80
|
+
const cheek = metrics.cheekboneWidth || 1e-6;
|
|
81
|
+
return [
|
|
82
|
+
// 0: faceWidthToHeight (W/H)
|
|
83
|
+
metrics.faceWidthToHeight,
|
|
84
|
+
// 1: jawCheekRatio (jawWidth / cheekboneWidth)
|
|
85
|
+
metrics.jawCheekRatio,
|
|
86
|
+
// 2: foreheadCheekRatio (foreheadWidth / cheekboneWidth)
|
|
87
|
+
metrics.foreheadCheekRatio ?? 0,
|
|
88
|
+
// 3: jawToEyeOuter (jawWidth / eyeOuterDistance)
|
|
89
|
+
(metrics.jawWidth || 0) / eyeOuter,
|
|
90
|
+
// 4: eyeOuterToCheek (eyeOuterDistance / cheekboneWidth)
|
|
91
|
+
eyeOuter / cheek,
|
|
92
|
+
// 5: noseBridgeRatio (noseBridgeWidth / faceWidth)
|
|
93
|
+
metrics.noseBridgeRatio ?? 0,
|
|
94
|
+
// 6: jawCurvature
|
|
95
|
+
metrics.jawCurvature ?? 0,
|
|
96
|
+
// 7: foreheadCurvature
|
|
97
|
+
metrics.foreheadCurvature ?? 0,
|
|
98
|
+
// 8: symmetryOffset
|
|
99
|
+
metrics.symmetryOffset ?? 0,
|
|
100
|
+
// 9: smileIntensity
|
|
101
|
+
metrics.smileIntensity ?? 0,
|
|
102
|
+
// 10: chinTypeEncoded
|
|
103
|
+
encodeChinType(metrics.chinType),
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=feature-extractor.js.map
|