@moveris/shared 1.0.2 → 2.1.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/dist/index.d.mts +248 -106
- package/dist/index.d.ts +248 -106
- package/dist/index.js +340 -11
- package/dist/index.mjs +330 -11
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,7 @@ var API_PATHS = {
|
|
|
9
9
|
health: "/health",
|
|
10
10
|
fastCheck: "/api/v1/fast-check",
|
|
11
11
|
fastCheckCrops: "/api/v1/fast-check-crops",
|
|
12
|
+
fastCheckStream: "/api/v1/fast-check-stream",
|
|
12
13
|
verify: "/api/v1/verify",
|
|
13
14
|
hybridCheck: "/api/v1/hybrid-check",
|
|
14
15
|
hybrid50: "/api/v1/hybrid-50",
|
|
@@ -129,6 +130,22 @@ function toLivenessResult(response) {
|
|
|
129
130
|
framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0
|
|
130
131
|
};
|
|
131
132
|
}
|
|
133
|
+
function toLivenessResultFromStream(response) {
|
|
134
|
+
if (response.status !== "complete") {
|
|
135
|
+
throw new LivenessApiError("Stream not complete", "stream_incomplete", 400);
|
|
136
|
+
}
|
|
137
|
+
if (!response.verdict) {
|
|
138
|
+
throw new LivenessApiError(response.error ?? "No verdict received", "no_verdict", 500);
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
verdict: response.verdict,
|
|
142
|
+
confidence: response.confidence ?? 0,
|
|
143
|
+
score: response.score ?? 0,
|
|
144
|
+
sessionId: response.session_id,
|
|
145
|
+
processingMs: response.processing_ms ?? 0,
|
|
146
|
+
framesProcessed: response.frames_processed ?? 0
|
|
147
|
+
};
|
|
148
|
+
}
|
|
132
149
|
function generateSessionId() {
|
|
133
150
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
134
151
|
return crypto.randomUUID();
|
|
@@ -263,6 +280,145 @@ var LivenessClient = class {
|
|
|
263
280
|
});
|
|
264
281
|
return toLivenessResult(response);
|
|
265
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Send a single captured frame to the streaming endpoint.
|
|
285
|
+
*
|
|
286
|
+
* Use this to implement **real-time** streaming: call once per captured
|
|
287
|
+
* frame instead of batching all frames first.
|
|
288
|
+
*
|
|
289
|
+
* - While the server is still collecting frames the response will have
|
|
290
|
+
* `status: 'buffering'` with `frames_received` / `frames_required`.
|
|
291
|
+
* - When the last required frame arrives the response will have
|
|
292
|
+
* `status: 'complete'` with the full liveness result.
|
|
293
|
+
*
|
|
294
|
+
* Each frame gets its own retry via {@link retryWithBackoff}.
|
|
295
|
+
*
|
|
296
|
+
* @param frame - A single captured frame
|
|
297
|
+
* @param options - Session, model and source
|
|
298
|
+
* @returns Stream response (buffering or complete)
|
|
299
|
+
*/
|
|
300
|
+
async streamFrame(frame, options) {
|
|
301
|
+
const frameData = {
|
|
302
|
+
index: frame.index,
|
|
303
|
+
timestamp_ms: frame.timestampMs,
|
|
304
|
+
pixels: frame.pixels
|
|
305
|
+
};
|
|
306
|
+
return this.sendStreamFrameInternal(frameData, {
|
|
307
|
+
sessionId: options.sessionId,
|
|
308
|
+
model: options.model ?? "10",
|
|
309
|
+
source: options.source ?? "live"
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Send a single FrameData to the streaming endpoint with retry (internal)
|
|
314
|
+
*
|
|
315
|
+
* @param frame - Single frame to send
|
|
316
|
+
* @param options - Session and model options
|
|
317
|
+
* @returns Stream response with status
|
|
318
|
+
*/
|
|
319
|
+
async sendStreamFrameInternal(frameData, options) {
|
|
320
|
+
const request = {
|
|
321
|
+
session_id: options.sessionId,
|
|
322
|
+
model: options.model,
|
|
323
|
+
source: options.source,
|
|
324
|
+
frame: frameData
|
|
325
|
+
};
|
|
326
|
+
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
body: JSON.stringify(request)
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Perform streaming liveness check by sending frames one at a time
|
|
333
|
+
*
|
|
334
|
+
* Each frame is sent separately to the streaming endpoint with individual retry.
|
|
335
|
+
* The last frame that completes the set will return the full liveness result.
|
|
336
|
+
*
|
|
337
|
+
* @param frames - Captured frames to analyze
|
|
338
|
+
* @param options - Additional options
|
|
339
|
+
* @param callbacks - Optional callbacks for progress tracking
|
|
340
|
+
* @returns Liveness result
|
|
341
|
+
*/
|
|
342
|
+
async fastCheckStream(frames, options = {}, callbacks) {
|
|
343
|
+
const sessionId = options.sessionId ?? generateSessionId();
|
|
344
|
+
const model = options.model ?? "10";
|
|
345
|
+
const source = options.source ?? "live";
|
|
346
|
+
const frameDataList = toFrameData(frames);
|
|
347
|
+
const total = frameDataList.length;
|
|
348
|
+
let finalResponse = null;
|
|
349
|
+
const sendPromises = frameDataList.map(async (frameData, index) => {
|
|
350
|
+
const response = await this.sendStreamFrameInternal(frameData, {
|
|
351
|
+
sessionId,
|
|
352
|
+
model,
|
|
353
|
+
source
|
|
354
|
+
});
|
|
355
|
+
callbacks?.onFrameSent?.(index + 1, total);
|
|
356
|
+
if (response.status === "buffering") {
|
|
357
|
+
callbacks?.onFrameBuffered?.(response.frames_received, response.frames_required);
|
|
358
|
+
}
|
|
359
|
+
return response;
|
|
360
|
+
});
|
|
361
|
+
const responses = await Promise.all(sendPromises);
|
|
362
|
+
finalResponse = responses.find((r) => r.status === "complete") ?? null;
|
|
363
|
+
if (!finalResponse) {
|
|
364
|
+
const lastResponse = responses[responses.length - 1];
|
|
365
|
+
if (lastResponse) {
|
|
366
|
+
throw new LivenessApiError(
|
|
367
|
+
`Streaming incomplete: received ${lastResponse.frames_received}/${lastResponse.frames_required} frames`,
|
|
368
|
+
"stream_incomplete",
|
|
369
|
+
400
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
throw new LivenessApiError(
|
|
373
|
+
"Streaming failed: no responses received",
|
|
374
|
+
"stream_incomplete",
|
|
375
|
+
400
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return toLivenessResultFromStream(finalResponse);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Perform streaming liveness check by sending frames sequentially
|
|
382
|
+
*
|
|
383
|
+
* Alternative to fastCheckStream that sends frames one by one in order.
|
|
384
|
+
* Useful when parallel uploads are not desired or network is constrained.
|
|
385
|
+
*
|
|
386
|
+
* @param frames - Captured frames to analyze
|
|
387
|
+
* @param options - Additional options
|
|
388
|
+
* @param callbacks - Optional callbacks for progress tracking
|
|
389
|
+
* @returns Liveness result
|
|
390
|
+
*/
|
|
391
|
+
async fastCheckStreamSequential(frames, options = {}, callbacks) {
|
|
392
|
+
const sessionId = options.sessionId ?? generateSessionId();
|
|
393
|
+
const model = options.model ?? "10";
|
|
394
|
+
const source = options.source ?? "live";
|
|
395
|
+
const frameDataList = toFrameData(frames);
|
|
396
|
+
const total = frameDataList.length;
|
|
397
|
+
let finalResponse = null;
|
|
398
|
+
for (const frameData of frameDataList) {
|
|
399
|
+
const response = await this.sendStreamFrameInternal(frameData, {
|
|
400
|
+
sessionId,
|
|
401
|
+
model,
|
|
402
|
+
source
|
|
403
|
+
});
|
|
404
|
+
const currentIndex = frameData.index;
|
|
405
|
+
callbacks?.onFrameSent?.(currentIndex + 1, total);
|
|
406
|
+
if (response.status === "buffering") {
|
|
407
|
+
callbacks?.onFrameBuffered?.(response.frames_received, response.frames_required);
|
|
408
|
+
} else if (response.status === "complete") {
|
|
409
|
+
finalResponse = response;
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
if (!finalResponse) {
|
|
414
|
+
throw new LivenessApiError(
|
|
415
|
+
"Streaming did not complete after all frames sent",
|
|
416
|
+
"stream_incomplete",
|
|
417
|
+
400
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return toLivenessResultFromStream(finalResponse);
|
|
421
|
+
}
|
|
266
422
|
// ===========================================================================
|
|
267
423
|
// Verify Endpoint (Spatial Features)
|
|
268
424
|
// ===========================================================================
|
|
@@ -519,6 +675,7 @@ var FrameQueue = class {
|
|
|
519
675
|
|
|
520
676
|
// src/types/models.ts
|
|
521
677
|
var MODEL_CONFIGS = {
|
|
678
|
+
// Standard fast-check models
|
|
522
679
|
"10": {
|
|
523
680
|
type: "10",
|
|
524
681
|
minFrames: 10,
|
|
@@ -536,6 +693,104 @@ var MODEL_CONFIGS = {
|
|
|
536
693
|
minFrames: 250,
|
|
537
694
|
recommendedFrames: 250,
|
|
538
695
|
description: "High-accuracy model - 250 frames, best accuracy"
|
|
696
|
+
},
|
|
697
|
+
// Hybrid V2 models with physiological features
|
|
698
|
+
"hybrid-v2-10": {
|
|
699
|
+
type: "hybrid-v2-10",
|
|
700
|
+
minFrames: 10,
|
|
701
|
+
recommendedFrames: 10,
|
|
702
|
+
description: "Hybrid V2 10-frame model with physio features"
|
|
703
|
+
},
|
|
704
|
+
"hybrid-v2-30": {
|
|
705
|
+
type: "hybrid-v2-30",
|
|
706
|
+
minFrames: 30,
|
|
707
|
+
recommendedFrames: 30,
|
|
708
|
+
description: "Hybrid V2 30-frame model with physio features"
|
|
709
|
+
},
|
|
710
|
+
"hybrid-v2-50": {
|
|
711
|
+
type: "hybrid-v2-50",
|
|
712
|
+
minFrames: 50,
|
|
713
|
+
recommendedFrames: 50,
|
|
714
|
+
description: "Hybrid V2 50-frame model with physio features"
|
|
715
|
+
},
|
|
716
|
+
"hybrid-v2-60": {
|
|
717
|
+
type: "hybrid-v2-60",
|
|
718
|
+
minFrames: 60,
|
|
719
|
+
recommendedFrames: 60,
|
|
720
|
+
description: "Hybrid V2 60-frame model with physio features"
|
|
721
|
+
},
|
|
722
|
+
"hybrid-v2-90": {
|
|
723
|
+
type: "hybrid-v2-90",
|
|
724
|
+
minFrames: 90,
|
|
725
|
+
recommendedFrames: 90,
|
|
726
|
+
description: "Hybrid V2 90-frame model with physio features"
|
|
727
|
+
},
|
|
728
|
+
"hybrid-v2-100": {
|
|
729
|
+
type: "hybrid-v2-100",
|
|
730
|
+
minFrames: 100,
|
|
731
|
+
recommendedFrames: 100,
|
|
732
|
+
description: "Hybrid V2 100-frame model with physio features"
|
|
733
|
+
},
|
|
734
|
+
"hybrid-v2-125": {
|
|
735
|
+
type: "hybrid-v2-125",
|
|
736
|
+
minFrames: 125,
|
|
737
|
+
recommendedFrames: 125,
|
|
738
|
+
description: "Hybrid V2 125-frame model with physio features"
|
|
739
|
+
},
|
|
740
|
+
"hybrid-v2-150": {
|
|
741
|
+
type: "hybrid-v2-150",
|
|
742
|
+
minFrames: 150,
|
|
743
|
+
recommendedFrames: 150,
|
|
744
|
+
description: "Hybrid V2 150-frame model with physio features"
|
|
745
|
+
},
|
|
746
|
+
"hybrid-v2-250": {
|
|
747
|
+
type: "hybrid-v2-250",
|
|
748
|
+
minFrames: 250,
|
|
749
|
+
recommendedFrames: 250,
|
|
750
|
+
description: "Hybrid V2 250-frame model with physio features"
|
|
751
|
+
},
|
|
752
|
+
// Mixed models (FastLivenessDetector with mixed-trained weights)
|
|
753
|
+
"mixed-10": {
|
|
754
|
+
type: "mixed-10",
|
|
755
|
+
minFrames: 10,
|
|
756
|
+
recommendedFrames: 10,
|
|
757
|
+
description: "Mixed 10-frame model"
|
|
758
|
+
},
|
|
759
|
+
"mixed-30": {
|
|
760
|
+
type: "mixed-30",
|
|
761
|
+
minFrames: 30,
|
|
762
|
+
recommendedFrames: 30,
|
|
763
|
+
description: "Mixed 30-frame model"
|
|
764
|
+
},
|
|
765
|
+
"mixed-60": {
|
|
766
|
+
type: "mixed-60",
|
|
767
|
+
minFrames: 60,
|
|
768
|
+
recommendedFrames: 60,
|
|
769
|
+
description: "Mixed 60-frame model"
|
|
770
|
+
},
|
|
771
|
+
"mixed-90": {
|
|
772
|
+
type: "mixed-90",
|
|
773
|
+
minFrames: 90,
|
|
774
|
+
recommendedFrames: 90,
|
|
775
|
+
description: "Mixed 90-frame model"
|
|
776
|
+
},
|
|
777
|
+
"mixed-120": {
|
|
778
|
+
type: "mixed-120",
|
|
779
|
+
minFrames: 120,
|
|
780
|
+
recommendedFrames: 120,
|
|
781
|
+
description: "Mixed 120-frame model"
|
|
782
|
+
},
|
|
783
|
+
"mixed-150": {
|
|
784
|
+
type: "mixed-150",
|
|
785
|
+
minFrames: 150,
|
|
786
|
+
recommendedFrames: 150,
|
|
787
|
+
description: "Mixed 150-frame model"
|
|
788
|
+
},
|
|
789
|
+
"mixed-250": {
|
|
790
|
+
type: "mixed-250",
|
|
791
|
+
minFrames: 250,
|
|
792
|
+
recommendedFrames: 250,
|
|
793
|
+
description: "Mixed 250-frame model"
|
|
539
794
|
}
|
|
540
795
|
};
|
|
541
796
|
var HYBRID_MODEL_CONFIGS = {
|
|
@@ -561,6 +816,29 @@ function hasEnoughFrames(model, frameCount) {
|
|
|
561
816
|
return frameCount >= MODEL_CONFIGS[model].minFrames;
|
|
562
817
|
}
|
|
563
818
|
|
|
819
|
+
// src/types/detectors.ts
|
|
820
|
+
var DEFAULT_GAZE_THRESHOLDS = {
|
|
821
|
+
maxYaw: 25,
|
|
822
|
+
maxPitch: 20
|
|
823
|
+
};
|
|
824
|
+
var DEFAULT_HAND_OCCLUSION_CONFIG = {
|
|
825
|
+
faceExpansionFactor: 0.5,
|
|
826
|
+
minLandmarksForOcclusion: 3,
|
|
827
|
+
faceBoxExpiryMs: 1e3,
|
|
828
|
+
centerRegion: 0.6
|
|
829
|
+
};
|
|
830
|
+
var DEFAULT_FACE_DETECTION_TIERS = {
|
|
831
|
+
primaryConfidence: 0.7,
|
|
832
|
+
secondaryConfidence: 0.4
|
|
833
|
+
};
|
|
834
|
+
var DEFAULT_STABILIZER_CONFIG = {
|
|
835
|
+
stabilityThreshold: 1.5,
|
|
836
|
+
requiredStableFrames: 15,
|
|
837
|
+
maxWaitMs: 4e3,
|
|
838
|
+
checkIntervalMs: 100,
|
|
839
|
+
sampleSize: 64
|
|
840
|
+
};
|
|
841
|
+
|
|
564
842
|
// src/constants/feedback.ts
|
|
565
843
|
var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
|
|
566
844
|
var ALIGNMENT_THRESHOLD_POOR = 0.5;
|
|
@@ -812,15 +1090,15 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
|
|
|
812
1090
|
var MIN_CAPTURE_ALIGNMENT = 0.6;
|
|
813
1091
|
var HIGH_ALIGNMENT = 0.85;
|
|
814
1092
|
var GOOD_ALIGNMENT = 0.5;
|
|
815
|
-
var IDEAL_CROP_MULTIPLIER =
|
|
816
|
-
var MIN_CROP_MULTIPLIER = 1.
|
|
817
|
-
var MAX_CROP_MULTIPLIER =
|
|
818
|
-
var FACE_CENTER_VERTICAL_OFFSET = 0.
|
|
819
|
-
var MIN_FACE_RATIO = 0.
|
|
1093
|
+
var IDEAL_CROP_MULTIPLIER = 2;
|
|
1094
|
+
var MIN_CROP_MULTIPLIER = 1.8;
|
|
1095
|
+
var MAX_CROP_MULTIPLIER = 2.5;
|
|
1096
|
+
var FACE_CENTER_VERTICAL_OFFSET = 0.05;
|
|
1097
|
+
var MIN_FACE_RATIO = 0.036;
|
|
820
1098
|
var MAX_FACE_RATIO = 0.7;
|
|
821
1099
|
var FACE_CROP_OUTPUT_SIZE = 224;
|
|
822
|
-
var MAX_FACE_PERCENTAGE_IN_CROP = 0.
|
|
823
|
-
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.
|
|
1100
|
+
var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
|
|
1101
|
+
var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
|
|
824
1102
|
function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
|
|
825
1103
|
const laplacian = [];
|
|
826
1104
|
for (let y = 1; y < height - 1; y++) {
|
|
@@ -909,10 +1187,10 @@ function isFaceFullyVisible(boundingBox, frameWidth, frameHeight) {
|
|
|
909
1187
|
var DEFAULT_OVAL_REGION = {
|
|
910
1188
|
centerX: 0.5,
|
|
911
1189
|
centerY: 0.5,
|
|
912
|
-
width: 0.
|
|
913
|
-
//
|
|
914
|
-
height: 0.
|
|
915
|
-
//
|
|
1190
|
+
width: 0.36,
|
|
1191
|
+
// 36% of frame width (+20%)
|
|
1192
|
+
height: 0.48
|
|
1193
|
+
// 36% * (4/3) = 48% of frame height (+20%)
|
|
916
1194
|
};
|
|
917
1195
|
function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
|
|
918
1196
|
const faceCenterX = (faceBox.originX + faceBox.width / 2) / frameWidth;
|
|
@@ -1101,6 +1379,37 @@ var BaseFrameCollector = class {
|
|
|
1101
1379
|
return this.frames.length;
|
|
1102
1380
|
}
|
|
1103
1381
|
};
|
|
1382
|
+
|
|
1383
|
+
// src/utils/landmarkValidator.ts
|
|
1384
|
+
var LANDMARK_INDEX = {
|
|
1385
|
+
/** Nose tip */
|
|
1386
|
+
NOSE_TIP: 1,
|
|
1387
|
+
/** Upper lip center */
|
|
1388
|
+
UPPER_LIP: 13,
|
|
1389
|
+
/** Lower lip center */
|
|
1390
|
+
LOWER_LIP: 14
|
|
1391
|
+
};
|
|
1392
|
+
var LANDMARK_MIN_BOUND = 0.1;
|
|
1393
|
+
var LANDMARK_MAX_BOUND = 0.9;
|
|
1394
|
+
var MIN_LANDMARK_COUNT = 15;
|
|
1395
|
+
function isInBounds(x, y) {
|
|
1396
|
+
return x >= LANDMARK_MIN_BOUND && x <= LANDMARK_MAX_BOUND && y >= LANDMARK_MIN_BOUND && y <= LANDMARK_MAX_BOUND;
|
|
1397
|
+
}
|
|
1398
|
+
function validateFaceLandmarks(landmarks) {
|
|
1399
|
+
if (!landmarks || landmarks.length < MIN_LANDMARK_COUNT) {
|
|
1400
|
+
return { valid: false, message: "Move closer to the camera" };
|
|
1401
|
+
}
|
|
1402
|
+
const noseTip = landmarks[LANDMARK_INDEX.NOSE_TIP];
|
|
1403
|
+
const upperLip = landmarks[LANDMARK_INDEX.UPPER_LIP];
|
|
1404
|
+
const lowerLip = landmarks[LANDMARK_INDEX.LOWER_LIP];
|
|
1405
|
+
if (!noseTip || !isInBounds(noseTip.x, noseTip.y)) {
|
|
1406
|
+
return { valid: false, message: "Make sure your full face is visible" };
|
|
1407
|
+
}
|
|
1408
|
+
if (!upperLip || !lowerLip || !isInBounds(upperLip.x, upperLip.y) || !isInBounds(lowerLip.x, lowerLip.y)) {
|
|
1409
|
+
return { valid: false, message: "Don't cover your mouth" };
|
|
1410
|
+
}
|
|
1411
|
+
return { valid: true };
|
|
1412
|
+
}
|
|
1104
1413
|
export {
|
|
1105
1414
|
ALIGNMENT_THRESHOLD_CAPTURE,
|
|
1106
1415
|
ALIGNMENT_THRESHOLD_GOOD,
|
|
@@ -1114,9 +1423,13 @@ export {
|
|
|
1114
1423
|
BaseFrameCollector,
|
|
1115
1424
|
DEFAULT_BLUR_THRESHOLD,
|
|
1116
1425
|
DEFAULT_ENDPOINT,
|
|
1426
|
+
DEFAULT_FACE_DETECTION_TIERS,
|
|
1427
|
+
DEFAULT_GAZE_THRESHOLDS,
|
|
1428
|
+
DEFAULT_HAND_OCCLUSION_CONFIG,
|
|
1117
1429
|
DEFAULT_LIVENESS_CONFIG,
|
|
1118
1430
|
DEFAULT_LOCALE,
|
|
1119
1431
|
DEFAULT_OVAL_REGION,
|
|
1432
|
+
DEFAULT_STABILIZER_CONFIG,
|
|
1120
1433
|
DEFAULT_STATUS_MESSAGES,
|
|
1121
1434
|
ES_LOCALE,
|
|
1122
1435
|
FACE_CENTER_VERTICAL_OFFSET,
|
|
@@ -1130,6 +1443,9 @@ export {
|
|
|
1130
1443
|
HIGH_ALIGNMENT,
|
|
1131
1444
|
HYBRID_MODEL_CONFIGS,
|
|
1132
1445
|
IDEAL_CROP_MULTIPLIER,
|
|
1446
|
+
LANDMARK_INDEX,
|
|
1447
|
+
LANDMARK_MAX_BOUND,
|
|
1448
|
+
LANDMARK_MIN_BOUND,
|
|
1133
1449
|
LOW_LIGHT_THRESHOLD,
|
|
1134
1450
|
LivenessApiError,
|
|
1135
1451
|
LivenessClient,
|
|
@@ -1142,6 +1458,7 @@ export {
|
|
|
1142
1458
|
MIN_FACE_RATIO,
|
|
1143
1459
|
MIN_FACE_SIDE_MARGIN,
|
|
1144
1460
|
MIN_FACE_TOP_MARGIN,
|
|
1461
|
+
MIN_LANDMARK_COUNT,
|
|
1145
1462
|
MODEL_CONFIGS,
|
|
1146
1463
|
OVAL_GUIDE_COLORS,
|
|
1147
1464
|
OVAL_GUIDE_STYLES,
|
|
@@ -1173,7 +1490,9 @@ export {
|
|
|
1173
1490
|
toFrameData,
|
|
1174
1491
|
toHybridFrameData,
|
|
1175
1492
|
toLivenessResult,
|
|
1493
|
+
toLivenessResultFromStream,
|
|
1176
1494
|
validateApiKey,
|
|
1495
|
+
validateFaceLandmarks,
|
|
1177
1496
|
validateFrameCount,
|
|
1178
1497
|
validateFrameData,
|
|
1179
1498
|
validateFrameIndex,
|