@moveris/shared 2.2.0 → 2.3.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
@@ -322,6 +322,21 @@ declare const LANDMARK_INDEX: {
322
322
  readonly UPPER_LIP: 13;
323
323
  readonly LOWER_LIP: 14;
324
324
  };
325
+ declare const EYE_LANDMARK_INDICES: {
326
+ readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
327
+ readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
328
+ };
329
+ interface EyeRegionBounds {
330
+ x: number;
331
+ y: number;
332
+ width: number;
333
+ height: number;
334
+ }
335
+ interface EyeRegionsBounds {
336
+ leftEye: EyeRegionBounds;
337
+ rightEye: EyeRegionBounds;
338
+ }
339
+ declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
325
340
  declare const LANDMARK_MIN_BOUND = 0.1;
326
341
  declare const LANDMARK_MAX_BOUND = 0.9;
327
342
  declare const MIN_LANDMARK_COUNT = 15;
@@ -655,6 +670,11 @@ declare const FEEDBACK_MESSAGES: {
655
670
  readonly too_dark: "Low lighting - move to a brighter area";
656
671
  readonly backlit: "Backlit - try facing the light source";
657
672
  readonly hand_detected: "Remove hand from face";
673
+ readonly eyes_not_visible: "Eyes not clearly visible";
674
+ readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
675
+ readonly eyes_overexposed: "Eye region overexposed - reduce lighting";
676
+ readonly glasses_glare: "Glare detected - adjust angle or remove glasses";
677
+ readonly eye_quality_poor: "Eye region quality is poor";
658
678
  readonly capturing: "Capturing...";
659
679
  readonly almost_done: "Almost done...";
660
680
  readonly verification_complete: "Verification complete";
@@ -708,4 +728,25 @@ interface RetryOptions {
708
728
  declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
709
729
  declare function sleep(ms: number): Promise<void>;
710
730
 
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 };
731
+ interface EyeRegionQuality {
732
+ passed: boolean;
733
+ brightness: number;
734
+ contrast: number;
735
+ hasGlare: boolean;
736
+ glareRatio: number;
737
+ message: string | null;
738
+ }
739
+ interface EyeQualityThresholds {
740
+ minBrightness: number;
741
+ maxBrightness: number;
742
+ minContrast: number;
743
+ glarePixelThreshold: number;
744
+ maxGlareRatio: number;
745
+ }
746
+ declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
747
+ declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
748
+ declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
749
+ declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
750
+ declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
751
+
752
+ 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 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectSpecularHighlights, encodeBase64, generateSessionId, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, 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
@@ -322,6 +322,21 @@ declare const LANDMARK_INDEX: {
322
322
  readonly UPPER_LIP: 13;
323
323
  readonly LOWER_LIP: 14;
324
324
  };
325
+ declare const EYE_LANDMARK_INDICES: {
326
+ readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
327
+ readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
328
+ };
329
+ interface EyeRegionBounds {
330
+ x: number;
331
+ y: number;
332
+ width: number;
333
+ height: number;
334
+ }
335
+ interface EyeRegionsBounds {
336
+ leftEye: EyeRegionBounds;
337
+ rightEye: EyeRegionBounds;
338
+ }
339
+ declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
325
340
  declare const LANDMARK_MIN_BOUND = 0.1;
326
341
  declare const LANDMARK_MAX_BOUND = 0.9;
327
342
  declare const MIN_LANDMARK_COUNT = 15;
@@ -655,6 +670,11 @@ declare const FEEDBACK_MESSAGES: {
655
670
  readonly too_dark: "Low lighting - move to a brighter area";
656
671
  readonly backlit: "Backlit - try facing the light source";
657
672
  readonly hand_detected: "Remove hand from face";
673
+ readonly eyes_not_visible: "Eyes not clearly visible";
674
+ readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
675
+ readonly eyes_overexposed: "Eye region overexposed - reduce lighting";
676
+ readonly glasses_glare: "Glare detected - adjust angle or remove glasses";
677
+ readonly eye_quality_poor: "Eye region quality is poor";
658
678
  readonly capturing: "Capturing...";
659
679
  readonly almost_done: "Almost done...";
660
680
  readonly verification_complete: "Verification complete";
@@ -708,4 +728,25 @@ interface RetryOptions {
708
728
  declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
709
729
  declare function sleep(ms: number): Promise<void>;
710
730
 
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 };
731
+ interface EyeRegionQuality {
732
+ passed: boolean;
733
+ brightness: number;
734
+ contrast: number;
735
+ hasGlare: boolean;
736
+ glareRatio: number;
737
+ message: string | null;
738
+ }
739
+ interface EyeQualityThresholds {
740
+ minBrightness: number;
741
+ maxBrightness: number;
742
+ minContrast: number;
743
+ glarePixelThreshold: number;
744
+ maxGlareRatio: number;
745
+ }
746
+ declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
747
+ declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
748
+ declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
749
+ declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
750
+ declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
751
+
752
+ 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 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectSpecularHighlights, encodeBase64, generateSessionId, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, 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,18 +79,23 @@ __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,
90
96
  getApiErrorMessage: () => getApiErrorMessage,
91
97
  getCaptureQualityFeedback: () => getCaptureQualityFeedback,
98
+ getEyeRegionBounds: () => getEyeRegionBounds,
92
99
  getFeedbackMessage: () => getFeedbackMessage,
93
100
  getMinFramesForModel: () => getMinFramesForModel,
94
101
  getOvalGuideState: () => getOvalGuideState,
@@ -1161,6 +1168,12 @@ var FEEDBACK_MESSAGES = {
1161
1168
  backlit: "Backlit - try facing the light source",
1162
1169
  // Hand occlusion
1163
1170
  hand_detected: "Remove hand from face",
1171
+ // Eye region quality
1172
+ eyes_not_visible: "Eyes not clearly visible",
1173
+ eyes_shadowed: "Eyes are in shadow - improve lighting",
1174
+ eyes_overexposed: "Eye region overexposed - reduce lighting",
1175
+ glasses_glare: "Glare detected - adjust angle or remove glasses",
1176
+ eye_quality_poor: "Eye region quality is poor",
1164
1177
  // Progress messages
1165
1178
  capturing: "Capturing...",
1166
1179
  almost_done: "Almost done...",
@@ -1212,6 +1225,12 @@ var ES_LOCALE = {
1212
1225
  backlit: "Contraluz - intenta mirar hacia la fuente de luz",
1213
1226
  // Hand occlusion
1214
1227
  hand_detected: "Retira la mano del rostro",
1228
+ // Eye region quality
1229
+ eyes_not_visible: "Ojos no visibles claramente",
1230
+ eyes_shadowed: "Ojos en sombra - mejora la iluminaci\xF3n",
1231
+ eyes_overexposed: "Regi\xF3n de ojos sobreexpuesta - reduce la iluminaci\xF3n",
1232
+ glasses_glare: "Reflejo detectado - ajusta el \xE1ngulo o retira los lentes",
1233
+ eye_quality_poor: "Calidad de la regi\xF3n de ojos insuficiente",
1215
1234
  // Progress messages
1216
1235
  capturing: "Capturando...",
1217
1236
  almost_done: "Casi listo...",
@@ -1645,6 +1664,44 @@ var LANDMARK_INDEX = {
1645
1664
  /** Lower lip center */
1646
1665
  LOWER_LIP: 14
1647
1666
  };
1667
+ var EYE_LANDMARK_INDICES = {
1668
+ /** Right eye contour (viewer's left) */
1669
+ RIGHT_EYE: [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246],
1670
+ /** Left eye contour (viewer's right) */
1671
+ LEFT_EYE: [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398]
1672
+ };
1673
+ function getEyeRegionBounds(landmarks) {
1674
+ if (landmarks.length < 468) return null;
1675
+ const PADDING = 0.2;
1676
+ function computeBounds(indices) {
1677
+ let minX = 1;
1678
+ let minY = 1;
1679
+ let maxX = 0;
1680
+ let maxY = 0;
1681
+ for (const idx of indices) {
1682
+ const lm = landmarks[idx];
1683
+ if (!lm) continue;
1684
+ if (lm.x < minX) minX = lm.x;
1685
+ if (lm.y < minY) minY = lm.y;
1686
+ if (lm.x > maxX) maxX = lm.x;
1687
+ if (lm.y > maxY) maxY = lm.y;
1688
+ }
1689
+ const width = maxX - minX;
1690
+ const height = maxY - minY;
1691
+ const padX = width * PADDING;
1692
+ const padY = height * PADDING;
1693
+ return {
1694
+ x: Math.max(0, minX - padX),
1695
+ y: Math.max(0, minY - padY),
1696
+ width: Math.min(1 - Math.max(0, minX - padX), width + padX * 2),
1697
+ height: Math.min(1 - Math.max(0, minY - padY), height + padY * 2)
1698
+ };
1699
+ }
1700
+ return {
1701
+ rightEye: computeBounds(EYE_LANDMARK_INDICES.RIGHT_EYE),
1702
+ leftEye: computeBounds(EYE_LANDMARK_INDICES.LEFT_EYE)
1703
+ };
1704
+ }
1648
1705
  var LANDMARK_MIN_BOUND = 0.1;
1649
1706
  var LANDMARK_MAX_BOUND = 0.9;
1650
1707
  var MIN_LANDMARK_COUNT = 15;
@@ -1666,6 +1723,115 @@ function validateFaceLandmarks(landmarks) {
1666
1723
  }
1667
1724
  return { valid: true };
1668
1725
  }
1726
+
1727
+ // src/utils/eyeRegionAnalysis.ts
1728
+ var EYE_QUALITY_THRESHOLDS = {
1729
+ minBrightness: 40,
1730
+ maxBrightness: 230,
1731
+ minContrast: 12,
1732
+ glarePixelThreshold: 240,
1733
+ maxGlareRatio: 0.15
1734
+ };
1735
+ function rgbaToLuminance(r, g, b) {
1736
+ return 0.299 * r + 0.587 * g + 0.114 * b;
1737
+ }
1738
+ function analyzeEyeRegionBrightness(pixels) {
1739
+ const pixelCount = pixels.length / 4;
1740
+ if (pixelCount === 0) return 0;
1741
+ let totalLuminance = 0;
1742
+ for (let i = 0; i < pixels.length; i += 4) {
1743
+ totalLuminance += rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1744
+ }
1745
+ return totalLuminance / pixelCount;
1746
+ }
1747
+ function analyzeEyeRegionContrast(pixels, meanBrightness) {
1748
+ const pixelCount = pixels.length / 4;
1749
+ if (pixelCount === 0) return 0;
1750
+ const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
1751
+ let sumSqDiff = 0;
1752
+ for (let i = 0; i < pixels.length; i += 4) {
1753
+ const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1754
+ const diff = lum - mean;
1755
+ sumSqDiff += diff * diff;
1756
+ }
1757
+ return Math.sqrt(sumSqDiff / pixelCount);
1758
+ }
1759
+ function detectSpecularHighlights(pixels, threshold = EYE_QUALITY_THRESHOLDS.glarePixelThreshold) {
1760
+ const pixelCount = pixels.length / 4;
1761
+ if (pixelCount === 0) return 0;
1762
+ let glareCount = 0;
1763
+ for (let i = 0; i < pixels.length; i += 4) {
1764
+ const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1765
+ if (lum >= threshold) {
1766
+ glareCount++;
1767
+ }
1768
+ }
1769
+ return glareCount / pixelCount;
1770
+ }
1771
+ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1772
+ if (pixels.length < 4) {
1773
+ return {
1774
+ passed: false,
1775
+ brightness: 0,
1776
+ contrast: 0,
1777
+ hasGlare: false,
1778
+ glareRatio: 0,
1779
+ message: "Eyes not clearly visible"
1780
+ };
1781
+ }
1782
+ const brightness = analyzeEyeRegionBrightness(pixels);
1783
+ const contrast = analyzeEyeRegionContrast(pixels, brightness);
1784
+ const glareRatio = detectSpecularHighlights(pixels, thresholds.glarePixelThreshold);
1785
+ const hasGlare = glareRatio > thresholds.maxGlareRatio;
1786
+ if (brightness < thresholds.minBrightness) {
1787
+ return {
1788
+ passed: false,
1789
+ brightness,
1790
+ contrast,
1791
+ hasGlare,
1792
+ glareRatio,
1793
+ message: "Eyes are in shadow - improve lighting"
1794
+ };
1795
+ }
1796
+ if (brightness > thresholds.maxBrightness) {
1797
+ return {
1798
+ passed: false,
1799
+ brightness,
1800
+ contrast,
1801
+ hasGlare,
1802
+ glareRatio,
1803
+ message: "Eye region overexposed - reduce lighting"
1804
+ };
1805
+ }
1806
+ if (hasGlare) {
1807
+ return {
1808
+ passed: false,
1809
+ brightness,
1810
+ contrast,
1811
+ hasGlare,
1812
+ glareRatio,
1813
+ message: "Glare detected - adjust angle or remove glasses"
1814
+ };
1815
+ }
1816
+ if (contrast < thresholds.minContrast) {
1817
+ return {
1818
+ passed: false,
1819
+ brightness,
1820
+ contrast,
1821
+ hasGlare,
1822
+ glareRatio,
1823
+ message: "Eyes not clearly visible"
1824
+ };
1825
+ }
1826
+ return {
1827
+ passed: true,
1828
+ brightness,
1829
+ contrast,
1830
+ hasGlare,
1831
+ glareRatio,
1832
+ message: null
1833
+ };
1834
+ }
1669
1835
  // Annotate the CommonJS export names for ESM import in node:
1670
1836
  0 && (module.exports = {
1671
1837
  ALIGNMENT_THRESHOLD_CAPTURE,
@@ -1692,6 +1858,8 @@ function validateFaceLandmarks(landmarks) {
1692
1858
  ERROR_MESSAGES,
1693
1859
  ERROR_MESSAGES_ES,
1694
1860
  ES_LOCALE,
1861
+ EYE_LANDMARK_INDICES,
1862
+ EYE_QUALITY_THRESHOLDS,
1695
1863
  FACE_CENTER_VERTICAL_OFFSET,
1696
1864
  FACE_CROP_OUTPUT_SIZE,
1697
1865
  FEEDBACK_MESSAGES,
@@ -1725,18 +1893,23 @@ function validateFaceLandmarks(landmarks) {
1725
1893
  RETRY_CONFIG,
1726
1894
  TARGET_FACE_PERCENTAGE_IN_CROP,
1727
1895
  analyzeBlur,
1896
+ analyzeEyeRegionBrightness,
1897
+ analyzeEyeRegionContrast,
1728
1898
  analyzeLighting,
1729
1899
  calculateAdaptiveCropMultiplier,
1730
1900
  calculateBrightness,
1731
1901
  calculateFaceAlignment,
1732
1902
  calculateFaceCropRegion,
1733
1903
  canCaptureFrame,
1904
+ checkEyeRegionQuality,
1734
1905
  checkFrameQuality,
1735
1906
  decodeBase64,
1907
+ detectSpecularHighlights,
1736
1908
  encodeBase64,
1737
1909
  generateSessionId,
1738
1910
  getApiErrorMessage,
1739
1911
  getCaptureQualityFeedback,
1912
+ getEyeRegionBounds,
1740
1913
  getFeedbackMessage,
1741
1914
  getMinFramesForModel,
1742
1915
  getOvalGuideState,
package/dist/index.mjs CHANGED
@@ -1043,6 +1043,12 @@ var FEEDBACK_MESSAGES = {
1043
1043
  backlit: "Backlit - try facing the light source",
1044
1044
  // Hand occlusion
1045
1045
  hand_detected: "Remove hand from face",
1046
+ // Eye region quality
1047
+ eyes_not_visible: "Eyes not clearly visible",
1048
+ eyes_shadowed: "Eyes are in shadow - improve lighting",
1049
+ eyes_overexposed: "Eye region overexposed - reduce lighting",
1050
+ glasses_glare: "Glare detected - adjust angle or remove glasses",
1051
+ eye_quality_poor: "Eye region quality is poor",
1046
1052
  // Progress messages
1047
1053
  capturing: "Capturing...",
1048
1054
  almost_done: "Almost done...",
@@ -1094,6 +1100,12 @@ var ES_LOCALE = {
1094
1100
  backlit: "Contraluz - intenta mirar hacia la fuente de luz",
1095
1101
  // Hand occlusion
1096
1102
  hand_detected: "Retira la mano del rostro",
1103
+ // Eye region quality
1104
+ eyes_not_visible: "Ojos no visibles claramente",
1105
+ eyes_shadowed: "Ojos en sombra - mejora la iluminaci\xF3n",
1106
+ eyes_overexposed: "Regi\xF3n de ojos sobreexpuesta - reduce la iluminaci\xF3n",
1107
+ glasses_glare: "Reflejo detectado - ajusta el \xE1ngulo o retira los lentes",
1108
+ eye_quality_poor: "Calidad de la regi\xF3n de ojos insuficiente",
1097
1109
  // Progress messages
1098
1110
  capturing: "Capturando...",
1099
1111
  almost_done: "Casi listo...",
@@ -1527,6 +1539,44 @@ var LANDMARK_INDEX = {
1527
1539
  /** Lower lip center */
1528
1540
  LOWER_LIP: 14
1529
1541
  };
1542
+ var EYE_LANDMARK_INDICES = {
1543
+ /** Right eye contour (viewer's left) */
1544
+ RIGHT_EYE: [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246],
1545
+ /** Left eye contour (viewer's right) */
1546
+ LEFT_EYE: [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398]
1547
+ };
1548
+ function getEyeRegionBounds(landmarks) {
1549
+ if (landmarks.length < 468) return null;
1550
+ const PADDING = 0.2;
1551
+ function computeBounds(indices) {
1552
+ let minX = 1;
1553
+ let minY = 1;
1554
+ let maxX = 0;
1555
+ let maxY = 0;
1556
+ for (const idx of indices) {
1557
+ const lm = landmarks[idx];
1558
+ if (!lm) continue;
1559
+ if (lm.x < minX) minX = lm.x;
1560
+ if (lm.y < minY) minY = lm.y;
1561
+ if (lm.x > maxX) maxX = lm.x;
1562
+ if (lm.y > maxY) maxY = lm.y;
1563
+ }
1564
+ const width = maxX - minX;
1565
+ const height = maxY - minY;
1566
+ const padX = width * PADDING;
1567
+ const padY = height * PADDING;
1568
+ return {
1569
+ x: Math.max(0, minX - padX),
1570
+ y: Math.max(0, minY - padY),
1571
+ width: Math.min(1 - Math.max(0, minX - padX), width + padX * 2),
1572
+ height: Math.min(1 - Math.max(0, minY - padY), height + padY * 2)
1573
+ };
1574
+ }
1575
+ return {
1576
+ rightEye: computeBounds(EYE_LANDMARK_INDICES.RIGHT_EYE),
1577
+ leftEye: computeBounds(EYE_LANDMARK_INDICES.LEFT_EYE)
1578
+ };
1579
+ }
1530
1580
  var LANDMARK_MIN_BOUND = 0.1;
1531
1581
  var LANDMARK_MAX_BOUND = 0.9;
1532
1582
  var MIN_LANDMARK_COUNT = 15;
@@ -1548,6 +1598,115 @@ function validateFaceLandmarks(landmarks) {
1548
1598
  }
1549
1599
  return { valid: true };
1550
1600
  }
1601
+
1602
+ // src/utils/eyeRegionAnalysis.ts
1603
+ var EYE_QUALITY_THRESHOLDS = {
1604
+ minBrightness: 40,
1605
+ maxBrightness: 230,
1606
+ minContrast: 12,
1607
+ glarePixelThreshold: 240,
1608
+ maxGlareRatio: 0.15
1609
+ };
1610
+ function rgbaToLuminance(r, g, b) {
1611
+ return 0.299 * r + 0.587 * g + 0.114 * b;
1612
+ }
1613
+ function analyzeEyeRegionBrightness(pixels) {
1614
+ const pixelCount = pixels.length / 4;
1615
+ if (pixelCount === 0) return 0;
1616
+ let totalLuminance = 0;
1617
+ for (let i = 0; i < pixels.length; i += 4) {
1618
+ totalLuminance += rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1619
+ }
1620
+ return totalLuminance / pixelCount;
1621
+ }
1622
+ function analyzeEyeRegionContrast(pixels, meanBrightness) {
1623
+ const pixelCount = pixels.length / 4;
1624
+ if (pixelCount === 0) return 0;
1625
+ const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
1626
+ let sumSqDiff = 0;
1627
+ for (let i = 0; i < pixels.length; i += 4) {
1628
+ const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1629
+ const diff = lum - mean;
1630
+ sumSqDiff += diff * diff;
1631
+ }
1632
+ return Math.sqrt(sumSqDiff / pixelCount);
1633
+ }
1634
+ function detectSpecularHighlights(pixels, threshold = EYE_QUALITY_THRESHOLDS.glarePixelThreshold) {
1635
+ const pixelCount = pixels.length / 4;
1636
+ if (pixelCount === 0) return 0;
1637
+ let glareCount = 0;
1638
+ for (let i = 0; i < pixels.length; i += 4) {
1639
+ const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
1640
+ if (lum >= threshold) {
1641
+ glareCount++;
1642
+ }
1643
+ }
1644
+ return glareCount / pixelCount;
1645
+ }
1646
+ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1647
+ if (pixels.length < 4) {
1648
+ return {
1649
+ passed: false,
1650
+ brightness: 0,
1651
+ contrast: 0,
1652
+ hasGlare: false,
1653
+ glareRatio: 0,
1654
+ message: "Eyes not clearly visible"
1655
+ };
1656
+ }
1657
+ const brightness = analyzeEyeRegionBrightness(pixels);
1658
+ const contrast = analyzeEyeRegionContrast(pixels, brightness);
1659
+ const glareRatio = detectSpecularHighlights(pixels, thresholds.glarePixelThreshold);
1660
+ const hasGlare = glareRatio > thresholds.maxGlareRatio;
1661
+ if (brightness < thresholds.minBrightness) {
1662
+ return {
1663
+ passed: false,
1664
+ brightness,
1665
+ contrast,
1666
+ hasGlare,
1667
+ glareRatio,
1668
+ message: "Eyes are in shadow - improve lighting"
1669
+ };
1670
+ }
1671
+ if (brightness > thresholds.maxBrightness) {
1672
+ return {
1673
+ passed: false,
1674
+ brightness,
1675
+ contrast,
1676
+ hasGlare,
1677
+ glareRatio,
1678
+ message: "Eye region overexposed - reduce lighting"
1679
+ };
1680
+ }
1681
+ if (hasGlare) {
1682
+ return {
1683
+ passed: false,
1684
+ brightness,
1685
+ contrast,
1686
+ hasGlare,
1687
+ glareRatio,
1688
+ message: "Glare detected - adjust angle or remove glasses"
1689
+ };
1690
+ }
1691
+ if (contrast < thresholds.minContrast) {
1692
+ return {
1693
+ passed: false,
1694
+ brightness,
1695
+ contrast,
1696
+ hasGlare,
1697
+ glareRatio,
1698
+ message: "Eyes not clearly visible"
1699
+ };
1700
+ }
1701
+ return {
1702
+ passed: true,
1703
+ brightness,
1704
+ contrast,
1705
+ hasGlare,
1706
+ glareRatio,
1707
+ message: null
1708
+ };
1709
+ }
1551
1710
  export {
1552
1711
  ALIGNMENT_THRESHOLD_CAPTURE,
1553
1712
  ALIGNMENT_THRESHOLD_GOOD,
@@ -1573,6 +1732,8 @@ export {
1573
1732
  ERROR_MESSAGES,
1574
1733
  ERROR_MESSAGES_ES,
1575
1734
  ES_LOCALE,
1735
+ EYE_LANDMARK_INDICES,
1736
+ EYE_QUALITY_THRESHOLDS,
1576
1737
  FACE_CENTER_VERTICAL_OFFSET,
1577
1738
  FACE_CROP_OUTPUT_SIZE,
1578
1739
  FEEDBACK_MESSAGES,
@@ -1606,18 +1767,23 @@ export {
1606
1767
  RETRY_CONFIG,
1607
1768
  TARGET_FACE_PERCENTAGE_IN_CROP,
1608
1769
  analyzeBlur,
1770
+ analyzeEyeRegionBrightness,
1771
+ analyzeEyeRegionContrast,
1609
1772
  analyzeLighting,
1610
1773
  calculateAdaptiveCropMultiplier,
1611
1774
  calculateBrightness,
1612
1775
  calculateFaceAlignment,
1613
1776
  calculateFaceCropRegion,
1614
1777
  canCaptureFrame,
1778
+ checkEyeRegionQuality,
1615
1779
  checkFrameQuality,
1616
1780
  decodeBase64,
1781
+ detectSpecularHighlights,
1617
1782
  encodeBase64,
1618
1783
  generateSessionId,
1619
1784
  getApiErrorMessage,
1620
1785
  getCaptureQualityFeedback,
1786
+ getEyeRegionBounds,
1621
1787
  getFeedbackMessage,
1622
1788
  getMinFramesForModel,
1623
1789
  getOvalGuideState,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",