@jupitermetalabs/face-zk-sdk 0.3.1 → 0.3.3

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.
@@ -9,7 +9,7 @@ const arrowRight = document.getElementById('arrow_right');
9
9
 
10
10
  // Configuration
11
11
  let TARGET_POSE = { yaw: 0, pitch: 0, roll: 0 };
12
- let REQUIRED_STABLE_DURATION = 2000; // 2 seconds
12
+ let REQUIRED_STABLE_DURATION = 1000; // 1 second
13
13
  let YAW_THRESHOLD = 6; // degrees
14
14
  let PITCH_THRESHOLD = 10; // degrees
15
15
  let GAZE_THRESHOLD = 0.3; // Deviation from center (0.5)
@@ -17,18 +17,17 @@
17
17
  "use strict";
18
18
 
19
19
  /**
20
- * Copies bundled WASM and worker assets into dist/assets/ after tsc compilation.
20
+ * Copies all bundled runtime assets into dist/assets/ after tsc compilation.
21
21
  *
22
- * The compiled JS in dist/react-native/hooks/ references these assets with
23
- * relative paths like ../../assets/wasm/... which resolve to dist/assets/wasm/
24
- * at runtime (Metro bundler resolves relative to the compiled file location).
22
+ * Why this is needed:
23
+ * bundledRuntimeAssets.ts lives at react-native/bundledRuntimeAssets.ts and
24
+ * uses require('../assets/...') paths. After tsc, the compiled file lands at
25
+ * dist/react-native/bundledRuntimeAssets.js — so '../assets/...' resolves to
26
+ * dist/assets/... at Metro bundle time. This script ensures all those files
27
+ * exist under dist/assets/.
25
28
  *
26
29
  * Runs automatically as part of the "build" npm script.
27
30
  * Safe to run multiple times (idempotent).
28
- *
29
- * Files copied:
30
- * assets/wasm/zk_face_wasm_bg.wasm → dist/assets/wasm/zk_face_wasm_bg.wasm
31
- * assets/zk-worker.html → dist/assets/zk-worker.html
32
31
  */
33
32
 
34
33
  const fs = require("fs");
@@ -37,23 +36,69 @@ const path = require("path");
37
36
  const ROOT = path.resolve(__dirname, "..");
38
37
  const DIST_ASSETS = path.resolve(ROOT, "dist/assets");
39
38
 
40
- const COPIES = [
41
- { src: "assets/wasm/zk_face_wasm_bg.wasm", dst: "wasm/zk_face_wasm_bg.wasm" },
42
- { src: "assets/zk-worker.html", dst: "zk-worker.html" },
39
+ // Individual file copies: [src relative to ROOT, dst relative to DIST_ASSETS]
40
+ const FILE_COPIES = [
41
+ // ZK WASM
42
+ { src: "assets/wasm/zk_face_wasm_bg.wasm", dst: "wasm/zk_face_wasm_bg.wasm" },
43
+ { src: "assets/zk-worker.html", dst: "zk-worker.html" },
44
+ // ORT Runtime
45
+ { src: "assets/onnx/ort.min.js.txt", dst: "onnx/ort.min.js.txt" },
46
+ { src: "assets/onnx/ort-wasm-simd.wasm", dst: "onnx/ort-wasm-simd.wasm" },
47
+ { src: "assets/onnx/ort-wasm.wasm", dst: "onnx/ort-wasm.wasm" },
48
+ // Liveness
49
+ { src: "assets/liveness/index.html", dst: "liveness/index.html" },
50
+ { src: "assets/liveness/antispoof.js.txt", dst: "liveness/antispoof.js.txt" },
51
+ { src: "assets/liveness/liveness.js.txt", dst: "liveness/liveness.js.txt" },
52
+ ];
53
+
54
+ // Directory copies: entire source directory mirrored under DIST_ASSETS
55
+ const DIR_COPIES = [
56
+ { src: "assets/mediapipe", dst: "mediapipe" },
57
+ { src: "assets/face-guidance", dst: "face-guidance" },
43
58
  ];
44
59
 
45
- fs.mkdirSync(path.join(DIST_ASSETS, "wasm"), { recursive: true });
60
+ // ── Helpers ──────────────────────────────────────────────────────────────────
46
61
 
47
- for (const { src, dst } of COPIES) {
62
+ function copyFile(src, dst) {
48
63
  const srcPath = path.join(ROOT, src);
49
64
  const dstPath = path.join(DIST_ASSETS, dst);
50
65
 
51
66
  if (!fs.existsSync(srcPath)) {
52
67
  process.stderr.write(`[copy-dist-assets] WARNING: ${src} not found — skipping\n`);
53
- continue;
68
+ return;
54
69
  }
55
70
 
71
+ fs.mkdirSync(path.dirname(dstPath), { recursive: true });
56
72
  fs.copyFileSync(srcPath, dstPath);
57
73
  const size = Math.round(fs.statSync(dstPath).size / 1024);
58
74
  process.stdout.write(`[copy-dist-assets] copied ${src} → dist/assets/${dst} (${size} KB)\n`);
59
75
  }
76
+
77
+ function copyDir(srcRel, dstRel) {
78
+ const srcPath = path.join(ROOT, srcRel);
79
+ const dstPath = path.join(DIST_ASSETS, dstRel);
80
+
81
+ if (!fs.existsSync(srcPath)) {
82
+ process.stderr.write(`[copy-dist-assets] WARNING: ${srcRel}/ not found — skipping\n`);
83
+ return;
84
+ }
85
+
86
+ fs.mkdirSync(dstPath, { recursive: true });
87
+
88
+ for (const entry of fs.readdirSync(srcPath, { withFileTypes: true })) {
89
+ if (entry.isFile()) {
90
+ copyFile(path.join(srcRel, entry.name), path.join(dstRel, entry.name));
91
+ }
92
+ // Not recursing into subdirectories — all current asset dirs are flat
93
+ }
94
+ }
95
+
96
+ // ── Run ───────────────────────────────────────────────────────────────────────
97
+
98
+ for (const { src, dst } of FILE_COPIES) {
99
+ copyFile(src, dst);
100
+ }
101
+
102
+ for (const { src, dst } of DIR_COPIES) {
103
+ copyDir(src, dst);
104
+ }
@@ -0,0 +1,77 @@
1
+ /* Face Pose Calculation Logic */
2
+
3
+ // --- POSE CALCULATION ---
4
+ // Distance-invariant yaw/pitch calculation with face dimension normalization
5
+ // This matches the calculation in liveness.js for consistent TARGET and CURRENT pose comparison
6
+ function calculatePose(landmarks) {
7
+ const nose = landmarks[1];
8
+ const leftCheek = landmarks[234];
9
+ const rightCheek = landmarks[454];
10
+
11
+ // Face Width for normalization (makes calculation distance-invariant)
12
+ const faceWidth = Math.hypot(
13
+ rightCheek.x - leftCheek.x,
14
+ rightCheek.y - leftCheek.y
15
+ );
16
+
17
+ // Yaw: Rotation around Y axis (Turning Left/Right)
18
+ const midPointX = (leftCheek.x + rightCheek.x) / 2;
19
+ // Normalize by face width to get comparable angle regardless of distance
20
+ // Multiplier 180 is heuristic to match standard degrees
21
+ const yaw = ((nose.x - midPointX) / faceWidth) * 180;
22
+
23
+ // Pitch: Rotation around X axis (Looking Up/Down)
24
+ const midEyeY = (landmarks[33].y + landmarks[263].y) / 2;
25
+ const mouthY = (landmarks[13].y + landmarks[14].y) / 2;
26
+ const midFaceY = (midEyeY + mouthY) / 2;
27
+
28
+ // Face Height for normalization (chin to forehead)
29
+ const chin = landmarks[152];
30
+ const forehead = landmarks[10];
31
+ const faceHeight = Math.hypot(
32
+ chin.x - forehead.x,
33
+ chin.y - forehead.y
34
+ );
35
+
36
+ const pitch = ((nose.y - midFaceY) / faceHeight) * 180;
37
+
38
+ return { yaw, pitch, roll: 0 };
39
+ }
40
+
41
+ function calculateGaze(landmarks) {
42
+ if (!landmarks[468] || !landmarks[473]) return { x: 0.5, y: 0.5 }; // Default centered if no iris
43
+
44
+ // Left Eye (User's Left = Right on screen if mirrored, but landmarks are consistent)
45
+ // Left Eye Inner: 362, Outer: 263, Iris: 473
46
+ const rightEyeInner = landmarks[362];
47
+ const rightEyeOuter = landmarks[263];
48
+ const rightIris = landmarks[473];
49
+
50
+ // Right Eye (User's Right)
51
+ // Right Eye Inner: 33, Outer: 133, Iris: 468
52
+ const leftEyeInner = landmarks[33];
53
+ const leftEyeOuter = landmarks[133];
54
+ const leftIris = landmarks[468]; // 468 is Left Iris Center
55
+
56
+ // Calculate Horizontal Ratio (0 = Looking Left, 1 = Looking Right, 0.5 = Center)
57
+
58
+ // Left Eye horizontal distance
59
+ const leftEyeWidth = Math.hypot(leftEyeOuter.x - leftEyeInner.x, leftEyeOuter.y - leftEyeInner.y);
60
+ const leftIrisDist = Math.hypot(leftIris.x - leftEyeInner.x, leftIris.y - leftEyeInner.y);
61
+ const leftRatio = leftIrisDist / leftEyeWidth;
62
+
63
+ // Right Eye horizontal distance
64
+ const rightEyeWidth = Math.hypot(rightEyeOuter.x - rightEyeInner.x, rightEyeOuter.y - rightEyeInner.y);
65
+ const rightIrisDist = Math.hypot(rightIris.x - rightEyeInner.x, rightIris.y - rightEyeInner.y);
66
+ const rightRatio = rightIrisDist / rightEyeWidth;
67
+
68
+ // Average ratio
69
+ const avgRatio = (leftRatio + rightRatio) / 2;
70
+ return { x: avgRatio, y: 0.5 };
71
+ }
72
+
73
+ // Export functions if using modules, but here we likely just inject into global scope
74
+ if (typeof window !== 'undefined') {
75
+ window.calculatePose = calculatePose;
76
+ window.calculateGaze = calculateGaze;
77
+ }
@@ -0,0 +1,173 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
8
+ />
9
+ <title>Face Pose Guidance</title>
10
+
11
+ <!-- Tailwind -->
12
+ <script src="https://cdn.tailwindcss.com"></script>
13
+
14
+ <!-- MediaPipe -->
15
+ <script
16
+ src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"
17
+ crossorigin="anonymous"
18
+ ></script>
19
+ <script
20
+ src="https://cdn.jsdelivr.net/npm/@mediapipe/control_utils/control_utils.js"
21
+ crossorigin="anonymous"
22
+ ></script>
23
+ <script
24
+ src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
25
+ crossorigin="anonymous"
26
+ ></script>
27
+ <script
28
+ src="https://cdn.jsdelivr.net/npm/@mediapipe/face_mesh/face_mesh.js"
29
+ crossorigin="anonymous"
30
+ ></script>
31
+ <!-- ONNX Runtime Web -->
32
+ <script src="https://cdn.jsdelivr.net/npm/onnxruntime-web@1.14.0/dist/ort.min.js"></script>
33
+
34
+ <style>
35
+ .camera-wrapper {
36
+ position: relative;
37
+ width: 100vw;
38
+ height: 100vh;
39
+ overflow: hidden;
40
+ background: #000;
41
+ display: flex;
42
+ justify-content: center;
43
+ align-items: center;
44
+ }
45
+
46
+ #input_video {
47
+ display: none;
48
+ }
49
+
50
+ #output_canvas {
51
+ max-width: 100%;
52
+ max-height: 100%;
53
+ object-fit: contain;
54
+ }
55
+
56
+ .mirror {
57
+ transform: scaleX(-1);
58
+ }
59
+
60
+ .overlay {
61
+ position: absolute;
62
+ top: 0;
63
+ left: 0;
64
+ right: 0;
65
+ bottom: 0;
66
+ pointer-events: none;
67
+ display: flex;
68
+ flex-direction: column;
69
+ justify-content: space-between;
70
+ padding: 40px 20px;
71
+ z-index: 10;
72
+ }
73
+
74
+ .message-box {
75
+ background: rgba(0, 0, 0, 0.6);
76
+ backdrop-filter: blur(4px);
77
+ padding: 16px 24px;
78
+ border-radius: 12px;
79
+ text-align: center;
80
+ margin: 0 auto;
81
+ border: 1px solid rgba(255, 255, 255, 0.1);
82
+ }
83
+
84
+ .guidance-arrow {
85
+ position: absolute;
86
+ top: 50%;
87
+ font-size: 4rem;
88
+ color: white;
89
+ text-shadow: 0 4px 8px rgba(0, 0, 0, 0.5);
90
+ opacity: 0;
91
+ transition: opacity 0.3s;
92
+ transform: translateY(-50%);
93
+ }
94
+
95
+ .arrow-left {
96
+ left: 20px;
97
+ animation: bounce-left 1s infinite;
98
+ }
99
+
100
+ .arrow-right {
101
+ right: 20px;
102
+ animation: bounce-right 1s infinite;
103
+ }
104
+
105
+ @keyframes bounce-left {
106
+ 0%,
107
+ 100% {
108
+ transform: translate(0, -50%);
109
+ }
110
+ 50% {
111
+ transform: translate(-10px, -50%);
112
+ }
113
+ }
114
+
115
+ @keyframes bounce-right {
116
+ 0%,
117
+ 100% {
118
+ transform: translate(0, -50%);
119
+ }
120
+ 50% {
121
+ transform: translate(10px, -50%);
122
+ }
123
+ }
124
+ </style>
125
+ </head>
126
+ <body class="bg-black m-0 overflow-hidden">
127
+ <div class="camera-wrapper">
128
+ <video id="input_video" playsinline></video>
129
+ <canvas id="output_canvas"></canvas>
130
+
131
+ <div class="overlay">
132
+ <!-- Top Status -->
133
+ <div id="status_box" class="message-box">
134
+ <p id="status_text" class="text-white text-lg font-medium">
135
+ Initializing...
136
+ </p>
137
+ </div>
138
+
139
+ <!-- Guidance Elements -->
140
+ <div id="arrow_left" class="guidance-arrow arrow-left">⬅️</div>
141
+ <div id="arrow_right" class="guidance-arrow arrow-right">➡️</div>
142
+
143
+ <!-- Bottom Status -->
144
+ <div id="instruction_box" class="message-box hidden">
145
+ <p id="instruction_text" class="text-slate-300 text-sm">
146
+ Align your face
147
+ </p>
148
+ </div>
149
+ </div>
150
+ </div>
151
+
152
+ <div
153
+ id="debug_info"
154
+ style="
155
+ position: absolute;
156
+ top: 10px;
157
+ left: 10px;
158
+ background: rgba(0, 0, 0, 0.7);
159
+ color: lime;
160
+ font-family: monospace;
161
+ font-size: 12px;
162
+ padding: 8px;
163
+ border-radius: 4px;
164
+ pointer-events: none;
165
+ z-index: 100;
166
+ white-space: pre;
167
+ "
168
+ ></div>
169
+
170
+ <!-- Main Logic Script -->
171
+ <script src="pose-guidance.js"></script>
172
+ </body>
173
+ </html>