@jupitermetalabs/face-zk-sdk 0.3.4 → 0.3.7

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 (74) hide show
  1. package/README.md +46 -1
  2. package/assets/face-guidance/pose-guidance.js.txt +4 -2
  3. package/assets/liveness/liveness.js.txt +3 -0
  4. package/dist/FaceZkSdk.d.ts +69 -0
  5. package/dist/FaceZkSdk.js +136 -0
  6. package/dist/assets/face-guidance/pose-guidance.js.txt +4 -2
  7. package/dist/assets/liveness/liveness.js.txt +3 -0
  8. package/dist/assets/onnx/ort-min.d.ts +1 -0
  9. package/dist/assets/onnx/ort-min.js +11 -0
  10. package/dist/config/defaults.d.ts +51 -0
  11. package/dist/config/defaults.js +61 -0
  12. package/dist/config/types.d.ts +169 -0
  13. package/dist/config/types.js +17 -0
  14. package/dist/core/enrollment-core.d.ts +70 -0
  15. package/dist/core/enrollment-core.js +206 -0
  16. package/dist/core/idGenerator.d.ts +11 -0
  17. package/dist/core/idGenerator.js +32 -0
  18. package/dist/core/matching.d.ts +69 -0
  19. package/dist/core/matching.js +101 -0
  20. package/dist/core/types.d.ts +379 -0
  21. package/dist/core/types.js +37 -0
  22. package/dist/core/verification-core.d.ts +120 -0
  23. package/dist/core/verification-core.js +442 -0
  24. package/dist/core/zk-core.d.ts +69 -0
  25. package/dist/core/zk-core.js +244 -0
  26. package/dist/index.d.ts +29 -0
  27. package/dist/index.js +51 -0
  28. package/dist/react-native/adapters/faceEmbeddingProvider.d.ts +38 -0
  29. package/dist/react-native/adapters/faceEmbeddingProvider.js +45 -0
  30. package/dist/react-native/adapters/imageDataProvider.d.ts +53 -0
  31. package/dist/react-native/adapters/imageDataProvider.js +134 -0
  32. package/dist/react-native/adapters/livenessProvider.d.ts +133 -0
  33. package/dist/react-native/adapters/livenessProvider.js +150 -0
  34. package/dist/react-native/adapters/zkProofEngine-webview.d.ts +73 -0
  35. package/dist/react-native/adapters/zkProofEngine-webview.js +135 -0
  36. package/dist/react-native/bundledRuntimeAssets.d.ts +39 -0
  37. package/dist/react-native/bundledRuntimeAssets.js +44 -0
  38. package/dist/react-native/components/FacePoseGuidanceWebView.d.ts +30 -0
  39. package/dist/react-native/components/FacePoseGuidanceWebView.js +530 -0
  40. package/dist/react-native/components/LivenessWebView.d.ts +39 -0
  41. package/dist/react-native/components/LivenessWebView.js +386 -0
  42. package/dist/react-native/components/OnnxRuntimeWebView.d.ts +58 -0
  43. package/dist/react-native/components/OnnxRuntimeWebView.js +518 -0
  44. package/dist/react-native/components/ZkProofWebView.d.ts +59 -0
  45. package/dist/react-native/components/ZkProofWebView.js +297 -0
  46. package/dist/react-native/dependencies.d.ts +144 -0
  47. package/dist/react-native/dependencies.js +130 -0
  48. package/dist/react-native/hooks/useOnnxLoader.d.ts +37 -0
  49. package/dist/react-native/hooks/useOnnxLoader.js +74 -0
  50. package/dist/react-native/hooks/useWasmLoader.d.ts +30 -0
  51. package/dist/react-native/hooks/useWasmLoader.js +158 -0
  52. package/dist/react-native/index.d.ts +61 -0
  53. package/dist/react-native/index.js +144 -0
  54. package/dist/react-native/services/FaceRecognition.d.ts +92 -0
  55. package/dist/react-native/services/FaceRecognition.js +674 -0
  56. package/dist/react-native/ui/FaceZkVerificationFlow.d.ts +97 -0
  57. package/dist/react-native/ui/FaceZkVerificationFlow.js +477 -0
  58. package/dist/react-native/ui/ReferenceEnrollmentFlow.d.ts +72 -0
  59. package/dist/react-native/ui/ReferenceEnrollmentFlow.js +369 -0
  60. package/dist/react-native/utils/faceAlignment.d.ts +37 -0
  61. package/dist/react-native/utils/faceAlignment.js +186 -0
  62. package/dist/react-native/utils/modelInitialisationChecks.d.ts +36 -0
  63. package/dist/react-native/utils/modelInitialisationChecks.js +128 -0
  64. package/dist/react-native/utils/resolveModelUri.d.ts +55 -0
  65. package/dist/react-native/utils/resolveModelUri.js +211 -0
  66. package/dist/react-native/utils/resolveRuntimeAsset.d.ts +25 -0
  67. package/dist/react-native/utils/resolveRuntimeAsset.js +94 -0
  68. package/dist/react-native/utils/resolveUiConfig.d.ts +41 -0
  69. package/dist/react-native/utils/resolveUiConfig.js +81 -0
  70. package/dist/storage/defaultStorageAdapter.d.ts +44 -0
  71. package/dist/storage/defaultStorageAdapter.js +344 -0
  72. package/dist/tsconfig.tsbuildinfo +1 -1
  73. package/face-zk.config.example.js +10 -3
  74. package/package.json +2 -2
@@ -0,0 +1,369 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright 2026 JupiterMeta Labs
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ Object.defineProperty(exports, "__esModule", { value: true });
51
+ exports.ReferenceEnrollmentFlow = void 0;
52
+ /**
53
+ * Reference Enrollment Flow Component
54
+ *
55
+ * A pre-built React Native UI component for enrolling a reference template.
56
+ * This component handles:
57
+ * - Camera capture with pose guidance
58
+ * - Face detection and embedding extraction
59
+ * - Reference template creation
60
+ * - Optional persistence via storage adapter
61
+ */
62
+ const react_1 = __importStar(require("react"));
63
+ const react_native_1 = require("react-native");
64
+ const enrollment_core_1 = require("../../core/enrollment-core");
65
+ const dependencies_1 = require("../dependencies");
66
+ const resolveUiConfig_1 = require("../utils/resolveUiConfig");
67
+ const FaceZkSdk_1 = require("../../FaceZkSdk");
68
+ /**
69
+ * A drop-in React Native UI component orchestrating face capture and template enrollment.
70
+ *
71
+ * This component guides a user through positioning their face correctly using the `FacePoseGuidanceWebView`, captures an optimal frame, extracts their facial embedding, and saves the resulting `ReferenceTemplate` directly to local storage (if `persist: true` is provided).
72
+ *
73
+ * **Context:** Once a reference is created via this UI, its `referenceId` is the key needed by `FaceZkVerificationFlow` to authenticate the user in the future.
74
+ *
75
+ * @param {ReferenceEnrollmentFlowProps} props - Configuration and dependencies.
76
+ * @returns {React.FC} An encapsulated camera flow for enrollment.
77
+ *
78
+ * @example
79
+ * <ReferenceEnrollmentFlow
80
+ * sdkConfig={config}
81
+ * embeddingProvider={provider}
82
+ * enrollmentOptions={{ persist: true, metadata: { role: "admin" } }}
83
+ * onComplete={(template) => {
84
+ * myBackend.saveRefId(template.referenceId);
85
+ * }}
86
+ * />
87
+ */
88
+ const ReferenceEnrollmentFlow = ({ sdkConfig, embeddingProvider, enrollmentOptions = {}, uiConfig = {}, onComplete, onCancel, onError, modal = false, visible = true, }) => {
89
+ const [stage, setStage] = (0, react_1.useState)("INIT");
90
+ const [error, setError] = (0, react_1.useState)(null);
91
+ const [bridgeReady, setBridgeReady] = (0, react_1.useState)(false);
92
+ // Resolve theme + strings from uiConfig
93
+ const ui = (0, resolveUiConfig_1.resolveUiConfig)(uiConfig);
94
+ const { theme, strings } = ui;
95
+ // Get injected dependencies
96
+ const deps = (0, dependencies_1.getSdkDependencies)();
97
+ const { OnnxRuntimeWebView, FacePoseGuidanceWebView, faceRecognitionService } = deps;
98
+ // Load models when bridge is ready
99
+ (0, react_1.useEffect)(() => {
100
+ if (bridgeReady && faceRecognitionService.isBridgeSet()) {
101
+ setStage("BRIDGE_LOADING");
102
+ faceRecognitionService
103
+ .loadModels()
104
+ .then(() => {
105
+ console.log("[ReferenceEnrollmentFlow] Models loaded, ready to capture");
106
+ setStage("CAPTURING");
107
+ })
108
+ .catch((err) => {
109
+ console.error("[ReferenceEnrollmentFlow] Model loading failed:", err);
110
+ const sdkError = {
111
+ code: "SYSTEM_ERROR",
112
+ message: "Failed to load face recognition models",
113
+ details: { error: String(err) },
114
+ };
115
+ setError(sdkError);
116
+ setStage("ERROR");
117
+ onError?.(sdkError);
118
+ });
119
+ }
120
+ }, [bridgeReady, faceRecognitionService, onError]);
121
+ // Guard: SDK must be initialized before rendering
122
+ if (!FaceZkSdk_1.FaceZkSdk.isInitialized()) {
123
+ return (<react_native_1.View style={{ flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }}>
124
+ <react_native_1.Text style={{ color: "#f97316", fontSize: 16, textAlign: "center" }}>
125
+ FaceZkSdk is not initialized.{"\n"}Call initializeSdk() from '@jupitermetalabs/face-zk-sdk/react-native' before rendering this component.
126
+ </react_native_1.Text>
127
+ </react_native_1.View>);
128
+ }
129
+ // Initialize bridge for face recognition
130
+ const handleBridgeReady = (bridge) => {
131
+ console.log("[ReferenceEnrollmentFlow] ONNX bridge ready");
132
+ faceRecognitionService.setBridge(bridge);
133
+ setBridgeReady(true);
134
+ };
135
+ // Handle image capture from pose guidance
136
+ const handleCaptureSuccess = async (imageUri, captureMetadata) => {
137
+ console.log("[ReferenceEnrollmentFlow] Image captured:", imageUri);
138
+ setStage("PROCESSING");
139
+ try {
140
+ // Create reference template using SDK
141
+ const template = await (0, enrollment_core_1.createReferenceFromImage)(imageUri, sdkConfig, embeddingProvider, {
142
+ ...enrollmentOptions,
143
+ metadata: {
144
+ ...(enrollmentOptions.metadata ?? {}),
145
+ captureResponse: {
146
+ antiSpoofCheckPassed: true,
147
+ targetPose: captureMetadata?.targetPose ?? null,
148
+ capturedPose: captureMetadata?.capturedPose ?? null,
149
+ faceMeshLandmarks: captureMetadata?.faceMeshLandmarks ?? null,
150
+ },
151
+ },
152
+ });
153
+ console.log("[ReferenceEnrollmentFlow] Reference created:", template.referenceId);
154
+ setStage("SUCCESS");
155
+ onComplete(template);
156
+ }
157
+ catch (err) {
158
+ console.error("[ReferenceEnrollmentFlow] Enrollment failed:", err);
159
+ const sdkError = err && typeof err === "object" && "code" in err
160
+ ? err
161
+ : {
162
+ code: "SYSTEM_ERROR",
163
+ message: err instanceof Error ? err.message : "Enrollment failed",
164
+ details: { error: String(err) },
165
+ };
166
+ setError(sdkError);
167
+ setStage("ERROR");
168
+ onError?.(sdkError);
169
+ }
170
+ };
171
+ const handleCaptureError = (message) => {
172
+ console.error("[ReferenceEnrollmentFlow] Capture error:", message);
173
+ const sdkError = {
174
+ code: "SYSTEM_ERROR",
175
+ message,
176
+ details: { stage: "capture" },
177
+ };
178
+ setError(sdkError);
179
+ setStage("ERROR");
180
+ onError?.(sdkError);
181
+ };
182
+ const handleRetry = () => {
183
+ setError(null);
184
+ setStage("CAPTURING");
185
+ };
186
+ const handleCancel = () => {
187
+ onCancel?.();
188
+ };
189
+ const content = (<react_native_1.SafeAreaView style={[styles.container, { backgroundColor: theme.colors.background }]}>
190
+ <react_native_1.StatusBar barStyle="light-content"/>
191
+
192
+ {/* Hidden ONNX Runtime WebView for face recognition */}
193
+ <OnnxRuntimeWebView onReady={handleBridgeReady} onError={(err) => {
194
+ console.error("[ReferenceEnrollmentFlow] Bridge error:", err);
195
+ const sdkError = {
196
+ code: "SYSTEM_ERROR",
197
+ message: "Face recognition initialization failed",
198
+ details: { error: err },
199
+ };
200
+ setError(sdkError);
201
+ setStage("ERROR");
202
+ onError?.(sdkError);
203
+ }}/>
204
+
205
+ {/* Loading States */}
206
+ {(stage === "INIT" || stage === "BRIDGE_LOADING") && (ui.renderLoading ? ui.renderLoading(stage, strings.loadingModels) : (<react_native_1.View style={[styles.loadingContainer, { backgroundColor: theme.colors.background }]}>
207
+ <react_native_1.ActivityIndicator size="large" color={theme.colors.primary}/>
208
+ <react_native_1.Text style={[styles.loadingText, { color: theme.colors.text }]}>
209
+ {strings.loadingModels}
210
+ </react_native_1.Text>
211
+ </react_native_1.View>))}
212
+
213
+ {/* Capture State */}
214
+ {stage === "CAPTURING" && (<react_native_1.View style={styles.captureContainer}>
215
+ <FacePoseGuidanceWebView onSuccess={handleCaptureSuccess} onError={handleCaptureError} headless={false}/>
216
+ <react_native_1.TouchableOpacity style={[styles.cancelButton, {
217
+ backgroundColor: theme.colors.surface,
218
+ borderRadius: theme.borderRadius,
219
+ }]} onPress={handleCancel}>
220
+ <react_native_1.Text style={[styles.cancelButtonText, { color: theme.colors.text }]}>
221
+ {strings.cancelButton}
222
+ </react_native_1.Text>
223
+ </react_native_1.TouchableOpacity>
224
+ </react_native_1.View>)}
225
+
226
+ {/* Processing State */}
227
+ {stage === "PROCESSING" && (ui.renderLoading ? ui.renderLoading(stage, strings.loadingProcessing) : (<react_native_1.View style={[styles.loadingContainer, { backgroundColor: theme.colors.background }]}>
228
+ <react_native_1.ActivityIndicator size="large" color={theme.colors.primary}/>
229
+ <react_native_1.Text style={[styles.loadingText, { color: theme.colors.text }]}>
230
+ {strings.loadingProcessing}
231
+ </react_native_1.Text>
232
+ </react_native_1.View>))}
233
+
234
+ {/* Success State */}
235
+ {stage === "SUCCESS" && (ui.renderSuccess ? ui.renderSuccess({ success: true, score: 100 }) : (<react_native_1.View style={[styles.resultContainer, { backgroundColor: theme.colors.background }]}>
236
+ <react_native_1.Text style={[styles.successIcon, { color: theme.colors.primary }]}>✓</react_native_1.Text>
237
+ <react_native_1.Text style={[styles.successTitle, { color: theme.colors.text }]}>
238
+ {strings.enrollmentSuccessTitle}
239
+ </react_native_1.Text>
240
+ <react_native_1.Text style={[styles.successText, { color: theme.colors.textMuted }]}>
241
+ {strings.enrollmentSuccessSubtitle}
242
+ </react_native_1.Text>
243
+ </react_native_1.View>))}
244
+
245
+ {/* Error State */}
246
+ {stage === "ERROR" && error && (ui.renderError ? ui.renderError(error, { onRetry: handleRetry, onCancel: handleCancel }) : (<react_native_1.View style={[styles.resultContainer, { backgroundColor: theme.colors.background }]}>
247
+ <react_native_1.Text style={[styles.errorIcon, { color: theme.colors.error }]}>✕</react_native_1.Text>
248
+ <react_native_1.Text style={[styles.errorTitle, { color: theme.colors.text }]}>
249
+ {strings.enrollmentErrorTitle}
250
+ </react_native_1.Text>
251
+ <react_native_1.Text style={[styles.errorText, { color: theme.colors.textMuted }]}>{error.message}</react_native_1.Text>
252
+ <react_native_1.View style={styles.buttonRow}>
253
+ <react_native_1.TouchableOpacity style={[styles.retryButton, {
254
+ backgroundColor: theme.colors.primary,
255
+ borderRadius: theme.borderRadius,
256
+ }]} onPress={handleRetry}>
257
+ <react_native_1.Text style={[styles.retryButtonText, { color: theme.colors.text }]}>
258
+ {strings.retryButton}
259
+ </react_native_1.Text>
260
+ </react_native_1.TouchableOpacity>
261
+ <react_native_1.TouchableOpacity style={[styles.cancelButton, {
262
+ backgroundColor: theme.colors.surface,
263
+ borderRadius: theme.borderRadius,
264
+ }]} onPress={handleCancel}>
265
+ <react_native_1.Text style={[styles.cancelButtonText, { color: theme.colors.text }]}>
266
+ {strings.cancelButton}
267
+ </react_native_1.Text>
268
+ </react_native_1.TouchableOpacity>
269
+ </react_native_1.View>
270
+ </react_native_1.View>))}
271
+ </react_native_1.SafeAreaView>);
272
+ if (modal) {
273
+ return (<react_native_1.Modal visible={visible} animationType="slide" presentationStyle="fullScreen" onRequestClose={handleCancel}>
274
+ {content}
275
+ </react_native_1.Modal>);
276
+ }
277
+ return content;
278
+ };
279
+ exports.ReferenceEnrollmentFlow = ReferenceEnrollmentFlow;
280
+ const styles = react_native_1.StyleSheet.create({
281
+ container: {
282
+ flex: 1,
283
+ backgroundColor: "#000",
284
+ },
285
+ loadingContainer: {
286
+ flex: 1,
287
+ justifyContent: "center",
288
+ alignItems: "center",
289
+ backgroundColor: "#000",
290
+ },
291
+ loadingText: {
292
+ marginTop: 16,
293
+ color: "#fff",
294
+ fontSize: 16,
295
+ },
296
+ captureContainer: {
297
+ flex: 1,
298
+ },
299
+ resultContainer: {
300
+ flex: 1,
301
+ justifyContent: "center",
302
+ alignItems: "center",
303
+ backgroundColor: "#000",
304
+ padding: 24,
305
+ },
306
+ successIcon: {
307
+ fontSize: 72,
308
+ color: "#4CAF50",
309
+ marginBottom: 24,
310
+ },
311
+ successTitle: {
312
+ fontSize: 24,
313
+ fontWeight: "bold",
314
+ color: "#fff",
315
+ marginBottom: 12,
316
+ },
317
+ successText: {
318
+ fontSize: 16,
319
+ color: "#aaa",
320
+ textAlign: "center",
321
+ },
322
+ errorIcon: {
323
+ fontSize: 72,
324
+ color: "#F44336",
325
+ marginBottom: 24,
326
+ },
327
+ errorTitle: {
328
+ fontSize: 24,
329
+ fontWeight: "bold",
330
+ color: "#fff",
331
+ marginBottom: 12,
332
+ },
333
+ errorText: {
334
+ fontSize: 16,
335
+ color: "#aaa",
336
+ textAlign: "center",
337
+ marginBottom: 32,
338
+ },
339
+ buttonRow: {
340
+ flexDirection: "row",
341
+ gap: 16,
342
+ },
343
+ retryButton: {
344
+ backgroundColor: "#4CAF50",
345
+ paddingHorizontal: 32,
346
+ paddingVertical: 12,
347
+ borderRadius: 8,
348
+ },
349
+ retryButtonText: {
350
+ color: "#fff",
351
+ fontSize: 16,
352
+ fontWeight: "600",
353
+ },
354
+ cancelButton: {
355
+ position: "absolute",
356
+ bottom: 40,
357
+ left: 24,
358
+ right: 24,
359
+ backgroundColor: "rgba(255, 255, 255, 0.1)",
360
+ paddingVertical: 16,
361
+ borderRadius: 8,
362
+ alignItems: "center",
363
+ },
364
+ cancelButtonText: {
365
+ color: "#fff",
366
+ fontSize: 16,
367
+ fontWeight: "600",
368
+ },
369
+ });
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Copyright 2026 JupiterMeta Labs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ export type Point = [number, number];
17
+ /**
18
+ * Calculates the similarity transform matrix (2x3) that maps src points to dst points.
19
+ * Uses the Least Squares method (Umeyama's algorithm simplified for 2D similarity).
20
+ *
21
+ * Matrix format: [a, b, tx, c, d, ty]
22
+ * where:
23
+ * x' = a*x + b*y + tx
24
+ * y' = c*x + d*y + ty
25
+ */
26
+ export declare function estimateUmeyama(src: Point[], dst?: Point[]): number[];
27
+ /**
28
+ * Applies affine transformation to an image buffer using bilinear interpolation.
29
+ *
30
+ * @param srcData Float32Array containing source image data (CHW format: RRR...GGG...BBB...)
31
+ * @param srcWidth Width of source image
32
+ * @param srcHeight Height of source image
33
+ * @param matrix The 2x3 Affine Matrix computed by estimateUmeyama
34
+ * @param dstSize Output size (default 112)
35
+ * @returns Float32Array (CHW format) of size 3 * dstSize * dstSize, normalized
36
+ */
37
+ export declare function warpAffine(srcData: Float32Array, srcWidth: number, srcHeight: number, matrix: number[], dstSize?: number): Float32Array;
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ /**
3
+ * Copyright 2026 JupiterMeta Labs
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.estimateUmeyama = estimateUmeyama;
19
+ exports.warpAffine = warpAffine;
20
+ // Standard ArcFace reference points (112x112)
21
+ // Source: insightface/utils/face_align.py
22
+ const ARCFACE_DST = [
23
+ [38.2946, 51.6963], // Left Eye
24
+ [73.5318, 51.6963], // Right Eye
25
+ [56.0252, 71.7366], // Nose
26
+ [41.5493, 92.3655], // Left Mouth
27
+ [70.7299, 92.3655], // Right Mouth
28
+ ];
29
+ /**
30
+ * Calculates the similarity transform matrix (2x3) that maps src points to dst points.
31
+ * Uses the Least Squares method (Umeyama's algorithm simplified for 2D similarity).
32
+ *
33
+ * Matrix format: [a, b, tx, c, d, ty]
34
+ * where:
35
+ * x' = a*x + b*y + tx
36
+ * y' = c*x + d*y + ty
37
+ */
38
+ function estimateUmeyama(src, dst = ARCFACE_DST) {
39
+ const num = src.length;
40
+ if (num !== 5 || dst.length !== 5) {
41
+ throw new Error("Umeyama expects 5 points");
42
+ }
43
+ let srcMeanX = 0, srcMeanY = 0, dstMeanX = 0, dstMeanY = 0;
44
+ for (let i = 0; i < num; i++) {
45
+ srcMeanX += src[i][0];
46
+ srcMeanY += src[i][1];
47
+ dstMeanX += dst[i][0];
48
+ dstMeanY += dst[i][1];
49
+ }
50
+ srcMeanX /= num;
51
+ srcMeanY /= num;
52
+ dstMeanX /= num;
53
+ dstMeanY /= num;
54
+ let srcVar = 0;
55
+ let crossCovarianceX = 0; // term 1 of numerator
56
+ let crossCovarianceY = 0; // term 2 of numerator
57
+ for (let i = 0; i < num; i++) {
58
+ const srcDiffX = src[i][0] - srcMeanX;
59
+ const srcDiffY = src[i][1] - srcMeanY;
60
+ const dstDiffX = dst[i][0] - dstMeanX;
61
+ const dstDiffY = dst[i][1] - dstMeanY;
62
+ srcVar += srcDiffX * srcDiffX + srcDiffY * srcDiffY;
63
+ // Sum(x*x' + y*y') and Sum(x*y' - y*x')
64
+ // For Rotation + Scale estimation
65
+ crossCovarianceX += srcDiffX * dstDiffX + srcDiffY * dstDiffY;
66
+ crossCovarianceY += srcDiffX * dstDiffY - srcDiffY * dstDiffX;
67
+ }
68
+ if (srcVar === 0) {
69
+ throw new Error("estimateUmeyama: all landmarks are coincident — degenerate detection");
70
+ }
71
+ // Scale
72
+ const scale = Math.sqrt((crossCovarianceX * crossCovarianceX +
73
+ crossCovarianceY * crossCovarianceY) /
74
+ (srcVar * srcVar));
75
+ // Rotation (cos theta, sin theta)
76
+ const norm = Math.sqrt(crossCovarianceX * crossCovarianceX + crossCovarianceY * crossCovarianceY);
77
+ if (norm === 0) {
78
+ throw new Error("estimateUmeyama: zero covariance norm — landmarks may be collinear or degenerate");
79
+ }
80
+ const cosTheta = crossCovarianceX / norm;
81
+ const sinTheta = crossCovarianceY / norm;
82
+ // Combined parameters
83
+ const a = scale * cosTheta;
84
+ const b = -scale * sinTheta; // note: standard affine usually -sin
85
+ const c = scale * sinTheta;
86
+ const d = scale * cosTheta;
87
+ // Translation
88
+ const tx = dstMeanX - (a * srcMeanX + b * srcMeanY);
89
+ const ty = dstMeanY - (c * srcMeanX + d * srcMeanY);
90
+ // M = [[a, b, tx], [c, d, ty]]
91
+ return [a, b, tx, c, d, ty];
92
+ }
93
+ /**
94
+ * Inverts a 2x3 affine matrix.
95
+ * M = [[a, b, tx], [c, d, ty]]
96
+ * Inverse is needed to map destination pixels back to source pixels for sampling.
97
+ */
98
+ function invertAffineMatrix(m) {
99
+ const [a, b, tx, c, d, ty] = m;
100
+ const det = a * d - b * c;
101
+ if (Math.abs(det) < 1e-6) {
102
+ throw new Error("Matrix not invertible");
103
+ }
104
+ const invDet = 1.0 / det;
105
+ const A = d * invDet;
106
+ const B = -b * invDet;
107
+ const C = -c * invDet;
108
+ const D = a * invDet;
109
+ const TX = -(A * tx + B * ty);
110
+ const TY = -(C * tx + D * ty);
111
+ return [A, B, TX, C, D, TY];
112
+ }
113
+ /**
114
+ * Applies affine transformation to an image buffer using bilinear interpolation.
115
+ *
116
+ * @param srcData Float32Array containing source image data (CHW format: RRR...GGG...BBB...)
117
+ * @param srcWidth Width of source image
118
+ * @param srcHeight Height of source image
119
+ * @param matrix The 2x3 Affine Matrix computed by estimateUmeyama
120
+ * @param dstSize Output size (default 112)
121
+ * @returns Float32Array (CHW format) of size 3 * dstSize * dstSize, normalized
122
+ */
123
+ function warpAffine(srcData, srcWidth, srcHeight, matrix, dstSize = 112) {
124
+ // 1. Invert matrix to map dst -> src
125
+ const [a, b, tx, c, d, ty] = invertAffineMatrix(matrix);
126
+ const dstData = new Float32Array(3 * dstSize * dstSize);
127
+ const channelSize = dstSize * dstSize;
128
+ const srcChannelSize = srcWidth * srcHeight;
129
+ // 2. Iterate over destination pixels
130
+ for (let y = 0; y < dstSize; y++) {
131
+ for (let x = 0; x < dstSize; x++) {
132
+ // Map to source coordinates
133
+ const srcX = a * x + b * y + tx;
134
+ const srcY = c * x + d * y + ty;
135
+ // Bilinear Interpolation
136
+ // Check bounds (with 1px padding for interpolation)
137
+ if (srcX >= 0 &&
138
+ srcX <= srcWidth - 1 &&
139
+ srcY >= 0 &&
140
+ srcY <= srcHeight - 1) {
141
+ const x0 = Math.floor(srcX);
142
+ const y0 = Math.floor(srcY);
143
+ const x1 = Math.min(x0 + 1, srcWidth - 1);
144
+ const y1 = Math.min(y0 + 1, srcHeight - 1);
145
+ const dx = srcX - x0;
146
+ const dy = srcY - y0;
147
+ const w00 = (1 - dx) * (1 - dy);
148
+ const w10 = dx * (1 - dy);
149
+ const w01 = (1 - dx) * dy;
150
+ const w11 = dx * dy;
151
+ const baseIdx = y * dstSize + x;
152
+ const srcBase00 = y0 * srcWidth + x0;
153
+ const srcBase10 = y0 * srcWidth + x1;
154
+ const srcBase01 = y1 * srcWidth + x0;
155
+ const srcBase11 = y1 * srcWidth + x1;
156
+ // Process R, G, B channels
157
+ for (let ch = 0; ch < 3; ch++) {
158
+ const chOffsetDst = ch * channelSize;
159
+ const chOffsetSrc = ch * srcChannelSize;
160
+ /*
161
+ Note: srcData is already normalized (CHW float),
162
+ or raw (HWC uint8)?
163
+
164
+ FaceRecognition.ts preprocessImage gives us CHW normalized float32.
165
+ So we can just interpolate directly.
166
+ */
167
+ const val = srcData[chOffsetSrc + srcBase00] * w00 +
168
+ srcData[chOffsetSrc + srcBase10] * w10 +
169
+ srcData[chOffsetSrc + srcBase01] * w01 +
170
+ srcData[chOffsetSrc + srcBase11] * w11;
171
+ dstData[chOffsetDst + baseIdx] = val;
172
+ }
173
+ }
174
+ else {
175
+ // Out of bounds - pad with black (or mean -0.0 in normalized space? -1.0?)
176
+ // Insightface uses 0 which corresponds to 127.5 in pixel space if not normalized.
177
+ // Since our input is normalized (-1..1 approx), we should probably use -0.99 (black) or 0 (grey).
178
+ // Let's use 0 (grey) if input was centered.
179
+ // Effectively, if we assume input is normalized, 0.0 is grey.
180
+ // Let's stick to 0.0 for now, or copy nearest edge?
181
+ // For simplicity, 0.0 is safe.
182
+ }
183
+ }
184
+ }
185
+ return dstData;
186
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Copyright 2026 JupiterMeta Labs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import type { FaceZkModelsConfig } from "../../config/types";
17
+ /** Keys corresponding to each entry in FaceZkModelsConfig. */
18
+ export type ModelKey = "detection" | "recognition" | "antispoof" | "wasm" | "zkWorkerHtml";
19
+ export interface ModelReadinessResult {
20
+ /** True when every configured model source is present on device. */
21
+ ready: boolean;
22
+ /** Models that are configured but not yet available locally. */
23
+ missing: ModelKey[];
24
+ /** Models that are configured and already available locally. */
25
+ present: ModelKey[];
26
+ }
27
+ /**
28
+ * Check whether all configured model sources are already resolved locally.
29
+ *
30
+ * Does NOT download anything. Safe to call on every app launch — fast when
31
+ * all models are present (only stat calls, no network).
32
+ *
33
+ * @param models The same FaceZkModelsConfig you intend to pass to initializeSdk().
34
+ * @returns Readiness result with `ready` flag and `missing`/`present` arrays.
35
+ */
36
+ export declare function modelInitialisationChecks(models: FaceZkModelsConfig): Promise<ModelReadinessResult>;