@moveris/shared 2.0.0 → 2.1.1
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 +76 -9
- package/dist/index.d.mts +205 -106
- package/dist/index.d.ts +205 -106
- package/dist/index.js +117 -16
- package/dist/index.mjs +108 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,11 +80,33 @@ const result = await client.fastCheck(frames, {
|
|
|
80
80
|
|
|
81
81
|
##### `fastCheckCrops(crops, options)`
|
|
82
82
|
|
|
83
|
-
Perform fast liveness check with pre-cropped 224x224 face images. Use this when you handle face detection client-side.
|
|
83
|
+
Perform fast liveness check with pre-cropped 224x224 face images. Use this when you handle face detection client-side. Expects `CropData[]` (no timestamps — only `index` and `pixels`).
|
|
84
84
|
|
|
85
85
|
```typescript
|
|
86
|
+
import type { CropData } from '@moveris/shared';
|
|
87
|
+
|
|
88
|
+
const crops: CropData[] = capturedFrames.map((f) => ({
|
|
89
|
+
index: f.index,
|
|
90
|
+
pixels: f.pixels, // base64 224x224 PNG
|
|
91
|
+
}));
|
|
92
|
+
|
|
86
93
|
const result = await client.fastCheckCrops(crops, {
|
|
87
|
-
model: '
|
|
94
|
+
model: '10',
|
|
95
|
+
source: 'live',
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
##### `streamFrame(frame, options)`
|
|
100
|
+
|
|
101
|
+
Send a single frame to the streaming endpoint. Returns the API response which may include a partial or final result. Used internally by `useLiveness` for sequential streaming.
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const response = await client.streamFrame(frame, {
|
|
105
|
+
sessionId: 'uuid',
|
|
106
|
+
model: '10',
|
|
107
|
+
source: 'live',
|
|
108
|
+
frameIndex: 0,
|
|
109
|
+
totalFrames: 10,
|
|
88
110
|
});
|
|
89
111
|
```
|
|
90
112
|
|
|
@@ -160,14 +182,23 @@ const job = await client.waitForJobResult('job-uuid', 30);
|
|
|
160
182
|
Model selection for liveness detection speed/accuracy trade-off.
|
|
161
183
|
|
|
162
184
|
```typescript
|
|
163
|
-
type FastCheckModel =
|
|
185
|
+
type FastCheckModel =
|
|
186
|
+
| '10'
|
|
187
|
+
| '50'
|
|
188
|
+
| '250' // Standard models
|
|
189
|
+
| 'hybrid-v2-10'
|
|
190
|
+
| 'hybrid-v2-50' // Hybrid V2 (physiological features)
|
|
191
|
+
| 'mixed-10'; // Mixed (visual + physiological scoring)
|
|
164
192
|
```
|
|
165
193
|
|
|
166
|
-
| Value
|
|
167
|
-
|
|
|
168
|
-
| `'10'`
|
|
169
|
-
| `'50'`
|
|
170
|
-
| `'250'`
|
|
194
|
+
| Value | Frames | Category | Description |
|
|
195
|
+
| ---------------- | ------ | --------- | ----------------------------------------- |
|
|
196
|
+
| `'10'` | 10 | Standard | Fast verification, lowest latency |
|
|
197
|
+
| `'50'` | 50 | Standard | Balanced speed and accuracy |
|
|
198
|
+
| `'250'` | 250 | Standard | Highest accuracy, slower |
|
|
199
|
+
| `'hybrid-v2-10'` | 10 | Hybrid V2 | Visual + physiological feature extraction |
|
|
200
|
+
| `'hybrid-v2-50'` | 50 | Hybrid V2 | Higher accuracy physiological features |
|
|
201
|
+
| `'mixed-10'` | 10 | Mixed | Combined visual + physiological scoring |
|
|
171
202
|
|
|
172
203
|
#### FrameSource
|
|
173
204
|
|
|
@@ -236,6 +267,17 @@ interface CapturedFrame {
|
|
|
236
267
|
}
|
|
237
268
|
```
|
|
238
269
|
|
|
270
|
+
#### CropData
|
|
271
|
+
|
|
272
|
+
Pre-cropped face image for the `fast-check-crops` endpoint. Omits `timestampMs` since timing is not needed for cropped submissions.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
interface CropData {
|
|
276
|
+
index: number; // Frame sequence number (0-based)
|
|
277
|
+
pixels: string; // Base64-encoded 224x224 PNG image
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
239
281
|
---
|
|
240
282
|
|
|
241
283
|
### Utilities
|
|
@@ -424,10 +466,28 @@ const inOval = isFaceInOval(faceBbox, ovalRegion);
|
|
|
424
466
|
console.log(inOval.isInOval); // true/false
|
|
425
467
|
console.log(inOval.alignmentScore); // 0-1
|
|
426
468
|
|
|
427
|
-
// Calculate crop region for face
|
|
469
|
+
// Calculate crop region for face (224x224, aligned with cognito-check constants)
|
|
428
470
|
const cropRegion = calculateFaceCropRegion(faceBbox, frameWidth, frameHeight);
|
|
471
|
+
// { x, y, size } — square crop region in pixel coordinates
|
|
472
|
+
|
|
473
|
+
// Calculate adaptive crop multiplier based on face size
|
|
474
|
+
import { calculateAdaptiveCropMultiplier } from '@moveris/shared';
|
|
475
|
+
const multiplier = calculateAdaptiveCropMultiplier(faceSize, videoSize);
|
|
476
|
+
// Returns 1.8–2.5x (matched to cognito-check for ~50% face coverage)
|
|
429
477
|
```
|
|
430
478
|
|
|
479
|
+
#### Crop Constants (aligned with cognito-check)
|
|
480
|
+
|
|
481
|
+
These constants control how face crops are generated for the `fast-check-crops` endpoint:
|
|
482
|
+
|
|
483
|
+
| Constant | Value | Description |
|
|
484
|
+
| -------------------------------- | ----- | ------------------------------------------------ |
|
|
485
|
+
| `IDEAL_CROP_MULTIPLIER` | 2.0 | Default crop region = 2x face size |
|
|
486
|
+
| `MIN_CROP_MULTIPLIER` | 1.8 | Minimum crop (face very close) |
|
|
487
|
+
| `MAX_CROP_MULTIPLIER` | 2.5 | Maximum crop (face very far) |
|
|
488
|
+
| `FACE_CENTER_VERTICAL_OFFSET` | 0.05 | Slight upward shift to include forehead for rPPG |
|
|
489
|
+
| `TARGET_FACE_PERCENTAGE_IN_CROP` | 0.5 | Face should occupy ~50% of 224x224 crop |
|
|
490
|
+
|
|
431
491
|
---
|
|
432
492
|
|
|
433
493
|
## Configuration Constants
|
|
@@ -456,7 +516,14 @@ import type {
|
|
|
456
516
|
FrameSource,
|
|
457
517
|
Verdict,
|
|
458
518
|
CapturedFrame,
|
|
519
|
+
CropData,
|
|
459
520
|
LivenessClientConfig,
|
|
521
|
+
DetectionResult,
|
|
522
|
+
DetectionSummary,
|
|
523
|
+
FaceBoundingBox,
|
|
524
|
+
HeadPose,
|
|
525
|
+
FaceLandmarkPoint,
|
|
526
|
+
LandmarkValidationResult,
|
|
460
527
|
} from '@moveris/shared';
|
|
461
528
|
```
|
|
462
529
|
|
package/dist/index.d.mts
CHANGED
|
@@ -204,6 +204,204 @@ interface LivenessCallbacks {
|
|
|
204
204
|
onStateChange?: OnStateChangeCallback;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
interface FaceBoundingBox {
|
|
208
|
+
originX: number;
|
|
209
|
+
originY: number;
|
|
210
|
+
width: number;
|
|
211
|
+
height: number;
|
|
212
|
+
}
|
|
213
|
+
interface BlurAnalysis {
|
|
214
|
+
variance: number;
|
|
215
|
+
isBlurry: boolean;
|
|
216
|
+
threshold: number;
|
|
217
|
+
}
|
|
218
|
+
interface LightingAnalysis {
|
|
219
|
+
faceBrightness: number;
|
|
220
|
+
backgroundBrightness: number;
|
|
221
|
+
ratio: number;
|
|
222
|
+
status: 'good' | 'backlit' | 'low_light';
|
|
223
|
+
warning?: string;
|
|
224
|
+
}
|
|
225
|
+
interface FaceVisibilityResult {
|
|
226
|
+
visible: boolean;
|
|
227
|
+
reason?: string;
|
|
228
|
+
}
|
|
229
|
+
interface FaceAlignmentResult {
|
|
230
|
+
score: number;
|
|
231
|
+
tooClose: boolean;
|
|
232
|
+
tooFar: boolean;
|
|
233
|
+
}
|
|
234
|
+
interface OvalRegion {
|
|
235
|
+
centerX: number;
|
|
236
|
+
centerY: number;
|
|
237
|
+
width: number;
|
|
238
|
+
height: number;
|
|
239
|
+
}
|
|
240
|
+
interface FaceInOvalResult {
|
|
241
|
+
isInside: boolean;
|
|
242
|
+
centerInOval: boolean;
|
|
243
|
+
sizeMatch: boolean;
|
|
244
|
+
feedback?: string;
|
|
245
|
+
}
|
|
246
|
+
interface FrameQualityResult {
|
|
247
|
+
passed: boolean;
|
|
248
|
+
blur?: BlurAnalysis;
|
|
249
|
+
lighting?: LightingAnalysis;
|
|
250
|
+
visibility?: FaceVisibilityResult;
|
|
251
|
+
alignment?: FaceAlignmentResult;
|
|
252
|
+
rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
|
|
253
|
+
}
|
|
254
|
+
declare const DEFAULT_BLUR_THRESHOLD = 100;
|
|
255
|
+
declare const BLUR_THRESHOLD_MOBILE = 150;
|
|
256
|
+
declare const BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
257
|
+
declare const LOW_LIGHT_THRESHOLD = 50;
|
|
258
|
+
declare const MIN_FACE_TOP_MARGIN = 0.1;
|
|
259
|
+
declare const MIN_FACE_BOTTOM_MARGIN = 0.08;
|
|
260
|
+
declare const MIN_FACE_SIDE_MARGIN = 0.05;
|
|
261
|
+
declare const MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
262
|
+
declare const HIGH_ALIGNMENT = 0.85;
|
|
263
|
+
declare const GOOD_ALIGNMENT = 0.5;
|
|
264
|
+
declare const IDEAL_CROP_MULTIPLIER = 2;
|
|
265
|
+
declare const MIN_CROP_MULTIPLIER = 1.8;
|
|
266
|
+
declare const MAX_CROP_MULTIPLIER = 2.5;
|
|
267
|
+
declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
268
|
+
declare const MIN_FACE_RATIO = 0.036;
|
|
269
|
+
declare const MAX_FACE_RATIO = 0.7;
|
|
270
|
+
declare const FACE_CROP_OUTPUT_SIZE = 224;
|
|
271
|
+
declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
|
|
272
|
+
declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
|
|
273
|
+
declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
|
|
274
|
+
declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
|
|
275
|
+
declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
|
|
276
|
+
declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
|
|
277
|
+
declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
|
|
278
|
+
declare const DEFAULT_OVAL_REGION: OvalRegion;
|
|
279
|
+
declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
|
|
280
|
+
declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
|
|
281
|
+
declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
|
|
282
|
+
declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
|
|
283
|
+
declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
|
|
284
|
+
x: number;
|
|
285
|
+
y: number;
|
|
286
|
+
size: number;
|
|
287
|
+
};
|
|
288
|
+
declare function checkFrameQuality(options: {
|
|
289
|
+
faceBox?: FaceBoundingBox;
|
|
290
|
+
frameWidth: number;
|
|
291
|
+
frameHeight: number;
|
|
292
|
+
blurAnalysis?: BlurAnalysis;
|
|
293
|
+
lightingAnalysis?: LightingAnalysis;
|
|
294
|
+
minAlignment?: number;
|
|
295
|
+
}): FrameQualityResult;
|
|
296
|
+
declare class BaseFrameCollector {
|
|
297
|
+
protected frames: CapturedFrame[];
|
|
298
|
+
protected maxFrames: number;
|
|
299
|
+
protected startTime: number;
|
|
300
|
+
constructor(maxFrames?: number);
|
|
301
|
+
setMaxFrames(max: number): void;
|
|
302
|
+
addFrame(frame: CapturedFrame): void;
|
|
303
|
+
getFrames(): CapturedFrame[];
|
|
304
|
+
getCount(): number;
|
|
305
|
+
isComplete(): boolean;
|
|
306
|
+
reset(): void;
|
|
307
|
+
getStartTime(): number;
|
|
308
|
+
getNextIndex(): number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
interface FaceLandmarkPoint {
|
|
312
|
+
x: number;
|
|
313
|
+
y: number;
|
|
314
|
+
z: number;
|
|
315
|
+
}
|
|
316
|
+
interface LandmarkValidationResult {
|
|
317
|
+
valid: boolean;
|
|
318
|
+
message?: string;
|
|
319
|
+
}
|
|
320
|
+
declare const LANDMARK_INDEX: {
|
|
321
|
+
readonly NOSE_TIP: 1;
|
|
322
|
+
readonly UPPER_LIP: 13;
|
|
323
|
+
readonly LOWER_LIP: 14;
|
|
324
|
+
};
|
|
325
|
+
declare const LANDMARK_MIN_BOUND = 0.1;
|
|
326
|
+
declare const LANDMARK_MAX_BOUND = 0.9;
|
|
327
|
+
declare const MIN_LANDMARK_COUNT = 15;
|
|
328
|
+
declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
|
|
329
|
+
|
|
330
|
+
interface DetectionResult {
|
|
331
|
+
type: string;
|
|
332
|
+
passed: boolean;
|
|
333
|
+
confidence: number;
|
|
334
|
+
message?: string;
|
|
335
|
+
metadata?: Record<string, unknown>;
|
|
336
|
+
}
|
|
337
|
+
interface DetectorConfig {
|
|
338
|
+
enabled: boolean;
|
|
339
|
+
minConfidence?: number;
|
|
340
|
+
}
|
|
341
|
+
interface DetectionSummary {
|
|
342
|
+
allPassed: boolean;
|
|
343
|
+
results: Map<string, DetectionResult>;
|
|
344
|
+
faceBox?: FaceBoundingBox;
|
|
345
|
+
faceLandmarks?: FaceLandmarkPoint[];
|
|
346
|
+
warnings: string[];
|
|
347
|
+
}
|
|
348
|
+
interface HeadPose {
|
|
349
|
+
yawDeg: number;
|
|
350
|
+
pitchDeg: number;
|
|
351
|
+
rollDeg: number;
|
|
352
|
+
}
|
|
353
|
+
interface GazeThresholds {
|
|
354
|
+
maxYaw: number;
|
|
355
|
+
maxPitch: number;
|
|
356
|
+
}
|
|
357
|
+
declare const DEFAULT_GAZE_THRESHOLDS: GazeThresholds;
|
|
358
|
+
interface HandOcclusionConfig {
|
|
359
|
+
faceExpansionFactor: number;
|
|
360
|
+
minLandmarksForOcclusion: number;
|
|
361
|
+
faceBoxExpiryMs: number;
|
|
362
|
+
centerRegion: number;
|
|
363
|
+
}
|
|
364
|
+
declare const DEFAULT_HAND_OCCLUSION_CONFIG: HandOcclusionConfig;
|
|
365
|
+
interface FaceDetectionTiers {
|
|
366
|
+
primaryConfidence: number;
|
|
367
|
+
secondaryConfidence: number;
|
|
368
|
+
}
|
|
369
|
+
declare const DEFAULT_FACE_DETECTION_TIERS: FaceDetectionTiers;
|
|
370
|
+
interface VideoFrameMetadata {
|
|
371
|
+
presentationTime: number;
|
|
372
|
+
expectedDisplayTime: number;
|
|
373
|
+
width: number;
|
|
374
|
+
height: number;
|
|
375
|
+
mediaTime: number;
|
|
376
|
+
presentedFrames: number;
|
|
377
|
+
processingDuration?: number;
|
|
378
|
+
}
|
|
379
|
+
interface StabilizationProgress {
|
|
380
|
+
elapsed: number;
|
|
381
|
+
stableFrameCount: number;
|
|
382
|
+
requiredFrames: number;
|
|
383
|
+
greenMean: number;
|
|
384
|
+
greenDelta: number | null;
|
|
385
|
+
threshold: number;
|
|
386
|
+
isStable: boolean;
|
|
387
|
+
progress: number;
|
|
388
|
+
}
|
|
389
|
+
interface StabilizationResult {
|
|
390
|
+
success: boolean;
|
|
391
|
+
elapsed: number;
|
|
392
|
+
finalGreenMean: number;
|
|
393
|
+
stableFrameCount: number;
|
|
394
|
+
message: string;
|
|
395
|
+
}
|
|
396
|
+
interface StabilizerConfig {
|
|
397
|
+
stabilityThreshold: number;
|
|
398
|
+
requiredStableFrames: number;
|
|
399
|
+
maxWaitMs: number;
|
|
400
|
+
checkIntervalMs: number;
|
|
401
|
+
sampleSize: number;
|
|
402
|
+
}
|
|
403
|
+
declare const DEFAULT_STABILIZER_CONFIG: StabilizerConfig;
|
|
404
|
+
|
|
207
405
|
interface LivenessClientConfig {
|
|
208
406
|
baseUrl?: string;
|
|
209
407
|
apiKey: string;
|
|
@@ -244,7 +442,12 @@ declare class LivenessClient {
|
|
|
244
442
|
model?: FastCheckModel;
|
|
245
443
|
source?: FrameSource;
|
|
246
444
|
}): Promise<LivenessResult>;
|
|
247
|
-
|
|
445
|
+
streamFrame(frame: CapturedFrame, options: {
|
|
446
|
+
sessionId: string;
|
|
447
|
+
model?: FastCheckModel;
|
|
448
|
+
source?: FrameSource;
|
|
449
|
+
}): Promise<FastCheckStreamResponse>;
|
|
450
|
+
private sendStreamFrameInternal;
|
|
248
451
|
fastCheckStream(frames: CapturedFrame[], options?: {
|
|
249
452
|
sessionId?: string;
|
|
250
453
|
model?: FastCheckModel;
|
|
@@ -480,108 +683,4 @@ interface RetryOptions {
|
|
|
480
683
|
declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
481
684
|
declare function sleep(ms: number): Promise<void>;
|
|
482
685
|
|
|
483
|
-
|
|
484
|
-
originX: number;
|
|
485
|
-
originY: number;
|
|
486
|
-
width: number;
|
|
487
|
-
height: number;
|
|
488
|
-
}
|
|
489
|
-
interface BlurAnalysis {
|
|
490
|
-
variance: number;
|
|
491
|
-
isBlurry: boolean;
|
|
492
|
-
threshold: number;
|
|
493
|
-
}
|
|
494
|
-
interface LightingAnalysis {
|
|
495
|
-
faceBrightness: number;
|
|
496
|
-
backgroundBrightness: number;
|
|
497
|
-
ratio: number;
|
|
498
|
-
status: 'good' | 'backlit' | 'low_light';
|
|
499
|
-
warning?: string;
|
|
500
|
-
}
|
|
501
|
-
interface FaceVisibilityResult {
|
|
502
|
-
visible: boolean;
|
|
503
|
-
reason?: string;
|
|
504
|
-
}
|
|
505
|
-
interface FaceAlignmentResult {
|
|
506
|
-
score: number;
|
|
507
|
-
tooClose: boolean;
|
|
508
|
-
tooFar: boolean;
|
|
509
|
-
}
|
|
510
|
-
interface OvalRegion {
|
|
511
|
-
centerX: number;
|
|
512
|
-
centerY: number;
|
|
513
|
-
width: number;
|
|
514
|
-
height: number;
|
|
515
|
-
}
|
|
516
|
-
interface FaceInOvalResult {
|
|
517
|
-
isInside: boolean;
|
|
518
|
-
centerInOval: boolean;
|
|
519
|
-
sizeMatch: boolean;
|
|
520
|
-
feedback?: string;
|
|
521
|
-
}
|
|
522
|
-
interface FrameQualityResult {
|
|
523
|
-
passed: boolean;
|
|
524
|
-
blur?: BlurAnalysis;
|
|
525
|
-
lighting?: LightingAnalysis;
|
|
526
|
-
visibility?: FaceVisibilityResult;
|
|
527
|
-
alignment?: FaceAlignmentResult;
|
|
528
|
-
rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
|
|
529
|
-
}
|
|
530
|
-
declare const DEFAULT_BLUR_THRESHOLD = 100;
|
|
531
|
-
declare const BLUR_THRESHOLD_MOBILE = 150;
|
|
532
|
-
declare const BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
533
|
-
declare const LOW_LIGHT_THRESHOLD = 50;
|
|
534
|
-
declare const MIN_FACE_TOP_MARGIN = 0.1;
|
|
535
|
-
declare const MIN_FACE_BOTTOM_MARGIN = 0.08;
|
|
536
|
-
declare const MIN_FACE_SIDE_MARGIN = 0.05;
|
|
537
|
-
declare const MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
538
|
-
declare const HIGH_ALIGNMENT = 0.85;
|
|
539
|
-
declare const GOOD_ALIGNMENT = 0.5;
|
|
540
|
-
declare const IDEAL_CROP_MULTIPLIER = 3.33;
|
|
541
|
-
declare const MIN_CROP_MULTIPLIER = 1.5;
|
|
542
|
-
declare const MAX_CROP_MULTIPLIER = 4;
|
|
543
|
-
declare const FACE_CENTER_VERTICAL_OFFSET = 0.15;
|
|
544
|
-
declare const MIN_FACE_RATIO = 0.03;
|
|
545
|
-
declare const MAX_FACE_RATIO = 0.7;
|
|
546
|
-
declare const FACE_CROP_OUTPUT_SIZE = 224;
|
|
547
|
-
declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.5;
|
|
548
|
-
declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.3;
|
|
549
|
-
declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
|
|
550
|
-
declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
|
|
551
|
-
declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
|
|
552
|
-
declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
|
|
553
|
-
declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
|
|
554
|
-
declare const DEFAULT_OVAL_REGION: OvalRegion;
|
|
555
|
-
declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
|
|
556
|
-
declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
|
|
557
|
-
declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
|
|
558
|
-
declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
|
|
559
|
-
declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
|
|
560
|
-
x: number;
|
|
561
|
-
y: number;
|
|
562
|
-
size: number;
|
|
563
|
-
};
|
|
564
|
-
declare function checkFrameQuality(options: {
|
|
565
|
-
faceBox?: FaceBoundingBox;
|
|
566
|
-
frameWidth: number;
|
|
567
|
-
frameHeight: number;
|
|
568
|
-
blurAnalysis?: BlurAnalysis;
|
|
569
|
-
lightingAnalysis?: LightingAnalysis;
|
|
570
|
-
minAlignment?: number;
|
|
571
|
-
}): FrameQualityResult;
|
|
572
|
-
declare class BaseFrameCollector {
|
|
573
|
-
protected frames: CapturedFrame[];
|
|
574
|
-
protected maxFrames: number;
|
|
575
|
-
protected startTime: number;
|
|
576
|
-
constructor(maxFrames?: number);
|
|
577
|
-
setMaxFrames(max: number): void;
|
|
578
|
-
addFrame(frame: CapturedFrame): void;
|
|
579
|
-
getFrames(): CapturedFrame[];
|
|
580
|
-
getCount(): number;
|
|
581
|
-
isComplete(): boolean;
|
|
582
|
-
reset(): void;
|
|
583
|
-
getStartTime(): number;
|
|
584
|
-
getNextIndex(): number;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_PATHS, AUTH_CONFIG, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_ENDPOINT, DEFAULT_LIVENESS_CONFIG, DEFAULT_LOCALE, DEFAULT_OVAL_REGION, DEFAULT_STATUS_MESSAGES, ES_LOCALE, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceInOvalResult, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LOW_LIGHT_THRESHOLD, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
|
|
686
|
+
export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_PATHS, AUTH_CONFIG, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_ENDPOINT, DEFAULT_FACE_DETECTION_TIERS, DEFAULT_GAZE_THRESHOLDS, DEFAULT_HAND_OCCLUSION_CONFIG, DEFAULT_LIVENESS_CONFIG, DEFAULT_LOCALE, DEFAULT_OVAL_REGION, DEFAULT_STABILIZER_CONFIG, DEFAULT_STATUS_MESSAGES, type DetectionResult, type DetectionSummary, type DetectorConfig, ES_LOCALE, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceDetectionTiers, type FaceInOvalResult, type FaceLandmarkPoint, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, type GazeThresholds, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HandOcclusionConfig, type HeadPose, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LANDMARK_INDEX, LANDMARK_MAX_BOUND, LANDMARK_MIN_BOUND, LOW_LIGHT_THRESHOLD, type LandmarkValidationResult, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -204,6 +204,204 @@ interface LivenessCallbacks {
|
|
|
204
204
|
onStateChange?: OnStateChangeCallback;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
interface FaceBoundingBox {
|
|
208
|
+
originX: number;
|
|
209
|
+
originY: number;
|
|
210
|
+
width: number;
|
|
211
|
+
height: number;
|
|
212
|
+
}
|
|
213
|
+
interface BlurAnalysis {
|
|
214
|
+
variance: number;
|
|
215
|
+
isBlurry: boolean;
|
|
216
|
+
threshold: number;
|
|
217
|
+
}
|
|
218
|
+
interface LightingAnalysis {
|
|
219
|
+
faceBrightness: number;
|
|
220
|
+
backgroundBrightness: number;
|
|
221
|
+
ratio: number;
|
|
222
|
+
status: 'good' | 'backlit' | 'low_light';
|
|
223
|
+
warning?: string;
|
|
224
|
+
}
|
|
225
|
+
interface FaceVisibilityResult {
|
|
226
|
+
visible: boolean;
|
|
227
|
+
reason?: string;
|
|
228
|
+
}
|
|
229
|
+
interface FaceAlignmentResult {
|
|
230
|
+
score: number;
|
|
231
|
+
tooClose: boolean;
|
|
232
|
+
tooFar: boolean;
|
|
233
|
+
}
|
|
234
|
+
interface OvalRegion {
|
|
235
|
+
centerX: number;
|
|
236
|
+
centerY: number;
|
|
237
|
+
width: number;
|
|
238
|
+
height: number;
|
|
239
|
+
}
|
|
240
|
+
interface FaceInOvalResult {
|
|
241
|
+
isInside: boolean;
|
|
242
|
+
centerInOval: boolean;
|
|
243
|
+
sizeMatch: boolean;
|
|
244
|
+
feedback?: string;
|
|
245
|
+
}
|
|
246
|
+
interface FrameQualityResult {
|
|
247
|
+
passed: boolean;
|
|
248
|
+
blur?: BlurAnalysis;
|
|
249
|
+
lighting?: LightingAnalysis;
|
|
250
|
+
visibility?: FaceVisibilityResult;
|
|
251
|
+
alignment?: FaceAlignmentResult;
|
|
252
|
+
rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
|
|
253
|
+
}
|
|
254
|
+
declare const DEFAULT_BLUR_THRESHOLD = 100;
|
|
255
|
+
declare const BLUR_THRESHOLD_MOBILE = 150;
|
|
256
|
+
declare const BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
257
|
+
declare const LOW_LIGHT_THRESHOLD = 50;
|
|
258
|
+
declare const MIN_FACE_TOP_MARGIN = 0.1;
|
|
259
|
+
declare const MIN_FACE_BOTTOM_MARGIN = 0.08;
|
|
260
|
+
declare const MIN_FACE_SIDE_MARGIN = 0.05;
|
|
261
|
+
declare const MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
262
|
+
declare const HIGH_ALIGNMENT = 0.85;
|
|
263
|
+
declare const GOOD_ALIGNMENT = 0.5;
|
|
264
|
+
declare const IDEAL_CROP_MULTIPLIER = 2;
|
|
265
|
+
declare const MIN_CROP_MULTIPLIER = 1.8;
|
|
266
|
+
declare const MAX_CROP_MULTIPLIER = 2.5;
|
|
267
|
+
declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
268
|
+
declare const MIN_FACE_RATIO = 0.036;
|
|
269
|
+
declare const MAX_FACE_RATIO = 0.7;
|
|
270
|
+
declare const FACE_CROP_OUTPUT_SIZE = 224;
|
|
271
|
+
declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
|
|
272
|
+
declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
|
|
273
|
+
declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
|
|
274
|
+
declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
|
|
275
|
+
declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
|
|
276
|
+
declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
|
|
277
|
+
declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
|
|
278
|
+
declare const DEFAULT_OVAL_REGION: OvalRegion;
|
|
279
|
+
declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
|
|
280
|
+
declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
|
|
281
|
+
declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
|
|
282
|
+
declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
|
|
283
|
+
declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
|
|
284
|
+
x: number;
|
|
285
|
+
y: number;
|
|
286
|
+
size: number;
|
|
287
|
+
};
|
|
288
|
+
declare function checkFrameQuality(options: {
|
|
289
|
+
faceBox?: FaceBoundingBox;
|
|
290
|
+
frameWidth: number;
|
|
291
|
+
frameHeight: number;
|
|
292
|
+
blurAnalysis?: BlurAnalysis;
|
|
293
|
+
lightingAnalysis?: LightingAnalysis;
|
|
294
|
+
minAlignment?: number;
|
|
295
|
+
}): FrameQualityResult;
|
|
296
|
+
declare class BaseFrameCollector {
|
|
297
|
+
protected frames: CapturedFrame[];
|
|
298
|
+
protected maxFrames: number;
|
|
299
|
+
protected startTime: number;
|
|
300
|
+
constructor(maxFrames?: number);
|
|
301
|
+
setMaxFrames(max: number): void;
|
|
302
|
+
addFrame(frame: CapturedFrame): void;
|
|
303
|
+
getFrames(): CapturedFrame[];
|
|
304
|
+
getCount(): number;
|
|
305
|
+
isComplete(): boolean;
|
|
306
|
+
reset(): void;
|
|
307
|
+
getStartTime(): number;
|
|
308
|
+
getNextIndex(): number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
interface FaceLandmarkPoint {
|
|
312
|
+
x: number;
|
|
313
|
+
y: number;
|
|
314
|
+
z: number;
|
|
315
|
+
}
|
|
316
|
+
interface LandmarkValidationResult {
|
|
317
|
+
valid: boolean;
|
|
318
|
+
message?: string;
|
|
319
|
+
}
|
|
320
|
+
declare const LANDMARK_INDEX: {
|
|
321
|
+
readonly NOSE_TIP: 1;
|
|
322
|
+
readonly UPPER_LIP: 13;
|
|
323
|
+
readonly LOWER_LIP: 14;
|
|
324
|
+
};
|
|
325
|
+
declare const LANDMARK_MIN_BOUND = 0.1;
|
|
326
|
+
declare const LANDMARK_MAX_BOUND = 0.9;
|
|
327
|
+
declare const MIN_LANDMARK_COUNT = 15;
|
|
328
|
+
declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
|
|
329
|
+
|
|
330
|
+
interface DetectionResult {
|
|
331
|
+
type: string;
|
|
332
|
+
passed: boolean;
|
|
333
|
+
confidence: number;
|
|
334
|
+
message?: string;
|
|
335
|
+
metadata?: Record<string, unknown>;
|
|
336
|
+
}
|
|
337
|
+
interface DetectorConfig {
|
|
338
|
+
enabled: boolean;
|
|
339
|
+
minConfidence?: number;
|
|
340
|
+
}
|
|
341
|
+
interface DetectionSummary {
|
|
342
|
+
allPassed: boolean;
|
|
343
|
+
results: Map<string, DetectionResult>;
|
|
344
|
+
faceBox?: FaceBoundingBox;
|
|
345
|
+
faceLandmarks?: FaceLandmarkPoint[];
|
|
346
|
+
warnings: string[];
|
|
347
|
+
}
|
|
348
|
+
interface HeadPose {
|
|
349
|
+
yawDeg: number;
|
|
350
|
+
pitchDeg: number;
|
|
351
|
+
rollDeg: number;
|
|
352
|
+
}
|
|
353
|
+
interface GazeThresholds {
|
|
354
|
+
maxYaw: number;
|
|
355
|
+
maxPitch: number;
|
|
356
|
+
}
|
|
357
|
+
declare const DEFAULT_GAZE_THRESHOLDS: GazeThresholds;
|
|
358
|
+
interface HandOcclusionConfig {
|
|
359
|
+
faceExpansionFactor: number;
|
|
360
|
+
minLandmarksForOcclusion: number;
|
|
361
|
+
faceBoxExpiryMs: number;
|
|
362
|
+
centerRegion: number;
|
|
363
|
+
}
|
|
364
|
+
declare const DEFAULT_HAND_OCCLUSION_CONFIG: HandOcclusionConfig;
|
|
365
|
+
interface FaceDetectionTiers {
|
|
366
|
+
primaryConfidence: number;
|
|
367
|
+
secondaryConfidence: number;
|
|
368
|
+
}
|
|
369
|
+
declare const DEFAULT_FACE_DETECTION_TIERS: FaceDetectionTiers;
|
|
370
|
+
interface VideoFrameMetadata {
|
|
371
|
+
presentationTime: number;
|
|
372
|
+
expectedDisplayTime: number;
|
|
373
|
+
width: number;
|
|
374
|
+
height: number;
|
|
375
|
+
mediaTime: number;
|
|
376
|
+
presentedFrames: number;
|
|
377
|
+
processingDuration?: number;
|
|
378
|
+
}
|
|
379
|
+
interface StabilizationProgress {
|
|
380
|
+
elapsed: number;
|
|
381
|
+
stableFrameCount: number;
|
|
382
|
+
requiredFrames: number;
|
|
383
|
+
greenMean: number;
|
|
384
|
+
greenDelta: number | null;
|
|
385
|
+
threshold: number;
|
|
386
|
+
isStable: boolean;
|
|
387
|
+
progress: number;
|
|
388
|
+
}
|
|
389
|
+
interface StabilizationResult {
|
|
390
|
+
success: boolean;
|
|
391
|
+
elapsed: number;
|
|
392
|
+
finalGreenMean: number;
|
|
393
|
+
stableFrameCount: number;
|
|
394
|
+
message: string;
|
|
395
|
+
}
|
|
396
|
+
interface StabilizerConfig {
|
|
397
|
+
stabilityThreshold: number;
|
|
398
|
+
requiredStableFrames: number;
|
|
399
|
+
maxWaitMs: number;
|
|
400
|
+
checkIntervalMs: number;
|
|
401
|
+
sampleSize: number;
|
|
402
|
+
}
|
|
403
|
+
declare const DEFAULT_STABILIZER_CONFIG: StabilizerConfig;
|
|
404
|
+
|
|
207
405
|
interface LivenessClientConfig {
|
|
208
406
|
baseUrl?: string;
|
|
209
407
|
apiKey: string;
|
|
@@ -244,7 +442,12 @@ declare class LivenessClient {
|
|
|
244
442
|
model?: FastCheckModel;
|
|
245
443
|
source?: FrameSource;
|
|
246
444
|
}): Promise<LivenessResult>;
|
|
247
|
-
|
|
445
|
+
streamFrame(frame: CapturedFrame, options: {
|
|
446
|
+
sessionId: string;
|
|
447
|
+
model?: FastCheckModel;
|
|
448
|
+
source?: FrameSource;
|
|
449
|
+
}): Promise<FastCheckStreamResponse>;
|
|
450
|
+
private sendStreamFrameInternal;
|
|
248
451
|
fastCheckStream(frames: CapturedFrame[], options?: {
|
|
249
452
|
sessionId?: string;
|
|
250
453
|
model?: FastCheckModel;
|
|
@@ -480,108 +683,4 @@ interface RetryOptions {
|
|
|
480
683
|
declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
481
684
|
declare function sleep(ms: number): Promise<void>;
|
|
482
685
|
|
|
483
|
-
|
|
484
|
-
originX: number;
|
|
485
|
-
originY: number;
|
|
486
|
-
width: number;
|
|
487
|
-
height: number;
|
|
488
|
-
}
|
|
489
|
-
interface BlurAnalysis {
|
|
490
|
-
variance: number;
|
|
491
|
-
isBlurry: boolean;
|
|
492
|
-
threshold: number;
|
|
493
|
-
}
|
|
494
|
-
interface LightingAnalysis {
|
|
495
|
-
faceBrightness: number;
|
|
496
|
-
backgroundBrightness: number;
|
|
497
|
-
ratio: number;
|
|
498
|
-
status: 'good' | 'backlit' | 'low_light';
|
|
499
|
-
warning?: string;
|
|
500
|
-
}
|
|
501
|
-
interface FaceVisibilityResult {
|
|
502
|
-
visible: boolean;
|
|
503
|
-
reason?: string;
|
|
504
|
-
}
|
|
505
|
-
interface FaceAlignmentResult {
|
|
506
|
-
score: number;
|
|
507
|
-
tooClose: boolean;
|
|
508
|
-
tooFar: boolean;
|
|
509
|
-
}
|
|
510
|
-
interface OvalRegion {
|
|
511
|
-
centerX: number;
|
|
512
|
-
centerY: number;
|
|
513
|
-
width: number;
|
|
514
|
-
height: number;
|
|
515
|
-
}
|
|
516
|
-
interface FaceInOvalResult {
|
|
517
|
-
isInside: boolean;
|
|
518
|
-
centerInOval: boolean;
|
|
519
|
-
sizeMatch: boolean;
|
|
520
|
-
feedback?: string;
|
|
521
|
-
}
|
|
522
|
-
interface FrameQualityResult {
|
|
523
|
-
passed: boolean;
|
|
524
|
-
blur?: BlurAnalysis;
|
|
525
|
-
lighting?: LightingAnalysis;
|
|
526
|
-
visibility?: FaceVisibilityResult;
|
|
527
|
-
alignment?: FaceAlignmentResult;
|
|
528
|
-
rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
|
|
529
|
-
}
|
|
530
|
-
declare const DEFAULT_BLUR_THRESHOLD = 100;
|
|
531
|
-
declare const BLUR_THRESHOLD_MOBILE = 150;
|
|
532
|
-
declare const BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
533
|
-
declare const LOW_LIGHT_THRESHOLD = 50;
|
|
534
|
-
declare const MIN_FACE_TOP_MARGIN = 0.1;
|
|
535
|
-
declare const MIN_FACE_BOTTOM_MARGIN = 0.08;
|
|
536
|
-
declare const MIN_FACE_SIDE_MARGIN = 0.05;
|
|
537
|
-
declare const MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
538
|
-
declare const HIGH_ALIGNMENT = 0.85;
|
|
539
|
-
declare const GOOD_ALIGNMENT = 0.5;
|
|
540
|
-
declare const IDEAL_CROP_MULTIPLIER = 3.33;
|
|
541
|
-
declare const MIN_CROP_MULTIPLIER = 1.5;
|
|
542
|
-
declare const MAX_CROP_MULTIPLIER = 4;
|
|
543
|
-
declare const FACE_CENTER_VERTICAL_OFFSET = 0.15;
|
|
544
|
-
declare const MIN_FACE_RATIO = 0.03;
|
|
545
|
-
declare const MAX_FACE_RATIO = 0.7;
|
|
546
|
-
declare const FACE_CROP_OUTPUT_SIZE = 224;
|
|
547
|
-
declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.5;
|
|
548
|
-
declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.3;
|
|
549
|
-
declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
|
|
550
|
-
declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
|
|
551
|
-
declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
|
|
552
|
-
declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
|
|
553
|
-
declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
|
|
554
|
-
declare const DEFAULT_OVAL_REGION: OvalRegion;
|
|
555
|
-
declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
|
|
556
|
-
declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
|
|
557
|
-
declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
|
|
558
|
-
declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
|
|
559
|
-
declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
|
|
560
|
-
x: number;
|
|
561
|
-
y: number;
|
|
562
|
-
size: number;
|
|
563
|
-
};
|
|
564
|
-
declare function checkFrameQuality(options: {
|
|
565
|
-
faceBox?: FaceBoundingBox;
|
|
566
|
-
frameWidth: number;
|
|
567
|
-
frameHeight: number;
|
|
568
|
-
blurAnalysis?: BlurAnalysis;
|
|
569
|
-
lightingAnalysis?: LightingAnalysis;
|
|
570
|
-
minAlignment?: number;
|
|
571
|
-
}): FrameQualityResult;
|
|
572
|
-
declare class BaseFrameCollector {
|
|
573
|
-
protected frames: CapturedFrame[];
|
|
574
|
-
protected maxFrames: number;
|
|
575
|
-
protected startTime: number;
|
|
576
|
-
constructor(maxFrames?: number);
|
|
577
|
-
setMaxFrames(max: number): void;
|
|
578
|
-
addFrame(frame: CapturedFrame): void;
|
|
579
|
-
getFrames(): CapturedFrame[];
|
|
580
|
-
getCount(): number;
|
|
581
|
-
isComplete(): boolean;
|
|
582
|
-
reset(): void;
|
|
583
|
-
getStartTime(): number;
|
|
584
|
-
getNextIndex(): number;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_PATHS, AUTH_CONFIG, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_ENDPOINT, DEFAULT_LIVENESS_CONFIG, DEFAULT_LOCALE, DEFAULT_OVAL_REGION, DEFAULT_STATUS_MESSAGES, ES_LOCALE, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceInOvalResult, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LOW_LIGHT_THRESHOLD, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
|
|
686
|
+
export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_PATHS, AUTH_CONFIG, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_ENDPOINT, DEFAULT_FACE_DETECTION_TIERS, DEFAULT_GAZE_THRESHOLDS, DEFAULT_HAND_OCCLUSION_CONFIG, DEFAULT_LIVENESS_CONFIG, DEFAULT_LOCALE, DEFAULT_OVAL_REGION, DEFAULT_STABILIZER_CONFIG, DEFAULT_STATUS_MESSAGES, type DetectionResult, type DetectionSummary, type DetectorConfig, ES_LOCALE, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceDetectionTiers, type FaceInOvalResult, type FaceLandmarkPoint, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, type GazeThresholds, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HandOcclusionConfig, type HeadPose, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LANDMARK_INDEX, LANDMARK_MAX_BOUND, LANDMARK_MIN_BOUND, LOW_LIGHT_THRESHOLD, type LandmarkValidationResult, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
|
package/dist/index.js
CHANGED
|
@@ -32,9 +32,13 @@ __export(index_exports, {
|
|
|
32
32
|
BaseFrameCollector: () => BaseFrameCollector,
|
|
33
33
|
DEFAULT_BLUR_THRESHOLD: () => DEFAULT_BLUR_THRESHOLD,
|
|
34
34
|
DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT,
|
|
35
|
+
DEFAULT_FACE_DETECTION_TIERS: () => DEFAULT_FACE_DETECTION_TIERS,
|
|
36
|
+
DEFAULT_GAZE_THRESHOLDS: () => DEFAULT_GAZE_THRESHOLDS,
|
|
37
|
+
DEFAULT_HAND_OCCLUSION_CONFIG: () => DEFAULT_HAND_OCCLUSION_CONFIG,
|
|
35
38
|
DEFAULT_LIVENESS_CONFIG: () => DEFAULT_LIVENESS_CONFIG,
|
|
36
39
|
DEFAULT_LOCALE: () => DEFAULT_LOCALE,
|
|
37
40
|
DEFAULT_OVAL_REGION: () => DEFAULT_OVAL_REGION,
|
|
41
|
+
DEFAULT_STABILIZER_CONFIG: () => DEFAULT_STABILIZER_CONFIG,
|
|
38
42
|
DEFAULT_STATUS_MESSAGES: () => DEFAULT_STATUS_MESSAGES,
|
|
39
43
|
ES_LOCALE: () => ES_LOCALE,
|
|
40
44
|
FACE_CENTER_VERTICAL_OFFSET: () => FACE_CENTER_VERTICAL_OFFSET,
|
|
@@ -48,6 +52,9 @@ __export(index_exports, {
|
|
|
48
52
|
HIGH_ALIGNMENT: () => HIGH_ALIGNMENT,
|
|
49
53
|
HYBRID_MODEL_CONFIGS: () => HYBRID_MODEL_CONFIGS,
|
|
50
54
|
IDEAL_CROP_MULTIPLIER: () => IDEAL_CROP_MULTIPLIER,
|
|
55
|
+
LANDMARK_INDEX: () => LANDMARK_INDEX,
|
|
56
|
+
LANDMARK_MAX_BOUND: () => LANDMARK_MAX_BOUND,
|
|
57
|
+
LANDMARK_MIN_BOUND: () => LANDMARK_MIN_BOUND,
|
|
51
58
|
LOW_LIGHT_THRESHOLD: () => LOW_LIGHT_THRESHOLD,
|
|
52
59
|
LivenessApiError: () => LivenessApiError,
|
|
53
60
|
LivenessClient: () => LivenessClient,
|
|
@@ -60,6 +67,7 @@ __export(index_exports, {
|
|
|
60
67
|
MIN_FACE_RATIO: () => MIN_FACE_RATIO,
|
|
61
68
|
MIN_FACE_SIDE_MARGIN: () => MIN_FACE_SIDE_MARGIN,
|
|
62
69
|
MIN_FACE_TOP_MARGIN: () => MIN_FACE_TOP_MARGIN,
|
|
70
|
+
MIN_LANDMARK_COUNT: () => MIN_LANDMARK_COUNT,
|
|
63
71
|
MODEL_CONFIGS: () => MODEL_CONFIGS,
|
|
64
72
|
OVAL_GUIDE_COLORS: () => OVAL_GUIDE_COLORS,
|
|
65
73
|
OVAL_GUIDE_STYLES: () => OVAL_GUIDE_STYLES,
|
|
@@ -93,6 +101,7 @@ __export(index_exports, {
|
|
|
93
101
|
toLivenessResult: () => toLivenessResult,
|
|
94
102
|
toLivenessResultFromStream: () => toLivenessResultFromStream,
|
|
95
103
|
validateApiKey: () => validateApiKey,
|
|
104
|
+
validateFaceLandmarks: () => validateFaceLandmarks,
|
|
96
105
|
validateFrameCount: () => validateFrameCount,
|
|
97
106
|
validateFrameData: () => validateFrameData,
|
|
98
107
|
validateFrameIndex: () => validateFrameIndex,
|
|
@@ -385,18 +394,47 @@ var LivenessClient = class {
|
|
|
385
394
|
return toLivenessResult(response);
|
|
386
395
|
}
|
|
387
396
|
/**
|
|
388
|
-
* Send a single frame to the streaming endpoint
|
|
397
|
+
* Send a single captured frame to the streaming endpoint.
|
|
398
|
+
*
|
|
399
|
+
* Use this to implement **real-time** streaming: call once per captured
|
|
400
|
+
* frame instead of batching all frames first.
|
|
401
|
+
*
|
|
402
|
+
* - While the server is still collecting frames the response will have
|
|
403
|
+
* `status: 'buffering'` with `frames_received` / `frames_required`.
|
|
404
|
+
* - When the last required frame arrives the response will have
|
|
405
|
+
* `status: 'complete'` with the full liveness result.
|
|
406
|
+
*
|
|
407
|
+
* Each frame gets its own retry via {@link retryWithBackoff}.
|
|
408
|
+
*
|
|
409
|
+
* @param frame - A single captured frame
|
|
410
|
+
* @param options - Session, model and source
|
|
411
|
+
* @returns Stream response (buffering or complete)
|
|
412
|
+
*/
|
|
413
|
+
async streamFrame(frame, options) {
|
|
414
|
+
const frameData = {
|
|
415
|
+
index: frame.index,
|
|
416
|
+
timestamp_ms: frame.timestampMs,
|
|
417
|
+
pixels: frame.pixels
|
|
418
|
+
};
|
|
419
|
+
return this.sendStreamFrameInternal(frameData, {
|
|
420
|
+
sessionId: options.sessionId,
|
|
421
|
+
model: options.model ?? "10",
|
|
422
|
+
source: options.source ?? "live"
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Send a single FrameData to the streaming endpoint with retry (internal)
|
|
389
427
|
*
|
|
390
428
|
* @param frame - Single frame to send
|
|
391
429
|
* @param options - Session and model options
|
|
392
430
|
* @returns Stream response with status
|
|
393
431
|
*/
|
|
394
|
-
async
|
|
432
|
+
async sendStreamFrameInternal(frameData, options) {
|
|
395
433
|
const request = {
|
|
396
434
|
session_id: options.sessionId,
|
|
397
435
|
model: options.model,
|
|
398
436
|
source: options.source,
|
|
399
|
-
frame
|
|
437
|
+
frame: frameData
|
|
400
438
|
};
|
|
401
439
|
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
402
440
|
method: "POST",
|
|
@@ -422,7 +460,7 @@ var LivenessClient = class {
|
|
|
422
460
|
const total = frameDataList.length;
|
|
423
461
|
let finalResponse = null;
|
|
424
462
|
const sendPromises = frameDataList.map(async (frameData, index) => {
|
|
425
|
-
const response = await this.
|
|
463
|
+
const response = await this.sendStreamFrameInternal(frameData, {
|
|
426
464
|
sessionId,
|
|
427
465
|
model,
|
|
428
466
|
source
|
|
@@ -471,7 +509,7 @@ var LivenessClient = class {
|
|
|
471
509
|
const total = frameDataList.length;
|
|
472
510
|
let finalResponse = null;
|
|
473
511
|
for (const frameData of frameDataList) {
|
|
474
|
-
const response = await this.
|
|
512
|
+
const response = await this.sendStreamFrameInternal(frameData, {
|
|
475
513
|
sessionId,
|
|
476
514
|
model,
|
|
477
515
|
source
|
|
@@ -891,6 +929,29 @@ function hasEnoughFrames(model, frameCount) {
|
|
|
891
929
|
return frameCount >= MODEL_CONFIGS[model].minFrames;
|
|
892
930
|
}
|
|
893
931
|
|
|
932
|
+
// src/types/detectors.ts
|
|
933
|
+
var DEFAULT_GAZE_THRESHOLDS = {
|
|
934
|
+
maxYaw: 25,
|
|
935
|
+
maxPitch: 20
|
|
936
|
+
};
|
|
937
|
+
var DEFAULT_HAND_OCCLUSION_CONFIG = {
|
|
938
|
+
faceExpansionFactor: 0.5,
|
|
939
|
+
minLandmarksForOcclusion: 3,
|
|
940
|
+
faceBoxExpiryMs: 1e3,
|
|
941
|
+
centerRegion: 0.6
|
|
942
|
+
};
|
|
943
|
+
var DEFAULT_FACE_DETECTION_TIERS = {
|
|
944
|
+
primaryConfidence: 0.7,
|
|
945
|
+
secondaryConfidence: 0.4
|
|
946
|
+
};
|
|
947
|
+
var DEFAULT_STABILIZER_CONFIG = {
|
|
948
|
+
stabilityThreshold: 1.5,
|
|
949
|
+
requiredStableFrames: 15,
|
|
950
|
+
maxWaitMs: 4e3,
|
|
951
|
+
checkIntervalMs: 100,
|
|
952
|
+
sampleSize: 64
|
|
953
|
+
};
|
|
954
|
+
|
|
894
955
|
// src/constants/feedback.ts
|
|
895
956
|
var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
|
|
896
957
|
var ALIGNMENT_THRESHOLD_POOR = 0.5;
|
|
@@ -1142,15 +1203,15 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
|
|
|
1142
1203
|
var MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
1143
1204
|
var HIGH_ALIGNMENT = 0.85;
|
|
1144
1205
|
var GOOD_ALIGNMENT = 0.5;
|
|
1145
|
-
var IDEAL_CROP_MULTIPLIER =
|
|
1146
|
-
var MIN_CROP_MULTIPLIER = 1.
|
|
1147
|
-
var MAX_CROP_MULTIPLIER =
|
|
1148
|
-
var FACE_CENTER_VERTICAL_OFFSET = 0.
|
|
1149
|
-
var MIN_FACE_RATIO = 0.
|
|
1206
|
+
var IDEAL_CROP_MULTIPLIER = 2;
|
|
1207
|
+
var MIN_CROP_MULTIPLIER = 1.8;
|
|
1208
|
+
var MAX_CROP_MULTIPLIER = 2.5;
|
|
1209
|
+
var FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
1210
|
+
var MIN_FACE_RATIO = 0.036;
|
|
1150
1211
|
var MAX_FACE_RATIO = 0.7;
|
|
1151
1212
|
var FACE_CROP_OUTPUT_SIZE = 224;
|
|
1152
|
-
var MAX_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1153
|
-
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1213
|
+
var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
|
|
1214
|
+
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
|
|
1154
1215
|
function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
|
|
1155
1216
|
const laplacian = [];
|
|
1156
1217
|
for (let y = 1; y < height - 1; y++) {
|
|
@@ -1239,10 +1300,10 @@ function isFaceFullyVisible(boundingBox, frameWidth, frameHeight) {
|
|
|
1239
1300
|
var DEFAULT_OVAL_REGION = {
|
|
1240
1301
|
centerX: 0.5,
|
|
1241
1302
|
centerY: 0.5,
|
|
1242
|
-
width: 0.
|
|
1243
|
-
//
|
|
1244
|
-
height: 0.
|
|
1245
|
-
//
|
|
1303
|
+
width: 0.36,
|
|
1304
|
+
// 36% of frame width (+20%)
|
|
1305
|
+
height: 0.48
|
|
1306
|
+
// 36% * (4/3) = 48% of frame height (+20%)
|
|
1246
1307
|
};
|
|
1247
1308
|
function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
|
|
1248
1309
|
const faceCenterX = (faceBox.originX + faceBox.width / 2) / frameWidth;
|
|
@@ -1431,6 +1492,37 @@ var BaseFrameCollector = class {
|
|
|
1431
1492
|
return this.frames.length;
|
|
1432
1493
|
}
|
|
1433
1494
|
};
|
|
1495
|
+
|
|
1496
|
+
// src/utils/landmarkValidator.ts
|
|
1497
|
+
var LANDMARK_INDEX = {
|
|
1498
|
+
/** Nose tip */
|
|
1499
|
+
NOSE_TIP: 1,
|
|
1500
|
+
/** Upper lip center */
|
|
1501
|
+
UPPER_LIP: 13,
|
|
1502
|
+
/** Lower lip center */
|
|
1503
|
+
LOWER_LIP: 14
|
|
1504
|
+
};
|
|
1505
|
+
var LANDMARK_MIN_BOUND = 0.1;
|
|
1506
|
+
var LANDMARK_MAX_BOUND = 0.9;
|
|
1507
|
+
var MIN_LANDMARK_COUNT = 15;
|
|
1508
|
+
function isInBounds(x, y) {
|
|
1509
|
+
return x >= LANDMARK_MIN_BOUND && x <= LANDMARK_MAX_BOUND && y >= LANDMARK_MIN_BOUND && y <= LANDMARK_MAX_BOUND;
|
|
1510
|
+
}
|
|
1511
|
+
function validateFaceLandmarks(landmarks) {
|
|
1512
|
+
if (!landmarks || landmarks.length < MIN_LANDMARK_COUNT) {
|
|
1513
|
+
return { valid: false, message: "Move closer to the camera" };
|
|
1514
|
+
}
|
|
1515
|
+
const noseTip = landmarks[LANDMARK_INDEX.NOSE_TIP];
|
|
1516
|
+
const upperLip = landmarks[LANDMARK_INDEX.UPPER_LIP];
|
|
1517
|
+
const lowerLip = landmarks[LANDMARK_INDEX.LOWER_LIP];
|
|
1518
|
+
if (!noseTip || !isInBounds(noseTip.x, noseTip.y)) {
|
|
1519
|
+
return { valid: false, message: "Make sure your full face is visible" };
|
|
1520
|
+
}
|
|
1521
|
+
if (!upperLip || !lowerLip || !isInBounds(upperLip.x, upperLip.y) || !isInBounds(lowerLip.x, lowerLip.y)) {
|
|
1522
|
+
return { valid: false, message: "Don't cover your mouth" };
|
|
1523
|
+
}
|
|
1524
|
+
return { valid: true };
|
|
1525
|
+
}
|
|
1434
1526
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1435
1527
|
0 && (module.exports = {
|
|
1436
1528
|
ALIGNMENT_THRESHOLD_CAPTURE,
|
|
@@ -1445,9 +1537,13 @@ var BaseFrameCollector = class {
|
|
|
1445
1537
|
BaseFrameCollector,
|
|
1446
1538
|
DEFAULT_BLUR_THRESHOLD,
|
|
1447
1539
|
DEFAULT_ENDPOINT,
|
|
1540
|
+
DEFAULT_FACE_DETECTION_TIERS,
|
|
1541
|
+
DEFAULT_GAZE_THRESHOLDS,
|
|
1542
|
+
DEFAULT_HAND_OCCLUSION_CONFIG,
|
|
1448
1543
|
DEFAULT_LIVENESS_CONFIG,
|
|
1449
1544
|
DEFAULT_LOCALE,
|
|
1450
1545
|
DEFAULT_OVAL_REGION,
|
|
1546
|
+
DEFAULT_STABILIZER_CONFIG,
|
|
1451
1547
|
DEFAULT_STATUS_MESSAGES,
|
|
1452
1548
|
ES_LOCALE,
|
|
1453
1549
|
FACE_CENTER_VERTICAL_OFFSET,
|
|
@@ -1461,6 +1557,9 @@ var BaseFrameCollector = class {
|
|
|
1461
1557
|
HIGH_ALIGNMENT,
|
|
1462
1558
|
HYBRID_MODEL_CONFIGS,
|
|
1463
1559
|
IDEAL_CROP_MULTIPLIER,
|
|
1560
|
+
LANDMARK_INDEX,
|
|
1561
|
+
LANDMARK_MAX_BOUND,
|
|
1562
|
+
LANDMARK_MIN_BOUND,
|
|
1464
1563
|
LOW_LIGHT_THRESHOLD,
|
|
1465
1564
|
LivenessApiError,
|
|
1466
1565
|
LivenessClient,
|
|
@@ -1473,6 +1572,7 @@ var BaseFrameCollector = class {
|
|
|
1473
1572
|
MIN_FACE_RATIO,
|
|
1474
1573
|
MIN_FACE_SIDE_MARGIN,
|
|
1475
1574
|
MIN_FACE_TOP_MARGIN,
|
|
1575
|
+
MIN_LANDMARK_COUNT,
|
|
1476
1576
|
MODEL_CONFIGS,
|
|
1477
1577
|
OVAL_GUIDE_COLORS,
|
|
1478
1578
|
OVAL_GUIDE_STYLES,
|
|
@@ -1506,6 +1606,7 @@ var BaseFrameCollector = class {
|
|
|
1506
1606
|
toLivenessResult,
|
|
1507
1607
|
toLivenessResultFromStream,
|
|
1508
1608
|
validateApiKey,
|
|
1609
|
+
validateFaceLandmarks,
|
|
1509
1610
|
validateFrameCount,
|
|
1510
1611
|
validateFrameData,
|
|
1511
1612
|
validateFrameIndex,
|
package/dist/index.mjs
CHANGED
|
@@ -281,18 +281,47 @@ var LivenessClient = class {
|
|
|
281
281
|
return toLivenessResult(response);
|
|
282
282
|
}
|
|
283
283
|
/**
|
|
284
|
-
* Send a single frame to the streaming endpoint
|
|
284
|
+
* Send a single captured frame to the streaming endpoint.
|
|
285
|
+
*
|
|
286
|
+
* Use this to implement **real-time** streaming: call once per captured
|
|
287
|
+
* frame instead of batching all frames first.
|
|
288
|
+
*
|
|
289
|
+
* - While the server is still collecting frames the response will have
|
|
290
|
+
* `status: 'buffering'` with `frames_received` / `frames_required`.
|
|
291
|
+
* - When the last required frame arrives the response will have
|
|
292
|
+
* `status: 'complete'` with the full liveness result.
|
|
293
|
+
*
|
|
294
|
+
* Each frame gets its own retry via {@link retryWithBackoff}.
|
|
295
|
+
*
|
|
296
|
+
* @param frame - A single captured frame
|
|
297
|
+
* @param options - Session, model and source
|
|
298
|
+
* @returns Stream response (buffering or complete)
|
|
299
|
+
*/
|
|
300
|
+
async streamFrame(frame, options) {
|
|
301
|
+
const frameData = {
|
|
302
|
+
index: frame.index,
|
|
303
|
+
timestamp_ms: frame.timestampMs,
|
|
304
|
+
pixels: frame.pixels
|
|
305
|
+
};
|
|
306
|
+
return this.sendStreamFrameInternal(frameData, {
|
|
307
|
+
sessionId: options.sessionId,
|
|
308
|
+
model: options.model ?? "10",
|
|
309
|
+
source: options.source ?? "live"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Send a single FrameData to the streaming endpoint with retry (internal)
|
|
285
314
|
*
|
|
286
315
|
* @param frame - Single frame to send
|
|
287
316
|
* @param options - Session and model options
|
|
288
317
|
* @returns Stream response with status
|
|
289
318
|
*/
|
|
290
|
-
async
|
|
319
|
+
async sendStreamFrameInternal(frameData, options) {
|
|
291
320
|
const request = {
|
|
292
321
|
session_id: options.sessionId,
|
|
293
322
|
model: options.model,
|
|
294
323
|
source: options.source,
|
|
295
|
-
frame
|
|
324
|
+
frame: frameData
|
|
296
325
|
};
|
|
297
326
|
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
298
327
|
method: "POST",
|
|
@@ -318,7 +347,7 @@ var LivenessClient = class {
|
|
|
318
347
|
const total = frameDataList.length;
|
|
319
348
|
let finalResponse = null;
|
|
320
349
|
const sendPromises = frameDataList.map(async (frameData, index) => {
|
|
321
|
-
const response = await this.
|
|
350
|
+
const response = await this.sendStreamFrameInternal(frameData, {
|
|
322
351
|
sessionId,
|
|
323
352
|
model,
|
|
324
353
|
source
|
|
@@ -367,7 +396,7 @@ var LivenessClient = class {
|
|
|
367
396
|
const total = frameDataList.length;
|
|
368
397
|
let finalResponse = null;
|
|
369
398
|
for (const frameData of frameDataList) {
|
|
370
|
-
const response = await this.
|
|
399
|
+
const response = await this.sendStreamFrameInternal(frameData, {
|
|
371
400
|
sessionId,
|
|
372
401
|
model,
|
|
373
402
|
source
|
|
@@ -787,6 +816,29 @@ function hasEnoughFrames(model, frameCount) {
|
|
|
787
816
|
return frameCount >= MODEL_CONFIGS[model].minFrames;
|
|
788
817
|
}
|
|
789
818
|
|
|
819
|
+
// src/types/detectors.ts
|
|
820
|
+
var DEFAULT_GAZE_THRESHOLDS = {
|
|
821
|
+
maxYaw: 25,
|
|
822
|
+
maxPitch: 20
|
|
823
|
+
};
|
|
824
|
+
var DEFAULT_HAND_OCCLUSION_CONFIG = {
|
|
825
|
+
faceExpansionFactor: 0.5,
|
|
826
|
+
minLandmarksForOcclusion: 3,
|
|
827
|
+
faceBoxExpiryMs: 1e3,
|
|
828
|
+
centerRegion: 0.6
|
|
829
|
+
};
|
|
830
|
+
var DEFAULT_FACE_DETECTION_TIERS = {
|
|
831
|
+
primaryConfidence: 0.7,
|
|
832
|
+
secondaryConfidence: 0.4
|
|
833
|
+
};
|
|
834
|
+
var DEFAULT_STABILIZER_CONFIG = {
|
|
835
|
+
stabilityThreshold: 1.5,
|
|
836
|
+
requiredStableFrames: 15,
|
|
837
|
+
maxWaitMs: 4e3,
|
|
838
|
+
checkIntervalMs: 100,
|
|
839
|
+
sampleSize: 64
|
|
840
|
+
};
|
|
841
|
+
|
|
790
842
|
// src/constants/feedback.ts
|
|
791
843
|
var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
|
|
792
844
|
var ALIGNMENT_THRESHOLD_POOR = 0.5;
|
|
@@ -1038,15 +1090,15 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
|
|
|
1038
1090
|
var MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
1039
1091
|
var HIGH_ALIGNMENT = 0.85;
|
|
1040
1092
|
var GOOD_ALIGNMENT = 0.5;
|
|
1041
|
-
var IDEAL_CROP_MULTIPLIER =
|
|
1042
|
-
var MIN_CROP_MULTIPLIER = 1.
|
|
1043
|
-
var MAX_CROP_MULTIPLIER =
|
|
1044
|
-
var FACE_CENTER_VERTICAL_OFFSET = 0.
|
|
1045
|
-
var MIN_FACE_RATIO = 0.
|
|
1093
|
+
var IDEAL_CROP_MULTIPLIER = 2;
|
|
1094
|
+
var MIN_CROP_MULTIPLIER = 1.8;
|
|
1095
|
+
var MAX_CROP_MULTIPLIER = 2.5;
|
|
1096
|
+
var FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
1097
|
+
var MIN_FACE_RATIO = 0.036;
|
|
1046
1098
|
var MAX_FACE_RATIO = 0.7;
|
|
1047
1099
|
var FACE_CROP_OUTPUT_SIZE = 224;
|
|
1048
|
-
var MAX_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1049
|
-
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1100
|
+
var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
|
|
1101
|
+
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
|
|
1050
1102
|
function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
|
|
1051
1103
|
const laplacian = [];
|
|
1052
1104
|
for (let y = 1; y < height - 1; y++) {
|
|
@@ -1135,10 +1187,10 @@ function isFaceFullyVisible(boundingBox, frameWidth, frameHeight) {
|
|
|
1135
1187
|
var DEFAULT_OVAL_REGION = {
|
|
1136
1188
|
centerX: 0.5,
|
|
1137
1189
|
centerY: 0.5,
|
|
1138
|
-
width: 0.
|
|
1139
|
-
//
|
|
1140
|
-
height: 0.
|
|
1141
|
-
//
|
|
1190
|
+
width: 0.36,
|
|
1191
|
+
// 36% of frame width (+20%)
|
|
1192
|
+
height: 0.48
|
|
1193
|
+
// 36% * (4/3) = 48% of frame height (+20%)
|
|
1142
1194
|
};
|
|
1143
1195
|
function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
|
|
1144
1196
|
const faceCenterX = (faceBox.originX + faceBox.width / 2) / frameWidth;
|
|
@@ -1327,6 +1379,37 @@ var BaseFrameCollector = class {
|
|
|
1327
1379
|
return this.frames.length;
|
|
1328
1380
|
}
|
|
1329
1381
|
};
|
|
1382
|
+
|
|
1383
|
+
// src/utils/landmarkValidator.ts
|
|
1384
|
+
var LANDMARK_INDEX = {
|
|
1385
|
+
/** Nose tip */
|
|
1386
|
+
NOSE_TIP: 1,
|
|
1387
|
+
/** Upper lip center */
|
|
1388
|
+
UPPER_LIP: 13,
|
|
1389
|
+
/** Lower lip center */
|
|
1390
|
+
LOWER_LIP: 14
|
|
1391
|
+
};
|
|
1392
|
+
var LANDMARK_MIN_BOUND = 0.1;
|
|
1393
|
+
var LANDMARK_MAX_BOUND = 0.9;
|
|
1394
|
+
var MIN_LANDMARK_COUNT = 15;
|
|
1395
|
+
function isInBounds(x, y) {
|
|
1396
|
+
return x >= LANDMARK_MIN_BOUND && x <= LANDMARK_MAX_BOUND && y >= LANDMARK_MIN_BOUND && y <= LANDMARK_MAX_BOUND;
|
|
1397
|
+
}
|
|
1398
|
+
function validateFaceLandmarks(landmarks) {
|
|
1399
|
+
if (!landmarks || landmarks.length < MIN_LANDMARK_COUNT) {
|
|
1400
|
+
return { valid: false, message: "Move closer to the camera" };
|
|
1401
|
+
}
|
|
1402
|
+
const noseTip = landmarks[LANDMARK_INDEX.NOSE_TIP];
|
|
1403
|
+
const upperLip = landmarks[LANDMARK_INDEX.UPPER_LIP];
|
|
1404
|
+
const lowerLip = landmarks[LANDMARK_INDEX.LOWER_LIP];
|
|
1405
|
+
if (!noseTip || !isInBounds(noseTip.x, noseTip.y)) {
|
|
1406
|
+
return { valid: false, message: "Make sure your full face is visible" };
|
|
1407
|
+
}
|
|
1408
|
+
if (!upperLip || !lowerLip || !isInBounds(upperLip.x, upperLip.y) || !isInBounds(lowerLip.x, lowerLip.y)) {
|
|
1409
|
+
return { valid: false, message: "Don't cover your mouth" };
|
|
1410
|
+
}
|
|
1411
|
+
return { valid: true };
|
|
1412
|
+
}
|
|
1330
1413
|
export {
|
|
1331
1414
|
ALIGNMENT_THRESHOLD_CAPTURE,
|
|
1332
1415
|
ALIGNMENT_THRESHOLD_GOOD,
|
|
@@ -1340,9 +1423,13 @@ export {
|
|
|
1340
1423
|
BaseFrameCollector,
|
|
1341
1424
|
DEFAULT_BLUR_THRESHOLD,
|
|
1342
1425
|
DEFAULT_ENDPOINT,
|
|
1426
|
+
DEFAULT_FACE_DETECTION_TIERS,
|
|
1427
|
+
DEFAULT_GAZE_THRESHOLDS,
|
|
1428
|
+
DEFAULT_HAND_OCCLUSION_CONFIG,
|
|
1343
1429
|
DEFAULT_LIVENESS_CONFIG,
|
|
1344
1430
|
DEFAULT_LOCALE,
|
|
1345
1431
|
DEFAULT_OVAL_REGION,
|
|
1432
|
+
DEFAULT_STABILIZER_CONFIG,
|
|
1346
1433
|
DEFAULT_STATUS_MESSAGES,
|
|
1347
1434
|
ES_LOCALE,
|
|
1348
1435
|
FACE_CENTER_VERTICAL_OFFSET,
|
|
@@ -1356,6 +1443,9 @@ export {
|
|
|
1356
1443
|
HIGH_ALIGNMENT,
|
|
1357
1444
|
HYBRID_MODEL_CONFIGS,
|
|
1358
1445
|
IDEAL_CROP_MULTIPLIER,
|
|
1446
|
+
LANDMARK_INDEX,
|
|
1447
|
+
LANDMARK_MAX_BOUND,
|
|
1448
|
+
LANDMARK_MIN_BOUND,
|
|
1359
1449
|
LOW_LIGHT_THRESHOLD,
|
|
1360
1450
|
LivenessApiError,
|
|
1361
1451
|
LivenessClient,
|
|
@@ -1368,6 +1458,7 @@ export {
|
|
|
1368
1458
|
MIN_FACE_RATIO,
|
|
1369
1459
|
MIN_FACE_SIDE_MARGIN,
|
|
1370
1460
|
MIN_FACE_TOP_MARGIN,
|
|
1461
|
+
MIN_LANDMARK_COUNT,
|
|
1371
1462
|
MODEL_CONFIGS,
|
|
1372
1463
|
OVAL_GUIDE_COLORS,
|
|
1373
1464
|
OVAL_GUIDE_STYLES,
|
|
@@ -1401,6 +1492,7 @@ export {
|
|
|
1401
1492
|
toLivenessResult,
|
|
1402
1493
|
toLivenessResultFromStream,
|
|
1403
1494
|
validateApiKey,
|
|
1495
|
+
validateFaceLandmarks,
|
|
1404
1496
|
validateFrameCount,
|
|
1405
1497
|
validateFrameData,
|
|
1406
1498
|
validateFrameIndex,
|