@moveris/shared 2.6.0 → 2.9.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 +55 -22
- package/dist/index.d.mts +29 -8
- package/dist/index.d.ts +29 -8
- package/dist/index.js +75 -37
- package/dist/index.mjs +72 -37
- 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
|
|
|
@@ -94,6 +94,7 @@ const crops: CropData[] = capturedFrames.map((f) => ({
|
|
|
94
94
|
const result = await client.fastCheckCrops(crops, {
|
|
95
95
|
model: '10',
|
|
96
96
|
source: 'live',
|
|
97
|
+
bgSegmentation: true, // optional — sent as bg_segmentation in payload
|
|
97
98
|
});
|
|
98
99
|
```
|
|
99
100
|
|
|
@@ -184,22 +185,34 @@ Model selection for liveness detection speed/accuracy trade-off.
|
|
|
184
185
|
|
|
185
186
|
```typescript
|
|
186
187
|
type FastCheckModel =
|
|
188
|
+
| 'mixed-10-v2'
|
|
189
|
+
| 'mixed-30-v2'
|
|
190
|
+
| 'mixed-60-v2'
|
|
191
|
+
| 'mixed-90-v2'
|
|
192
|
+
| 'mixed-120-v2' // Active mixed-v2 models (recommended)
|
|
187
193
|
| '10'
|
|
188
194
|
| '50'
|
|
189
|
-
| '250' //
|
|
190
|
-
|
|
|
191
|
-
| 'hybrid-v2-50' // Hybrid V2 (physiological features)
|
|
192
|
-
| 'mixed-10'; // Mixed (visual + physiological scoring)
|
|
195
|
+
| '250' // Legacy standard models
|
|
196
|
+
| (string & object); // Forward-compatible with future model IDs
|
|
193
197
|
```
|
|
194
198
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
|
198
|
-
|
|
|
199
|
-
| `'
|
|
200
|
-
| `'
|
|
201
|
-
| `'
|
|
202
|
-
| `'mixed-
|
|
199
|
+
Active models (recommended):
|
|
200
|
+
|
|
201
|
+
| Value | Frames | Description |
|
|
202
|
+
| ---------------- | ------ | ------------------------------ |
|
|
203
|
+
| `'mixed-10-v2'` | 10 | Fast verification, low latency |
|
|
204
|
+
| `'mixed-30-v2'` | 30 | Balanced speed and accuracy |
|
|
205
|
+
| `'mixed-60-v2'` | 60 | Higher accuracy |
|
|
206
|
+
| `'mixed-90-v2'` | 90 | High accuracy |
|
|
207
|
+
| `'mixed-120-v2'` | 120 | Highest accuracy, slower |
|
|
208
|
+
|
|
209
|
+
Legacy models (still supported):
|
|
210
|
+
|
|
211
|
+
| Value | Frames | Description |
|
|
212
|
+
| ------- | ------ | --------------------------- |
|
|
213
|
+
| `'10'` | 10 | Standard — fast |
|
|
214
|
+
| `'50'` | 50 | Standard — balanced |
|
|
215
|
+
| `'250'` | 250 | Standard — highest accuracy |
|
|
203
216
|
|
|
204
217
|
#### FrameSource
|
|
205
218
|
|
|
@@ -431,6 +444,9 @@ const status = getStatusMessage('capturing', DEFAULT_LOCALE);
|
|
|
431
444
|
| `eyes_overexposed` | "Eye region overexposed…" | Eye region too bright |
|
|
432
445
|
| `glasses_glare` | "Glare detected…" | Specular highlights on eyes |
|
|
433
446
|
| `eye_quality_poor` | "Eye region quality is poor" | Generic eye quality failure |
|
|
447
|
+
| `camera_angle_low` | "Raise camera to eye level" | Camera below face (Y < 0.3) |
|
|
448
|
+
| `camera_angle_high` | "Lower camera to eye level" | Camera above face (Y > 0.7) |
|
|
449
|
+
| `camera_tilted` | "Hold camera level" | Eye line deviates > 15° |
|
|
434
450
|
|
|
435
451
|
---
|
|
436
452
|
|
|
@@ -492,6 +508,8 @@ const glareRatio = detectSpecularHighlights(pixels);
|
|
|
492
508
|
| Glare | `maxGlareRatio: 0.15` | >15% of pixels are specular highlights |
|
|
493
509
|
| Occluded | `minContrast: 12` | Standard deviation of luminance too low |
|
|
494
510
|
|
|
511
|
+
Glare detection uses a **relative threshold** (`mean brightness × 2.5`) so sensitivity adapts to ambient lighting, avoiding false positives in bright environments.
|
|
512
|
+
|
|
495
513
|
Custom thresholds can be passed as a second argument to `checkEyeRegionQuality()`.
|
|
496
514
|
|
|
497
515
|
### Eye Region Landmarks
|
|
@@ -515,7 +533,13 @@ if (bounds) {
|
|
|
515
533
|
Utilities for assessing frame quality before submission.
|
|
516
534
|
|
|
517
535
|
```typescript
|
|
518
|
-
import {
|
|
536
|
+
import {
|
|
537
|
+
isFaceFullyVisible,
|
|
538
|
+
isFaceInOval,
|
|
539
|
+
calculateFaceCropRegion,
|
|
540
|
+
detectFaceRoll,
|
|
541
|
+
detectCameraAngle,
|
|
542
|
+
} from '@moveris/shared';
|
|
519
543
|
|
|
520
544
|
// Check if face is fully visible in frame
|
|
521
545
|
const visibility = isFaceFullyVisible(faceBbox, frameWidth, frameHeight);
|
|
@@ -533,8 +557,17 @@ const cropRegion = calculateFaceCropRegion(faceBbox, frameWidth, frameHeight);
|
|
|
533
557
|
|
|
534
558
|
// Calculate adaptive crop multiplier based on face size
|
|
535
559
|
import { calculateAdaptiveCropMultiplier } from '@moveris/shared';
|
|
536
|
-
const multiplier = calculateAdaptiveCropMultiplier(
|
|
537
|
-
// Returns
|
|
560
|
+
const multiplier = calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight);
|
|
561
|
+
// Returns 2.5–4.0x (matched to cognito-check for ~33% face coverage in 224×224 crop)
|
|
562
|
+
|
|
563
|
+
// Detect face roll (device tilt) from eye-corner landmarks
|
|
564
|
+
const rollResult = detectFaceRoll(landmarks); // requires ≥364 landmarks
|
|
565
|
+
// { roll: number (degrees), tooTilted: boolean }
|
|
566
|
+
|
|
567
|
+
// Detect camera vertical angle from forehead/nose/chin landmark ratio
|
|
568
|
+
const angleResult = detectCameraAngle(landmarks); // requires ≥153 landmarks
|
|
569
|
+
// { ratio: number, cameraAbove: boolean, cameraBelow: boolean }
|
|
570
|
+
// ratio ~1.0 at eye level; >1.35 = camera above user; <0.75 = camera below user
|
|
538
571
|
```
|
|
539
572
|
|
|
540
573
|
#### Crop Constants (aligned with cognito-check)
|
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 {
|
|
@@ -296,7 +301,7 @@ interface FrameQualityResult {
|
|
|
296
301
|
alignment?: FaceAlignmentResult;
|
|
297
302
|
rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
|
|
298
303
|
}
|
|
299
|
-
declare const DEFAULT_BLUR_THRESHOLD =
|
|
304
|
+
declare const DEFAULT_BLUR_THRESHOLD = 110;
|
|
300
305
|
declare const BLUR_THRESHOLD_MOBILE = 60;
|
|
301
306
|
declare const BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
302
307
|
declare const LOW_LIGHT_THRESHOLD = 50;
|
|
@@ -348,6 +353,14 @@ interface FaceRollResult {
|
|
|
348
353
|
}
|
|
349
354
|
declare const MAX_FACE_ROLL_DEGREES = 15;
|
|
350
355
|
declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
|
|
356
|
+
interface CameraAngleResult {
|
|
357
|
+
ratio: number;
|
|
358
|
+
cameraAbove: boolean;
|
|
359
|
+
cameraBelow: boolean;
|
|
360
|
+
}
|
|
361
|
+
declare const CAMERA_ANGLE_HIGH_RATIO = 1.35;
|
|
362
|
+
declare const CAMERA_ANGLE_LOW_RATIO = 0.75;
|
|
363
|
+
declare function detectCameraAngle(landmarks: FaceLandmarkPoint[]): CameraAngleResult;
|
|
351
364
|
declare class BaseFrameCollector {
|
|
352
365
|
protected frames: CapturedFrame[];
|
|
353
366
|
protected maxFrames: number;
|
|
@@ -477,22 +490,27 @@ declare class LivenessClient {
|
|
|
477
490
|
sessionId?: string;
|
|
478
491
|
model?: FastCheckModel;
|
|
479
492
|
source?: FrameSource;
|
|
493
|
+
warnings?: string[];
|
|
480
494
|
}): Promise<LivenessResult>;
|
|
481
495
|
fastCheckCrops(crops: CropData[], options?: {
|
|
482
496
|
sessionId?: string;
|
|
483
497
|
model?: FastCheckModel;
|
|
484
498
|
source?: FrameSource;
|
|
499
|
+
warnings?: string[];
|
|
500
|
+
bgSegmentation?: boolean;
|
|
485
501
|
}): Promise<LivenessResult>;
|
|
486
502
|
streamFrame(frame: CapturedFrame, options: {
|
|
487
503
|
sessionId: string;
|
|
488
504
|
model?: FastCheckModel;
|
|
489
505
|
source?: FrameSource;
|
|
506
|
+
warnings?: string[];
|
|
490
507
|
}): Promise<FastCheckStreamResponse>;
|
|
491
508
|
private sendStreamFrameInternal;
|
|
492
509
|
fastCheckStream(frames: CapturedFrame[], options?: {
|
|
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;
|
|
@@ -501,6 +519,7 @@ declare class LivenessClient {
|
|
|
501
519
|
sessionId?: string;
|
|
502
520
|
model?: FastCheckModel;
|
|
503
521
|
source?: FrameSource;
|
|
522
|
+
warnings?: string[];
|
|
504
523
|
}, callbacks?: {
|
|
505
524
|
onFrameSent?: (index: number, total: number) => void;
|
|
506
525
|
onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
|
|
@@ -694,8 +713,9 @@ declare const FEEDBACK_MESSAGES: {
|
|
|
694
713
|
readonly poor_lighting: "Improve lighting";
|
|
695
714
|
readonly too_dark: "Low lighting - move to a brighter area";
|
|
696
715
|
readonly backlit: "Backlit - try facing the light source";
|
|
697
|
-
readonly
|
|
698
|
-
readonly
|
|
716
|
+
readonly camera_angle_low: "Raise camera to eye level";
|
|
717
|
+
readonly camera_angle_high: "Lower camera to eye level";
|
|
718
|
+
readonly camera_tilted: "Hold camera level";
|
|
699
719
|
readonly hand_detected: "Remove hand from face";
|
|
700
720
|
readonly eyes_not_visible: "Eyes not clearly visible";
|
|
701
721
|
readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
|
|
@@ -728,8 +748,9 @@ interface CaptureQualityState {
|
|
|
728
748
|
targetFrames: number;
|
|
729
749
|
tooFarFromIdeal?: boolean;
|
|
730
750
|
tooCloseToIdeal?: boolean;
|
|
731
|
-
|
|
732
|
-
|
|
751
|
+
cameraAngleLow?: boolean;
|
|
752
|
+
cameraAngleHigh?: boolean;
|
|
753
|
+
cameraTilted?: boolean;
|
|
733
754
|
}
|
|
734
755
|
declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
|
|
735
756
|
declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
|
|
@@ -771,13 +792,13 @@ interface EyeQualityThresholds {
|
|
|
771
792
|
minBrightness: number;
|
|
772
793
|
maxBrightness: number;
|
|
773
794
|
minContrast: number;
|
|
774
|
-
|
|
795
|
+
glareRelativeFactor: number;
|
|
775
796
|
maxGlareRatio: number;
|
|
776
797
|
}
|
|
777
798
|
declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
|
|
778
799
|
declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
|
|
779
800
|
declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
|
|
780
|
-
declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray,
|
|
801
|
+
declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
|
|
781
802
|
declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
|
|
782
803
|
|
|
783
|
-
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 };
|
|
804
|
+
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, CAMERA_ANGLE_HIGH_RATIO, CAMERA_ANGLE_LOW_RATIO, type CameraAngleResult, 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, detectCameraAngle, 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 {
|
|
@@ -296,7 +301,7 @@ interface FrameQualityResult {
|
|
|
296
301
|
alignment?: FaceAlignmentResult;
|
|
297
302
|
rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
|
|
298
303
|
}
|
|
299
|
-
declare const DEFAULT_BLUR_THRESHOLD =
|
|
304
|
+
declare const DEFAULT_BLUR_THRESHOLD = 110;
|
|
300
305
|
declare const BLUR_THRESHOLD_MOBILE = 60;
|
|
301
306
|
declare const BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
302
307
|
declare const LOW_LIGHT_THRESHOLD = 50;
|
|
@@ -348,6 +353,14 @@ interface FaceRollResult {
|
|
|
348
353
|
}
|
|
349
354
|
declare const MAX_FACE_ROLL_DEGREES = 15;
|
|
350
355
|
declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
|
|
356
|
+
interface CameraAngleResult {
|
|
357
|
+
ratio: number;
|
|
358
|
+
cameraAbove: boolean;
|
|
359
|
+
cameraBelow: boolean;
|
|
360
|
+
}
|
|
361
|
+
declare const CAMERA_ANGLE_HIGH_RATIO = 1.35;
|
|
362
|
+
declare const CAMERA_ANGLE_LOW_RATIO = 0.75;
|
|
363
|
+
declare function detectCameraAngle(landmarks: FaceLandmarkPoint[]): CameraAngleResult;
|
|
351
364
|
declare class BaseFrameCollector {
|
|
352
365
|
protected frames: CapturedFrame[];
|
|
353
366
|
protected maxFrames: number;
|
|
@@ -477,22 +490,27 @@ declare class LivenessClient {
|
|
|
477
490
|
sessionId?: string;
|
|
478
491
|
model?: FastCheckModel;
|
|
479
492
|
source?: FrameSource;
|
|
493
|
+
warnings?: string[];
|
|
480
494
|
}): Promise<LivenessResult>;
|
|
481
495
|
fastCheckCrops(crops: CropData[], options?: {
|
|
482
496
|
sessionId?: string;
|
|
483
497
|
model?: FastCheckModel;
|
|
484
498
|
source?: FrameSource;
|
|
499
|
+
warnings?: string[];
|
|
500
|
+
bgSegmentation?: boolean;
|
|
485
501
|
}): Promise<LivenessResult>;
|
|
486
502
|
streamFrame(frame: CapturedFrame, options: {
|
|
487
503
|
sessionId: string;
|
|
488
504
|
model?: FastCheckModel;
|
|
489
505
|
source?: FrameSource;
|
|
506
|
+
warnings?: string[];
|
|
490
507
|
}): Promise<FastCheckStreamResponse>;
|
|
491
508
|
private sendStreamFrameInternal;
|
|
492
509
|
fastCheckStream(frames: CapturedFrame[], options?: {
|
|
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;
|
|
@@ -501,6 +519,7 @@ declare class LivenessClient {
|
|
|
501
519
|
sessionId?: string;
|
|
502
520
|
model?: FastCheckModel;
|
|
503
521
|
source?: FrameSource;
|
|
522
|
+
warnings?: string[];
|
|
504
523
|
}, callbacks?: {
|
|
505
524
|
onFrameSent?: (index: number, total: number) => void;
|
|
506
525
|
onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
|
|
@@ -694,8 +713,9 @@ declare const FEEDBACK_MESSAGES: {
|
|
|
694
713
|
readonly poor_lighting: "Improve lighting";
|
|
695
714
|
readonly too_dark: "Low lighting - move to a brighter area";
|
|
696
715
|
readonly backlit: "Backlit - try facing the light source";
|
|
697
|
-
readonly
|
|
698
|
-
readonly
|
|
716
|
+
readonly camera_angle_low: "Raise camera to eye level";
|
|
717
|
+
readonly camera_angle_high: "Lower camera to eye level";
|
|
718
|
+
readonly camera_tilted: "Hold camera level";
|
|
699
719
|
readonly hand_detected: "Remove hand from face";
|
|
700
720
|
readonly eyes_not_visible: "Eyes not clearly visible";
|
|
701
721
|
readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
|
|
@@ -728,8 +748,9 @@ interface CaptureQualityState {
|
|
|
728
748
|
targetFrames: number;
|
|
729
749
|
tooFarFromIdeal?: boolean;
|
|
730
750
|
tooCloseToIdeal?: boolean;
|
|
731
|
-
|
|
732
|
-
|
|
751
|
+
cameraAngleLow?: boolean;
|
|
752
|
+
cameraAngleHigh?: boolean;
|
|
753
|
+
cameraTilted?: boolean;
|
|
733
754
|
}
|
|
734
755
|
declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
|
|
735
756
|
declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
|
|
@@ -771,13 +792,13 @@ interface EyeQualityThresholds {
|
|
|
771
792
|
minBrightness: number;
|
|
772
793
|
maxBrightness: number;
|
|
773
794
|
minContrast: number;
|
|
774
|
-
|
|
795
|
+
glareRelativeFactor: number;
|
|
775
796
|
maxGlareRatio: number;
|
|
776
797
|
}
|
|
777
798
|
declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
|
|
778
799
|
declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
|
|
779
800
|
declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
|
|
780
|
-
declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray,
|
|
801
|
+
declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
|
|
781
802
|
declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
|
|
782
803
|
|
|
783
|
-
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 };
|
|
804
|
+
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, CAMERA_ANGLE_HIGH_RATIO, CAMERA_ANGLE_LOW_RATIO, type CameraAngleResult, 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, detectCameraAngle, 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
|
@@ -31,6 +31,8 @@ __export(index_exports, {
|
|
|
31
31
|
BACKLIT_RATIO_THRESHOLD: () => BACKLIT_RATIO_THRESHOLD,
|
|
32
32
|
BLUR_THRESHOLD_MOBILE: () => BLUR_THRESHOLD_MOBILE,
|
|
33
33
|
BaseFrameCollector: () => BaseFrameCollector,
|
|
34
|
+
CAMERA_ANGLE_HIGH_RATIO: () => CAMERA_ANGLE_HIGH_RATIO,
|
|
35
|
+
CAMERA_ANGLE_LOW_RATIO: () => CAMERA_ANGLE_LOW_RATIO,
|
|
34
36
|
DEFAULT_BLUR_THRESHOLD: () => DEFAULT_BLUR_THRESHOLD,
|
|
35
37
|
DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT,
|
|
36
38
|
DEFAULT_FACE_DETECTION_TIERS: () => DEFAULT_FACE_DETECTION_TIERS,
|
|
@@ -95,6 +97,7 @@ __export(index_exports, {
|
|
|
95
97
|
checkEyeRegionQuality: () => checkEyeRegionQuality,
|
|
96
98
|
checkFrameQuality: () => checkFrameQuality,
|
|
97
99
|
decodeBase64: () => decodeBase64,
|
|
100
|
+
detectCameraAngle: () => detectCameraAngle,
|
|
98
101
|
detectFaceRoll: () => detectFaceRoll,
|
|
99
102
|
detectSpecularHighlights: () => detectSpecularHighlights,
|
|
100
103
|
encodeBase64: () => encodeBase64,
|
|
@@ -261,7 +264,8 @@ function toLivenessResult(response) {
|
|
|
261
264
|
score: response.score ?? 0,
|
|
262
265
|
sessionId: response.session_id,
|
|
263
266
|
processingMs: response.processing_ms,
|
|
264
|
-
framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0
|
|
267
|
+
framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0,
|
|
268
|
+
warnings: "warnings" in response && Array.isArray(response.warnings) ? response.warnings : void 0
|
|
265
269
|
};
|
|
266
270
|
}
|
|
267
271
|
function toLivenessResultFromStream(response) {
|
|
@@ -278,7 +282,8 @@ function toLivenessResultFromStream(response) {
|
|
|
278
282
|
score: response.score ?? 0,
|
|
279
283
|
sessionId: response.session_id,
|
|
280
284
|
processingMs: response.processing_ms ?? 0,
|
|
281
|
-
framesProcessed: response.frames_processed ?? 0
|
|
285
|
+
framesProcessed: response.frames_processed ?? 0,
|
|
286
|
+
warnings: Array.isArray(response.warnings) ? response.warnings : void 0
|
|
282
287
|
};
|
|
283
288
|
}
|
|
284
289
|
function generateSessionId() {
|
|
@@ -457,7 +462,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
457
462
|
session_id: options.sessionId ?? generateSessionId(),
|
|
458
463
|
model: options.model ?? "10",
|
|
459
464
|
source: options.source ?? "live",
|
|
460
|
-
frames: toFrameData(frames)
|
|
465
|
+
frames: toFrameData(frames),
|
|
466
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
461
467
|
};
|
|
462
468
|
const response = await this.requestWithRetry(API_PATHS.fastCheck, {
|
|
463
469
|
method: "POST",
|
|
@@ -477,7 +483,9 @@ var LivenessClient = class _LivenessClient {
|
|
|
477
483
|
session_id: options.sessionId ?? generateSessionId(),
|
|
478
484
|
model: options.model ?? "10",
|
|
479
485
|
source: options.source ?? "live",
|
|
480
|
-
crops
|
|
486
|
+
crops,
|
|
487
|
+
...options.warnings?.length ? { warnings: options.warnings } : {},
|
|
488
|
+
...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
|
|
481
489
|
};
|
|
482
490
|
const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
|
|
483
491
|
method: "POST",
|
|
@@ -511,7 +519,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
511
519
|
return this.sendStreamFrameInternal(frameData, {
|
|
512
520
|
sessionId: options.sessionId,
|
|
513
521
|
model: options.model ?? "10",
|
|
514
|
-
source: options.source ?? "live"
|
|
522
|
+
source: options.source ?? "live",
|
|
523
|
+
warnings: options.warnings
|
|
515
524
|
});
|
|
516
525
|
}
|
|
517
526
|
/**
|
|
@@ -526,7 +535,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
526
535
|
session_id: options.sessionId,
|
|
527
536
|
model: options.model,
|
|
528
537
|
source: options.source,
|
|
529
|
-
frame: frameData
|
|
538
|
+
frame: frameData,
|
|
539
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
530
540
|
};
|
|
531
541
|
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
532
542
|
method: "POST",
|
|
@@ -555,7 +565,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
555
565
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
556
566
|
sessionId,
|
|
557
567
|
model,
|
|
558
|
-
source
|
|
568
|
+
source,
|
|
569
|
+
warnings: options.warnings
|
|
559
570
|
});
|
|
560
571
|
callbacks?.onFrameSent?.(index + 1, total);
|
|
561
572
|
if (response.status === "buffering") {
|
|
@@ -604,7 +615,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
604
615
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
605
616
|
sessionId,
|
|
606
617
|
model,
|
|
607
|
-
source
|
|
618
|
+
source,
|
|
619
|
+
warnings: options.warnings
|
|
608
620
|
});
|
|
609
621
|
const currentIndex = frameData.index;
|
|
610
622
|
callbacks?.onFrameSent?.(currentIndex + 1, total);
|
|
@@ -1246,9 +1258,10 @@ var FEEDBACK_MESSAGES = {
|
|
|
1246
1258
|
poor_lighting: "Improve lighting",
|
|
1247
1259
|
too_dark: "Low lighting - move to a brighter area",
|
|
1248
1260
|
backlit: "Backlit - try facing the light source",
|
|
1249
|
-
//
|
|
1250
|
-
|
|
1251
|
-
|
|
1261
|
+
// Camera angle (platform-agnostic)
|
|
1262
|
+
camera_angle_low: "Raise camera to eye level",
|
|
1263
|
+
camera_angle_high: "Lower camera to eye level",
|
|
1264
|
+
camera_tilted: "Hold camera level",
|
|
1252
1265
|
// Hand occlusion
|
|
1253
1266
|
hand_detected: "Remove hand from face",
|
|
1254
1267
|
// Eye region quality
|
|
@@ -1308,9 +1321,10 @@ var ES_LOCALE = {
|
|
|
1308
1321
|
poor_lighting: "Mejora la iluminaci\xF3n",
|
|
1309
1322
|
too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
|
|
1310
1323
|
backlit: "Contraluz - intenta mirar hacia la fuente de luz",
|
|
1311
|
-
//
|
|
1312
|
-
|
|
1313
|
-
|
|
1324
|
+
// Camera angle (platform-agnostic)
|
|
1325
|
+
camera_angle_low: "Levanta la c\xE1mara a la altura de los ojos",
|
|
1326
|
+
camera_angle_high: "Baja la c\xE1mara a la altura de los ojos",
|
|
1327
|
+
camera_tilted: "Mant\xE9n la c\xE1mara nivelada",
|
|
1314
1328
|
// Hand occlusion
|
|
1315
1329
|
hand_detected: "Retira la mano del rostro",
|
|
1316
1330
|
// Eye region quality
|
|
@@ -1346,17 +1360,21 @@ function getCaptureQualityFeedback(state) {
|
|
|
1346
1360
|
targetFrames,
|
|
1347
1361
|
tooFarFromIdeal,
|
|
1348
1362
|
tooCloseToIdeal,
|
|
1349
|
-
|
|
1350
|
-
|
|
1363
|
+
cameraAngleLow,
|
|
1364
|
+
cameraAngleHigh,
|
|
1365
|
+
cameraTilted
|
|
1351
1366
|
} = state;
|
|
1352
1367
|
if (!hasFace) {
|
|
1353
1368
|
return FEEDBACK_MESSAGES.no_face;
|
|
1354
1369
|
}
|
|
1355
|
-
if (
|
|
1356
|
-
return FEEDBACK_MESSAGES.
|
|
1370
|
+
if (cameraTilted) {
|
|
1371
|
+
return FEEDBACK_MESSAGES.camera_tilted;
|
|
1357
1372
|
}
|
|
1358
|
-
if (
|
|
1359
|
-
return FEEDBACK_MESSAGES.
|
|
1373
|
+
if (cameraAngleLow) {
|
|
1374
|
+
return FEEDBACK_MESSAGES.camera_angle_low;
|
|
1375
|
+
}
|
|
1376
|
+
if (cameraAngleHigh) {
|
|
1377
|
+
return FEEDBACK_MESSAGES.camera_angle_high;
|
|
1360
1378
|
}
|
|
1361
1379
|
if (tooClose) {
|
|
1362
1380
|
return FEEDBACK_MESSAGES.too_close;
|
|
@@ -1400,10 +1418,11 @@ function canCaptureFrame(state) {
|
|
|
1400
1418
|
isPartialFace,
|
|
1401
1419
|
tooFarFromIdeal,
|
|
1402
1420
|
tooCloseToIdeal,
|
|
1403
|
-
|
|
1404
|
-
|
|
1421
|
+
cameraAngleLow,
|
|
1422
|
+
cameraAngleHigh,
|
|
1423
|
+
cameraTilted
|
|
1405
1424
|
} = state;
|
|
1406
|
-
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !
|
|
1425
|
+
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !cameraAngleLow && !cameraAngleHigh && !cameraTilted;
|
|
1407
1426
|
}
|
|
1408
1427
|
|
|
1409
1428
|
// src/utils/validators.ts
|
|
@@ -1470,7 +1489,7 @@ function decodeBase64(base64) {
|
|
|
1470
1489
|
}
|
|
1471
1490
|
|
|
1472
1491
|
// src/utils/frameAnalysis.ts
|
|
1473
|
-
var DEFAULT_BLUR_THRESHOLD =
|
|
1492
|
+
var DEFAULT_BLUR_THRESHOLD = 110;
|
|
1474
1493
|
var BLUR_THRESHOLD_MOBILE = 60;
|
|
1475
1494
|
var BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
1476
1495
|
var LOW_LIGHT_THRESHOLD = 50;
|
|
@@ -1771,6 +1790,30 @@ function detectFaceRoll(landmarks) {
|
|
|
1771
1790
|
tooTilted: roll > MAX_FACE_ROLL_DEGREES
|
|
1772
1791
|
};
|
|
1773
1792
|
}
|
|
1793
|
+
var CAMERA_ANGLE_HIGH_RATIO = 1.35;
|
|
1794
|
+
var CAMERA_ANGLE_LOW_RATIO = 0.75;
|
|
1795
|
+
function detectCameraAngle(landmarks) {
|
|
1796
|
+
if (landmarks.length < 153) {
|
|
1797
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1798
|
+
}
|
|
1799
|
+
const forehead = landmarks[10];
|
|
1800
|
+
const noseTip = landmarks[1];
|
|
1801
|
+
const chin = landmarks[152];
|
|
1802
|
+
if (!forehead || !noseTip || !chin) {
|
|
1803
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1804
|
+
}
|
|
1805
|
+
const foreheadToNose = Math.abs(forehead.y - noseTip.y);
|
|
1806
|
+
const noseToChin = Math.abs(noseTip.y - chin.y);
|
|
1807
|
+
if (noseToChin < 1e-3) {
|
|
1808
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1809
|
+
}
|
|
1810
|
+
const ratio = foreheadToNose / noseToChin;
|
|
1811
|
+
return {
|
|
1812
|
+
ratio,
|
|
1813
|
+
cameraAbove: ratio > CAMERA_ANGLE_HIGH_RATIO,
|
|
1814
|
+
cameraBelow: ratio < CAMERA_ANGLE_LOW_RATIO
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1774
1817
|
var BaseFrameCollector = class {
|
|
1775
1818
|
constructor(maxFrames = 10) {
|
|
1776
1819
|
this.frames = [];
|
|
@@ -1880,7 +1923,7 @@ var EYE_QUALITY_THRESHOLDS = {
|
|
|
1880
1923
|
minBrightness: 40,
|
|
1881
1924
|
maxBrightness: 230,
|
|
1882
1925
|
minContrast: 12,
|
|
1883
|
-
|
|
1926
|
+
glareRelativeFactor: 2.5,
|
|
1884
1927
|
maxGlareRatio: 0.15
|
|
1885
1928
|
};
|
|
1886
1929
|
function rgbaToLuminance(r, g, b) {
|
|
@@ -1907,9 +1950,11 @@ function analyzeEyeRegionContrast(pixels, meanBrightness) {
|
|
|
1907
1950
|
}
|
|
1908
1951
|
return Math.sqrt(sumSqDiff / pixelCount);
|
|
1909
1952
|
}
|
|
1910
|
-
function detectSpecularHighlights(pixels,
|
|
1953
|
+
function detectSpecularHighlights(pixels, relativeFactor = EYE_QUALITY_THRESHOLDS.glareRelativeFactor, meanBrightness) {
|
|
1911
1954
|
const pixelCount = pixels.length / 4;
|
|
1912
1955
|
if (pixelCount === 0) return 0;
|
|
1956
|
+
const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
|
|
1957
|
+
const threshold = mean * relativeFactor;
|
|
1913
1958
|
let glareCount = 0;
|
|
1914
1959
|
for (let i = 0; i < pixels.length; i += 4) {
|
|
1915
1960
|
const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
@@ -1932,7 +1977,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1932
1977
|
}
|
|
1933
1978
|
const brightness = analyzeEyeRegionBrightness(pixels);
|
|
1934
1979
|
const contrast = analyzeEyeRegionContrast(pixels, brightness);
|
|
1935
|
-
const glareRatio = detectSpecularHighlights(pixels, thresholds.
|
|
1980
|
+
const glareRatio = detectSpecularHighlights(pixels, thresholds.glareRelativeFactor, brightness);
|
|
1936
1981
|
const hasGlare = glareRatio > thresholds.maxGlareRatio;
|
|
1937
1982
|
if (brightness < thresholds.minBrightness) {
|
|
1938
1983
|
return {
|
|
@@ -1954,16 +1999,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1954
1999
|
message: "Eye region overexposed - reduce lighting"
|
|
1955
2000
|
};
|
|
1956
2001
|
}
|
|
1957
|
-
if (hasGlare) {
|
|
1958
|
-
return {
|
|
1959
|
-
passed: false,
|
|
1960
|
-
brightness,
|
|
1961
|
-
contrast,
|
|
1962
|
-
hasGlare,
|
|
1963
|
-
glareRatio,
|
|
1964
|
-
message: "Glare detected - adjust angle or remove glasses"
|
|
1965
|
-
};
|
|
1966
|
-
}
|
|
1967
2002
|
if (contrast < thresholds.minContrast) {
|
|
1968
2003
|
return {
|
|
1969
2004
|
passed: false,
|
|
@@ -1996,6 +2031,8 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1996
2031
|
BACKLIT_RATIO_THRESHOLD,
|
|
1997
2032
|
BLUR_THRESHOLD_MOBILE,
|
|
1998
2033
|
BaseFrameCollector,
|
|
2034
|
+
CAMERA_ANGLE_HIGH_RATIO,
|
|
2035
|
+
CAMERA_ANGLE_LOW_RATIO,
|
|
1999
2036
|
DEFAULT_BLUR_THRESHOLD,
|
|
2000
2037
|
DEFAULT_ENDPOINT,
|
|
2001
2038
|
DEFAULT_FACE_DETECTION_TIERS,
|
|
@@ -2060,6 +2097,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
2060
2097
|
checkEyeRegionQuality,
|
|
2061
2098
|
checkFrameQuality,
|
|
2062
2099
|
decodeBase64,
|
|
2100
|
+
detectCameraAngle,
|
|
2063
2101
|
detectFaceRoll,
|
|
2064
2102
|
detectSpecularHighlights,
|
|
2065
2103
|
encodeBase64,
|
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,9 @@ 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 } : {},
|
|
352
|
+
...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
|
|
348
353
|
};
|
|
349
354
|
const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
|
|
350
355
|
method: "POST",
|
|
@@ -378,7 +383,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
378
383
|
return this.sendStreamFrameInternal(frameData, {
|
|
379
384
|
sessionId: options.sessionId,
|
|
380
385
|
model: options.model ?? "10",
|
|
381
|
-
source: options.source ?? "live"
|
|
386
|
+
source: options.source ?? "live",
|
|
387
|
+
warnings: options.warnings
|
|
382
388
|
});
|
|
383
389
|
}
|
|
384
390
|
/**
|
|
@@ -393,7 +399,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
393
399
|
session_id: options.sessionId,
|
|
394
400
|
model: options.model,
|
|
395
401
|
source: options.source,
|
|
396
|
-
frame: frameData
|
|
402
|
+
frame: frameData,
|
|
403
|
+
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
397
404
|
};
|
|
398
405
|
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
399
406
|
method: "POST",
|
|
@@ -422,7 +429,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
422
429
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
423
430
|
sessionId,
|
|
424
431
|
model,
|
|
425
|
-
source
|
|
432
|
+
source,
|
|
433
|
+
warnings: options.warnings
|
|
426
434
|
});
|
|
427
435
|
callbacks?.onFrameSent?.(index + 1, total);
|
|
428
436
|
if (response.status === "buffering") {
|
|
@@ -471,7 +479,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
471
479
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
472
480
|
sessionId,
|
|
473
481
|
model,
|
|
474
|
-
source
|
|
482
|
+
source,
|
|
483
|
+
warnings: options.warnings
|
|
475
484
|
});
|
|
476
485
|
const currentIndex = frameData.index;
|
|
477
486
|
callbacks?.onFrameSent?.(currentIndex + 1, total);
|
|
@@ -1113,9 +1122,10 @@ var FEEDBACK_MESSAGES = {
|
|
|
1113
1122
|
poor_lighting: "Improve lighting",
|
|
1114
1123
|
too_dark: "Low lighting - move to a brighter area",
|
|
1115
1124
|
backlit: "Backlit - try facing the light source",
|
|
1116
|
-
//
|
|
1117
|
-
|
|
1118
|
-
|
|
1125
|
+
// Camera angle (platform-agnostic)
|
|
1126
|
+
camera_angle_low: "Raise camera to eye level",
|
|
1127
|
+
camera_angle_high: "Lower camera to eye level",
|
|
1128
|
+
camera_tilted: "Hold camera level",
|
|
1119
1129
|
// Hand occlusion
|
|
1120
1130
|
hand_detected: "Remove hand from face",
|
|
1121
1131
|
// Eye region quality
|
|
@@ -1175,9 +1185,10 @@ var ES_LOCALE = {
|
|
|
1175
1185
|
poor_lighting: "Mejora la iluminaci\xF3n",
|
|
1176
1186
|
too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
|
|
1177
1187
|
backlit: "Contraluz - intenta mirar hacia la fuente de luz",
|
|
1178
|
-
//
|
|
1179
|
-
|
|
1180
|
-
|
|
1188
|
+
// Camera angle (platform-agnostic)
|
|
1189
|
+
camera_angle_low: "Levanta la c\xE1mara a la altura de los ojos",
|
|
1190
|
+
camera_angle_high: "Baja la c\xE1mara a la altura de los ojos",
|
|
1191
|
+
camera_tilted: "Mant\xE9n la c\xE1mara nivelada",
|
|
1181
1192
|
// Hand occlusion
|
|
1182
1193
|
hand_detected: "Retira la mano del rostro",
|
|
1183
1194
|
// Eye region quality
|
|
@@ -1213,17 +1224,21 @@ function getCaptureQualityFeedback(state) {
|
|
|
1213
1224
|
targetFrames,
|
|
1214
1225
|
tooFarFromIdeal,
|
|
1215
1226
|
tooCloseToIdeal,
|
|
1216
|
-
|
|
1217
|
-
|
|
1227
|
+
cameraAngleLow,
|
|
1228
|
+
cameraAngleHigh,
|
|
1229
|
+
cameraTilted
|
|
1218
1230
|
} = state;
|
|
1219
1231
|
if (!hasFace) {
|
|
1220
1232
|
return FEEDBACK_MESSAGES.no_face;
|
|
1221
1233
|
}
|
|
1222
|
-
if (
|
|
1223
|
-
return FEEDBACK_MESSAGES.
|
|
1234
|
+
if (cameraTilted) {
|
|
1235
|
+
return FEEDBACK_MESSAGES.camera_tilted;
|
|
1224
1236
|
}
|
|
1225
|
-
if (
|
|
1226
|
-
return FEEDBACK_MESSAGES.
|
|
1237
|
+
if (cameraAngleLow) {
|
|
1238
|
+
return FEEDBACK_MESSAGES.camera_angle_low;
|
|
1239
|
+
}
|
|
1240
|
+
if (cameraAngleHigh) {
|
|
1241
|
+
return FEEDBACK_MESSAGES.camera_angle_high;
|
|
1227
1242
|
}
|
|
1228
1243
|
if (tooClose) {
|
|
1229
1244
|
return FEEDBACK_MESSAGES.too_close;
|
|
@@ -1267,10 +1282,11 @@ function canCaptureFrame(state) {
|
|
|
1267
1282
|
isPartialFace,
|
|
1268
1283
|
tooFarFromIdeal,
|
|
1269
1284
|
tooCloseToIdeal,
|
|
1270
|
-
|
|
1271
|
-
|
|
1285
|
+
cameraAngleLow,
|
|
1286
|
+
cameraAngleHigh,
|
|
1287
|
+
cameraTilted
|
|
1272
1288
|
} = state;
|
|
1273
|
-
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !
|
|
1289
|
+
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !cameraAngleLow && !cameraAngleHigh && !cameraTilted;
|
|
1274
1290
|
}
|
|
1275
1291
|
|
|
1276
1292
|
// src/utils/validators.ts
|
|
@@ -1337,7 +1353,7 @@ function decodeBase64(base64) {
|
|
|
1337
1353
|
}
|
|
1338
1354
|
|
|
1339
1355
|
// src/utils/frameAnalysis.ts
|
|
1340
|
-
var DEFAULT_BLUR_THRESHOLD =
|
|
1356
|
+
var DEFAULT_BLUR_THRESHOLD = 110;
|
|
1341
1357
|
var BLUR_THRESHOLD_MOBILE = 60;
|
|
1342
1358
|
var BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
1343
1359
|
var LOW_LIGHT_THRESHOLD = 50;
|
|
@@ -1638,6 +1654,30 @@ function detectFaceRoll(landmarks) {
|
|
|
1638
1654
|
tooTilted: roll > MAX_FACE_ROLL_DEGREES
|
|
1639
1655
|
};
|
|
1640
1656
|
}
|
|
1657
|
+
var CAMERA_ANGLE_HIGH_RATIO = 1.35;
|
|
1658
|
+
var CAMERA_ANGLE_LOW_RATIO = 0.75;
|
|
1659
|
+
function detectCameraAngle(landmarks) {
|
|
1660
|
+
if (landmarks.length < 153) {
|
|
1661
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1662
|
+
}
|
|
1663
|
+
const forehead = landmarks[10];
|
|
1664
|
+
const noseTip = landmarks[1];
|
|
1665
|
+
const chin = landmarks[152];
|
|
1666
|
+
if (!forehead || !noseTip || !chin) {
|
|
1667
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1668
|
+
}
|
|
1669
|
+
const foreheadToNose = Math.abs(forehead.y - noseTip.y);
|
|
1670
|
+
const noseToChin = Math.abs(noseTip.y - chin.y);
|
|
1671
|
+
if (noseToChin < 1e-3) {
|
|
1672
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1673
|
+
}
|
|
1674
|
+
const ratio = foreheadToNose / noseToChin;
|
|
1675
|
+
return {
|
|
1676
|
+
ratio,
|
|
1677
|
+
cameraAbove: ratio > CAMERA_ANGLE_HIGH_RATIO,
|
|
1678
|
+
cameraBelow: ratio < CAMERA_ANGLE_LOW_RATIO
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1641
1681
|
var BaseFrameCollector = class {
|
|
1642
1682
|
constructor(maxFrames = 10) {
|
|
1643
1683
|
this.frames = [];
|
|
@@ -1747,7 +1787,7 @@ var EYE_QUALITY_THRESHOLDS = {
|
|
|
1747
1787
|
minBrightness: 40,
|
|
1748
1788
|
maxBrightness: 230,
|
|
1749
1789
|
minContrast: 12,
|
|
1750
|
-
|
|
1790
|
+
glareRelativeFactor: 2.5,
|
|
1751
1791
|
maxGlareRatio: 0.15
|
|
1752
1792
|
};
|
|
1753
1793
|
function rgbaToLuminance(r, g, b) {
|
|
@@ -1774,9 +1814,11 @@ function analyzeEyeRegionContrast(pixels, meanBrightness) {
|
|
|
1774
1814
|
}
|
|
1775
1815
|
return Math.sqrt(sumSqDiff / pixelCount);
|
|
1776
1816
|
}
|
|
1777
|
-
function detectSpecularHighlights(pixels,
|
|
1817
|
+
function detectSpecularHighlights(pixels, relativeFactor = EYE_QUALITY_THRESHOLDS.glareRelativeFactor, meanBrightness) {
|
|
1778
1818
|
const pixelCount = pixels.length / 4;
|
|
1779
1819
|
if (pixelCount === 0) return 0;
|
|
1820
|
+
const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
|
|
1821
|
+
const threshold = mean * relativeFactor;
|
|
1780
1822
|
let glareCount = 0;
|
|
1781
1823
|
for (let i = 0; i < pixels.length; i += 4) {
|
|
1782
1824
|
const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
@@ -1799,7 +1841,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1799
1841
|
}
|
|
1800
1842
|
const brightness = analyzeEyeRegionBrightness(pixels);
|
|
1801
1843
|
const contrast = analyzeEyeRegionContrast(pixels, brightness);
|
|
1802
|
-
const glareRatio = detectSpecularHighlights(pixels, thresholds.
|
|
1844
|
+
const glareRatio = detectSpecularHighlights(pixels, thresholds.glareRelativeFactor, brightness);
|
|
1803
1845
|
const hasGlare = glareRatio > thresholds.maxGlareRatio;
|
|
1804
1846
|
if (brightness < thresholds.minBrightness) {
|
|
1805
1847
|
return {
|
|
@@ -1821,16 +1863,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1821
1863
|
message: "Eye region overexposed - reduce lighting"
|
|
1822
1864
|
};
|
|
1823
1865
|
}
|
|
1824
|
-
if (hasGlare) {
|
|
1825
|
-
return {
|
|
1826
|
-
passed: false,
|
|
1827
|
-
brightness,
|
|
1828
|
-
contrast,
|
|
1829
|
-
hasGlare,
|
|
1830
|
-
glareRatio,
|
|
1831
|
-
message: "Glare detected - adjust angle or remove glasses"
|
|
1832
|
-
};
|
|
1833
|
-
}
|
|
1834
1866
|
if (contrast < thresholds.minContrast) {
|
|
1835
1867
|
return {
|
|
1836
1868
|
passed: false,
|
|
@@ -1862,6 +1894,8 @@ export {
|
|
|
1862
1894
|
BACKLIT_RATIO_THRESHOLD,
|
|
1863
1895
|
BLUR_THRESHOLD_MOBILE,
|
|
1864
1896
|
BaseFrameCollector,
|
|
1897
|
+
CAMERA_ANGLE_HIGH_RATIO,
|
|
1898
|
+
CAMERA_ANGLE_LOW_RATIO,
|
|
1865
1899
|
DEFAULT_BLUR_THRESHOLD,
|
|
1866
1900
|
DEFAULT_ENDPOINT,
|
|
1867
1901
|
DEFAULT_FACE_DETECTION_TIERS,
|
|
@@ -1926,6 +1960,7 @@ export {
|
|
|
1926
1960
|
checkEyeRegionQuality,
|
|
1927
1961
|
checkFrameQuality,
|
|
1928
1962
|
decodeBase64,
|
|
1963
|
+
detectCameraAngle,
|
|
1929
1964
|
detectFaceRoll,
|
|
1930
1965
|
detectSpecularHighlights,
|
|
1931
1966
|
encodeBase64,
|