@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.
Files changed (86) hide show
  1. package/README.md +58 -0
  2. package/dist/__fixtures__/faceFixtures.d.ts +13 -0
  3. package/dist/__fixtures__/faceFixtures.d.ts.map +1 -0
  4. package/dist/__fixtures__/faceFixtures.js +206 -0
  5. package/dist/__fixtures__/faceFixtures.js.map +1 -0
  6. package/dist/adapter/visutry-site.d.ts +76 -0
  7. package/dist/adapter/visutry-site.d.ts.map +1 -0
  8. package/dist/adapter/visutry-site.js +172 -0
  9. package/dist/adapter/visutry-site.js.map +1 -0
  10. package/dist/analytics.d.ts +89 -0
  11. package/dist/analytics.d.ts.map +1 -0
  12. package/dist/analytics.js +49 -0
  13. package/dist/analytics.js.map +1 -0
  14. package/dist/coordinate/CoordinateSystem.d.ts +46 -0
  15. package/dist/coordinate/CoordinateSystem.d.ts.map +1 -0
  16. package/dist/coordinate/CoordinateSystem.js +88 -0
  17. package/dist/coordinate/CoordinateSystem.js.map +1 -0
  18. package/dist/face/FaceMetricsCalculator.d.ts +52 -0
  19. package/dist/face/FaceMetricsCalculator.d.ts.map +1 -0
  20. package/dist/face/FaceMetricsCalculator.js +375 -0
  21. package/dist/face/FaceMetricsCalculator.js.map +1 -0
  22. package/dist/face/FaceSemanticMapper.d.ts +54 -0
  23. package/dist/face/FaceSemanticMapper.d.ts.map +1 -0
  24. package/dist/face/FaceSemanticMapper.js +129 -0
  25. package/dist/face/FaceSemanticMapper.js.map +1 -0
  26. package/dist/face/FaceShapeScorer.d.ts +35 -0
  27. package/dist/face/FaceShapeScorer.d.ts.map +1 -0
  28. package/dist/face/FaceShapeScorer.js +265 -0
  29. package/dist/face/FaceShapeScorer.js.map +1 -0
  30. package/dist/face/feature-extractor.d.ts +46 -0
  31. package/dist/face/feature-extractor.d.ts.map +1 -0
  32. package/dist/face/feature-extractor.js +106 -0
  33. package/dist/face/feature-extractor.js.map +1 -0
  34. package/dist/face/logreg-classifier.d.ts +117 -0
  35. package/dist/face/logreg-classifier.d.ts.map +1 -0
  36. package/dist/face/logreg-classifier.js +304 -0
  37. package/dist/face/logreg-classifier.js.map +1 -0
  38. package/dist/feature-flag.d.ts +43 -0
  39. package/dist/feature-flag.d.ts.map +1 -0
  40. package/dist/feature-flag.js +58 -0
  41. package/dist/feature-flag.js.map +1 -0
  42. package/dist/i18n/index.d.ts +15 -0
  43. package/dist/i18n/index.d.ts.map +1 -0
  44. package/dist/i18n/index.js +91 -0
  45. package/dist/i18n/index.js.map +1 -0
  46. package/dist/index.d.ts +24 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +33 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/manifest/ManifestValidator.d.ts +23 -0
  51. package/dist/manifest/ManifestValidator.d.ts.map +1 -0
  52. package/dist/manifest/ManifestValidator.js +129 -0
  53. package/dist/manifest/ManifestValidator.js.map +1 -0
  54. package/dist/pose/GlassesPoseSolver.d.ts +64 -0
  55. package/dist/pose/GlassesPoseSolver.d.ts.map +1 -0
  56. package/dist/pose/GlassesPoseSolver.js +299 -0
  57. package/dist/pose/GlassesPoseSolver.js.map +1 -0
  58. package/dist/privacy/PrivacyGuard.d.ts +44 -0
  59. package/dist/privacy/PrivacyGuard.d.ts.map +1 -0
  60. package/dist/privacy/PrivacyGuard.js +105 -0
  61. package/dist/privacy/PrivacyGuard.js.map +1 -0
  62. package/dist/quality/QualityGate.d.ts +22 -0
  63. package/dist/quality/QualityGate.d.ts.map +1 -0
  64. package/dist/quality/QualityGate.js +161 -0
  65. package/dist/quality/QualityGate.js.map +1 -0
  66. package/dist/smoothing/PoseSmoothing.d.ts +37 -0
  67. package/dist/smoothing/PoseSmoothing.d.ts.map +1 -0
  68. package/dist/smoothing/PoseSmoothing.js +146 -0
  69. package/dist/smoothing/PoseSmoothing.js.map +1 -0
  70. package/dist/types/index.d.ts +444 -0
  71. package/dist/types/index.d.ts.map +1 -0
  72. package/dist/types/index.js +8 -0
  73. package/dist/types/index.js.map +1 -0
  74. package/dist/utils/errors.d.ts +7 -0
  75. package/dist/utils/errors.d.ts.map +1 -0
  76. package/dist/utils/errors.js +20 -0
  77. package/dist/utils/errors.js.map +1 -0
  78. package/dist/utils/index.d.ts +2 -0
  79. package/dist/utils/index.d.ts.map +1 -0
  80. package/dist/utils/index.js +2 -0
  81. package/dist/utils/index.js.map +1 -0
  82. package/dist/utils/math.d.ts +40 -0
  83. package/dist/utils/math.d.ts.map +1 -0
  84. package/dist/utils/math.js +149 -0
  85. package/dist/utils/math.js.map +1 -0
  86. package/package.json +31 -0
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @visutry/tryon-core
2
+
3
+ Platform-agnostic core of the [VisuTry](https://github.com/franksunye/visutry-tryon-sdk) AR glasses try-on SDK.
4
+
5
+ It contains no platform (browser / WeChat) code — only the shared foundation:
6
+ shared types, coordinate-system transforms, face semantic mapping, face metrics,
7
+ face-shape scoring, the glasses pose solver, pose smoothing, the quality gate,
8
+ the asset-manifest validator, error helpers, and the i18n message catalogue.
9
+
10
+ This package is consumed by the platform adapters (`@visutry/tryon-web`,
11
+ `@visutry/tryon-wechat`) and the `@visutry/recommender`. Application code
12
+ usually depends on a platform adapter rather than the core directly.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pnpm add @visutry/tryon-core
18
+ # or
19
+ npm install @visutry/tryon-core
20
+ ```
21
+
22
+ > Requires Node.js >= 18.
23
+
24
+ ## Basic usage
25
+
26
+ ```ts
27
+ import {
28
+ createSDKError,
29
+ GlassesPoseSolver,
30
+ QualityGate,
31
+ setLocale,
32
+ t,
33
+ type FaceShapeResult,
34
+ } from "@visutry/tryon-core";
35
+
36
+ // Localise user-facing SDK error messages (default locale: "en").
37
+ setLocale("zh-CN");
38
+
39
+ try {
40
+ // ...core pipeline helpers run here...
41
+ } catch (err) {
42
+ // err is a normalised SDKError whose message comes from the i18n catalogue.
43
+ console.error(t("error.sdk_destroyed"));
44
+ throw createSDKError("UNKNOWN", t("error.sdk_destroyed"), err);
45
+ }
46
+ ```
47
+
48
+ ## API surface
49
+
50
+ - Types: `VisuTrySDK`, `VisuTrySDKConfig`, `FaceShapeResult`, `FaceMetrics`,
51
+ `GlassesAssetManifest`, `GlassesItem`, `RecommendationInput`, ...
52
+ - Pipeline: `GlassesPoseSolver`, `PoseSmoother`, `QualityGate`,
53
+ `FaceShapeScorer`, `PrivacyGuard`.
54
+ - Helpers: `createSDKError`, `ManifestValidator`, `setLocale` / `getLocale` / `t`.
55
+
56
+ ## Full documentation
57
+
58
+ See the monorepo docs: <https://github.com/franksunye/visutry-tryon-sdk#readme>
@@ -0,0 +1,13 @@
1
+ import type { FaceSemanticPoints, NormalizedFaceResult, GlassesAssetManifest } from "../types/index.js";
2
+ /**
3
+ * Build a synthetic `FaceSemanticPoints` representing a specific face shape.
4
+ * Coordinates are in normalized image space (origin top-left, y down).
5
+ *
6
+ * The geometry is constructed so each shape exhibits the discriminating
7
+ * feature ratios the scorer relies on (width/height, jaw/cheek,
8
+ * jaw/eye-outer, eye-outer/cheek, chin type).
9
+ */
10
+ export declare function buildSemanticPoints(shape: "oval" | "round" | "square" | "heart" | "diamond" | "oblong"): FaceSemanticPoints;
11
+ export declare function buildFaceResult(semantic: FaceSemanticPoints, overrides?: Partial<NormalizedFaceResult>): NormalizedFaceResult;
12
+ export declare function buildManifest(overrides?: Partial<GlassesAssetManifest>): GlassesAssetManifest;
13
+ //# sourceMappingURL=faceFixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faceFixtures.d.ts","sourceRoot":"","sources":["../../src/__fixtures__/faceFixtures.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EAEpB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAG3B;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAClE,kBAAkB,CAyHpB;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,kBAAkB,EAC5B,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,oBAAoB,CAqCtB;AAED,wBAAgB,aAAa,CAAC,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAAG,oBAAoB,CA4CjG"}
@@ -0,0 +1,206 @@
1
+ import { MEDIAPIPE_SEMANTIC_INDEX_MAP } from "../face/FaceSemanticMapper.js";
2
+ /**
3
+ * Build a synthetic `FaceSemanticPoints` representing a specific face shape.
4
+ * Coordinates are in normalized image space (origin top-left, y down).
5
+ *
6
+ * The geometry is constructed so each shape exhibits the discriminating
7
+ * feature ratios the scorer relies on (width/height, jaw/cheek,
8
+ * jaw/eye-outer, eye-outer/cheek, chin type).
9
+ */
10
+ export function buildSemanticPoints(shape) {
11
+ const cx = 0.5;
12
+ const cy = 0.5;
13
+ // Eye geometry (symmetric, level by default).
14
+ const eyeHalfSpan = 0.12; // outer corner half-span → eyeOuterDistance = 0.24
15
+ const eyeInnerOffset = 0.04;
16
+ const eyeY = cy - 0.08; // 0.42
17
+ const leftEyeOuter = { x: cx - eyeHalfSpan, y: eyeY, z: -0.02 };
18
+ const leftEyeInner = { x: cx - eyeInnerOffset, y: eyeY, z: -0.02 };
19
+ const rightEyeInner = { x: cx + eyeInnerOffset, y: eyeY, z: -0.02 };
20
+ const rightEyeOuter = { x: cx + eyeHalfSpan, y: eyeY, z: -0.02 };
21
+ const leftEyeCenter = { x: (leftEyeOuter.x + leftEyeInner.x) / 2, y: eyeY, z: -0.02 };
22
+ const rightEyeCenter = { x: (rightEyeInner.x + rightEyeOuter.x) / 2, y: eyeY, z: -0.02 };
23
+ const eyesCenter = { x: cx, y: eyeY, z: -0.02 };
24
+ const noseBridge = { x: cx, y: cy - 0.02, z: -0.03 };
25
+ const noseTip = { x: cx, y: cy + 0.04, z: -0.08 };
26
+ const foreheadCenter = { x: cx, y: cy - 0.18, z: -0.02 }; // y = 0.32
27
+ // Per-shape jaw / cheek geometry, tuned to hit target feature ratios.
28
+ // faceWidth = cheekboneWidth = 2 * cheekHalf
29
+ // jawWidth = 2 * jawHalf
30
+ // faceHeight = chinY - 0.32
31
+ // whr = cheekboneWidth / faceHeight
32
+ // jcr = jawWidth / cheekboneWidth
33
+ let cheekHalf = 0.15;
34
+ let jawHalf = 0.12;
35
+ let chinY = cy + 0.195; // 0.695
36
+ let browHalf = 0.07;
37
+ // visutry additions: face outline, forehead, nose wing half-widths
38
+ let faceHalf = 0.16; // leftFace/rightFace
39
+ let foreheadHalf = 0.14; // leftForehead/rightForehead
40
+ let noseHalf = 0.05; // noseLeft/noseRight
41
+ switch (shape) {
42
+ case "oval": // whr~0.80, jcr~0.80, rounded chin
43
+ cheekHalf = 0.15;
44
+ jawHalf = 0.12;
45
+ chinY = 0.695;
46
+ faceHalf = 0.155;
47
+ foreheadHalf = 0.135;
48
+ noseHalf = 0.045;
49
+ break;
50
+ case "round": // whr~0.96, jcr~0.91, rounded chin
51
+ cheekHalf = 0.16;
52
+ jawHalf = 0.1455;
53
+ chinY = 0.653;
54
+ faceHalf = 0.165;
55
+ foreheadHalf = 0.15;
56
+ noseHalf = 0.05;
57
+ break;
58
+ case "square": // whr~0.85, jcr~0.93, square chin
59
+ cheekHalf = 0.156;
60
+ jawHalf = 0.145;
61
+ chinY = 0.687;
62
+ faceHalf = 0.158;
63
+ foreheadHalf = 0.148; // broad forehead (fcr ~0.95, not triangle)
64
+ noseHalf = 0.048;
65
+ break;
66
+ case "heart": // whr~0.80, jcr~0.58, pointed chin, upper wide
67
+ cheekHalf = 0.12;
68
+ jawHalf = 0.0695;
69
+ chinY = 0.62;
70
+ browHalf = 0.09;
71
+ faceHalf = 0.125;
72
+ foreheadHalf = 0.13; // broad forehead (fcr > 0.9)
73
+ noseHalf = 0.04;
74
+ break;
75
+ case "diamond": // whr~0.75, jcr~0.64, pointed chin, cheek dominant
76
+ cheekHalf = 0.171;
77
+ jawHalf = 0.11;
78
+ chinY = 0.777;
79
+ faceHalf = 0.175;
80
+ foreheadHalf = 0.12; // narrow forehead (fcr < 0.8)
81
+ noseHalf = 0.042;
82
+ break;
83
+ case "oblong": // whr~0.66, jcr~0.82, rounded chin
84
+ cheekHalf = 0.1395;
85
+ jawHalf = 0.115;
86
+ chinY = 0.743;
87
+ faceHalf = 0.142;
88
+ foreheadHalf = 0.13;
89
+ noseHalf = 0.044;
90
+ break;
91
+ }
92
+ const leftCheek = { x: cx - cheekHalf, y: cy + 0.02, z: -0.03 };
93
+ const rightCheek = { x: cx + cheekHalf, y: cy + 0.02, z: -0.03 };
94
+ const leftJaw = { x: cx - jawHalf, y: cy + 0.13, z: -0.02 };
95
+ const rightJaw = { x: cx + jawHalf, y: cy + 0.13, z: -0.02 };
96
+ const chin = { x: cx, y: chinY, z: -0.04 };
97
+ return {
98
+ leftEyeOuter,
99
+ leftEyeInner,
100
+ rightEyeInner,
101
+ rightEyeOuter,
102
+ leftEyeCenter,
103
+ rightEyeCenter,
104
+ eyesCenter,
105
+ noseBridge,
106
+ noseTip,
107
+ leftBrowCenter: { x: cx - browHalf, y: eyeY - 0.04, z: -0.02 },
108
+ rightBrowCenter: { x: cx + browHalf, y: eyeY - 0.04, z: -0.02 },
109
+ foreheadCenter,
110
+ chin,
111
+ leftCheek,
112
+ rightCheek,
113
+ leftJaw,
114
+ rightJaw,
115
+ // visutry additions
116
+ leftFace: { x: cx - faceHalf, y: cy, z: -0.01 },
117
+ rightFace: { x: cx + faceHalf, y: cy, z: -0.01 },
118
+ leftForehead: { x: cx - foreheadHalf, y: cy - 0.14, z: -0.02 },
119
+ rightForehead: { x: cx + foreheadHalf, y: cy - 0.14, z: -0.02 },
120
+ noseLeft: { x: cx - noseHalf, y: cy, z: -0.03 },
121
+ noseRight: { x: cx + noseHalf, y: cy, z: -0.03 },
122
+ };
123
+ }
124
+ export function buildFaceResult(semantic, overrides = {}) {
125
+ const raw = [];
126
+ const maxIndex = Math.max(...Object.values(MEDIAPIPE_SEMANTIC_INDEX_MAP).filter((v) => v !== undefined));
127
+ for (let i = 0; i <= maxIndex; i++)
128
+ raw.push({ x: 0.5, y: 0.5, z: 0 });
129
+ for (const [key, idx] of Object.entries(MEDIAPIPE_SEMANTIC_INDEX_MAP)) {
130
+ const pt = semantic[key];
131
+ if (pt && idx !== undefined)
132
+ raw[idx] = { ...pt };
133
+ }
134
+ return {
135
+ source: "mediapipe",
136
+ timestamp: Date.now(),
137
+ landmarks: {
138
+ raw,
139
+ normalized: raw,
140
+ semantic,
141
+ },
142
+ pose: {
143
+ yaw: 0,
144
+ pitch: 0,
145
+ roll: 0,
146
+ confidence: 0.95,
147
+ ...overrides.pose,
148
+ },
149
+ bbox: { x: 0.2, y: 0.15, width: 0.6, height: 0.7, ...overrides.bbox },
150
+ quality: {
151
+ confidence: 0.92,
152
+ faceVisible: true,
153
+ frontalScore: 0.9,
154
+ stabilityScore: 0.85,
155
+ warnings: [],
156
+ ...overrides.quality,
157
+ },
158
+ ...overrides,
159
+ };
160
+ }
161
+ export function buildManifest(overrides = {}) {
162
+ return {
163
+ id: "test-glasses",
164
+ name: "Test Glasses",
165
+ modelUrl: "https://example.com/glasses.glb",
166
+ format: "glb",
167
+ coordinateSystem: {
168
+ unit: "millimeter",
169
+ forwardAxis: "+z",
170
+ upAxis: "+y",
171
+ },
172
+ dimensions: {
173
+ frameWidthMm: 140,
174
+ lensWidthMm: 50,
175
+ lensHeightMm: 40,
176
+ bridgeWidthMm: 20,
177
+ templeLengthMm: 140,
178
+ },
179
+ anchors: {
180
+ origin: { x: 0, y: 0, z: 0 },
181
+ noseBridge: { x: 0, y: 0, z: 0 },
182
+ leftHinge: { x: -70, y: 0, z: 0 },
183
+ rightHinge: { x: 70, y: 0, z: 0 },
184
+ },
185
+ fitting: {
186
+ defaultScale: 1,
187
+ defaultOffset: { x: 0, y: 0, z: 0 },
188
+ defaultRotation: { x: 0, y: 0, z: 0 },
189
+ minScale: 0.2,
190
+ maxScale: 3,
191
+ },
192
+ material: {
193
+ lensOpacity: 0.5,
194
+ frameRoughness: 0.4,
195
+ supportsTransparency: true,
196
+ },
197
+ metadata: {
198
+ brand: "VisuTry",
199
+ shapeCategory: "rectangle",
200
+ colors: ["black"],
201
+ tags: ["demo"],
202
+ },
203
+ ...overrides,
204
+ };
205
+ }
206
+ //# sourceMappingURL=faceFixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"faceFixtures.js","sourceRoot":"","sources":["../../src/__fixtures__/faceFixtures.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * VisuTry main-site adapter.
3
+ *
4
+ * Converts the SDK's honest `FaceShapeResult` into the `landmark-v1`
5
+ * `FaceGeometryAnalysis` contract consumed by the VisuTry main site
6
+ * (`src/types/face-analysis.ts`). This is the SINGLE adapter the spec
7
+ * (§12) requires — the main site must not run a parallel classification
8
+ * path.
9
+ *
10
+ * Key conversions (spec §4.3):
11
+ * - SDK `faceWidthToHeight` (W/H) → site `faceAspectRatio` (H/W), i.e. reciprocal.
12
+ * - SDK `analysisQuality` (0..1) → site `qualityScore` (0..100 integer).
13
+ * - SDK `matchStrength` (0..1) → site `measuredConfidence` (kept as-is;
14
+ * the field name is legacy but the value is match strength, NOT a probability).
15
+ * - SDK `classification: "unknown"` → site `status: "unavailable"`.
16
+ */
17
+ import type { Classification, FaceMetrics, FaceShapeResult } from "../types/index.js";
18
+ /** The 7 canonical face shapes used by the VisuTry main site. */
19
+ export type SiteFaceShape = "round" | "square" | "oval" | "heart" | "diamond" | "oblong" | "triangle";
20
+ /** Ratio block consumed by the main site's report builder and VLM prompt. */
21
+ export interface SiteFaceGeometryRatios {
22
+ /** faceHeight / faceWidth — reciprocal of SDK `faceWidthToHeight`. */
23
+ faceAspectRatio: number;
24
+ /** cheekboneWidth / faceWidth. */
25
+ cheekToFaceWidth: number;
26
+ /** jawWidth / cheekboneWidth — same as SDK `jawCheekRatio`. */
27
+ jawToCheekWidth: number;
28
+ /** foreheadWidth / cheekboneWidth — same as SDK `foreheadCheekRatio`. */
29
+ foreheadToCheekWidth: number;
30
+ eyeLineTiltDeg: number;
31
+ symmetryOffset: number;
32
+ /** noseBridgeWidth / faceWidth — same as SDK `noseBridgeRatio`. */
33
+ noseBridgeToFaceWidth: number;
34
+ }
35
+ /** The `landmark-v1` geometry analysis contract consumed by the main site. */
36
+ export interface SiteFaceGeometryAnalysis {
37
+ version: "landmark-v1";
38
+ status: "measured" | "unavailable";
39
+ source: "mediapipe-face-landmarker" | "ai-fallback";
40
+ faceDetected: boolean;
41
+ faceCount: number;
42
+ qualityScore: number;
43
+ measuredShape?: SiteFaceShape;
44
+ measuredConfidence?: number;
45
+ ratios?: SiteFaceGeometryRatios;
46
+ signals: string[];
47
+ warnings: string[];
48
+ }
49
+ /** Bumped when the mapping logic or mirrored contract changes. */
50
+ export declare const VISUTRY_SITE_ADAPTER_VERSION = "1.0.0";
51
+ /**
52
+ * Convert an SDK `FaceShapeResult` into the main-site `landmark-v1`
53
+ * `FaceGeometryAnalysis` contract.
54
+ *
55
+ * - `classification: "unknown"` → `status: "unavailable"` (no shape, no ratios).
56
+ * - `classification: "single" | "mixed"` → `status: "measured"` with ratios.
57
+ * - `analysisQuality` (0..1) is scaled to `qualityScore` (0..100).
58
+ * - `faceWidthToHeight` (W/H) is converted to `faceAspectRatio` (H/W).
59
+ *
60
+ * The result is fully serialisable and carries no MediaPipe references.
61
+ */
62
+ export declare function toFaceGeometryAnalysis(result: FaceShapeResult): SiteFaceGeometryAnalysis;
63
+ /**
64
+ * Build the ratio block from SDK metrics, applying the W/H → H/W conversion.
65
+ *
66
+ * Returns `undefined` when the essential raw measurements are missing or
67
+ * non-finite, so the main site's `normalizeGeometryAnalysis()` will treat
68
+ * the result as `unavailable`.
69
+ */
70
+ export declare function buildRatios(metrics: FaceMetrics): SiteFaceGeometryRatios | undefined;
71
+ /**
72
+ * Lightweight classification-to-status helper for callers that only need the
73
+ * status flag without building the full analysis object.
74
+ */
75
+ export declare function classificationToStatus(classification: Classification): "measured" | "unavailable";
76
+ //# sourceMappingURL=visutry-site.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visutry-site.d.ts","sourceRoot":"","sources":["../../src/adapter/visutry-site.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAEV,cAAc,EACd,WAAW,EAEX,eAAe,EAChB,MAAM,mBAAmB,CAAC;AAU3B,iEAAiE;AACjE,MAAM,MAAM,aAAa,GACrB,OAAO,GACP,QAAQ,GACR,MAAM,GACN,OAAO,GACP,SAAS,GACT,QAAQ,GACR,UAAU,CAAC;AAEf,6EAA6E;AAC7E,MAAM,WAAW,sBAAsB;IACrC,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,gBAAgB,EAAE,MAAM,CAAC;IACzB,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,UAAU,GAAG,aAAa,CAAC;IACnC,MAAM,EAAE,2BAA2B,GAAG,aAAa,CAAC;IACpD,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAMD,kEAAkE;AAClE,eAAO,MAAM,4BAA4B,UAAU,CAAC;AAqFpD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,GACtB,wBAAwB,CAsC1B;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,GACnB,sBAAsB,GAAG,SAAS,CAqCpC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,cAAc,GAC7B,UAAU,GAAG,aAAa,CAE5B"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * VisuTry main-site adapter.
3
+ *
4
+ * Converts the SDK's honest `FaceShapeResult` into the `landmark-v1`
5
+ * `FaceGeometryAnalysis` contract consumed by the VisuTry main site
6
+ * (`src/types/face-analysis.ts`). This is the SINGLE adapter the spec
7
+ * (§12) requires — the main site must not run a parallel classification
8
+ * path.
9
+ *
10
+ * Key conversions (spec §4.3):
11
+ * - SDK `faceWidthToHeight` (W/H) → site `faceAspectRatio` (H/W), i.e. reciprocal.
12
+ * - SDK `analysisQuality` (0..1) → site `qualityScore` (0..100 integer).
13
+ * - SDK `matchStrength` (0..1) → site `measuredConfidence` (kept as-is;
14
+ * the field name is legacy but the value is match strength, NOT a probability).
15
+ * - SDK `classification: "unknown"` → site `status: "unavailable"`.
16
+ */
17
+ // ---------------------------------------------------------------------------
18
+ // Adapter version
19
+ // ---------------------------------------------------------------------------
20
+ /** Bumped when the mapping logic or mirrored contract changes. */
21
+ export const VISUTRY_SITE_ADAPTER_VERSION = "1.0.0";
22
+ // ---------------------------------------------------------------------------
23
+ // Helpers
24
+ // ---------------------------------------------------------------------------
25
+ function round(value, digits = 3) {
26
+ if (!Number.isFinite(value))
27
+ return 0;
28
+ const factor = 10 ** digits;
29
+ return Math.round(value * factor) / factor;
30
+ }
31
+ function clamp(value, min, max) {
32
+ return Math.min(max, Math.max(min, value));
33
+ }
34
+ /** Map SDK FaceShape (7 values) to site SiteFaceShape (same 7 values). */
35
+ function toSiteShape(shape) {
36
+ if (!shape)
37
+ return undefined;
38
+ return shape;
39
+ }
40
+ /** Human-readable warning strings matching the main-site style. */
41
+ const WARNING_MESSAGES = {
42
+ LOW_QUALITY: "Photo quality is too low for reliable face-shape measurement.",
43
+ AMBIGUOUS_MATCH: "The face shape is ambiguous — features match multiple shapes similarly.",
44
+ UNSTABLE_FRAMES: "Frame-to-frame measurements were unstable; try holding still.",
45
+ EXTREME_EXPRESSION: "An extreme expression was detected; use a neutral face for best results.",
46
+ MISSING_KEY_POINTS: "Some key facial reference points were missing or unreliable.",
47
+ NON_FRONTAL: "The face appears turned; use a straight-on photo.",
48
+ FACE_TOO_SMALL: "The face is too small in the photo. Move closer and keep the full face visible.",
49
+ MULTIPLE_FACES: "Multiple faces were detected. Use a photo with exactly one face.",
50
+ };
51
+ function mapWarnings(warnings) {
52
+ return warnings.map((w) => WARNING_MESSAGES[w] ?? w);
53
+ }
54
+ /**
55
+ * Build human-readable geometry signals from metrics, mirroring the main
56
+ * site's `buildGeometrySignals()` so the report text stays consistent.
57
+ */
58
+ function buildSignals(shape, metrics) {
59
+ const signals = [];
60
+ if (shape) {
61
+ const label = shape[0].toUpperCase() + shape.slice(1);
62
+ signals.push(`${label} shape supported by measured proportions`);
63
+ }
64
+ const wth = metrics.faceWidthToHeight;
65
+ signals.push(wth < 0.83
66
+ ? "Longer vertical face proportion"
67
+ : wth > 0.93
68
+ ? "Compact face length relative to width"
69
+ : "Balanced face length-to-width ratio");
70
+ const jcr = metrics.jawCheekRatio;
71
+ signals.push(jcr >= 0.94
72
+ ? "Jaw width is close to cheekbone width"
73
+ : jcr <= 0.78
74
+ ? "Jawline tapers below the cheekbones"
75
+ : "Jawline has moderate taper");
76
+ const fcr = metrics.foreheadCheekRatio ?? 0;
77
+ signals.push(fcr >= 0.9
78
+ ? "Forehead width is close to cheekbone width"
79
+ : "Cheekbones read wider than the upper face");
80
+ return signals;
81
+ }
82
+ // ---------------------------------------------------------------------------
83
+ // Public API
84
+ // ---------------------------------------------------------------------------
85
+ /**
86
+ * Convert an SDK `FaceShapeResult` into the main-site `landmark-v1`
87
+ * `FaceGeometryAnalysis` contract.
88
+ *
89
+ * - `classification: "unknown"` → `status: "unavailable"` (no shape, no ratios).
90
+ * - `classification: "single" | "mixed"` → `status: "measured"` with ratios.
91
+ * - `analysisQuality` (0..1) is scaled to `qualityScore` (0..100).
92
+ * - `faceWidthToHeight` (W/H) is converted to `faceAspectRatio` (H/W).
93
+ *
94
+ * The result is fully serialisable and carries no MediaPipe references.
95
+ */
96
+ export function toFaceGeometryAnalysis(result) {
97
+ const { classification, metrics, warnings, analysisQuality, matchStrength } = result;
98
+ const isUnavailable = classification === "unknown";
99
+ // qualityScore: 0..1 → 0..100 integer, clamped.
100
+ const qualityScore = Math.round(clamp(analysisQuality * 100, 0, 100));
101
+ if (isUnavailable) {
102
+ return {
103
+ version: "landmark-v1",
104
+ status: "unavailable",
105
+ source: "mediapipe-face-landmarker",
106
+ faceDetected: true,
107
+ faceCount: 1,
108
+ qualityScore,
109
+ signals: [],
110
+ warnings: mapWarnings(warnings),
111
+ };
112
+ }
113
+ const ratios = buildRatios(metrics);
114
+ const measuredShape = toSiteShape(result.primary);
115
+ return {
116
+ version: "landmark-v1",
117
+ status: "measured",
118
+ source: "mediapipe-face-landmarker",
119
+ faceDetected: true,
120
+ faceCount: 1,
121
+ qualityScore,
122
+ measuredShape,
123
+ measuredConfidence: round(matchStrength, 2),
124
+ ratios,
125
+ signals: buildSignals(measuredShape, metrics),
126
+ warnings: mapWarnings(warnings),
127
+ };
128
+ }
129
+ /**
130
+ * Build the ratio block from SDK metrics, applying the W/H → H/W conversion.
131
+ *
132
+ * Returns `undefined` when the essential raw measurements are missing or
133
+ * non-finite, so the main site's `normalizeGeometryAnalysis()` will treat
134
+ * the result as `unavailable`.
135
+ */
136
+ export function buildRatios(metrics) {
137
+ const { faceWidth, faceHeight, cheekboneWidth } = metrics;
138
+ if (!Number.isFinite(faceWidth) ||
139
+ !Number.isFinite(faceHeight) ||
140
+ !Number.isFinite(cheekboneWidth) ||
141
+ faceWidth <= 0 ||
142
+ faceHeight <= 0 ||
143
+ cheekboneWidth <= 0) {
144
+ return undefined;
145
+ }
146
+ const faceAspectRatio = round(faceHeight / faceWidth); // H/W — reciprocal
147
+ const cheekToFaceWidth = round(cheekboneWidth / faceWidth);
148
+ const jawToCheekWidth = round(metrics.jawCheekRatio);
149
+ const foreheadToCheekWidth = metrics.foreheadCheekRatio != null && Number.isFinite(metrics.foreheadCheekRatio)
150
+ ? round(metrics.foreheadCheekRatio)
151
+ : round((metrics.foreheadWidth ?? 0) / cheekboneWidth);
152
+ const noseBridgeToFaceWidth = metrics.noseBridgeRatio != null && Number.isFinite(metrics.noseBridgeRatio)
153
+ ? round(metrics.noseBridgeRatio)
154
+ : 0;
155
+ return {
156
+ faceAspectRatio,
157
+ cheekToFaceWidth,
158
+ jawToCheekWidth,
159
+ foreheadToCheekWidth,
160
+ eyeLineTiltDeg: round(metrics.eyeLineTiltDeg ?? 0, 1),
161
+ symmetryOffset: round(metrics.symmetryOffset ?? 0),
162
+ noseBridgeToFaceWidth,
163
+ };
164
+ }
165
+ /**
166
+ * Lightweight classification-to-status helper for callers that only need the
167
+ * status flag without building the full analysis object.
168
+ */
169
+ export function classificationToStatus(classification) {
170
+ return classification === "unknown" ? "unavailable" : "measured";
171
+ }
172
+ //# sourceMappingURL=visutry-site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visutry-site.js","sourceRoot":"","sources":["../../src/adapter/visutry-site.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Analytics event types for face-shape analysis (spec §12).
3
+ *
4
+ * The host application wires these events into its analytics pipeline
5
+ * (e.g. GA4). Events carry algorithm metadata and outcome signals only —
6
+ * NEVER photos, base64, landmarks, or any reversible biometric data.
7
+ */
8
+ import type { AnalysisWarning, Classification, FaceShape } from "./types/index.js";
9
+ export type FaceAnalysisEventType = "start" | "complete" | "failed" | "abstained" | "retry";
10
+ /** Common fields present on every analytics event. */
11
+ interface AnalyticsEventBase {
12
+ /** Event type. */
13
+ type: FaceAnalysisEventType;
14
+ /** UTC epoch milliseconds. */
15
+ timestamp: number;
16
+ /** Input method. */
17
+ inputKind: "image" | "video";
18
+ /** Classification engine that produced (or attempted) the result. */
19
+ engine: "rules" | "logreg";
20
+ /** Algorithm version from `FaceShapeResult.model.version`. */
21
+ algorithmVersion: string;
22
+ /** SDK package version for audit / rollback tracking. */
23
+ sdkVersion?: string;
24
+ }
25
+ /** Emitted when an analysis session begins. */
26
+ export interface FaceAnalysisStartEvent extends AnalyticsEventBase {
27
+ type: "start";
28
+ }
29
+ /** Emitted when analysis completes with a usable result. */
30
+ export interface FaceAnalysisCompleteEvent extends AnalyticsEventBase {
31
+ type: "complete";
32
+ classification: Classification;
33
+ primary?: FaceShape;
34
+ secondary?: FaceShape;
35
+ /** 0..1 — analysis quality (NOT a probability). */
36
+ analysisQuality: number;
37
+ /** 0..1 — match strength of the primary shape. */
38
+ matchStrength: number;
39
+ /** Wall-clock duration from start to complete, in milliseconds. */
40
+ durationMs: number;
41
+ warnings: AnalysisWarning[];
42
+ }
43
+ /** Emitted when analysis fails with an error (no result produced). */
44
+ export interface FaceAnalysisFailedEvent extends AnalyticsEventBase {
45
+ type: "failed";
46
+ /** Wall-clock duration from start to failure. */
47
+ durationMs: number;
48
+ /** SDK error code. */
49
+ errorCode: string;
50
+ /** Short, non-PII error message. */
51
+ errorMessage: string;
52
+ }
53
+ /** Emitted when the SDK abstains (`classification: "unknown"`). */
54
+ export interface FaceAnalysisAbstainedEvent extends AnalyticsEventBase {
55
+ type: "abstained";
56
+ /** 0..1 — analysis quality at the point of abstention. */
57
+ analysisQuality: number;
58
+ /** Wall-clock duration from start to abstention. */
59
+ durationMs: number;
60
+ warnings: AnalysisWarning[];
61
+ }
62
+ /** Emitted when the user retries after a failure or abstention. */
63
+ export interface FaceAnalysisRetryEvent extends AnalyticsEventBase {
64
+ type: "retry";
65
+ /** The event type that preceded this retry. */
66
+ previousOutcome: "failed" | "abstained";
67
+ }
68
+ export type FaceAnalysisAnalyticsEvent = FaceAnalysisStartEvent | FaceAnalysisCompleteEvent | FaceAnalysisFailedEvent | FaceAnalysisAbstainedEvent | FaceAnalysisRetryEvent;
69
+ /**
70
+ * Callback that receives analytics events. The host application implements
71
+ * this to forward events to GA4, Axiom, etc.
72
+ */
73
+ export type AnalyticsReporter = (event: FaceAnalysisAnalyticsEvent) => void;
74
+ /**
75
+ * Create a no-op reporter (default when analytics is disabled).
76
+ * Useful as a safe default in tests and when `privacy.allowAnalytics` is false.
77
+ */
78
+ export declare function noopReporter(): AnalyticsReporter;
79
+ /**
80
+ * Create a reporter that buffers events and flushes them via the provided
81
+ * sink. Useful for batching in production.
82
+ */
83
+ export declare function createBufferedReporter(sink: (events: FaceAnalysisAnalyticsEvent[]) => void, flushIntervalMs?: number): {
84
+ reporter: AnalyticsReporter;
85
+ flush: () => void;
86
+ destroy: () => void;
87
+ };
88
+ export {};
89
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAMnF,MAAM,MAAM,qBAAqB,GAC7B,OAAO,GACP,UAAU,GACV,QAAQ,GACR,WAAW,GACX,OAAO,CAAC;AAEZ,sDAAsD;AACtD,UAAU,kBAAkB;IAC1B,kBAAkB;IAClB,IAAI,EAAE,qBAAqB,CAAC;IAC5B,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC;IAC7B,qEAAqE;IACrE,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC3B,8DAA8D;IAC9D,gBAAgB,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+CAA+C;AAC/C,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,IAAI,EAAE,OAAO,CAAC;CACf;AAED,4DAA4D;AAC5D,MAAM,WAAW,yBAA0B,SAAQ,kBAAkB;IACnE,IAAI,EAAE,UAAU,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,mDAAmD;IACnD,eAAe,EAAE,MAAM,CAAC;IACxB,kDAAkD;IAClD,aAAa,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,uBAAwB,SAAQ,kBAAkB;IACjE,IAAI,EAAE,QAAQ,CAAC;IACf,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,mEAAmE;AACnE,MAAM,WAAW,0BAA2B,SAAQ,kBAAkB;IACpE,IAAI,EAAE,WAAW,CAAC;IAClB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,mEAAmE;AACnE,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,IAAI,EAAE,OAAO,CAAC;IACd,+CAA+C;IAC/C,eAAe,EAAE,QAAQ,GAAG,WAAW,CAAC;CACzC;AAED,MAAM,MAAM,0BAA0B,GAClC,sBAAsB,GACtB,yBAAyB,GACzB,uBAAuB,GACvB,0BAA0B,GAC1B,sBAAsB,CAAC;AAM3B;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;AAE5E;;;GAGG;AACH,wBAAgB,YAAY,IAAI,iBAAiB,CAIhD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,CAAC,MAAM,EAAE,0BAA0B,EAAE,KAAK,IAAI,EACpD,eAAe,SAAO,GACrB;IAAE,QAAQ,EAAE,iBAAiB,CAAC;IAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CA4BzE"}