@moveris/shared 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -19
- package/dist/index.d.mts +69 -43
- package/dist/index.d.ts +69 -43
- package/dist/index.js +107 -33
- package/dist/index.mjs +103 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ const client = new LivenessClient({
|
|
|
26
26
|
// Perform a fast liveness check
|
|
27
27
|
const result = await client.fastCheck(frames, {
|
|
28
28
|
sessionId: 'my-session-id', // optional — auto-generated if omitted
|
|
29
|
-
model: '10',
|
|
29
|
+
model: 'mixed-10-v2',
|
|
30
30
|
source: 'live',
|
|
31
31
|
});
|
|
32
32
|
|
|
@@ -64,18 +64,18 @@ Perform fast liveness check with server-side face detection. Ideal for quick ver
|
|
|
64
64
|
```typescript
|
|
65
65
|
const result = await client.fastCheck(frames, {
|
|
66
66
|
sessionId: 'optional-uuid',
|
|
67
|
-
model: '10',
|
|
67
|
+
model: 'mixed-10-v2',
|
|
68
68
|
source: 'live', // 'live' | 'media'
|
|
69
69
|
});
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
**Parameters:**
|
|
73
73
|
|
|
74
|
-
| Option | Type | Default
|
|
75
|
-
| ----------- | ---------------- |
|
|
76
|
-
| `sessionId` | `string` | auto-generated
|
|
77
|
-
| `model` | `FastCheckModel` | `'10'`
|
|
78
|
-
| `source` | `FrameSource` | `'live'`
|
|
74
|
+
| Option | Type | Default | Description |
|
|
75
|
+
| ----------- | ---------------- | --------------- | ------------------------------------------------------------- |
|
|
76
|
+
| `sessionId` | `string` | auto-generated | Unique session identifier (UUID) |
|
|
77
|
+
| `model` | `FastCheckModel` | `'mixed-10-v2'` | Model to use (see Models section) |
|
|
78
|
+
| `source` | `FrameSource` | `'live'` | Frame source: `'live'` (camera) or `'media'` (recorded video) |
|
|
79
79
|
|
|
80
80
|
**Returns:** `Promise<LivenessResult>`
|
|
81
81
|
|
|
@@ -184,22 +184,34 @@ Model selection for liveness detection speed/accuracy trade-off.
|
|
|
184
184
|
|
|
185
185
|
```typescript
|
|
186
186
|
type FastCheckModel =
|
|
187
|
+
| 'mixed-10-v2'
|
|
188
|
+
| 'mixed-30-v2'
|
|
189
|
+
| 'mixed-60-v2'
|
|
190
|
+
| 'mixed-90-v2'
|
|
191
|
+
| 'mixed-120-v2' // Active mixed-v2 models (recommended)
|
|
187
192
|
| '10'
|
|
188
193
|
| '50'
|
|
189
|
-
| '250' //
|
|
190
|
-
|
|
|
191
|
-
| 'hybrid-v2-50' // Hybrid V2 (physiological features)
|
|
192
|
-
| 'mixed-10'; // Mixed (visual + physiological scoring)
|
|
194
|
+
| '250' // Legacy standard models
|
|
195
|
+
| (string & object); // Forward-compatible with future model IDs
|
|
193
196
|
```
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
|
198
|
-
|
|
|
199
|
-
| `'
|
|
200
|
-
| `'
|
|
201
|
-
| `'
|
|
202
|
-
| `'mixed-
|
|
198
|
+
Active models (recommended):
|
|
199
|
+
|
|
200
|
+
| Value | Frames | Description |
|
|
201
|
+
| ---------------- | ------ | ------------------------------ |
|
|
202
|
+
| `'mixed-10-v2'` | 10 | Fast verification, low latency |
|
|
203
|
+
| `'mixed-30-v2'` | 30 | Balanced speed and accuracy |
|
|
204
|
+
| `'mixed-60-v2'` | 60 | Higher accuracy |
|
|
205
|
+
| `'mixed-90-v2'` | 90 | High accuracy |
|
|
206
|
+
| `'mixed-120-v2'` | 120 | Highest accuracy, slower |
|
|
207
|
+
|
|
208
|
+
Legacy models (still supported):
|
|
209
|
+
|
|
210
|
+
| Value | Frames | Description |
|
|
211
|
+
| ------- | ------ | --------------------------- |
|
|
212
|
+
| `'10'` | 10 | Standard — fast |
|
|
213
|
+
| `'50'` | 50 | Standard — balanced |
|
|
214
|
+
| `'250'` | 250 | Standard — highest accuracy |
|
|
203
215
|
|
|
204
216
|
#### FrameSource
|
|
205
217
|
|
|
@@ -492,6 +504,8 @@ const glareRatio = detectSpecularHighlights(pixels);
|
|
|
492
504
|
| Glare | `maxGlareRatio: 0.15` | >15% of pixels are specular highlights |
|
|
493
505
|
| Occluded | `minContrast: 12` | Standard deviation of luminance too low |
|
|
494
506
|
|
|
507
|
+
Glare detection uses a **relative threshold** (`mean brightness × 2.5`) so sensitivity adapts to ambient lighting, avoiding false positives in bright environments.
|
|
508
|
+
|
|
495
509
|
Custom thresholds can be passed as a second argument to `checkEyeRegionQuality()`.
|
|
496
510
|
|
|
497
511
|
### Eye Region Landmarks
|
package/dist/index.d.mts
CHANGED
|
@@ -29,6 +29,7 @@ interface FastCheckRequest {
|
|
|
29
29
|
model?: FastCheckModel;
|
|
30
30
|
source?: FrameSource;
|
|
31
31
|
frames: FrameData[];
|
|
32
|
+
warnings?: string[];
|
|
32
33
|
}
|
|
33
34
|
interface FastCheckCropsRequest {
|
|
34
35
|
session_id: string;
|
|
@@ -64,6 +65,7 @@ interface FastCheckStreamRequest {
|
|
|
64
65
|
model?: FastCheckModel;
|
|
65
66
|
source?: FrameSource;
|
|
66
67
|
frame: FrameData;
|
|
68
|
+
warnings?: string[];
|
|
67
69
|
}
|
|
68
70
|
interface FastCheckResponse {
|
|
69
71
|
verdict: Verdict | null;
|
|
@@ -76,6 +78,7 @@ interface FastCheckResponse {
|
|
|
76
78
|
frames_processed: number;
|
|
77
79
|
available: boolean;
|
|
78
80
|
warning: string | null;
|
|
81
|
+
warnings?: string[] | null;
|
|
79
82
|
error: string | null;
|
|
80
83
|
}
|
|
81
84
|
interface FastCheckStreamResponse {
|
|
@@ -93,6 +96,7 @@ interface FastCheckStreamResponse {
|
|
|
93
96
|
frames_processed: number | null;
|
|
94
97
|
available: boolean;
|
|
95
98
|
warning: string | null;
|
|
99
|
+
warnings?: string[] | null;
|
|
96
100
|
error: string | null;
|
|
97
101
|
}
|
|
98
102
|
interface VerifyResponse {
|
|
@@ -157,6 +161,7 @@ interface LivenessResult {
|
|
|
157
161
|
sessionId: string;
|
|
158
162
|
processingMs: number;
|
|
159
163
|
framesProcessed: number;
|
|
164
|
+
warnings?: string[];
|
|
160
165
|
}
|
|
161
166
|
type LivenessState = 'idle' | 'capturing' | 'uploading' | 'processing' | 'complete' | 'error';
|
|
162
167
|
interface LivenessConfig {
|
|
@@ -215,6 +220,40 @@ interface LivenessCallbacks {
|
|
|
215
220
|
onStateChange?: OnStateChangeCallback;
|
|
216
221
|
}
|
|
217
222
|
|
|
223
|
+
interface FaceLandmarkPoint {
|
|
224
|
+
x: number;
|
|
225
|
+
y: number;
|
|
226
|
+
z: number;
|
|
227
|
+
}
|
|
228
|
+
interface LandmarkValidationResult {
|
|
229
|
+
valid: boolean;
|
|
230
|
+
message?: string;
|
|
231
|
+
}
|
|
232
|
+
declare const LANDMARK_INDEX: {
|
|
233
|
+
readonly NOSE_TIP: 1;
|
|
234
|
+
readonly UPPER_LIP: 13;
|
|
235
|
+
readonly LOWER_LIP: 14;
|
|
236
|
+
};
|
|
237
|
+
declare const EYE_LANDMARK_INDICES: {
|
|
238
|
+
readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
|
|
239
|
+
readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
|
|
240
|
+
};
|
|
241
|
+
interface EyeRegionBounds {
|
|
242
|
+
x: number;
|
|
243
|
+
y: number;
|
|
244
|
+
width: number;
|
|
245
|
+
height: number;
|
|
246
|
+
}
|
|
247
|
+
interface EyeRegionsBounds {
|
|
248
|
+
leftEye: EyeRegionBounds;
|
|
249
|
+
rightEye: EyeRegionBounds;
|
|
250
|
+
}
|
|
251
|
+
declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
|
|
252
|
+
declare const LANDMARK_MIN_BOUND = 0.1;
|
|
253
|
+
declare const LANDMARK_MAX_BOUND = 0.9;
|
|
254
|
+
declare const MIN_LANDMARK_COUNT = 15;
|
|
255
|
+
declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
|
|
256
|
+
|
|
218
257
|
interface FaceBoundingBox {
|
|
219
258
|
originX: number;
|
|
220
259
|
originY: number;
|
|
@@ -272,15 +311,17 @@ declare const MIN_FACE_SIDE_MARGIN = 0.05;
|
|
|
272
311
|
declare const MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
273
312
|
declare const HIGH_ALIGNMENT = 0.85;
|
|
274
313
|
declare const GOOD_ALIGNMENT = 0.5;
|
|
275
|
-
declare const IDEAL_CROP_MULTIPLIER =
|
|
276
|
-
declare const MIN_CROP_MULTIPLIER =
|
|
277
|
-
declare const MAX_CROP_MULTIPLIER =
|
|
314
|
+
declare const IDEAL_CROP_MULTIPLIER = 3;
|
|
315
|
+
declare const MIN_CROP_MULTIPLIER = 2.5;
|
|
316
|
+
declare const MAX_CROP_MULTIPLIER = 4;
|
|
278
317
|
declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
318
|
+
declare const MIN_IDEAL_FACE_RATIO = 0.05;
|
|
319
|
+
declare const MAX_IDEAL_FACE_RATIO = 0.2;
|
|
279
320
|
declare const MIN_FACE_RATIO = 0.036;
|
|
280
321
|
declare const MAX_FACE_RATIO = 0.7;
|
|
281
322
|
declare const FACE_CROP_OUTPUT_SIZE = 224;
|
|
282
|
-
declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.
|
|
283
|
-
declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.
|
|
323
|
+
declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
|
|
324
|
+
declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
|
|
284
325
|
declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
|
|
285
326
|
declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
|
|
286
327
|
declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
|
|
@@ -306,6 +347,12 @@ declare function checkFrameQuality(options: {
|
|
|
306
347
|
lightingAnalysis?: LightingAnalysis;
|
|
307
348
|
minAlignment?: number;
|
|
308
349
|
}): FrameQualityResult;
|
|
350
|
+
interface FaceRollResult {
|
|
351
|
+
roll: number;
|
|
352
|
+
tooTilted: boolean;
|
|
353
|
+
}
|
|
354
|
+
declare const MAX_FACE_ROLL_DEGREES = 15;
|
|
355
|
+
declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
|
|
309
356
|
declare class BaseFrameCollector {
|
|
310
357
|
protected frames: CapturedFrame[];
|
|
311
358
|
protected maxFrames: number;
|
|
@@ -321,40 +368,6 @@ declare class BaseFrameCollector {
|
|
|
321
368
|
getNextIndex(): number;
|
|
322
369
|
}
|
|
323
370
|
|
|
324
|
-
interface FaceLandmarkPoint {
|
|
325
|
-
x: number;
|
|
326
|
-
y: number;
|
|
327
|
-
z: number;
|
|
328
|
-
}
|
|
329
|
-
interface LandmarkValidationResult {
|
|
330
|
-
valid: boolean;
|
|
331
|
-
message?: string;
|
|
332
|
-
}
|
|
333
|
-
declare const LANDMARK_INDEX: {
|
|
334
|
-
readonly NOSE_TIP: 1;
|
|
335
|
-
readonly UPPER_LIP: 13;
|
|
336
|
-
readonly LOWER_LIP: 14;
|
|
337
|
-
};
|
|
338
|
-
declare const EYE_LANDMARK_INDICES: {
|
|
339
|
-
readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
|
|
340
|
-
readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
|
|
341
|
-
};
|
|
342
|
-
interface EyeRegionBounds {
|
|
343
|
-
x: number;
|
|
344
|
-
y: number;
|
|
345
|
-
width: number;
|
|
346
|
-
height: number;
|
|
347
|
-
}
|
|
348
|
-
interface EyeRegionsBounds {
|
|
349
|
-
leftEye: EyeRegionBounds;
|
|
350
|
-
rightEye: EyeRegionBounds;
|
|
351
|
-
}
|
|
352
|
-
declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
|
|
353
|
-
declare const LANDMARK_MIN_BOUND = 0.1;
|
|
354
|
-
declare const LANDMARK_MAX_BOUND = 0.9;
|
|
355
|
-
declare const MIN_LANDMARK_COUNT = 15;
|
|
356
|
-
declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
|
|
357
|
-
|
|
358
371
|
interface DetectionResult {
|
|
359
372
|
type: string;
|
|
360
373
|
passed: boolean;
|
|
@@ -469,22 +482,26 @@ declare class LivenessClient {
|
|
|
469
482
|
sessionId?: string;
|
|
470
483
|
model?: FastCheckModel;
|
|
471
484
|
source?: FrameSource;
|
|
485
|
+
warnings?: string[];
|
|
472
486
|
}): Promise<LivenessResult>;
|
|
473
487
|
fastCheckCrops(crops: CropData[], options?: {
|
|
474
488
|
sessionId?: string;
|
|
475
489
|
model?: FastCheckModel;
|
|
476
490
|
source?: FrameSource;
|
|
491
|
+
warnings?: string[];
|
|
477
492
|
}): Promise<LivenessResult>;
|
|
478
493
|
streamFrame(frame: CapturedFrame, options: {
|
|
479
494
|
sessionId: string;
|
|
480
495
|
model?: FastCheckModel;
|
|
481
496
|
source?: FrameSource;
|
|
497
|
+
warnings?: string[];
|
|
482
498
|
}): Promise<FastCheckStreamResponse>;
|
|
483
499
|
private sendStreamFrameInternal;
|
|
484
500
|
fastCheckStream(frames: CapturedFrame[], options?: {
|
|
485
501
|
sessionId?: string;
|
|
486
502
|
model?: FastCheckModel;
|
|
487
503
|
source?: FrameSource;
|
|
504
|
+
warnings?: string[];
|
|
488
505
|
}, callbacks?: {
|
|
489
506
|
onFrameSent?: (index: number, total: number) => void;
|
|
490
507
|
onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
|
|
@@ -493,6 +510,7 @@ declare class LivenessClient {
|
|
|
493
510
|
sessionId?: string;
|
|
494
511
|
model?: FastCheckModel;
|
|
495
512
|
source?: FrameSource;
|
|
513
|
+
warnings?: string[];
|
|
496
514
|
}, callbacks?: {
|
|
497
515
|
onFrameSent?: (index: number, total: number) => void;
|
|
498
516
|
onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
|
|
@@ -620,7 +638,7 @@ declare const ERROR_MESSAGES_ES: Record<string, string>;
|
|
|
620
638
|
declare function getApiErrorMessage(code: string | undefined, message?: string, customMessages?: Record<string, string>): string;
|
|
621
639
|
declare function isRetryableError(code: string | undefined): boolean;
|
|
622
640
|
|
|
623
|
-
declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.
|
|
641
|
+
declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
|
|
624
642
|
declare const ALIGNMENT_THRESHOLD_POOR = 0.6;
|
|
625
643
|
declare const ALIGNMENT_THRESHOLD_GOOD = 0.6;
|
|
626
644
|
declare const ALIGNMENT_THRESHOLD_PERFECT = 0.85;
|
|
@@ -677,6 +695,8 @@ declare const FEEDBACK_MESSAGES: {
|
|
|
677
695
|
readonly move_back: "Move back - face too close";
|
|
678
696
|
readonly too_close: "Move back - face too close";
|
|
679
697
|
readonly too_far: "Move closer - face too far";
|
|
698
|
+
readonly move_closer_ideal: "Move a little closer";
|
|
699
|
+
readonly move_back_ideal: "Move back slightly";
|
|
680
700
|
readonly face_not_visible: "Center your face - edges cut off";
|
|
681
701
|
readonly partial_face: "Center your face - edges cut off";
|
|
682
702
|
readonly hold_still: "Hold still - image blurry";
|
|
@@ -684,6 +704,8 @@ declare const FEEDBACK_MESSAGES: {
|
|
|
684
704
|
readonly poor_lighting: "Improve lighting";
|
|
685
705
|
readonly too_dark: "Low lighting - move to a brighter area";
|
|
686
706
|
readonly backlit: "Backlit - try facing the light source";
|
|
707
|
+
readonly phone_angle_low: "Raise your phone to eye level";
|
|
708
|
+
readonly phone_tilted: "Hold your phone level";
|
|
687
709
|
readonly hand_detected: "Remove hand from face";
|
|
688
710
|
readonly eyes_not_visible: "Eyes not clearly visible";
|
|
689
711
|
readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
|
|
@@ -714,6 +736,10 @@ interface CaptureQualityState {
|
|
|
714
736
|
isPartialFace: boolean;
|
|
715
737
|
framesCaptured: number;
|
|
716
738
|
targetFrames: number;
|
|
739
|
+
tooFarFromIdeal?: boolean;
|
|
740
|
+
tooCloseToIdeal?: boolean;
|
|
741
|
+
phoneAngled?: boolean;
|
|
742
|
+
phoneTilted?: boolean;
|
|
717
743
|
}
|
|
718
744
|
declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
|
|
719
745
|
declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
|
|
@@ -755,13 +781,13 @@ interface EyeQualityThresholds {
|
|
|
755
781
|
minBrightness: number;
|
|
756
782
|
maxBrightness: number;
|
|
757
783
|
minContrast: number;
|
|
758
|
-
|
|
784
|
+
glareRelativeFactor: number;
|
|
759
785
|
maxGlareRatio: number;
|
|
760
786
|
}
|
|
761
787
|
declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
|
|
762
788
|
declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
|
|
763
789
|
declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
|
|
764
|
-
declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray,
|
|
790
|
+
declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
|
|
765
791
|
declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
|
|
766
792
|
|
|
767
|
-
export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, 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, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, 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 ModelEntry, type ModelType, type ModelsResponse, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, OVAL_REGION_DESKTOP, OVAL_REGION_MOBILE, 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
|
|
793
|
+
export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, 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, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, 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 FaceRollResult, 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, MAX_FACE_ROLL_DEGREES, MAX_IDEAL_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_IDEAL_FACE_RATIO, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelEntry, type ModelType, type ModelsResponse, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, OVAL_REGION_DESKTOP, OVAL_REGION_MOBILE, 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectFaceRoll, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ interface FastCheckRequest {
|
|
|
29
29
|
model?: FastCheckModel;
|
|
30
30
|
source?: FrameSource;
|
|
31
31
|
frames: FrameData[];
|
|
32
|
+
warnings?: string[];
|
|
32
33
|
}
|
|
33
34
|
interface FastCheckCropsRequest {
|
|
34
35
|
session_id: string;
|
|
@@ -64,6 +65,7 @@ interface FastCheckStreamRequest {
|
|
|
64
65
|
model?: FastCheckModel;
|
|
65
66
|
source?: FrameSource;
|
|
66
67
|
frame: FrameData;
|
|
68
|
+
warnings?: string[];
|
|
67
69
|
}
|
|
68
70
|
interface FastCheckResponse {
|
|
69
71
|
verdict: Verdict | null;
|
|
@@ -76,6 +78,7 @@ interface FastCheckResponse {
|
|
|
76
78
|
frames_processed: number;
|
|
77
79
|
available: boolean;
|
|
78
80
|
warning: string | null;
|
|
81
|
+
warnings?: string[] | null;
|
|
79
82
|
error: string | null;
|
|
80
83
|
}
|
|
81
84
|
interface FastCheckStreamResponse {
|
|
@@ -93,6 +96,7 @@ interface FastCheckStreamResponse {
|
|
|
93
96
|
frames_processed: number | null;
|
|
94
97
|
available: boolean;
|
|
95
98
|
warning: string | null;
|
|
99
|
+
warnings?: string[] | null;
|
|
96
100
|
error: string | null;
|
|
97
101
|
}
|
|
98
102
|
interface VerifyResponse {
|
|
@@ -157,6 +161,7 @@ interface LivenessResult {
|
|
|
157
161
|
sessionId: string;
|
|
158
162
|
processingMs: number;
|
|
159
163
|
framesProcessed: number;
|
|
164
|
+
warnings?: string[];
|
|
160
165
|
}
|
|
161
166
|
type LivenessState = 'idle' | 'capturing' | 'uploading' | 'processing' | 'complete' | 'error';
|
|
162
167
|
interface LivenessConfig {
|
|
@@ -215,6 +220,40 @@ interface LivenessCallbacks {
|
|
|
215
220
|
onStateChange?: OnStateChangeCallback;
|
|
216
221
|
}
|
|
217
222
|
|
|
223
|
+
interface FaceLandmarkPoint {
|
|
224
|
+
x: number;
|
|
225
|
+
y: number;
|
|
226
|
+
z: number;
|
|
227
|
+
}
|
|
228
|
+
interface LandmarkValidationResult {
|
|
229
|
+
valid: boolean;
|
|
230
|
+
message?: string;
|
|
231
|
+
}
|
|
232
|
+
declare const LANDMARK_INDEX: {
|
|
233
|
+
readonly NOSE_TIP: 1;
|
|
234
|
+
readonly UPPER_LIP: 13;
|
|
235
|
+
readonly LOWER_LIP: 14;
|
|
236
|
+
};
|
|
237
|
+
declare const EYE_LANDMARK_INDICES: {
|
|
238
|
+
readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
|
|
239
|
+
readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
|
|
240
|
+
};
|
|
241
|
+
interface EyeRegionBounds {
|
|
242
|
+
x: number;
|
|
243
|
+
y: number;
|
|
244
|
+
width: number;
|
|
245
|
+
height: number;
|
|
246
|
+
}
|
|
247
|
+
interface EyeRegionsBounds {
|
|
248
|
+
leftEye: EyeRegionBounds;
|
|
249
|
+
rightEye: EyeRegionBounds;
|
|
250
|
+
}
|
|
251
|
+
declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
|
|
252
|
+
declare const LANDMARK_MIN_BOUND = 0.1;
|
|
253
|
+
declare const LANDMARK_MAX_BOUND = 0.9;
|
|
254
|
+
declare const MIN_LANDMARK_COUNT = 15;
|
|
255
|
+
declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
|
|
256
|
+
|
|
218
257
|
interface FaceBoundingBox {
|
|
219
258
|
originX: number;
|
|
220
259
|
originY: number;
|
|
@@ -272,15 +311,17 @@ declare const MIN_FACE_SIDE_MARGIN = 0.05;
|
|
|
272
311
|
declare const MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
273
312
|
declare const HIGH_ALIGNMENT = 0.85;
|
|
274
313
|
declare const GOOD_ALIGNMENT = 0.5;
|
|
275
|
-
declare const IDEAL_CROP_MULTIPLIER =
|
|
276
|
-
declare const MIN_CROP_MULTIPLIER =
|
|
277
|
-
declare const MAX_CROP_MULTIPLIER =
|
|
314
|
+
declare const IDEAL_CROP_MULTIPLIER = 3;
|
|
315
|
+
declare const MIN_CROP_MULTIPLIER = 2.5;
|
|
316
|
+
declare const MAX_CROP_MULTIPLIER = 4;
|
|
278
317
|
declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
318
|
+
declare const MIN_IDEAL_FACE_RATIO = 0.05;
|
|
319
|
+
declare const MAX_IDEAL_FACE_RATIO = 0.2;
|
|
279
320
|
declare const MIN_FACE_RATIO = 0.036;
|
|
280
321
|
declare const MAX_FACE_RATIO = 0.7;
|
|
281
322
|
declare const FACE_CROP_OUTPUT_SIZE = 224;
|
|
282
|
-
declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.
|
|
283
|
-
declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.
|
|
323
|
+
declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
|
|
324
|
+
declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
|
|
284
325
|
declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
|
|
285
326
|
declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
|
|
286
327
|
declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
|
|
@@ -306,6 +347,12 @@ declare function checkFrameQuality(options: {
|
|
|
306
347
|
lightingAnalysis?: LightingAnalysis;
|
|
307
348
|
minAlignment?: number;
|
|
308
349
|
}): FrameQualityResult;
|
|
350
|
+
interface FaceRollResult {
|
|
351
|
+
roll: number;
|
|
352
|
+
tooTilted: boolean;
|
|
353
|
+
}
|
|
354
|
+
declare const MAX_FACE_ROLL_DEGREES = 15;
|
|
355
|
+
declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
|
|
309
356
|
declare class BaseFrameCollector {
|
|
310
357
|
protected frames: CapturedFrame[];
|
|
311
358
|
protected maxFrames: number;
|
|
@@ -321,40 +368,6 @@ declare class BaseFrameCollector {
|
|
|
321
368
|
getNextIndex(): number;
|
|
322
369
|
}
|
|
323
370
|
|
|
324
|
-
interface FaceLandmarkPoint {
|
|
325
|
-
x: number;
|
|
326
|
-
y: number;
|
|
327
|
-
z: number;
|
|
328
|
-
}
|
|
329
|
-
interface LandmarkValidationResult {
|
|
330
|
-
valid: boolean;
|
|
331
|
-
message?: string;
|
|
332
|
-
}
|
|
333
|
-
declare const LANDMARK_INDEX: {
|
|
334
|
-
readonly NOSE_TIP: 1;
|
|
335
|
-
readonly UPPER_LIP: 13;
|
|
336
|
-
readonly LOWER_LIP: 14;
|
|
337
|
-
};
|
|
338
|
-
declare const EYE_LANDMARK_INDICES: {
|
|
339
|
-
readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
|
|
340
|
-
readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
|
|
341
|
-
};
|
|
342
|
-
interface EyeRegionBounds {
|
|
343
|
-
x: number;
|
|
344
|
-
y: number;
|
|
345
|
-
width: number;
|
|
346
|
-
height: number;
|
|
347
|
-
}
|
|
348
|
-
interface EyeRegionsBounds {
|
|
349
|
-
leftEye: EyeRegionBounds;
|
|
350
|
-
rightEye: EyeRegionBounds;
|
|
351
|
-
}
|
|
352
|
-
declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
|
|
353
|
-
declare const LANDMARK_MIN_BOUND = 0.1;
|
|
354
|
-
declare const LANDMARK_MAX_BOUND = 0.9;
|
|
355
|
-
declare const MIN_LANDMARK_COUNT = 15;
|
|
356
|
-
declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
|
|
357
|
-
|
|
358
371
|
interface DetectionResult {
|
|
359
372
|
type: string;
|
|
360
373
|
passed: boolean;
|
|
@@ -469,22 +482,26 @@ declare class LivenessClient {
|
|
|
469
482
|
sessionId?: string;
|
|
470
483
|
model?: FastCheckModel;
|
|
471
484
|
source?: FrameSource;
|
|
485
|
+
warnings?: string[];
|
|
472
486
|
}): Promise<LivenessResult>;
|
|
473
487
|
fastCheckCrops(crops: CropData[], options?: {
|
|
474
488
|
sessionId?: string;
|
|
475
489
|
model?: FastCheckModel;
|
|
476
490
|
source?: FrameSource;
|
|
491
|
+
warnings?: string[];
|
|
477
492
|
}): Promise<LivenessResult>;
|
|
478
493
|
streamFrame(frame: CapturedFrame, options: {
|
|
479
494
|
sessionId: string;
|
|
480
495
|
model?: FastCheckModel;
|
|
481
496
|
source?: FrameSource;
|
|
497
|
+
warnings?: string[];
|
|
482
498
|
}): Promise<FastCheckStreamResponse>;
|
|
483
499
|
private sendStreamFrameInternal;
|
|
484
500
|
fastCheckStream(frames: CapturedFrame[], options?: {
|
|
485
501
|
sessionId?: string;
|
|
486
502
|
model?: FastCheckModel;
|
|
487
503
|
source?: FrameSource;
|
|
504
|
+
warnings?: string[];
|
|
488
505
|
}, callbacks?: {
|
|
489
506
|
onFrameSent?: (index: number, total: number) => void;
|
|
490
507
|
onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
|
|
@@ -493,6 +510,7 @@ declare class LivenessClient {
|
|
|
493
510
|
sessionId?: string;
|
|
494
511
|
model?: FastCheckModel;
|
|
495
512
|
source?: FrameSource;
|
|
513
|
+
warnings?: string[];
|
|
496
514
|
}, callbacks?: {
|
|
497
515
|
onFrameSent?: (index: number, total: number) => void;
|
|
498
516
|
onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
|
|
@@ -620,7 +638,7 @@ declare const ERROR_MESSAGES_ES: Record<string, string>;
|
|
|
620
638
|
declare function getApiErrorMessage(code: string | undefined, message?: string, customMessages?: Record<string, string>): string;
|
|
621
639
|
declare function isRetryableError(code: string | undefined): boolean;
|
|
622
640
|
|
|
623
|
-
declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.
|
|
641
|
+
declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
|
|
624
642
|
declare const ALIGNMENT_THRESHOLD_POOR = 0.6;
|
|
625
643
|
declare const ALIGNMENT_THRESHOLD_GOOD = 0.6;
|
|
626
644
|
declare const ALIGNMENT_THRESHOLD_PERFECT = 0.85;
|
|
@@ -677,6 +695,8 @@ declare const FEEDBACK_MESSAGES: {
|
|
|
677
695
|
readonly move_back: "Move back - face too close";
|
|
678
696
|
readonly too_close: "Move back - face too close";
|
|
679
697
|
readonly too_far: "Move closer - face too far";
|
|
698
|
+
readonly move_closer_ideal: "Move a little closer";
|
|
699
|
+
readonly move_back_ideal: "Move back slightly";
|
|
680
700
|
readonly face_not_visible: "Center your face - edges cut off";
|
|
681
701
|
readonly partial_face: "Center your face - edges cut off";
|
|
682
702
|
readonly hold_still: "Hold still - image blurry";
|
|
@@ -684,6 +704,8 @@ declare const FEEDBACK_MESSAGES: {
|
|
|
684
704
|
readonly poor_lighting: "Improve lighting";
|
|
685
705
|
readonly too_dark: "Low lighting - move to a brighter area";
|
|
686
706
|
readonly backlit: "Backlit - try facing the light source";
|
|
707
|
+
readonly phone_angle_low: "Raise your phone to eye level";
|
|
708
|
+
readonly phone_tilted: "Hold your phone level";
|
|
687
709
|
readonly hand_detected: "Remove hand from face";
|
|
688
710
|
readonly eyes_not_visible: "Eyes not clearly visible";
|
|
689
711
|
readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
|
|
@@ -714,6 +736,10 @@ interface CaptureQualityState {
|
|
|
714
736
|
isPartialFace: boolean;
|
|
715
737
|
framesCaptured: number;
|
|
716
738
|
targetFrames: number;
|
|
739
|
+
tooFarFromIdeal?: boolean;
|
|
740
|
+
tooCloseToIdeal?: boolean;
|
|
741
|
+
phoneAngled?: boolean;
|
|
742
|
+
phoneTilted?: boolean;
|
|
717
743
|
}
|
|
718
744
|
declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
|
|
719
745
|
declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
|
|
@@ -755,13 +781,13 @@ interface EyeQualityThresholds {
|
|
|
755
781
|
minBrightness: number;
|
|
756
782
|
maxBrightness: number;
|
|
757
783
|
minContrast: number;
|
|
758
|
-
|
|
784
|
+
glareRelativeFactor: number;
|
|
759
785
|
maxGlareRatio: number;
|
|
760
786
|
}
|
|
761
787
|
declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
|
|
762
788
|
declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
|
|
763
789
|
declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
|
|
764
|
-
declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray,
|
|
790
|
+
declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
|
|
765
791
|
declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
|
|
766
792
|
|
|
767
|
-
export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, 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, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, 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 ModelEntry, type ModelType, type ModelsResponse, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, OVAL_REGION_DESKTOP, OVAL_REGION_MOBILE, 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
|
|
793
|
+
export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, 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, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, 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 FaceRollResult, 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, MAX_FACE_ROLL_DEGREES, MAX_IDEAL_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_IDEAL_FACE_RATIO, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelEntry, type ModelType, type ModelsResponse, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, OVAL_REGION_DESKTOP, OVAL_REGION_MOBILE, 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectFaceRoll, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
|
package/dist/index.js
CHANGED
|
@@ -66,12 +66,15 @@ __export(index_exports, {
|
|
|
66
66
|
MAX_CROP_MULTIPLIER: () => MAX_CROP_MULTIPLIER,
|
|
67
67
|
MAX_FACE_PERCENTAGE_IN_CROP: () => MAX_FACE_PERCENTAGE_IN_CROP,
|
|
68
68
|
MAX_FACE_RATIO: () => MAX_FACE_RATIO,
|
|
69
|
+
MAX_FACE_ROLL_DEGREES: () => MAX_FACE_ROLL_DEGREES,
|
|
70
|
+
MAX_IDEAL_FACE_RATIO: () => MAX_IDEAL_FACE_RATIO,
|
|
69
71
|
MIN_CAPTURE_ALIGNMENT: () => MIN_CAPTURE_ALIGNMENT,
|
|
70
72
|
MIN_CROP_MULTIPLIER: () => MIN_CROP_MULTIPLIER,
|
|
71
73
|
MIN_FACE_BOTTOM_MARGIN: () => MIN_FACE_BOTTOM_MARGIN,
|
|
72
74
|
MIN_FACE_RATIO: () => MIN_FACE_RATIO,
|
|
73
75
|
MIN_FACE_SIDE_MARGIN: () => MIN_FACE_SIDE_MARGIN,
|
|
74
76
|
MIN_FACE_TOP_MARGIN: () => MIN_FACE_TOP_MARGIN,
|
|
77
|
+
MIN_IDEAL_FACE_RATIO: () => MIN_IDEAL_FACE_RATIO,
|
|
75
78
|
MIN_LANDMARK_COUNT: () => MIN_LANDMARK_COUNT,
|
|
76
79
|
MODEL_CONFIGS: () => MODEL_CONFIGS,
|
|
77
80
|
OVAL_GUIDE_COLORS: () => OVAL_GUIDE_COLORS,
|
|
@@ -92,6 +95,7 @@ __export(index_exports, {
|
|
|
92
95
|
checkEyeRegionQuality: () => checkEyeRegionQuality,
|
|
93
96
|
checkFrameQuality: () => checkFrameQuality,
|
|
94
97
|
decodeBase64: () => decodeBase64,
|
|
98
|
+
detectFaceRoll: () => detectFaceRoll,
|
|
95
99
|
detectSpecularHighlights: () => detectSpecularHighlights,
|
|
96
100
|
encodeBase64: () => encodeBase64,
|
|
97
101
|
generateSessionId: () => generateSessionId,
|
|
@@ -257,7 +261,8 @@ function toLivenessResult(response) {
|
|
|
257
261
|
score: response.score ?? 0,
|
|
258
262
|
sessionId: response.session_id,
|
|
259
263
|
processingMs: response.processing_ms,
|
|
260
|
-
framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0
|
|
264
|
+
framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0,
|
|
265
|
+
warnings: "warnings" in response && Array.isArray(response.warnings) ? response.warnings : void 0
|
|
261
266
|
};
|
|
262
267
|
}
|
|
263
268
|
function toLivenessResultFromStream(response) {
|
|
@@ -274,7 +279,8 @@ function toLivenessResultFromStream(response) {
|
|
|
274
279
|
score: response.score ?? 0,
|
|
275
280
|
sessionId: response.session_id,
|
|
276
281
|
processingMs: response.processing_ms ?? 0,
|
|
277
|
-
framesProcessed: response.frames_processed ?? 0
|
|
282
|
+
framesProcessed: response.frames_processed ?? 0,
|
|
283
|
+
warnings: Array.isArray(response.warnings) ? response.warnings : void 0
|
|
278
284
|
};
|
|
279
285
|
}
|
|
280
286
|
function generateSessionId() {
|
|
@@ -453,7 +459,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
453
459
|
session_id: options.sessionId ?? generateSessionId(),
|
|
454
460
|
model: options.model ?? "10",
|
|
455
461
|
source: options.source ?? "live",
|
|
456
|
-
frames: toFrameData(frames)
|
|
462
|
+
frames: toFrameData(frames),
|
|
463
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
457
464
|
};
|
|
458
465
|
const response = await this.requestWithRetry(API_PATHS.fastCheck, {
|
|
459
466
|
method: "POST",
|
|
@@ -473,7 +480,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
473
480
|
session_id: options.sessionId ?? generateSessionId(),
|
|
474
481
|
model: options.model ?? "10",
|
|
475
482
|
source: options.source ?? "live",
|
|
476
|
-
crops
|
|
483
|
+
crops,
|
|
484
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
477
485
|
};
|
|
478
486
|
const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
|
|
479
487
|
method: "POST",
|
|
@@ -507,7 +515,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
507
515
|
return this.sendStreamFrameInternal(frameData, {
|
|
508
516
|
sessionId: options.sessionId,
|
|
509
517
|
model: options.model ?? "10",
|
|
510
|
-
source: options.source ?? "live"
|
|
518
|
+
source: options.source ?? "live",
|
|
519
|
+
warnings: options.warnings
|
|
511
520
|
});
|
|
512
521
|
}
|
|
513
522
|
/**
|
|
@@ -522,7 +531,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
522
531
|
session_id: options.sessionId,
|
|
523
532
|
model: options.model,
|
|
524
533
|
source: options.source,
|
|
525
|
-
frame: frameData
|
|
534
|
+
frame: frameData,
|
|
535
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
526
536
|
};
|
|
527
537
|
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
528
538
|
method: "POST",
|
|
@@ -551,7 +561,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
551
561
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
552
562
|
sessionId,
|
|
553
563
|
model,
|
|
554
|
-
source
|
|
564
|
+
source,
|
|
565
|
+
warnings: options.warnings
|
|
555
566
|
});
|
|
556
567
|
callbacks?.onFrameSent?.(index + 1, total);
|
|
557
568
|
if (response.status === "buffering") {
|
|
@@ -600,7 +611,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
600
611
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
601
612
|
sessionId,
|
|
602
613
|
model,
|
|
603
|
-
source
|
|
614
|
+
source,
|
|
615
|
+
warnings: options.warnings
|
|
604
616
|
});
|
|
605
617
|
const currentIndex = frameData.index;
|
|
606
618
|
callbacks?.onFrameSent?.(currentIndex + 1, total);
|
|
@@ -1177,7 +1189,7 @@ function isRetryableError(code) {
|
|
|
1177
1189
|
}
|
|
1178
1190
|
|
|
1179
1191
|
// src/constants/feedback.ts
|
|
1180
|
-
var ALIGNMENT_THRESHOLD_CAPTURE = 0.
|
|
1192
|
+
var ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
|
|
1181
1193
|
var ALIGNMENT_THRESHOLD_POOR = 0.6;
|
|
1182
1194
|
var ALIGNMENT_THRESHOLD_GOOD = 0.6;
|
|
1183
1195
|
var ALIGNMENT_THRESHOLD_PERFECT = 0.85;
|
|
@@ -1230,6 +1242,8 @@ var FEEDBACK_MESSAGES = {
|
|
|
1230
1242
|
move_back: "Move back - face too close",
|
|
1231
1243
|
too_close: "Move back - face too close",
|
|
1232
1244
|
too_far: "Move closer - face too far",
|
|
1245
|
+
move_closer_ideal: "Move a little closer",
|
|
1246
|
+
move_back_ideal: "Move back slightly",
|
|
1233
1247
|
// Visibility issues
|
|
1234
1248
|
face_not_visible: "Center your face - edges cut off",
|
|
1235
1249
|
partial_face: "Center your face - edges cut off",
|
|
@@ -1240,6 +1254,9 @@ var FEEDBACK_MESSAGES = {
|
|
|
1240
1254
|
poor_lighting: "Improve lighting",
|
|
1241
1255
|
too_dark: "Low lighting - move to a brighter area",
|
|
1242
1256
|
backlit: "Backlit - try facing the light source",
|
|
1257
|
+
// Phone orientation
|
|
1258
|
+
phone_angle_low: "Raise your phone to eye level",
|
|
1259
|
+
phone_tilted: "Hold your phone level",
|
|
1243
1260
|
// Hand occlusion
|
|
1244
1261
|
hand_detected: "Remove hand from face",
|
|
1245
1262
|
// Eye region quality
|
|
@@ -1287,6 +1304,8 @@ var ES_LOCALE = {
|
|
|
1287
1304
|
move_back: "Al\xE9jate - rostro muy cerca",
|
|
1288
1305
|
too_close: "Al\xE9jate - rostro muy cerca",
|
|
1289
1306
|
too_far: "Ac\xE9rcate - rostro muy lejos",
|
|
1307
|
+
move_closer_ideal: "Ac\xE9rcate un poco",
|
|
1308
|
+
move_back_ideal: "Al\xE9jate un poco",
|
|
1290
1309
|
// Visibility issues
|
|
1291
1310
|
face_not_visible: "Centra tu rostro - bordes cortados",
|
|
1292
1311
|
partial_face: "Centra tu rostro - bordes cortados",
|
|
@@ -1297,6 +1316,9 @@ var ES_LOCALE = {
|
|
|
1297
1316
|
poor_lighting: "Mejora la iluminaci\xF3n",
|
|
1298
1317
|
too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
|
|
1299
1318
|
backlit: "Contraluz - intenta mirar hacia la fuente de luz",
|
|
1319
|
+
// Phone orientation
|
|
1320
|
+
phone_angle_low: "Levanta el tel\xE9fono a la altura de los ojos",
|
|
1321
|
+
phone_tilted: "Mant\xE9n el tel\xE9fono nivelado",
|
|
1300
1322
|
// Hand occlusion
|
|
1301
1323
|
hand_detected: "Retira la mano del rostro",
|
|
1302
1324
|
// Eye region quality
|
|
@@ -1329,11 +1351,21 @@ function getCaptureQualityFeedback(state) {
|
|
|
1329
1351
|
isBlurry,
|
|
1330
1352
|
isPartialFace,
|
|
1331
1353
|
framesCaptured,
|
|
1332
|
-
targetFrames
|
|
1354
|
+
targetFrames,
|
|
1355
|
+
tooFarFromIdeal,
|
|
1356
|
+
tooCloseToIdeal,
|
|
1357
|
+
phoneAngled,
|
|
1358
|
+
phoneTilted
|
|
1333
1359
|
} = state;
|
|
1334
1360
|
if (!hasFace) {
|
|
1335
1361
|
return FEEDBACK_MESSAGES.no_face;
|
|
1336
1362
|
}
|
|
1363
|
+
if (phoneTilted) {
|
|
1364
|
+
return FEEDBACK_MESSAGES.phone_tilted;
|
|
1365
|
+
}
|
|
1366
|
+
if (phoneAngled) {
|
|
1367
|
+
return FEEDBACK_MESSAGES.phone_angle_low;
|
|
1368
|
+
}
|
|
1337
1369
|
if (tooClose) {
|
|
1338
1370
|
return FEEDBACK_MESSAGES.too_close;
|
|
1339
1371
|
}
|
|
@@ -1346,6 +1378,12 @@ function getCaptureQualityFeedback(state) {
|
|
|
1346
1378
|
if (isBlurry) {
|
|
1347
1379
|
return FEEDBACK_MESSAGES.blurry;
|
|
1348
1380
|
}
|
|
1381
|
+
if (tooFarFromIdeal) {
|
|
1382
|
+
return FEEDBACK_MESSAGES.move_closer_ideal;
|
|
1383
|
+
}
|
|
1384
|
+
if (tooCloseToIdeal) {
|
|
1385
|
+
return FEEDBACK_MESSAGES.move_back_ideal;
|
|
1386
|
+
}
|
|
1349
1387
|
if (alignment < ALIGNMENT_THRESHOLD_POOR) {
|
|
1350
1388
|
return FEEDBACK_MESSAGES.move_closer_to_center;
|
|
1351
1389
|
}
|
|
@@ -1361,8 +1399,19 @@ function getCaptureQualityFeedback(state) {
|
|
|
1361
1399
|
return FEEDBACK_MESSAGES.perfect;
|
|
1362
1400
|
}
|
|
1363
1401
|
function canCaptureFrame(state) {
|
|
1364
|
-
const {
|
|
1365
|
-
|
|
1402
|
+
const {
|
|
1403
|
+
hasFace,
|
|
1404
|
+
alignment,
|
|
1405
|
+
tooClose,
|
|
1406
|
+
tooFar,
|
|
1407
|
+
isBlurry,
|
|
1408
|
+
isPartialFace,
|
|
1409
|
+
tooFarFromIdeal,
|
|
1410
|
+
tooCloseToIdeal,
|
|
1411
|
+
phoneAngled,
|
|
1412
|
+
phoneTilted
|
|
1413
|
+
} = state;
|
|
1414
|
+
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !phoneAngled && !phoneTilted;
|
|
1366
1415
|
}
|
|
1367
1416
|
|
|
1368
1417
|
// src/utils/validators.ts
|
|
@@ -1439,15 +1488,17 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
|
|
|
1439
1488
|
var MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
1440
1489
|
var HIGH_ALIGNMENT = 0.85;
|
|
1441
1490
|
var GOOD_ALIGNMENT = 0.5;
|
|
1442
|
-
var IDEAL_CROP_MULTIPLIER =
|
|
1443
|
-
var MIN_CROP_MULTIPLIER =
|
|
1444
|
-
var MAX_CROP_MULTIPLIER =
|
|
1491
|
+
var IDEAL_CROP_MULTIPLIER = 3;
|
|
1492
|
+
var MIN_CROP_MULTIPLIER = 2.5;
|
|
1493
|
+
var MAX_CROP_MULTIPLIER = 4;
|
|
1445
1494
|
var FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
1495
|
+
var MIN_IDEAL_FACE_RATIO = 0.05;
|
|
1496
|
+
var MAX_IDEAL_FACE_RATIO = 0.2;
|
|
1446
1497
|
var MIN_FACE_RATIO = 0.036;
|
|
1447
1498
|
var MAX_FACE_RATIO = 0.7;
|
|
1448
1499
|
var FACE_CROP_OUTPUT_SIZE = 224;
|
|
1449
|
-
var MAX_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1450
|
-
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1500
|
+
var MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
|
|
1501
|
+
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
|
|
1451
1502
|
function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
|
|
1452
1503
|
const laplacian = [];
|
|
1453
1504
|
for (let y = 1; y < height - 1; y++) {
|
|
@@ -1543,9 +1594,9 @@ var OVAL_REGION_DESKTOP = {
|
|
|
1543
1594
|
var OVAL_REGION_MOBILE = {
|
|
1544
1595
|
centerX: 0.5,
|
|
1545
1596
|
centerY: 0.5,
|
|
1546
|
-
width: 0.
|
|
1547
|
-
height: 0.
|
|
1548
|
-
// 0.
|
|
1597
|
+
width: 0.48,
|
|
1598
|
+
height: 0.64
|
|
1599
|
+
// 0.48 * (4/3)
|
|
1549
1600
|
};
|
|
1550
1601
|
var DEFAULT_OVAL_REGION = OVAL_REGION_DESKTOP;
|
|
1551
1602
|
function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
|
|
@@ -1701,6 +1752,33 @@ function checkFrameQuality(options) {
|
|
|
1701
1752
|
}
|
|
1702
1753
|
return result;
|
|
1703
1754
|
}
|
|
1755
|
+
var MAX_FACE_ROLL_DEGREES = 15;
|
|
1756
|
+
function detectFaceRoll(landmarks) {
|
|
1757
|
+
if (landmarks.length < 364) {
|
|
1758
|
+
return { roll: 0, tooTilted: false };
|
|
1759
|
+
}
|
|
1760
|
+
const l33 = landmarks[33];
|
|
1761
|
+
const l133 = landmarks[133];
|
|
1762
|
+
const l362 = landmarks[362];
|
|
1763
|
+
const l263 = landmarks[263];
|
|
1764
|
+
if (!l33 || !l133 || !l362 || !l263) {
|
|
1765
|
+
return { roll: 0, tooTilted: false };
|
|
1766
|
+
}
|
|
1767
|
+
const leftEyeX = (l33.x + l133.x) / 2;
|
|
1768
|
+
const leftEyeY = (l33.y + l133.y) / 2;
|
|
1769
|
+
const rightEyeX = (l362.x + l263.x) / 2;
|
|
1770
|
+
const rightEyeY = (l362.y + l263.y) / 2;
|
|
1771
|
+
const eyeXDist = Math.abs(leftEyeX - rightEyeX);
|
|
1772
|
+
const eyeYDiff = Math.abs(leftEyeY - rightEyeY);
|
|
1773
|
+
if (eyeXDist < 0.01) {
|
|
1774
|
+
return { roll: 0, tooTilted: false };
|
|
1775
|
+
}
|
|
1776
|
+
const roll = Math.atan2(eyeYDiff, eyeXDist) * (180 / Math.PI);
|
|
1777
|
+
return {
|
|
1778
|
+
roll,
|
|
1779
|
+
tooTilted: roll > MAX_FACE_ROLL_DEGREES
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1704
1782
|
var BaseFrameCollector = class {
|
|
1705
1783
|
constructor(maxFrames = 10) {
|
|
1706
1784
|
this.frames = [];
|
|
@@ -1810,7 +1888,7 @@ var EYE_QUALITY_THRESHOLDS = {
|
|
|
1810
1888
|
minBrightness: 40,
|
|
1811
1889
|
maxBrightness: 230,
|
|
1812
1890
|
minContrast: 12,
|
|
1813
|
-
|
|
1891
|
+
glareRelativeFactor: 2.5,
|
|
1814
1892
|
maxGlareRatio: 0.15
|
|
1815
1893
|
};
|
|
1816
1894
|
function rgbaToLuminance(r, g, b) {
|
|
@@ -1837,9 +1915,11 @@ function analyzeEyeRegionContrast(pixels, meanBrightness) {
|
|
|
1837
1915
|
}
|
|
1838
1916
|
return Math.sqrt(sumSqDiff / pixelCount);
|
|
1839
1917
|
}
|
|
1840
|
-
function detectSpecularHighlights(pixels,
|
|
1918
|
+
function detectSpecularHighlights(pixels, relativeFactor = EYE_QUALITY_THRESHOLDS.glareRelativeFactor, meanBrightness) {
|
|
1841
1919
|
const pixelCount = pixels.length / 4;
|
|
1842
1920
|
if (pixelCount === 0) return 0;
|
|
1921
|
+
const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
|
|
1922
|
+
const threshold = mean * relativeFactor;
|
|
1843
1923
|
let glareCount = 0;
|
|
1844
1924
|
for (let i = 0; i < pixels.length; i += 4) {
|
|
1845
1925
|
const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
@@ -1862,7 +1942,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1862
1942
|
}
|
|
1863
1943
|
const brightness = analyzeEyeRegionBrightness(pixels);
|
|
1864
1944
|
const contrast = analyzeEyeRegionContrast(pixels, brightness);
|
|
1865
|
-
const glareRatio = detectSpecularHighlights(pixels, thresholds.
|
|
1945
|
+
const glareRatio = detectSpecularHighlights(pixels, thresholds.glareRelativeFactor, brightness);
|
|
1866
1946
|
const hasGlare = glareRatio > thresholds.maxGlareRatio;
|
|
1867
1947
|
if (brightness < thresholds.minBrightness) {
|
|
1868
1948
|
return {
|
|
@@ -1884,16 +1964,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1884
1964
|
message: "Eye region overexposed - reduce lighting"
|
|
1885
1965
|
};
|
|
1886
1966
|
}
|
|
1887
|
-
if (hasGlare) {
|
|
1888
|
-
return {
|
|
1889
|
-
passed: false,
|
|
1890
|
-
brightness,
|
|
1891
|
-
contrast,
|
|
1892
|
-
hasGlare,
|
|
1893
|
-
glareRatio,
|
|
1894
|
-
message: "Glare detected - adjust angle or remove glasses"
|
|
1895
|
-
};
|
|
1896
|
-
}
|
|
1897
1967
|
if (contrast < thresholds.minContrast) {
|
|
1898
1968
|
return {
|
|
1899
1969
|
passed: false,
|
|
@@ -1961,12 +2031,15 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1961
2031
|
MAX_CROP_MULTIPLIER,
|
|
1962
2032
|
MAX_FACE_PERCENTAGE_IN_CROP,
|
|
1963
2033
|
MAX_FACE_RATIO,
|
|
2034
|
+
MAX_FACE_ROLL_DEGREES,
|
|
2035
|
+
MAX_IDEAL_FACE_RATIO,
|
|
1964
2036
|
MIN_CAPTURE_ALIGNMENT,
|
|
1965
2037
|
MIN_CROP_MULTIPLIER,
|
|
1966
2038
|
MIN_FACE_BOTTOM_MARGIN,
|
|
1967
2039
|
MIN_FACE_RATIO,
|
|
1968
2040
|
MIN_FACE_SIDE_MARGIN,
|
|
1969
2041
|
MIN_FACE_TOP_MARGIN,
|
|
2042
|
+
MIN_IDEAL_FACE_RATIO,
|
|
1970
2043
|
MIN_LANDMARK_COUNT,
|
|
1971
2044
|
MODEL_CONFIGS,
|
|
1972
2045
|
OVAL_GUIDE_COLORS,
|
|
@@ -1987,6 +2060,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1987
2060
|
checkEyeRegionQuality,
|
|
1988
2061
|
checkFrameQuality,
|
|
1989
2062
|
decodeBase64,
|
|
2063
|
+
detectFaceRoll,
|
|
1990
2064
|
detectSpecularHighlights,
|
|
1991
2065
|
encodeBase64,
|
|
1992
2066
|
generateSessionId,
|
package/dist/index.mjs
CHANGED
|
@@ -128,7 +128,8 @@ function toLivenessResult(response) {
|
|
|
128
128
|
score: response.score ?? 0,
|
|
129
129
|
sessionId: response.session_id,
|
|
130
130
|
processingMs: response.processing_ms,
|
|
131
|
-
framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0
|
|
131
|
+
framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0,
|
|
132
|
+
warnings: "warnings" in response && Array.isArray(response.warnings) ? response.warnings : void 0
|
|
132
133
|
};
|
|
133
134
|
}
|
|
134
135
|
function toLivenessResultFromStream(response) {
|
|
@@ -145,7 +146,8 @@ function toLivenessResultFromStream(response) {
|
|
|
145
146
|
score: response.score ?? 0,
|
|
146
147
|
sessionId: response.session_id,
|
|
147
148
|
processingMs: response.processing_ms ?? 0,
|
|
148
|
-
framesProcessed: response.frames_processed ?? 0
|
|
149
|
+
framesProcessed: response.frames_processed ?? 0,
|
|
150
|
+
warnings: Array.isArray(response.warnings) ? response.warnings : void 0
|
|
149
151
|
};
|
|
150
152
|
}
|
|
151
153
|
function generateSessionId() {
|
|
@@ -324,7 +326,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
324
326
|
session_id: options.sessionId ?? generateSessionId(),
|
|
325
327
|
model: options.model ?? "10",
|
|
326
328
|
source: options.source ?? "live",
|
|
327
|
-
frames: toFrameData(frames)
|
|
329
|
+
frames: toFrameData(frames),
|
|
330
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
328
331
|
};
|
|
329
332
|
const response = await this.requestWithRetry(API_PATHS.fastCheck, {
|
|
330
333
|
method: "POST",
|
|
@@ -344,7 +347,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
344
347
|
session_id: options.sessionId ?? generateSessionId(),
|
|
345
348
|
model: options.model ?? "10",
|
|
346
349
|
source: options.source ?? "live",
|
|
347
|
-
crops
|
|
350
|
+
crops,
|
|
351
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
348
352
|
};
|
|
349
353
|
const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
|
|
350
354
|
method: "POST",
|
|
@@ -378,7 +382,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
378
382
|
return this.sendStreamFrameInternal(frameData, {
|
|
379
383
|
sessionId: options.sessionId,
|
|
380
384
|
model: options.model ?? "10",
|
|
381
|
-
source: options.source ?? "live"
|
|
385
|
+
source: options.source ?? "live",
|
|
386
|
+
warnings: options.warnings
|
|
382
387
|
});
|
|
383
388
|
}
|
|
384
389
|
/**
|
|
@@ -393,7 +398,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
393
398
|
session_id: options.sessionId,
|
|
394
399
|
model: options.model,
|
|
395
400
|
source: options.source,
|
|
396
|
-
frame: frameData
|
|
401
|
+
frame: frameData,
|
|
402
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
397
403
|
};
|
|
398
404
|
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
399
405
|
method: "POST",
|
|
@@ -422,7 +428,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
422
428
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
423
429
|
sessionId,
|
|
424
430
|
model,
|
|
425
|
-
source
|
|
431
|
+
source,
|
|
432
|
+
warnings: options.warnings
|
|
426
433
|
});
|
|
427
434
|
callbacks?.onFrameSent?.(index + 1, total);
|
|
428
435
|
if (response.status === "buffering") {
|
|
@@ -471,7 +478,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
471
478
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
472
479
|
sessionId,
|
|
473
480
|
model,
|
|
474
|
-
source
|
|
481
|
+
source,
|
|
482
|
+
warnings: options.warnings
|
|
475
483
|
});
|
|
476
484
|
const currentIndex = frameData.index;
|
|
477
485
|
callbacks?.onFrameSent?.(currentIndex + 1, total);
|
|
@@ -1048,7 +1056,7 @@ function isRetryableError(code) {
|
|
|
1048
1056
|
}
|
|
1049
1057
|
|
|
1050
1058
|
// src/constants/feedback.ts
|
|
1051
|
-
var ALIGNMENT_THRESHOLD_CAPTURE = 0.
|
|
1059
|
+
var ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
|
|
1052
1060
|
var ALIGNMENT_THRESHOLD_POOR = 0.6;
|
|
1053
1061
|
var ALIGNMENT_THRESHOLD_GOOD = 0.6;
|
|
1054
1062
|
var ALIGNMENT_THRESHOLD_PERFECT = 0.85;
|
|
@@ -1101,6 +1109,8 @@ var FEEDBACK_MESSAGES = {
|
|
|
1101
1109
|
move_back: "Move back - face too close",
|
|
1102
1110
|
too_close: "Move back - face too close",
|
|
1103
1111
|
too_far: "Move closer - face too far",
|
|
1112
|
+
move_closer_ideal: "Move a little closer",
|
|
1113
|
+
move_back_ideal: "Move back slightly",
|
|
1104
1114
|
// Visibility issues
|
|
1105
1115
|
face_not_visible: "Center your face - edges cut off",
|
|
1106
1116
|
partial_face: "Center your face - edges cut off",
|
|
@@ -1111,6 +1121,9 @@ var FEEDBACK_MESSAGES = {
|
|
|
1111
1121
|
poor_lighting: "Improve lighting",
|
|
1112
1122
|
too_dark: "Low lighting - move to a brighter area",
|
|
1113
1123
|
backlit: "Backlit - try facing the light source",
|
|
1124
|
+
// Phone orientation
|
|
1125
|
+
phone_angle_low: "Raise your phone to eye level",
|
|
1126
|
+
phone_tilted: "Hold your phone level",
|
|
1114
1127
|
// Hand occlusion
|
|
1115
1128
|
hand_detected: "Remove hand from face",
|
|
1116
1129
|
// Eye region quality
|
|
@@ -1158,6 +1171,8 @@ var ES_LOCALE = {
|
|
|
1158
1171
|
move_back: "Al\xE9jate - rostro muy cerca",
|
|
1159
1172
|
too_close: "Al\xE9jate - rostro muy cerca",
|
|
1160
1173
|
too_far: "Ac\xE9rcate - rostro muy lejos",
|
|
1174
|
+
move_closer_ideal: "Ac\xE9rcate un poco",
|
|
1175
|
+
move_back_ideal: "Al\xE9jate un poco",
|
|
1161
1176
|
// Visibility issues
|
|
1162
1177
|
face_not_visible: "Centra tu rostro - bordes cortados",
|
|
1163
1178
|
partial_face: "Centra tu rostro - bordes cortados",
|
|
@@ -1168,6 +1183,9 @@ var ES_LOCALE = {
|
|
|
1168
1183
|
poor_lighting: "Mejora la iluminaci\xF3n",
|
|
1169
1184
|
too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
|
|
1170
1185
|
backlit: "Contraluz - intenta mirar hacia la fuente de luz",
|
|
1186
|
+
// Phone orientation
|
|
1187
|
+
phone_angle_low: "Levanta el tel\xE9fono a la altura de los ojos",
|
|
1188
|
+
phone_tilted: "Mant\xE9n el tel\xE9fono nivelado",
|
|
1171
1189
|
// Hand occlusion
|
|
1172
1190
|
hand_detected: "Retira la mano del rostro",
|
|
1173
1191
|
// Eye region quality
|
|
@@ -1200,11 +1218,21 @@ function getCaptureQualityFeedback(state) {
|
|
|
1200
1218
|
isBlurry,
|
|
1201
1219
|
isPartialFace,
|
|
1202
1220
|
framesCaptured,
|
|
1203
|
-
targetFrames
|
|
1221
|
+
targetFrames,
|
|
1222
|
+
tooFarFromIdeal,
|
|
1223
|
+
tooCloseToIdeal,
|
|
1224
|
+
phoneAngled,
|
|
1225
|
+
phoneTilted
|
|
1204
1226
|
} = state;
|
|
1205
1227
|
if (!hasFace) {
|
|
1206
1228
|
return FEEDBACK_MESSAGES.no_face;
|
|
1207
1229
|
}
|
|
1230
|
+
if (phoneTilted) {
|
|
1231
|
+
return FEEDBACK_MESSAGES.phone_tilted;
|
|
1232
|
+
}
|
|
1233
|
+
if (phoneAngled) {
|
|
1234
|
+
return FEEDBACK_MESSAGES.phone_angle_low;
|
|
1235
|
+
}
|
|
1208
1236
|
if (tooClose) {
|
|
1209
1237
|
return FEEDBACK_MESSAGES.too_close;
|
|
1210
1238
|
}
|
|
@@ -1217,6 +1245,12 @@ function getCaptureQualityFeedback(state) {
|
|
|
1217
1245
|
if (isBlurry) {
|
|
1218
1246
|
return FEEDBACK_MESSAGES.blurry;
|
|
1219
1247
|
}
|
|
1248
|
+
if (tooFarFromIdeal) {
|
|
1249
|
+
return FEEDBACK_MESSAGES.move_closer_ideal;
|
|
1250
|
+
}
|
|
1251
|
+
if (tooCloseToIdeal) {
|
|
1252
|
+
return FEEDBACK_MESSAGES.move_back_ideal;
|
|
1253
|
+
}
|
|
1220
1254
|
if (alignment < ALIGNMENT_THRESHOLD_POOR) {
|
|
1221
1255
|
return FEEDBACK_MESSAGES.move_closer_to_center;
|
|
1222
1256
|
}
|
|
@@ -1232,8 +1266,19 @@ function getCaptureQualityFeedback(state) {
|
|
|
1232
1266
|
return FEEDBACK_MESSAGES.perfect;
|
|
1233
1267
|
}
|
|
1234
1268
|
function canCaptureFrame(state) {
|
|
1235
|
-
const {
|
|
1236
|
-
|
|
1269
|
+
const {
|
|
1270
|
+
hasFace,
|
|
1271
|
+
alignment,
|
|
1272
|
+
tooClose,
|
|
1273
|
+
tooFar,
|
|
1274
|
+
isBlurry,
|
|
1275
|
+
isPartialFace,
|
|
1276
|
+
tooFarFromIdeal,
|
|
1277
|
+
tooCloseToIdeal,
|
|
1278
|
+
phoneAngled,
|
|
1279
|
+
phoneTilted
|
|
1280
|
+
} = state;
|
|
1281
|
+
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !phoneAngled && !phoneTilted;
|
|
1237
1282
|
}
|
|
1238
1283
|
|
|
1239
1284
|
// src/utils/validators.ts
|
|
@@ -1310,15 +1355,17 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
|
|
|
1310
1355
|
var MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
1311
1356
|
var HIGH_ALIGNMENT = 0.85;
|
|
1312
1357
|
var GOOD_ALIGNMENT = 0.5;
|
|
1313
|
-
var IDEAL_CROP_MULTIPLIER =
|
|
1314
|
-
var MIN_CROP_MULTIPLIER =
|
|
1315
|
-
var MAX_CROP_MULTIPLIER =
|
|
1358
|
+
var IDEAL_CROP_MULTIPLIER = 3;
|
|
1359
|
+
var MIN_CROP_MULTIPLIER = 2.5;
|
|
1360
|
+
var MAX_CROP_MULTIPLIER = 4;
|
|
1316
1361
|
var FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
1362
|
+
var MIN_IDEAL_FACE_RATIO = 0.05;
|
|
1363
|
+
var MAX_IDEAL_FACE_RATIO = 0.2;
|
|
1317
1364
|
var MIN_FACE_RATIO = 0.036;
|
|
1318
1365
|
var MAX_FACE_RATIO = 0.7;
|
|
1319
1366
|
var FACE_CROP_OUTPUT_SIZE = 224;
|
|
1320
|
-
var MAX_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1321
|
-
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1367
|
+
var MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
|
|
1368
|
+
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
|
|
1322
1369
|
function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
|
|
1323
1370
|
const laplacian = [];
|
|
1324
1371
|
for (let y = 1; y < height - 1; y++) {
|
|
@@ -1414,9 +1461,9 @@ var OVAL_REGION_DESKTOP = {
|
|
|
1414
1461
|
var OVAL_REGION_MOBILE = {
|
|
1415
1462
|
centerX: 0.5,
|
|
1416
1463
|
centerY: 0.5,
|
|
1417
|
-
width: 0.
|
|
1418
|
-
height: 0.
|
|
1419
|
-
// 0.
|
|
1464
|
+
width: 0.48,
|
|
1465
|
+
height: 0.64
|
|
1466
|
+
// 0.48 * (4/3)
|
|
1420
1467
|
};
|
|
1421
1468
|
var DEFAULT_OVAL_REGION = OVAL_REGION_DESKTOP;
|
|
1422
1469
|
function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
|
|
@@ -1572,6 +1619,33 @@ function checkFrameQuality(options) {
|
|
|
1572
1619
|
}
|
|
1573
1620
|
return result;
|
|
1574
1621
|
}
|
|
1622
|
+
var MAX_FACE_ROLL_DEGREES = 15;
|
|
1623
|
+
function detectFaceRoll(landmarks) {
|
|
1624
|
+
if (landmarks.length < 364) {
|
|
1625
|
+
return { roll: 0, tooTilted: false };
|
|
1626
|
+
}
|
|
1627
|
+
const l33 = landmarks[33];
|
|
1628
|
+
const l133 = landmarks[133];
|
|
1629
|
+
const l362 = landmarks[362];
|
|
1630
|
+
const l263 = landmarks[263];
|
|
1631
|
+
if (!l33 || !l133 || !l362 || !l263) {
|
|
1632
|
+
return { roll: 0, tooTilted: false };
|
|
1633
|
+
}
|
|
1634
|
+
const leftEyeX = (l33.x + l133.x) / 2;
|
|
1635
|
+
const leftEyeY = (l33.y + l133.y) / 2;
|
|
1636
|
+
const rightEyeX = (l362.x + l263.x) / 2;
|
|
1637
|
+
const rightEyeY = (l362.y + l263.y) / 2;
|
|
1638
|
+
const eyeXDist = Math.abs(leftEyeX - rightEyeX);
|
|
1639
|
+
const eyeYDiff = Math.abs(leftEyeY - rightEyeY);
|
|
1640
|
+
if (eyeXDist < 0.01) {
|
|
1641
|
+
return { roll: 0, tooTilted: false };
|
|
1642
|
+
}
|
|
1643
|
+
const roll = Math.atan2(eyeYDiff, eyeXDist) * (180 / Math.PI);
|
|
1644
|
+
return {
|
|
1645
|
+
roll,
|
|
1646
|
+
tooTilted: roll > MAX_FACE_ROLL_DEGREES
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1575
1649
|
var BaseFrameCollector = class {
|
|
1576
1650
|
constructor(maxFrames = 10) {
|
|
1577
1651
|
this.frames = [];
|
|
@@ -1681,7 +1755,7 @@ var EYE_QUALITY_THRESHOLDS = {
|
|
|
1681
1755
|
minBrightness: 40,
|
|
1682
1756
|
maxBrightness: 230,
|
|
1683
1757
|
minContrast: 12,
|
|
1684
|
-
|
|
1758
|
+
glareRelativeFactor: 2.5,
|
|
1685
1759
|
maxGlareRatio: 0.15
|
|
1686
1760
|
};
|
|
1687
1761
|
function rgbaToLuminance(r, g, b) {
|
|
@@ -1708,9 +1782,11 @@ function analyzeEyeRegionContrast(pixels, meanBrightness) {
|
|
|
1708
1782
|
}
|
|
1709
1783
|
return Math.sqrt(sumSqDiff / pixelCount);
|
|
1710
1784
|
}
|
|
1711
|
-
function detectSpecularHighlights(pixels,
|
|
1785
|
+
function detectSpecularHighlights(pixels, relativeFactor = EYE_QUALITY_THRESHOLDS.glareRelativeFactor, meanBrightness) {
|
|
1712
1786
|
const pixelCount = pixels.length / 4;
|
|
1713
1787
|
if (pixelCount === 0) return 0;
|
|
1788
|
+
const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
|
|
1789
|
+
const threshold = mean * relativeFactor;
|
|
1714
1790
|
let glareCount = 0;
|
|
1715
1791
|
for (let i = 0; i < pixels.length; i += 4) {
|
|
1716
1792
|
const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
@@ -1733,7 +1809,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1733
1809
|
}
|
|
1734
1810
|
const brightness = analyzeEyeRegionBrightness(pixels);
|
|
1735
1811
|
const contrast = analyzeEyeRegionContrast(pixels, brightness);
|
|
1736
|
-
const glareRatio = detectSpecularHighlights(pixels, thresholds.
|
|
1812
|
+
const glareRatio = detectSpecularHighlights(pixels, thresholds.glareRelativeFactor, brightness);
|
|
1737
1813
|
const hasGlare = glareRatio > thresholds.maxGlareRatio;
|
|
1738
1814
|
if (brightness < thresholds.minBrightness) {
|
|
1739
1815
|
return {
|
|
@@ -1755,16 +1831,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1755
1831
|
message: "Eye region overexposed - reduce lighting"
|
|
1756
1832
|
};
|
|
1757
1833
|
}
|
|
1758
|
-
if (hasGlare) {
|
|
1759
|
-
return {
|
|
1760
|
-
passed: false,
|
|
1761
|
-
brightness,
|
|
1762
|
-
contrast,
|
|
1763
|
-
hasGlare,
|
|
1764
|
-
glareRatio,
|
|
1765
|
-
message: "Glare detected - adjust angle or remove glasses"
|
|
1766
|
-
};
|
|
1767
|
-
}
|
|
1768
1834
|
if (contrast < thresholds.minContrast) {
|
|
1769
1835
|
return {
|
|
1770
1836
|
passed: false,
|
|
@@ -1831,12 +1897,15 @@ export {
|
|
|
1831
1897
|
MAX_CROP_MULTIPLIER,
|
|
1832
1898
|
MAX_FACE_PERCENTAGE_IN_CROP,
|
|
1833
1899
|
MAX_FACE_RATIO,
|
|
1900
|
+
MAX_FACE_ROLL_DEGREES,
|
|
1901
|
+
MAX_IDEAL_FACE_RATIO,
|
|
1834
1902
|
MIN_CAPTURE_ALIGNMENT,
|
|
1835
1903
|
MIN_CROP_MULTIPLIER,
|
|
1836
1904
|
MIN_FACE_BOTTOM_MARGIN,
|
|
1837
1905
|
MIN_FACE_RATIO,
|
|
1838
1906
|
MIN_FACE_SIDE_MARGIN,
|
|
1839
1907
|
MIN_FACE_TOP_MARGIN,
|
|
1908
|
+
MIN_IDEAL_FACE_RATIO,
|
|
1840
1909
|
MIN_LANDMARK_COUNT,
|
|
1841
1910
|
MODEL_CONFIGS,
|
|
1842
1911
|
OVAL_GUIDE_COLORS,
|
|
@@ -1857,6 +1926,7 @@ export {
|
|
|
1857
1926
|
checkEyeRegionQuality,
|
|
1858
1927
|
checkFrameQuality,
|
|
1859
1928
|
decodeBase64,
|
|
1929
|
+
detectFaceRoll,
|
|
1860
1930
|
detectSpecularHighlights,
|
|
1861
1931
|
encodeBase64,
|
|
1862
1932
|
generateSessionId,
|