@moveris/shared 2.2.0 → 2.4.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 CHANGED
@@ -426,6 +426,11 @@ const status = getStatusMessage('capturing', DEFAULT_LOCALE);
426
426
  | `processing` | "Processing..." | API verification in progress |
427
427
  | `success` | "Verification complete" | Successful completion |
428
428
  | `failed` | "Verification failed" | Failed verification |
429
+ | `eyes_not_visible` | "Eyes not clearly visible" | Eye region featureless |
430
+ | `eyes_shadowed` | "Eyes are in shadow…" | Eye region too dark |
431
+ | `eyes_overexposed` | "Eye region overexposed…" | Eye region too bright |
432
+ | `glasses_glare` | "Glare detected…" | Specular highlights on eyes |
433
+ | `eye_quality_poor` | "Eye region quality is poor" | Generic eye quality failure |
429
434
 
430
435
  ---
431
436
 
@@ -452,6 +457,59 @@ const color = OVAL_GUIDE_COLORS[state];
452
457
 
453
458
  ---
454
459
 
460
+ ### Eye Region Quality Analysis
461
+
462
+ Pre-request eye-region quality gate. Platform-agnostic functions that analyze RGBA pixel data from eye regions to detect shadows, glare, occlusion, and poor visibility.
463
+
464
+ ```typescript
465
+ import {
466
+ checkEyeRegionQuality,
467
+ analyzeEyeRegionBrightness,
468
+ analyzeEyeRegionContrast,
469
+ detectSpecularHighlights,
470
+ EYE_QUALITY_THRESHOLDS,
471
+ } from '@moveris/shared';
472
+
473
+ // Combined quality check (recommended)
474
+ const quality = checkEyeRegionQuality(eyeRegionPixels);
475
+ console.log(quality.passed); // boolean
476
+ console.log(quality.brightness); // 0-255
477
+ console.log(quality.contrast); // standard deviation of luminance
478
+ console.log(quality.hasGlare); // boolean
479
+ console.log(quality.glareRatio); // 0-1
480
+ console.log(quality.message); // user-facing feedback or null
481
+
482
+ // Individual checks
483
+ const brightness = analyzeEyeRegionBrightness(pixels);
484
+ const contrast = analyzeEyeRegionContrast(pixels);
485
+ const glareRatio = detectSpecularHighlights(pixels);
486
+ ```
487
+
488
+ | Check | Threshold | Condition |
489
+ | ----------- | --------------------- | --------------------------------------- |
490
+ | Shadowed | `minBrightness: 40` | Average luminance too low |
491
+ | Overexposed | `maxBrightness: 230` | Average luminance too high |
492
+ | Glare | `maxGlareRatio: 0.15` | >15% of pixels are specular highlights |
493
+ | Occluded | `minContrast: 12` | Standard deviation of luminance too low |
494
+
495
+ Custom thresholds can be passed as a second argument to `checkEyeRegionQuality()`.
496
+
497
+ ### Eye Region Landmarks
498
+
499
+ Extract bounding boxes for left and right eye regions from MediaPipe 468-point face mesh landmarks.
500
+
501
+ ```typescript
502
+ import { getEyeRegionBounds, EYE_LANDMARK_INDICES, validateFaceLandmarks } from '@moveris/shared';
503
+
504
+ const bounds = getEyeRegionBounds(landmarks);
505
+ if (bounds) {
506
+ console.log(bounds.leftEye); // { x, y, width, height } (normalized 0-1)
507
+ console.log(bounds.rightEye); // { x, y, width, height } (normalized 0-1)
508
+ }
509
+ ```
510
+
511
+ ---
512
+
455
513
  ### Frame Analysis Utilities
456
514
 
457
515
  Utilities for assessing frame quality before submission.
@@ -527,6 +585,10 @@ import type {
527
585
  HeadPose,
528
586
  FaceLandmarkPoint,
529
587
  LandmarkValidationResult,
588
+ EyeRegionQuality,
589
+ EyeQualityThresholds,
590
+ EyeRegionBounds,
591
+ EyeRegionsBounds,
530
592
  } from '@moveris/shared';
531
593
  ```
532
594
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,13 @@
1
1
  type Verdict = 'live' | 'fake';
2
- type FastCheckModel = '10' | '50' | '250' | 'hybrid-v2-10' | 'hybrid-v2-30' | 'hybrid-v2-50' | 'hybrid-v2-60' | 'hybrid-v2-90' | 'hybrid-v2-100' | 'hybrid-v2-125' | 'hybrid-v2-150' | 'hybrid-v2-250' | 'mixed-10' | 'mixed-30' | 'mixed-60' | 'mixed-90' | 'mixed-120' | 'mixed-150' | 'mixed-250';
2
+ interface ModelEntry {
3
+ id: string;
4
+ label: string;
5
+ description: string;
6
+ min_frames: number;
7
+ deprecated: boolean;
8
+ }
9
+ type ModelsResponse = ModelEntry[];
10
+ type FastCheckModel = '10' | '50' | '250' | 'hybrid-v2-10' | 'hybrid-v2-30' | 'hybrid-v2-50' | 'hybrid-v2-60' | 'hybrid-v2-90' | 'hybrid-v2-100' | 'hybrid-v2-125' | 'hybrid-v2-150' | 'hybrid-v2-250' | 'mixed-10' | 'mixed-30' | 'mixed-60' | 'mixed-90' | 'mixed-120' | 'mixed-150' | 'mixed-250' | 'mixed-10-v2' | 'mixed-30-v2' | 'mixed-60-v2' | 'mixed-90-v2' | 'mixed-120-v2' | (string & object);
3
11
  type StreamingStatus = 'buffering' | 'complete';
4
12
  type FrameSource = 'media' | 'live';
5
13
  type JobStatus = 'queued' | 'processing' | 'complete' | 'failed';
@@ -172,6 +180,7 @@ interface ModelConfig {
172
180
  minFrames: number;
173
181
  recommendedFrames: number;
174
182
  description: string;
183
+ deprecated: boolean;
175
184
  }
176
185
  declare const MODEL_CONFIGS: Record<FastCheckModel, ModelConfig>;
177
186
  interface HybridModelConfig {
@@ -184,6 +193,8 @@ interface HybridModelConfig {
184
193
  declare const HYBRID_MODEL_CONFIGS: Record<string, HybridModelConfig>;
185
194
  declare function getMinFramesForModel(model: FastCheckModel): number;
186
195
  declare function hasEnoughFrames(model: FastCheckModel, frameCount: number): boolean;
196
+ declare function isDeprecatedModel(model: FastCheckModel): boolean;
197
+ declare function getActiveModels(): ModelConfig[];
187
198
 
188
199
  interface Frame {
189
200
  index: number;
@@ -322,6 +333,21 @@ declare const LANDMARK_INDEX: {
322
333
  readonly UPPER_LIP: 13;
323
334
  readonly LOWER_LIP: 14;
324
335
  };
336
+ declare const EYE_LANDMARK_INDICES: {
337
+ readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
338
+ readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
339
+ };
340
+ interface EyeRegionBounds {
341
+ x: number;
342
+ y: number;
343
+ width: number;
344
+ height: number;
345
+ }
346
+ interface EyeRegionsBounds {
347
+ leftEye: EyeRegionBounds;
348
+ rightEye: EyeRegionBounds;
349
+ }
350
+ declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
325
351
  declare const LANDMARK_MIN_BOUND = 0.1;
326
352
  declare const LANDMARK_MAX_BOUND = 0.9;
327
353
  declare const MIN_LANDMARK_COUNT = 15;
@@ -435,6 +461,7 @@ declare class LivenessClient {
435
461
  private static extractCodeFromText;
436
462
  private requestWithRetry;
437
463
  health(): Promise<HealthResponse>;
464
+ getModels(): Promise<ModelEntry[]>;
438
465
  queueStats(): Promise<QueueStatsResponse>;
439
466
  fastCheck(frames: CapturedFrame[], options?: {
440
467
  sessionId?: string;
@@ -531,6 +558,7 @@ declare const API_ENDPOINTS: {
531
558
  declare const DEFAULT_ENDPOINT: "https://api.moveris.com";
532
559
  declare const API_PATHS: {
533
560
  readonly health: "/health";
561
+ readonly models: "/api/v1/models";
534
562
  readonly fastCheck: "/api/v1/fast-check";
535
563
  readonly fastCheckCrops: "/api/v1/fast-check-crops";
536
564
  readonly fastCheckStream: "/api/v1/fast-check-stream";
@@ -655,6 +683,11 @@ declare const FEEDBACK_MESSAGES: {
655
683
  readonly too_dark: "Low lighting - move to a brighter area";
656
684
  readonly backlit: "Backlit - try facing the light source";
657
685
  readonly hand_detected: "Remove hand from face";
686
+ readonly eyes_not_visible: "Eyes not clearly visible";
687
+ readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
688
+ readonly eyes_overexposed: "Eye region overexposed - reduce lighting";
689
+ readonly glasses_glare: "Glare detected - adjust angle or remove glasses";
690
+ readonly eye_quality_poor: "Eye region quality is poor";
658
691
  readonly capturing: "Capturing...";
659
692
  readonly almost_done: "Almost done...";
660
693
  readonly verification_complete: "Verification complete";
@@ -708,4 +741,25 @@ interface RetryOptions {
708
741
  declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
709
742
  declare function sleep(ms: number): Promise<void>;
710
743
 
711
- 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, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceDetectionTiers, type FaceInOvalResult, type FaceLandmarkPoint, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, type GazeThresholds, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HandOcclusionConfig, type HeadPose, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LANDMARK_INDEX, LANDMARK_MAX_BOUND, LANDMARK_MIN_BOUND, LOW_LIGHT_THRESHOLD, type LandmarkValidationResult, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getApiErrorMessage, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
744
+ interface EyeRegionQuality {
745
+ passed: boolean;
746
+ brightness: number;
747
+ contrast: number;
748
+ hasGlare: boolean;
749
+ glareRatio: number;
750
+ message: string | null;
751
+ }
752
+ interface EyeQualityThresholds {
753
+ minBrightness: number;
754
+ maxBrightness: number;
755
+ minContrast: number;
756
+ glarePixelThreshold: number;
757
+ maxGlareRatio: number;
758
+ }
759
+ declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
760
+ declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
761
+ declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
762
+ declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
763
+ declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
764
+
765
+ 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, 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 };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  type Verdict = 'live' | 'fake';
2
- type FastCheckModel = '10' | '50' | '250' | 'hybrid-v2-10' | 'hybrid-v2-30' | 'hybrid-v2-50' | 'hybrid-v2-60' | 'hybrid-v2-90' | 'hybrid-v2-100' | 'hybrid-v2-125' | 'hybrid-v2-150' | 'hybrid-v2-250' | 'mixed-10' | 'mixed-30' | 'mixed-60' | 'mixed-90' | 'mixed-120' | 'mixed-150' | 'mixed-250';
2
+ interface ModelEntry {
3
+ id: string;
4
+ label: string;
5
+ description: string;
6
+ min_frames: number;
7
+ deprecated: boolean;
8
+ }
9
+ type ModelsResponse = ModelEntry[];
10
+ type FastCheckModel = '10' | '50' | '250' | 'hybrid-v2-10' | 'hybrid-v2-30' | 'hybrid-v2-50' | 'hybrid-v2-60' | 'hybrid-v2-90' | 'hybrid-v2-100' | 'hybrid-v2-125' | 'hybrid-v2-150' | 'hybrid-v2-250' | 'mixed-10' | 'mixed-30' | 'mixed-60' | 'mixed-90' | 'mixed-120' | 'mixed-150' | 'mixed-250' | 'mixed-10-v2' | 'mixed-30-v2' | 'mixed-60-v2' | 'mixed-90-v2' | 'mixed-120-v2' | (string & object);
3
11
  type StreamingStatus = 'buffering' | 'complete';
4
12
  type FrameSource = 'media' | 'live';
5
13
  type JobStatus = 'queued' | 'processing' | 'complete' | 'failed';
@@ -172,6 +180,7 @@ interface ModelConfig {
172
180
  minFrames: number;
173
181
  recommendedFrames: number;
174
182
  description: string;
183
+ deprecated: boolean;
175
184
  }
176
185
  declare const MODEL_CONFIGS: Record<FastCheckModel, ModelConfig>;
177
186
  interface HybridModelConfig {
@@ -184,6 +193,8 @@ interface HybridModelConfig {
184
193
  declare const HYBRID_MODEL_CONFIGS: Record<string, HybridModelConfig>;
185
194
  declare function getMinFramesForModel(model: FastCheckModel): number;
186
195
  declare function hasEnoughFrames(model: FastCheckModel, frameCount: number): boolean;
196
+ declare function isDeprecatedModel(model: FastCheckModel): boolean;
197
+ declare function getActiveModels(): ModelConfig[];
187
198
 
188
199
  interface Frame {
189
200
  index: number;
@@ -322,6 +333,21 @@ declare const LANDMARK_INDEX: {
322
333
  readonly UPPER_LIP: 13;
323
334
  readonly LOWER_LIP: 14;
324
335
  };
336
+ declare const EYE_LANDMARK_INDICES: {
337
+ readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
338
+ readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
339
+ };
340
+ interface EyeRegionBounds {
341
+ x: number;
342
+ y: number;
343
+ width: number;
344
+ height: number;
345
+ }
346
+ interface EyeRegionsBounds {
347
+ leftEye: EyeRegionBounds;
348
+ rightEye: EyeRegionBounds;
349
+ }
350
+ declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
325
351
  declare const LANDMARK_MIN_BOUND = 0.1;
326
352
  declare const LANDMARK_MAX_BOUND = 0.9;
327
353
  declare const MIN_LANDMARK_COUNT = 15;
@@ -435,6 +461,7 @@ declare class LivenessClient {
435
461
  private static extractCodeFromText;
436
462
  private requestWithRetry;
437
463
  health(): Promise<HealthResponse>;
464
+ getModels(): Promise<ModelEntry[]>;
438
465
  queueStats(): Promise<QueueStatsResponse>;
439
466
  fastCheck(frames: CapturedFrame[], options?: {
440
467
  sessionId?: string;
@@ -531,6 +558,7 @@ declare const API_ENDPOINTS: {
531
558
  declare const DEFAULT_ENDPOINT: "https://api.moveris.com";
532
559
  declare const API_PATHS: {
533
560
  readonly health: "/health";
561
+ readonly models: "/api/v1/models";
534
562
  readonly fastCheck: "/api/v1/fast-check";
535
563
  readonly fastCheckCrops: "/api/v1/fast-check-crops";
536
564
  readonly fastCheckStream: "/api/v1/fast-check-stream";
@@ -655,6 +683,11 @@ declare const FEEDBACK_MESSAGES: {
655
683
  readonly too_dark: "Low lighting - move to a brighter area";
656
684
  readonly backlit: "Backlit - try facing the light source";
657
685
  readonly hand_detected: "Remove hand from face";
686
+ readonly eyes_not_visible: "Eyes not clearly visible";
687
+ readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
688
+ readonly eyes_overexposed: "Eye region overexposed - reduce lighting";
689
+ readonly glasses_glare: "Glare detected - adjust angle or remove glasses";
690
+ readonly eye_quality_poor: "Eye region quality is poor";
658
691
  readonly capturing: "Capturing...";
659
692
  readonly almost_done: "Almost done...";
660
693
  readonly verification_complete: "Verification complete";
@@ -708,4 +741,25 @@ interface RetryOptions {
708
741
  declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
709
742
  declare function sleep(ms: number): Promise<void>;
710
743
 
711
- 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, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceDetectionTiers, type FaceInOvalResult, type FaceLandmarkPoint, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, type GazeThresholds, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HandOcclusionConfig, type HeadPose, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LANDMARK_INDEX, LANDMARK_MAX_BOUND, LANDMARK_MIN_BOUND, LOW_LIGHT_THRESHOLD, type LandmarkValidationResult, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getApiErrorMessage, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
744
+ interface EyeRegionQuality {
745
+ passed: boolean;
746
+ brightness: number;
747
+ contrast: number;
748
+ hasGlare: boolean;
749
+ glareRatio: number;
750
+ message: string | null;
751
+ }
752
+ interface EyeQualityThresholds {
753
+ minBrightness: number;
754
+ maxBrightness: number;
755
+ minContrast: number;
756
+ glarePixelThreshold: number;
757
+ maxGlareRatio: number;
758
+ }
759
+ declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
760
+ declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
761
+ declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
762
+ declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
763
+ declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
764
+
765
+ 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, 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 };
package/dist/index.js CHANGED
@@ -44,6 +44,8 @@ __export(index_exports, {
44
44
  ERROR_MESSAGES: () => ERROR_MESSAGES,
45
45
  ERROR_MESSAGES_ES: () => ERROR_MESSAGES_ES,
46
46
  ES_LOCALE: () => ES_LOCALE,
47
+ EYE_LANDMARK_INDICES: () => EYE_LANDMARK_INDICES,
48
+ EYE_QUALITY_THRESHOLDS: () => EYE_QUALITY_THRESHOLDS,
47
49
  FACE_CENTER_VERTICAL_OFFSET: () => FACE_CENTER_VERTICAL_OFFSET,
48
50
  FACE_CROP_OUTPUT_SIZE: () => FACE_CROP_OUTPUT_SIZE,
49
51
  FEEDBACK_MESSAGES: () => FEEDBACK_MESSAGES,
@@ -77,23 +79,30 @@ __export(index_exports, {
77
79
  RETRY_CONFIG: () => RETRY_CONFIG,
78
80
  TARGET_FACE_PERCENTAGE_IN_CROP: () => TARGET_FACE_PERCENTAGE_IN_CROP,
79
81
  analyzeBlur: () => analyzeBlur,
82
+ analyzeEyeRegionBrightness: () => analyzeEyeRegionBrightness,
83
+ analyzeEyeRegionContrast: () => analyzeEyeRegionContrast,
80
84
  analyzeLighting: () => analyzeLighting,
81
85
  calculateAdaptiveCropMultiplier: () => calculateAdaptiveCropMultiplier,
82
86
  calculateBrightness: () => calculateBrightness,
83
87
  calculateFaceAlignment: () => calculateFaceAlignment,
84
88
  calculateFaceCropRegion: () => calculateFaceCropRegion,
85
89
  canCaptureFrame: () => canCaptureFrame,
90
+ checkEyeRegionQuality: () => checkEyeRegionQuality,
86
91
  checkFrameQuality: () => checkFrameQuality,
87
92
  decodeBase64: () => decodeBase64,
93
+ detectSpecularHighlights: () => detectSpecularHighlights,
88
94
  encodeBase64: () => encodeBase64,
89
95
  generateSessionId: () => generateSessionId,
96
+ getActiveModels: () => getActiveModels,
90
97
  getApiErrorMessage: () => getApiErrorMessage,
91
98
  getCaptureQualityFeedback: () => getCaptureQualityFeedback,
99
+ getEyeRegionBounds: () => getEyeRegionBounds,
92
100
  getFeedbackMessage: () => getFeedbackMessage,
93
101
  getMinFramesForModel: () => getMinFramesForModel,
94
102
  getOvalGuideState: () => getOvalGuideState,
95
103
  getStatusMessage: () => getStatusMessage,
96
104
  hasEnoughFrames: () => hasEnoughFrames,
105
+ isDeprecatedModel: () => isDeprecatedModel,
97
106
  isFaceCropFullyInFrame: () => isFaceCropFullyInFrame,
98
107
  isFaceFullyVisible: () => isFaceFullyVisible,
99
108
  isFaceInOval: () => isFaceInOval,
@@ -125,6 +134,7 @@ var API_ENDPOINTS = {
125
134
  var DEFAULT_ENDPOINT = API_ENDPOINTS.production;
126
135
  var API_PATHS = {
127
136
  health: "/health",
137
+ models: "/api/v1/models",
128
138
  fastCheck: "/api/v1/fast-check",
129
139
  fastCheckCrops: "/api/v1/fast-check-crops",
130
140
  fastCheckStream: "/api/v1/fast-check-stream",
@@ -412,6 +422,14 @@ var LivenessClient = class _LivenessClient {
412
422
  async health() {
413
423
  return this.request(API_PATHS.health);
414
424
  }
425
+ /**
426
+ * Fetch the full model registry from the API.
427
+ * Includes all models with their deprecated status.
428
+ * Use this instead of the local MODEL_CONFIGS for a live list.
429
+ */
430
+ async getModels() {
431
+ return this.request(API_PATHS.models);
432
+ }
415
433
  /**
416
434
  * Get queue statistics
417
435
  */
@@ -861,117 +879,172 @@ var MODEL_CONFIGS = {
861
879
  type: "10",
862
880
  minFrames: 10,
863
881
  recommendedFrames: 10,
864
- description: "Fast model - 10 frames, quick verification"
882
+ description: "Fast model - 10 frames, quick verification",
883
+ deprecated: false
865
884
  },
866
885
  "50": {
867
886
  type: "50",
868
887
  minFrames: 50,
869
888
  recommendedFrames: 50,
870
- description: "Balanced model - 50 frames, good accuracy"
889
+ description: "Balanced model - 50 frames, good accuracy",
890
+ deprecated: false
871
891
  },
872
892
  "250": {
873
893
  type: "250",
874
894
  minFrames: 250,
875
895
  recommendedFrames: 250,
876
- description: "High-accuracy model - 250 frames, best accuracy"
896
+ description: "High-accuracy model - 250 frames, best accuracy",
897
+ deprecated: false
877
898
  },
878
899
  // Hybrid V2 models with physiological features
879
900
  "hybrid-v2-10": {
880
901
  type: "hybrid-v2-10",
881
902
  minFrames: 10,
882
903
  recommendedFrames: 10,
883
- description: "Hybrid V2 10-frame model with physio features"
904
+ description: "Hybrid V2 10-frame model with physio features",
905
+ deprecated: false
884
906
  },
885
907
  "hybrid-v2-30": {
886
908
  type: "hybrid-v2-30",
887
909
  minFrames: 30,
888
910
  recommendedFrames: 30,
889
- description: "Hybrid V2 30-frame model with physio features"
911
+ description: "Hybrid V2 30-frame model with physio features",
912
+ deprecated: false
890
913
  },
891
914
  "hybrid-v2-50": {
892
915
  type: "hybrid-v2-50",
893
916
  minFrames: 50,
894
917
  recommendedFrames: 50,
895
- description: "Hybrid V2 50-frame model with physio features"
918
+ description: "Hybrid V2 50-frame model with physio features",
919
+ deprecated: false
896
920
  },
897
921
  "hybrid-v2-60": {
898
922
  type: "hybrid-v2-60",
899
923
  minFrames: 60,
900
924
  recommendedFrames: 60,
901
- description: "Hybrid V2 60-frame model with physio features"
925
+ description: "Hybrid V2 60-frame model with physio features",
926
+ deprecated: false
902
927
  },
903
928
  "hybrid-v2-90": {
904
929
  type: "hybrid-v2-90",
905
930
  minFrames: 90,
906
931
  recommendedFrames: 90,
907
- description: "Hybrid V2 90-frame model with physio features"
932
+ description: "Hybrid V2 90-frame model with physio features",
933
+ deprecated: false
908
934
  },
909
935
  "hybrid-v2-100": {
910
936
  type: "hybrid-v2-100",
911
937
  minFrames: 100,
912
938
  recommendedFrames: 100,
913
- description: "Hybrid V2 100-frame model with physio features"
939
+ description: "Hybrid V2 100-frame model with physio features",
940
+ deprecated: false
914
941
  },
915
942
  "hybrid-v2-125": {
916
943
  type: "hybrid-v2-125",
917
944
  minFrames: 125,
918
945
  recommendedFrames: 125,
919
- description: "Hybrid V2 125-frame model with physio features"
946
+ description: "Hybrid V2 125-frame model with physio features",
947
+ deprecated: false
920
948
  },
921
949
  "hybrid-v2-150": {
922
950
  type: "hybrid-v2-150",
923
951
  minFrames: 150,
924
952
  recommendedFrames: 150,
925
- description: "Hybrid V2 150-frame model with physio features"
953
+ description: "Hybrid V2 150-frame model with physio features",
954
+ deprecated: false
926
955
  },
927
956
  "hybrid-v2-250": {
928
957
  type: "hybrid-v2-250",
929
958
  minFrames: 250,
930
959
  recommendedFrames: 250,
931
- description: "Hybrid V2 250-frame model with physio features"
960
+ description: "Hybrid V2 250-frame model with physio features",
961
+ deprecated: false
932
962
  },
933
- // Mixed models (FastLivenessDetector with mixed-trained weights)
963
+ // Mixed V1 models deprecated, use mixed-*-v2 equivalents instead
934
964
  "mixed-10": {
935
965
  type: "mixed-10",
936
966
  minFrames: 10,
937
967
  recommendedFrames: 10,
938
- description: "Mixed 10-frame model"
968
+ description: "Mixed 10-frame model",
969
+ deprecated: true
939
970
  },
940
971
  "mixed-30": {
941
972
  type: "mixed-30",
942
973
  minFrames: 30,
943
974
  recommendedFrames: 30,
944
- description: "Mixed 30-frame model"
975
+ description: "Mixed 30-frame model",
976
+ deprecated: true
945
977
  },
946
978
  "mixed-60": {
947
979
  type: "mixed-60",
948
980
  minFrames: 60,
949
981
  recommendedFrames: 60,
950
- description: "Mixed 60-frame model"
982
+ description: "Mixed 60-frame model",
983
+ deprecated: true
951
984
  },
952
985
  "mixed-90": {
953
986
  type: "mixed-90",
954
987
  minFrames: 90,
955
988
  recommendedFrames: 90,
956
- description: "Mixed 90-frame model"
989
+ description: "Mixed 90-frame model",
990
+ deprecated: true
957
991
  },
958
992
  "mixed-120": {
959
993
  type: "mixed-120",
960
994
  minFrames: 120,
961
995
  recommendedFrames: 120,
962
- description: "Mixed 120-frame model"
996
+ description: "Mixed 120-frame model",
997
+ deprecated: true
963
998
  },
964
999
  "mixed-150": {
965
1000
  type: "mixed-150",
966
1001
  minFrames: 150,
967
1002
  recommendedFrames: 150,
968
- description: "Mixed 150-frame model"
1003
+ description: "Mixed 150-frame model",
1004
+ deprecated: true
969
1005
  },
970
1006
  "mixed-250": {
971
1007
  type: "mixed-250",
972
1008
  minFrames: 250,
973
1009
  recommendedFrames: 250,
974
- description: "Mixed 250-frame model"
1010
+ description: "Mixed 250-frame model",
1011
+ deprecated: true
1012
+ },
1013
+ // Mixed V2 models (exp_042 checkpoints — EER: 4.0% / AUC: 0.991 at 30f)
1014
+ "mixed-10-v2": {
1015
+ type: "mixed-10-v2",
1016
+ minFrames: 10,
1017
+ recommendedFrames: 10,
1018
+ description: "Mixed V2 10-frame model",
1019
+ deprecated: false
1020
+ },
1021
+ "mixed-30-v2": {
1022
+ type: "mixed-30-v2",
1023
+ minFrames: 30,
1024
+ recommendedFrames: 30,
1025
+ description: "Mixed V2 30-frame model (recommended \u2014 95.7% balanced accuracy)",
1026
+ deprecated: false
1027
+ },
1028
+ "mixed-60-v2": {
1029
+ type: "mixed-60-v2",
1030
+ minFrames: 60,
1031
+ recommendedFrames: 60,
1032
+ description: "Mixed V2 60-frame model",
1033
+ deprecated: false
1034
+ },
1035
+ "mixed-90-v2": {
1036
+ type: "mixed-90-v2",
1037
+ minFrames: 90,
1038
+ recommendedFrames: 90,
1039
+ description: "Mixed V2 90-frame model",
1040
+ deprecated: false
1041
+ },
1042
+ "mixed-120-v2": {
1043
+ type: "mixed-120-v2",
1044
+ minFrames: 120,
1045
+ recommendedFrames: 120,
1046
+ description: "Mixed V2 120-frame model",
1047
+ deprecated: false
975
1048
  }
976
1049
  };
977
1050
  var HYBRID_MODEL_CONFIGS = {
@@ -991,10 +1064,16 @@ var HYBRID_MODEL_CONFIGS = {
991
1064
  }
992
1065
  };
993
1066
  function getMinFramesForModel(model) {
994
- return MODEL_CONFIGS[model].minFrames;
1067
+ return MODEL_CONFIGS[model]?.minFrames ?? 10;
995
1068
  }
996
1069
  function hasEnoughFrames(model, frameCount) {
997
- return frameCount >= MODEL_CONFIGS[model].minFrames;
1070
+ return frameCount >= getMinFramesForModel(model);
1071
+ }
1072
+ function isDeprecatedModel(model) {
1073
+ return MODEL_CONFIGS[model]?.deprecated ?? false;
1074
+ }
1075
+ function getActiveModels() {
1076
+ return Object.values(MODEL_CONFIGS).filter((c) => !c.deprecated);
998
1077
  }
999
1078
 
1000
1079
  // src/types/detectors.ts
@@ -1161,6 +1240,12 @@ var FEEDBACK_MESSAGES = {
1161
1240
  backlit: "Backlit - try facing the light source",
1162
1241
  // Hand occlusion
1163
1242
  hand_detected: "Remove hand from face",
1243
+ // Eye region quality
1244
+ eyes_not_visible: "Eyes not clearly visible",
1245
+ eyes_shadowed: "Eyes are in shadow - improve lighting",
1246
+ eyes_overexposed: "Eye region overexposed - reduce lighting",
1247
+ glasses_glare: "Glare detected - adjust angle or remove glasses",
1248
+ eye_quality_poor: "Eye region quality is poor",
1164
1249
  // Progress messages
1165
1250
  capturing: "Capturing...",
1166
1251
  almost_done: "Almost done...",
@@ -1212,6 +1297,12 @@ var ES_LOCALE = {
1212
1297
  backlit: "Contraluz - intenta mirar hacia la fuente de luz",
1213
1298
  // Hand occlusion
1214
1299
  hand_detected: "Retira la mano del rostro",
1300
+ // Eye region quality
1301
+ eyes_not_visible: "Ojos no visibles claramente",
1302
+ eyes_shadowed: "Ojos en sombra - mejora la iluminaci\xF3n",
1303
+ eyes_overexposed: "Regi\xF3n de ojos sobreexpuesta - reduce la iluminaci\xF3n",
1304
+ glasses_glare: "Reflejo detectado - ajusta el \xE1ngulo o retira los lentes",
1305
+ eye_quality_poor: "Calidad de la regi\xF3n de ojos insuficiente",
1215
1306
  // Progress messages
1216
1307
  capturing: "Capturando...",
1217
1308
  almost_done: "Casi listo...",
@@ -1295,7 +1386,7 @@ function validateTimestamp(timestamp) {
1295
1386
  }
1296
1387
  function validateFrameCount(model, frameCount) {
1297
1388
  const config = MODEL_CONFIGS[model];
1298
- const required = config.minFrames;
1389
+ const required = config?.minFrames ?? 10;
1299
1390
  return {
1300
1391
  valid: frameCount >= required,
1301
1392
  required,
@@ -1645,6 +1736,44 @@ var LANDMARK_INDEX = {
1645
1736
  /** Lower lip center */
1646
1737
  LOWER_LIP: 14
1647
1738
  };
1739
+ var EYE_LANDMARK_INDICES = {
1740
+ /** Right eye contour (viewer's left) */
1741
+ RIGHT_EYE: [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246],
1742
+ /** Left eye contour (viewer's right) */
1743
+ LEFT_EYE: [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398]
1744
+ };
1745
+ function getEyeRegionBounds(landmarks) {
1746
+ if (landmarks.length < 468) return null;
1747
+ const PADDING = 0.2;
1748
+ function computeBounds(indices) {
1749
+ let minX = 1;
1750
+ let minY = 1;
1751
+ let maxX = 0;
1752
+ let maxY = 0;
1753
+ for (const idx of indices) {
1754
+ const lm = landmarks[idx];
1755
+ if (!lm) continue;
1756
+ if (lm.x < minX) minX = lm.x;
1757
+ if (lm.y < minY) minY = lm.y;
1758
+ if (lm.x > maxX) maxX = lm.x;
1759
+ if (lm.y > maxY) maxY = lm.y;
1760
+ }
1761
+ const width = maxX - minX;
1762
+ const height = maxY - minY;
1763
+ const padX = width * PADDING;
1764
+ const padY = height * PADDING;
1765
+ return {
1766
+ x: Math.max(0, minX - padX),
1767
+ y: Math.max(0, minY - padY),
1768
+ width: Math.min(1 - Math.max(0, minX - padX), width + padX * 2),
1769
+ height: Math.min(1 - Math.max(0, minY - padY), height + padY * 2)
1770
+ };
1771
+ }
1772
+ return {
1773
+ rightEye: computeBounds(EYE_LANDMARK_INDICES.RIGHT_EYE),
1774
+ leftEye: computeBounds(EYE_LANDMARK_INDICES.LEFT_EYE)
1775
+ };
1776
+ }
1648
1777
  var LANDMARK_MIN_BOUND = 0.1;
1649
1778
  var LANDMARK_MAX_BOUND = 0.9;
1650
1779
  var MIN_LANDMARK_COUNT = 15;
@@ -1666,6 +1795,115 @@ function validateFaceLandmarks(landmarks) {
1666
1795
  }
1667
1796
  return { valid: true };
1668
1797
  }
1798
+
1799
+ // src/utils/eyeRegionAnalysis.ts
1800
+ var EYE_QUALITY_THRESHOLDS = {
1801
+ minBrightness: 40,
1802
+ maxBrightness: 230,
1803
+ minContrast: 12,
1804
+ glarePixelThreshold: 240,
1805
+ maxGlareRatio: 0.15
1806
+ };
1807
+ function rgbaToLuminance(r, g, b) {
1808
+ return 0.299 * r + 0.587 * g + 0.114 * b;
1809
+ }
1810
+ function analyzeEyeRegionBrightness(pixels) {
1811
+ const pixelCount = pixels.length / 4;
1812
+ if (pixelCount === 0) return 0;
1813
+ let totalLuminance = 0;
1814
+ for (let i = 0; i < pixels.length; i += 4) {
1815
+ totalLuminance += rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1816
+ }
1817
+ return totalLuminance / pixelCount;
1818
+ }
1819
+ function analyzeEyeRegionContrast(pixels, meanBrightness) {
1820
+ const pixelCount = pixels.length / 4;
1821
+ if (pixelCount === 0) return 0;
1822
+ const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
1823
+ let sumSqDiff = 0;
1824
+ for (let i = 0; i < pixels.length; i += 4) {
1825
+ const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1826
+ const diff = lum - mean;
1827
+ sumSqDiff += diff * diff;
1828
+ }
1829
+ return Math.sqrt(sumSqDiff / pixelCount);
1830
+ }
1831
+ function detectSpecularHighlights(pixels, threshold = EYE_QUALITY_THRESHOLDS.glarePixelThreshold) {
1832
+ const pixelCount = pixels.length / 4;
1833
+ if (pixelCount === 0) return 0;
1834
+ let glareCount = 0;
1835
+ for (let i = 0; i < pixels.length; i += 4) {
1836
+ const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1837
+ if (lum >= threshold) {
1838
+ glareCount++;
1839
+ }
1840
+ }
1841
+ return glareCount / pixelCount;
1842
+ }
1843
+ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1844
+ if (pixels.length < 4) {
1845
+ return {
1846
+ passed: false,
1847
+ brightness: 0,
1848
+ contrast: 0,
1849
+ hasGlare: false,
1850
+ glareRatio: 0,
1851
+ message: "Eyes not clearly visible"
1852
+ };
1853
+ }
1854
+ const brightness = analyzeEyeRegionBrightness(pixels);
1855
+ const contrast = analyzeEyeRegionContrast(pixels, brightness);
1856
+ const glareRatio = detectSpecularHighlights(pixels, thresholds.glarePixelThreshold);
1857
+ const hasGlare = glareRatio > thresholds.maxGlareRatio;
1858
+ if (brightness < thresholds.minBrightness) {
1859
+ return {
1860
+ passed: false,
1861
+ brightness,
1862
+ contrast,
1863
+ hasGlare,
1864
+ glareRatio,
1865
+ message: "Eyes are in shadow - improve lighting"
1866
+ };
1867
+ }
1868
+ if (brightness > thresholds.maxBrightness) {
1869
+ return {
1870
+ passed: false,
1871
+ brightness,
1872
+ contrast,
1873
+ hasGlare,
1874
+ glareRatio,
1875
+ message: "Eye region overexposed - reduce lighting"
1876
+ };
1877
+ }
1878
+ if (hasGlare) {
1879
+ return {
1880
+ passed: false,
1881
+ brightness,
1882
+ contrast,
1883
+ hasGlare,
1884
+ glareRatio,
1885
+ message: "Glare detected - adjust angle or remove glasses"
1886
+ };
1887
+ }
1888
+ if (contrast < thresholds.minContrast) {
1889
+ return {
1890
+ passed: false,
1891
+ brightness,
1892
+ contrast,
1893
+ hasGlare,
1894
+ glareRatio,
1895
+ message: "Eyes not clearly visible"
1896
+ };
1897
+ }
1898
+ return {
1899
+ passed: true,
1900
+ brightness,
1901
+ contrast,
1902
+ hasGlare,
1903
+ glareRatio,
1904
+ message: null
1905
+ };
1906
+ }
1669
1907
  // Annotate the CommonJS export names for ESM import in node:
1670
1908
  0 && (module.exports = {
1671
1909
  ALIGNMENT_THRESHOLD_CAPTURE,
@@ -1692,6 +1930,8 @@ function validateFaceLandmarks(landmarks) {
1692
1930
  ERROR_MESSAGES,
1693
1931
  ERROR_MESSAGES_ES,
1694
1932
  ES_LOCALE,
1933
+ EYE_LANDMARK_INDICES,
1934
+ EYE_QUALITY_THRESHOLDS,
1695
1935
  FACE_CENTER_VERTICAL_OFFSET,
1696
1936
  FACE_CROP_OUTPUT_SIZE,
1697
1937
  FEEDBACK_MESSAGES,
@@ -1725,23 +1965,30 @@ function validateFaceLandmarks(landmarks) {
1725
1965
  RETRY_CONFIG,
1726
1966
  TARGET_FACE_PERCENTAGE_IN_CROP,
1727
1967
  analyzeBlur,
1968
+ analyzeEyeRegionBrightness,
1969
+ analyzeEyeRegionContrast,
1728
1970
  analyzeLighting,
1729
1971
  calculateAdaptiveCropMultiplier,
1730
1972
  calculateBrightness,
1731
1973
  calculateFaceAlignment,
1732
1974
  calculateFaceCropRegion,
1733
1975
  canCaptureFrame,
1976
+ checkEyeRegionQuality,
1734
1977
  checkFrameQuality,
1735
1978
  decodeBase64,
1979
+ detectSpecularHighlights,
1736
1980
  encodeBase64,
1737
1981
  generateSessionId,
1982
+ getActiveModels,
1738
1983
  getApiErrorMessage,
1739
1984
  getCaptureQualityFeedback,
1985
+ getEyeRegionBounds,
1740
1986
  getFeedbackMessage,
1741
1987
  getMinFramesForModel,
1742
1988
  getOvalGuideState,
1743
1989
  getStatusMessage,
1744
1990
  hasEnoughFrames,
1991
+ isDeprecatedModel,
1745
1992
  isFaceCropFullyInFrame,
1746
1993
  isFaceFullyVisible,
1747
1994
  isFaceInOval,
package/dist/index.mjs CHANGED
@@ -7,6 +7,7 @@ var API_ENDPOINTS = {
7
7
  var DEFAULT_ENDPOINT = API_ENDPOINTS.production;
8
8
  var API_PATHS = {
9
9
  health: "/health",
10
+ models: "/api/v1/models",
10
11
  fastCheck: "/api/v1/fast-check",
11
12
  fastCheckCrops: "/api/v1/fast-check-crops",
12
13
  fastCheckStream: "/api/v1/fast-check-stream",
@@ -294,6 +295,14 @@ var LivenessClient = class _LivenessClient {
294
295
  async health() {
295
296
  return this.request(API_PATHS.health);
296
297
  }
298
+ /**
299
+ * Fetch the full model registry from the API.
300
+ * Includes all models with their deprecated status.
301
+ * Use this instead of the local MODEL_CONFIGS for a live list.
302
+ */
303
+ async getModels() {
304
+ return this.request(API_PATHS.models);
305
+ }
297
306
  /**
298
307
  * Get queue statistics
299
308
  */
@@ -743,117 +752,172 @@ var MODEL_CONFIGS = {
743
752
  type: "10",
744
753
  minFrames: 10,
745
754
  recommendedFrames: 10,
746
- description: "Fast model - 10 frames, quick verification"
755
+ description: "Fast model - 10 frames, quick verification",
756
+ deprecated: false
747
757
  },
748
758
  "50": {
749
759
  type: "50",
750
760
  minFrames: 50,
751
761
  recommendedFrames: 50,
752
- description: "Balanced model - 50 frames, good accuracy"
762
+ description: "Balanced model - 50 frames, good accuracy",
763
+ deprecated: false
753
764
  },
754
765
  "250": {
755
766
  type: "250",
756
767
  minFrames: 250,
757
768
  recommendedFrames: 250,
758
- description: "High-accuracy model - 250 frames, best accuracy"
769
+ description: "High-accuracy model - 250 frames, best accuracy",
770
+ deprecated: false
759
771
  },
760
772
  // Hybrid V2 models with physiological features
761
773
  "hybrid-v2-10": {
762
774
  type: "hybrid-v2-10",
763
775
  minFrames: 10,
764
776
  recommendedFrames: 10,
765
- description: "Hybrid V2 10-frame model with physio features"
777
+ description: "Hybrid V2 10-frame model with physio features",
778
+ deprecated: false
766
779
  },
767
780
  "hybrid-v2-30": {
768
781
  type: "hybrid-v2-30",
769
782
  minFrames: 30,
770
783
  recommendedFrames: 30,
771
- description: "Hybrid V2 30-frame model with physio features"
784
+ description: "Hybrid V2 30-frame model with physio features",
785
+ deprecated: false
772
786
  },
773
787
  "hybrid-v2-50": {
774
788
  type: "hybrid-v2-50",
775
789
  minFrames: 50,
776
790
  recommendedFrames: 50,
777
- description: "Hybrid V2 50-frame model with physio features"
791
+ description: "Hybrid V2 50-frame model with physio features",
792
+ deprecated: false
778
793
  },
779
794
  "hybrid-v2-60": {
780
795
  type: "hybrid-v2-60",
781
796
  minFrames: 60,
782
797
  recommendedFrames: 60,
783
- description: "Hybrid V2 60-frame model with physio features"
798
+ description: "Hybrid V2 60-frame model with physio features",
799
+ deprecated: false
784
800
  },
785
801
  "hybrid-v2-90": {
786
802
  type: "hybrid-v2-90",
787
803
  minFrames: 90,
788
804
  recommendedFrames: 90,
789
- description: "Hybrid V2 90-frame model with physio features"
805
+ description: "Hybrid V2 90-frame model with physio features",
806
+ deprecated: false
790
807
  },
791
808
  "hybrid-v2-100": {
792
809
  type: "hybrid-v2-100",
793
810
  minFrames: 100,
794
811
  recommendedFrames: 100,
795
- description: "Hybrid V2 100-frame model with physio features"
812
+ description: "Hybrid V2 100-frame model with physio features",
813
+ deprecated: false
796
814
  },
797
815
  "hybrid-v2-125": {
798
816
  type: "hybrid-v2-125",
799
817
  minFrames: 125,
800
818
  recommendedFrames: 125,
801
- description: "Hybrid V2 125-frame model with physio features"
819
+ description: "Hybrid V2 125-frame model with physio features",
820
+ deprecated: false
802
821
  },
803
822
  "hybrid-v2-150": {
804
823
  type: "hybrid-v2-150",
805
824
  minFrames: 150,
806
825
  recommendedFrames: 150,
807
- description: "Hybrid V2 150-frame model with physio features"
826
+ description: "Hybrid V2 150-frame model with physio features",
827
+ deprecated: false
808
828
  },
809
829
  "hybrid-v2-250": {
810
830
  type: "hybrid-v2-250",
811
831
  minFrames: 250,
812
832
  recommendedFrames: 250,
813
- description: "Hybrid V2 250-frame model with physio features"
833
+ description: "Hybrid V2 250-frame model with physio features",
834
+ deprecated: false
814
835
  },
815
- // Mixed models (FastLivenessDetector with mixed-trained weights)
836
+ // Mixed V1 models deprecated, use mixed-*-v2 equivalents instead
816
837
  "mixed-10": {
817
838
  type: "mixed-10",
818
839
  minFrames: 10,
819
840
  recommendedFrames: 10,
820
- description: "Mixed 10-frame model"
841
+ description: "Mixed 10-frame model",
842
+ deprecated: true
821
843
  },
822
844
  "mixed-30": {
823
845
  type: "mixed-30",
824
846
  minFrames: 30,
825
847
  recommendedFrames: 30,
826
- description: "Mixed 30-frame model"
848
+ description: "Mixed 30-frame model",
849
+ deprecated: true
827
850
  },
828
851
  "mixed-60": {
829
852
  type: "mixed-60",
830
853
  minFrames: 60,
831
854
  recommendedFrames: 60,
832
- description: "Mixed 60-frame model"
855
+ description: "Mixed 60-frame model",
856
+ deprecated: true
833
857
  },
834
858
  "mixed-90": {
835
859
  type: "mixed-90",
836
860
  minFrames: 90,
837
861
  recommendedFrames: 90,
838
- description: "Mixed 90-frame model"
862
+ description: "Mixed 90-frame model",
863
+ deprecated: true
839
864
  },
840
865
  "mixed-120": {
841
866
  type: "mixed-120",
842
867
  minFrames: 120,
843
868
  recommendedFrames: 120,
844
- description: "Mixed 120-frame model"
869
+ description: "Mixed 120-frame model",
870
+ deprecated: true
845
871
  },
846
872
  "mixed-150": {
847
873
  type: "mixed-150",
848
874
  minFrames: 150,
849
875
  recommendedFrames: 150,
850
- description: "Mixed 150-frame model"
876
+ description: "Mixed 150-frame model",
877
+ deprecated: true
851
878
  },
852
879
  "mixed-250": {
853
880
  type: "mixed-250",
854
881
  minFrames: 250,
855
882
  recommendedFrames: 250,
856
- description: "Mixed 250-frame model"
883
+ description: "Mixed 250-frame model",
884
+ deprecated: true
885
+ },
886
+ // Mixed V2 models (exp_042 checkpoints — EER: 4.0% / AUC: 0.991 at 30f)
887
+ "mixed-10-v2": {
888
+ type: "mixed-10-v2",
889
+ minFrames: 10,
890
+ recommendedFrames: 10,
891
+ description: "Mixed V2 10-frame model",
892
+ deprecated: false
893
+ },
894
+ "mixed-30-v2": {
895
+ type: "mixed-30-v2",
896
+ minFrames: 30,
897
+ recommendedFrames: 30,
898
+ description: "Mixed V2 30-frame model (recommended \u2014 95.7% balanced accuracy)",
899
+ deprecated: false
900
+ },
901
+ "mixed-60-v2": {
902
+ type: "mixed-60-v2",
903
+ minFrames: 60,
904
+ recommendedFrames: 60,
905
+ description: "Mixed V2 60-frame model",
906
+ deprecated: false
907
+ },
908
+ "mixed-90-v2": {
909
+ type: "mixed-90-v2",
910
+ minFrames: 90,
911
+ recommendedFrames: 90,
912
+ description: "Mixed V2 90-frame model",
913
+ deprecated: false
914
+ },
915
+ "mixed-120-v2": {
916
+ type: "mixed-120-v2",
917
+ minFrames: 120,
918
+ recommendedFrames: 120,
919
+ description: "Mixed V2 120-frame model",
920
+ deprecated: false
857
921
  }
858
922
  };
859
923
  var HYBRID_MODEL_CONFIGS = {
@@ -873,10 +937,16 @@ var HYBRID_MODEL_CONFIGS = {
873
937
  }
874
938
  };
875
939
  function getMinFramesForModel(model) {
876
- return MODEL_CONFIGS[model].minFrames;
940
+ return MODEL_CONFIGS[model]?.minFrames ?? 10;
877
941
  }
878
942
  function hasEnoughFrames(model, frameCount) {
879
- return frameCount >= MODEL_CONFIGS[model].minFrames;
943
+ return frameCount >= getMinFramesForModel(model);
944
+ }
945
+ function isDeprecatedModel(model) {
946
+ return MODEL_CONFIGS[model]?.deprecated ?? false;
947
+ }
948
+ function getActiveModels() {
949
+ return Object.values(MODEL_CONFIGS).filter((c) => !c.deprecated);
880
950
  }
881
951
 
882
952
  // src/types/detectors.ts
@@ -1043,6 +1113,12 @@ var FEEDBACK_MESSAGES = {
1043
1113
  backlit: "Backlit - try facing the light source",
1044
1114
  // Hand occlusion
1045
1115
  hand_detected: "Remove hand from face",
1116
+ // Eye region quality
1117
+ eyes_not_visible: "Eyes not clearly visible",
1118
+ eyes_shadowed: "Eyes are in shadow - improve lighting",
1119
+ eyes_overexposed: "Eye region overexposed - reduce lighting",
1120
+ glasses_glare: "Glare detected - adjust angle or remove glasses",
1121
+ eye_quality_poor: "Eye region quality is poor",
1046
1122
  // Progress messages
1047
1123
  capturing: "Capturing...",
1048
1124
  almost_done: "Almost done...",
@@ -1094,6 +1170,12 @@ var ES_LOCALE = {
1094
1170
  backlit: "Contraluz - intenta mirar hacia la fuente de luz",
1095
1171
  // Hand occlusion
1096
1172
  hand_detected: "Retira la mano del rostro",
1173
+ // Eye region quality
1174
+ eyes_not_visible: "Ojos no visibles claramente",
1175
+ eyes_shadowed: "Ojos en sombra - mejora la iluminaci\xF3n",
1176
+ eyes_overexposed: "Regi\xF3n de ojos sobreexpuesta - reduce la iluminaci\xF3n",
1177
+ glasses_glare: "Reflejo detectado - ajusta el \xE1ngulo o retira los lentes",
1178
+ eye_quality_poor: "Calidad de la regi\xF3n de ojos insuficiente",
1097
1179
  // Progress messages
1098
1180
  capturing: "Capturando...",
1099
1181
  almost_done: "Casi listo...",
@@ -1177,7 +1259,7 @@ function validateTimestamp(timestamp) {
1177
1259
  }
1178
1260
  function validateFrameCount(model, frameCount) {
1179
1261
  const config = MODEL_CONFIGS[model];
1180
- const required = config.minFrames;
1262
+ const required = config?.minFrames ?? 10;
1181
1263
  return {
1182
1264
  valid: frameCount >= required,
1183
1265
  required,
@@ -1527,6 +1609,44 @@ var LANDMARK_INDEX = {
1527
1609
  /** Lower lip center */
1528
1610
  LOWER_LIP: 14
1529
1611
  };
1612
+ var EYE_LANDMARK_INDICES = {
1613
+ /** Right eye contour (viewer's left) */
1614
+ RIGHT_EYE: [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246],
1615
+ /** Left eye contour (viewer's right) */
1616
+ LEFT_EYE: [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398]
1617
+ };
1618
+ function getEyeRegionBounds(landmarks) {
1619
+ if (landmarks.length < 468) return null;
1620
+ const PADDING = 0.2;
1621
+ function computeBounds(indices) {
1622
+ let minX = 1;
1623
+ let minY = 1;
1624
+ let maxX = 0;
1625
+ let maxY = 0;
1626
+ for (const idx of indices) {
1627
+ const lm = landmarks[idx];
1628
+ if (!lm) continue;
1629
+ if (lm.x < minX) minX = lm.x;
1630
+ if (lm.y < minY) minY = lm.y;
1631
+ if (lm.x > maxX) maxX = lm.x;
1632
+ if (lm.y > maxY) maxY = lm.y;
1633
+ }
1634
+ const width = maxX - minX;
1635
+ const height = maxY - minY;
1636
+ const padX = width * PADDING;
1637
+ const padY = height * PADDING;
1638
+ return {
1639
+ x: Math.max(0, minX - padX),
1640
+ y: Math.max(0, minY - padY),
1641
+ width: Math.min(1 - Math.max(0, minX - padX), width + padX * 2),
1642
+ height: Math.min(1 - Math.max(0, minY - padY), height + padY * 2)
1643
+ };
1644
+ }
1645
+ return {
1646
+ rightEye: computeBounds(EYE_LANDMARK_INDICES.RIGHT_EYE),
1647
+ leftEye: computeBounds(EYE_LANDMARK_INDICES.LEFT_EYE)
1648
+ };
1649
+ }
1530
1650
  var LANDMARK_MIN_BOUND = 0.1;
1531
1651
  var LANDMARK_MAX_BOUND = 0.9;
1532
1652
  var MIN_LANDMARK_COUNT = 15;
@@ -1548,6 +1668,115 @@ function validateFaceLandmarks(landmarks) {
1548
1668
  }
1549
1669
  return { valid: true };
1550
1670
  }
1671
+
1672
+ // src/utils/eyeRegionAnalysis.ts
1673
+ var EYE_QUALITY_THRESHOLDS = {
1674
+ minBrightness: 40,
1675
+ maxBrightness: 230,
1676
+ minContrast: 12,
1677
+ glarePixelThreshold: 240,
1678
+ maxGlareRatio: 0.15
1679
+ };
1680
+ function rgbaToLuminance(r, g, b) {
1681
+ return 0.299 * r + 0.587 * g + 0.114 * b;
1682
+ }
1683
+ function analyzeEyeRegionBrightness(pixels) {
1684
+ const pixelCount = pixels.length / 4;
1685
+ if (pixelCount === 0) return 0;
1686
+ let totalLuminance = 0;
1687
+ for (let i = 0; i < pixels.length; i += 4) {
1688
+ totalLuminance += rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1689
+ }
1690
+ return totalLuminance / pixelCount;
1691
+ }
1692
+ function analyzeEyeRegionContrast(pixels, meanBrightness) {
1693
+ const pixelCount = pixels.length / 4;
1694
+ if (pixelCount === 0) return 0;
1695
+ const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
1696
+ let sumSqDiff = 0;
1697
+ for (let i = 0; i < pixels.length; i += 4) {
1698
+ const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1699
+ const diff = lum - mean;
1700
+ sumSqDiff += diff * diff;
1701
+ }
1702
+ return Math.sqrt(sumSqDiff / pixelCount);
1703
+ }
1704
+ function detectSpecularHighlights(pixels, threshold = EYE_QUALITY_THRESHOLDS.glarePixelThreshold) {
1705
+ const pixelCount = pixels.length / 4;
1706
+ if (pixelCount === 0) return 0;
1707
+ let glareCount = 0;
1708
+ for (let i = 0; i < pixels.length; i += 4) {
1709
+ const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1710
+ if (lum >= threshold) {
1711
+ glareCount++;
1712
+ }
1713
+ }
1714
+ return glareCount / pixelCount;
1715
+ }
1716
+ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1717
+ if (pixels.length < 4) {
1718
+ return {
1719
+ passed: false,
1720
+ brightness: 0,
1721
+ contrast: 0,
1722
+ hasGlare: false,
1723
+ glareRatio: 0,
1724
+ message: "Eyes not clearly visible"
1725
+ };
1726
+ }
1727
+ const brightness = analyzeEyeRegionBrightness(pixels);
1728
+ const contrast = analyzeEyeRegionContrast(pixels, brightness);
1729
+ const glareRatio = detectSpecularHighlights(pixels, thresholds.glarePixelThreshold);
1730
+ const hasGlare = glareRatio > thresholds.maxGlareRatio;
1731
+ if (brightness < thresholds.minBrightness) {
1732
+ return {
1733
+ passed: false,
1734
+ brightness,
1735
+ contrast,
1736
+ hasGlare,
1737
+ glareRatio,
1738
+ message: "Eyes are in shadow - improve lighting"
1739
+ };
1740
+ }
1741
+ if (brightness > thresholds.maxBrightness) {
1742
+ return {
1743
+ passed: false,
1744
+ brightness,
1745
+ contrast,
1746
+ hasGlare,
1747
+ glareRatio,
1748
+ message: "Eye region overexposed - reduce lighting"
1749
+ };
1750
+ }
1751
+ if (hasGlare) {
1752
+ return {
1753
+ passed: false,
1754
+ brightness,
1755
+ contrast,
1756
+ hasGlare,
1757
+ glareRatio,
1758
+ message: "Glare detected - adjust angle or remove glasses"
1759
+ };
1760
+ }
1761
+ if (contrast < thresholds.minContrast) {
1762
+ return {
1763
+ passed: false,
1764
+ brightness,
1765
+ contrast,
1766
+ hasGlare,
1767
+ glareRatio,
1768
+ message: "Eyes not clearly visible"
1769
+ };
1770
+ }
1771
+ return {
1772
+ passed: true,
1773
+ brightness,
1774
+ contrast,
1775
+ hasGlare,
1776
+ glareRatio,
1777
+ message: null
1778
+ };
1779
+ }
1551
1780
  export {
1552
1781
  ALIGNMENT_THRESHOLD_CAPTURE,
1553
1782
  ALIGNMENT_THRESHOLD_GOOD,
@@ -1573,6 +1802,8 @@ export {
1573
1802
  ERROR_MESSAGES,
1574
1803
  ERROR_MESSAGES_ES,
1575
1804
  ES_LOCALE,
1805
+ EYE_LANDMARK_INDICES,
1806
+ EYE_QUALITY_THRESHOLDS,
1576
1807
  FACE_CENTER_VERTICAL_OFFSET,
1577
1808
  FACE_CROP_OUTPUT_SIZE,
1578
1809
  FEEDBACK_MESSAGES,
@@ -1606,23 +1837,30 @@ export {
1606
1837
  RETRY_CONFIG,
1607
1838
  TARGET_FACE_PERCENTAGE_IN_CROP,
1608
1839
  analyzeBlur,
1840
+ analyzeEyeRegionBrightness,
1841
+ analyzeEyeRegionContrast,
1609
1842
  analyzeLighting,
1610
1843
  calculateAdaptiveCropMultiplier,
1611
1844
  calculateBrightness,
1612
1845
  calculateFaceAlignment,
1613
1846
  calculateFaceCropRegion,
1614
1847
  canCaptureFrame,
1848
+ checkEyeRegionQuality,
1615
1849
  checkFrameQuality,
1616
1850
  decodeBase64,
1851
+ detectSpecularHighlights,
1617
1852
  encodeBase64,
1618
1853
  generateSessionId,
1854
+ getActiveModels,
1619
1855
  getApiErrorMessage,
1620
1856
  getCaptureQualityFeedback,
1857
+ getEyeRegionBounds,
1621
1858
  getFeedbackMessage,
1622
1859
  getMinFramesForModel,
1623
1860
  getOvalGuideState,
1624
1861
  getStatusMessage,
1625
1862
  hasEnoughFrames,
1863
+ isDeprecatedModel,
1626
1864
  isFaceCropFullyInFrame,
1627
1865
  isFaceFullyVisible,
1628
1866
  isFaceInOval,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",