@mikashboks/capture-intelligence-core 0.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/cjs/__fixtures__/address-flow.fixtures.d.ts +35 -0
- package/dist/cjs/__fixtures__/address-flow.fixtures.d.ts.map +1 -0
- package/dist/cjs/__fixtures__/address-flow.fixtures.js +186 -0
- package/dist/cjs/__fixtures__/address-flow.fixtures.js.map +1 -0
- package/dist/cjs/__fixtures__/metadata-review.fixtures.d.ts +11 -0
- package/dist/cjs/__fixtures__/metadata-review.fixtures.d.ts.map +1 -0
- package/dist/cjs/__fixtures__/metadata-review.fixtures.js +69 -0
- package/dist/cjs/__fixtures__/metadata-review.fixtures.js.map +1 -0
- package/dist/cjs/address-proof.d.ts +33 -0
- package/dist/cjs/address-proof.d.ts.map +1 -0
- package/dist/cjs/address-proof.js +97 -0
- package/dist/cjs/address-proof.js.map +1 -0
- package/dist/cjs/document-classifier.d.ts +19 -0
- package/dist/cjs/document-classifier.d.ts.map +1 -0
- package/dist/cjs/document-classifier.js +213 -0
- package/dist/cjs/document-classifier.js.map +1 -0
- package/dist/cjs/flow-routing.d.ts +7 -0
- package/dist/cjs/flow-routing.d.ts.map +1 -0
- package/dist/cjs/flow-routing.js +45 -0
- package/dist/cjs/flow-routing.js.map +1 -0
- package/dist/cjs/index.d.ts +25 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +83 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/metadata-review.d.ts +10 -0
- package/dist/cjs/metadata-review.d.ts.map +1 -0
- package/dist/cjs/metadata-review.js +93 -0
- package/dist/cjs/metadata-review.js.map +1 -0
- package/dist/cjs/metrics.d.ts +115 -0
- package/dist/cjs/metrics.d.ts.map +1 -0
- package/dist/cjs/metrics.js +302 -0
- package/dist/cjs/metrics.js.map +1 -0
- package/dist/cjs/quality-checks.d.ts +170 -0
- package/dist/cjs/quality-checks.d.ts.map +1 -0
- package/dist/cjs/quality-checks.js +417 -0
- package/dist/cjs/quality-checks.js.map +1 -0
- package/dist/cjs/sobel.d.ts +61 -0
- package/dist/cjs/sobel.d.ts.map +1 -0
- package/dist/cjs/sobel.js +179 -0
- package/dist/cjs/sobel.js.map +1 -0
- package/dist/cjs/types.d.ts +98 -0
- package/dist/cjs/types.d.ts.map +1 -0
- package/dist/cjs/types.js +38 -0
- package/dist/cjs/types.js.map +1 -0
- package/dist/esm/__fixtures__/address-flow.fixtures.d.ts +35 -0
- package/dist/esm/__fixtures__/address-flow.fixtures.d.ts.map +1 -0
- package/dist/esm/__fixtures__/address-flow.fixtures.js +183 -0
- package/dist/esm/__fixtures__/address-flow.fixtures.js.map +1 -0
- package/dist/esm/__fixtures__/metadata-review.fixtures.d.ts +11 -0
- package/dist/esm/__fixtures__/metadata-review.fixtures.d.ts.map +1 -0
- package/dist/esm/__fixtures__/metadata-review.fixtures.js +66 -0
- package/dist/esm/__fixtures__/metadata-review.fixtures.js.map +1 -0
- package/dist/esm/address-proof.d.ts +33 -0
- package/dist/esm/address-proof.d.ts.map +1 -0
- package/dist/esm/address-proof.js +86 -0
- package/dist/esm/address-proof.js.map +1 -0
- package/dist/esm/document-classifier.d.ts +19 -0
- package/dist/esm/document-classifier.d.ts.map +1 -0
- package/dist/esm/document-classifier.js +209 -0
- package/dist/esm/document-classifier.js.map +1 -0
- package/dist/esm/flow-routing.d.ts +7 -0
- package/dist/esm/flow-routing.d.ts.map +1 -0
- package/dist/esm/flow-routing.js +41 -0
- package/dist/esm/flow-routing.js.map +1 -0
- package/dist/esm/index.d.ts +25 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +19 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/metadata-review.d.ts +10 -0
- package/dist/esm/metadata-review.d.ts.map +1 -0
- package/dist/esm/metadata-review.js +90 -0
- package/dist/esm/metadata-review.js.map +1 -0
- package/dist/esm/metrics.d.ts +115 -0
- package/dist/esm/metrics.d.ts.map +1 -0
- package/dist/esm/metrics.js +291 -0
- package/dist/esm/metrics.js.map +1 -0
- package/dist/esm/quality-checks.d.ts +170 -0
- package/dist/esm/quality-checks.d.ts.map +1 -0
- package/dist/esm/quality-checks.js +400 -0
- package/dist/esm/quality-checks.js.map +1 -0
- package/dist/esm/sobel.d.ts +61 -0
- package/dist/esm/sobel.d.ts.map +1 -0
- package/dist/esm/sobel.js +173 -0
- package/dist/esm/sobel.js.map +1 -0
- package/dist/esm/types.d.ts +98 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/dist/esm/types.js +35 -0
- package/dist/esm/types.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quality checks -- threshold comparison logic and failure escalation rules.
|
|
3
|
+
*
|
|
4
|
+
* DOM-free: no HTMLElement, no document.*, no navigator.*. All functions
|
|
5
|
+
* operate on plain data (FrameMetrics, QualityCheckResults, numeric values).
|
|
6
|
+
*
|
|
7
|
+
* The DOM-coupled parts of quality-review.ts (banner updates, button state,
|
|
8
|
+
* shake animations, Svelte store, etc.) remain in the SDK. This module
|
|
9
|
+
* exposes the pure decision logic that can be reused in React Native or
|
|
10
|
+
* Node.js environments.
|
|
11
|
+
*/
|
|
12
|
+
import { CAM_BRIGHTNESS_MIN, CAM_BRIGHTNESS_MAX, DOC_SUBMIT_ANYWAY_FAILURE_THRESHOLD, SELFIE_SUBMIT_ANYWAY_FAILURE_THRESHOLD, ADDRESS_SUBMIT_ANYWAY_FAILURE_THRESHOLD, } from './types.js';
|
|
13
|
+
import { checkDocumentLikelihood } from './document-classifier.js';
|
|
14
|
+
/* -- Quality bucket classification ---------------------------------------- */
|
|
15
|
+
/**
|
|
16
|
+
* Map a step signature string and optional review mode to a quality bucket.
|
|
17
|
+
*/
|
|
18
|
+
export function getQualityBucket(stepSig, reviewMode = 'document') {
|
|
19
|
+
if (!stepSig)
|
|
20
|
+
return '';
|
|
21
|
+
if (reviewMode === 'front-door')
|
|
22
|
+
return 'address_front_door';
|
|
23
|
+
if (reviewMode === 'address-document')
|
|
24
|
+
return 'address_document';
|
|
25
|
+
if (stepSig.indexOf('selfie') !== -1)
|
|
26
|
+
return 'selfie';
|
|
27
|
+
if (stepSig.indexOf('address') !== -1)
|
|
28
|
+
return 'address_document';
|
|
29
|
+
if (stepSig.indexOf('back') !== -1)
|
|
30
|
+
return 'document_back';
|
|
31
|
+
return 'document_front';
|
|
32
|
+
}
|
|
33
|
+
/* -- Submit-anyway threshold lookup --------------------------------------- */
|
|
34
|
+
/**
|
|
35
|
+
* Get the number of consecutive failures required before the "submit anyway"
|
|
36
|
+
* override becomes available for a given step signature.
|
|
37
|
+
*/
|
|
38
|
+
export function getSubmitAnywayThreshold(stepSig, reviewMode) {
|
|
39
|
+
const bucket = getQualityBucket(stepSig, reviewMode);
|
|
40
|
+
if (bucket === 'selfie')
|
|
41
|
+
return SELFIE_SUBMIT_ANYWAY_FAILURE_THRESHOLD;
|
|
42
|
+
if (bucket === 'document_front' || bucket === 'document_back') {
|
|
43
|
+
return DOC_SUBMIT_ANYWAY_FAILURE_THRESHOLD;
|
|
44
|
+
}
|
|
45
|
+
if (bucket === 'address_document' || bucket === 'address_front_door') {
|
|
46
|
+
return ADDRESS_SUBMIT_ANYWAY_FAILURE_THRESHOLD;
|
|
47
|
+
}
|
|
48
|
+
return Number.POSITIVE_INFINITY;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create a fresh failure state.
|
|
52
|
+
*/
|
|
53
|
+
export function createQualityFailureState() {
|
|
54
|
+
return {
|
|
55
|
+
document_front: 0,
|
|
56
|
+
document_back: 0,
|
|
57
|
+
selfie: 0,
|
|
58
|
+
address_document: 0,
|
|
59
|
+
address_front_door: 0,
|
|
60
|
+
lastFailedSignature: '',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Increment the failure counter for a bucket if this is a new failure
|
|
65
|
+
* (not a re-render of the same failed review).
|
|
66
|
+
*
|
|
67
|
+
* @param state - Mutable failure state
|
|
68
|
+
* @param stepSig - Step signature string
|
|
69
|
+
* @param failSignature - Unique signature for this failure (e.g. bucket + image src)
|
|
70
|
+
* @param anyFail - Whether any quality check actually failed
|
|
71
|
+
* @returns true if the counter was incremented
|
|
72
|
+
*/
|
|
73
|
+
export function countFailedReview(state, stepSig, failSignature, anyFail, reviewMode) {
|
|
74
|
+
if (!anyFail)
|
|
75
|
+
return false;
|
|
76
|
+
const bucket = getQualityBucket(stepSig, reviewMode);
|
|
77
|
+
if (!bucket)
|
|
78
|
+
return false;
|
|
79
|
+
if (!failSignature || failSignature === state.lastFailedSignature)
|
|
80
|
+
return false;
|
|
81
|
+
const current = getFailureCount(state, bucket);
|
|
82
|
+
setFailureCount(state, bucket, current + 1);
|
|
83
|
+
state.lastFailedSignature = failSignature;
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check whether the submit-anyway override should be available.
|
|
88
|
+
*/
|
|
89
|
+
export function shouldShowSubmitAnyway(state, stepSig, reviewMode) {
|
|
90
|
+
const bucket = getQualityBucket(stepSig, reviewMode);
|
|
91
|
+
if (!bucket)
|
|
92
|
+
return false;
|
|
93
|
+
const failCount = getFailureCount(state, bucket);
|
|
94
|
+
return failCount >= getSubmitAnywayThreshold(stepSig, reviewMode);
|
|
95
|
+
}
|
|
96
|
+
/* -- Internal helpers for typed bucket access ------------------------------ */
|
|
97
|
+
function getFailureCount(state, bucket) {
|
|
98
|
+
switch (bucket) {
|
|
99
|
+
case 'document_front': return state.document_front;
|
|
100
|
+
case 'document_back': return state.document_back;
|
|
101
|
+
case 'selfie': return state.selfie;
|
|
102
|
+
case 'address_document': return state.address_document;
|
|
103
|
+
case 'address_front_door': return state.address_front_door;
|
|
104
|
+
default: return 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function setFailureCount(state, bucket, value) {
|
|
108
|
+
switch (bucket) {
|
|
109
|
+
case 'document_front':
|
|
110
|
+
state.document_front = value;
|
|
111
|
+
break;
|
|
112
|
+
case 'document_back':
|
|
113
|
+
state.document_back = value;
|
|
114
|
+
break;
|
|
115
|
+
case 'selfie':
|
|
116
|
+
state.selfie = value;
|
|
117
|
+
break;
|
|
118
|
+
case 'address_document':
|
|
119
|
+
state.address_document = value;
|
|
120
|
+
break;
|
|
121
|
+
case 'address_front_door':
|
|
122
|
+
state.address_front_door = value;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Run quality checks on a static document image. Returns the check results
|
|
128
|
+
* and a human-readable failure message.
|
|
129
|
+
*
|
|
130
|
+
* This is the pure-logic extraction from `runQualityCheck()`. The caller
|
|
131
|
+
* handles image loading, Canvas rasterization, and UI updates.
|
|
132
|
+
*/
|
|
133
|
+
export function runDocumentQualityChecks(input) {
|
|
134
|
+
const checks = {};
|
|
135
|
+
const brightnessOk = input.avgBrightness >= CAM_BRIGHTNESS_MIN && input.avgBrightness <= CAM_BRIGHTNESS_MAX;
|
|
136
|
+
const sharpnessOk = input.laplacianVariance >= 60;
|
|
137
|
+
if (input.isAddressProof) {
|
|
138
|
+
checks['address_readable'] = sharpnessOk && brightnessOk;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const textReadableOk = sharpnessOk && input.cornersOk !== false;
|
|
142
|
+
checks['text_readable'] = textReadableOk;
|
|
143
|
+
checks['good_lighting'] = brightnessOk;
|
|
144
|
+
if (input.cornersOk === true) {
|
|
145
|
+
checks['corners_visible'] = true;
|
|
146
|
+
}
|
|
147
|
+
else if (input.cornersOk === false) {
|
|
148
|
+
checks['corners_visible'] = false;
|
|
149
|
+
}
|
|
150
|
+
// Canvas heuristic: does this look like the submitted document type?
|
|
151
|
+
const docResult = checkDocumentLikelihood(input.grey, input.imgData, input.w, input.h, input.documentType);
|
|
152
|
+
const docLooksValid = docResult.isLikelyDoc && input.cornersOk !== false && textReadableOk;
|
|
153
|
+
checks['content_valid'] = docLooksValid;
|
|
154
|
+
// Surface glare detection
|
|
155
|
+
if (docResult.glareDetected) {
|
|
156
|
+
checks['glare_free'] = false;
|
|
157
|
+
}
|
|
158
|
+
if (!docResult.isLikelyDoc) {
|
|
159
|
+
checks['text_readable'] = false;
|
|
160
|
+
}
|
|
161
|
+
else if (input.cornersOk === false) {
|
|
162
|
+
checks['text_readable'] = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Determine the prioritised failure message
|
|
166
|
+
const { anyFail, failMessage } = evaluateDocumentFailure(checks, input.isAddressProof);
|
|
167
|
+
return { checks, anyFail, failMessage };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Run basic selfie quality checks (lighting + sharpness, before face
|
|
171
|
+
* detection). Returns the `good_lighting` check result.
|
|
172
|
+
*/
|
|
173
|
+
export function runSelfieQualityChecks(input) {
|
|
174
|
+
const checks = {};
|
|
175
|
+
const sharpnessOk = input.laplacianVariance >= 50;
|
|
176
|
+
const brightnessOk = input.avgBrightness >= 35 && input.avgBrightness <= 240;
|
|
177
|
+
checks['good_lighting'] = brightnessOk && sharpnessOk;
|
|
178
|
+
return checks;
|
|
179
|
+
}
|
|
180
|
+
/* -- Face quality checks -------------------------------------------------- */
|
|
181
|
+
/**
|
|
182
|
+
* Check face position and size relative to image dimensions.
|
|
183
|
+
* Returns an array of warning strings (empty = face is OK).
|
|
184
|
+
*
|
|
185
|
+
* @param faces - Detected faces
|
|
186
|
+
* @param imageWidth - Full image width
|
|
187
|
+
* @param imageHeight - Full image height
|
|
188
|
+
*/
|
|
189
|
+
export function checkFaceInImage(faces, imageWidth, imageHeight) {
|
|
190
|
+
const warnings = [];
|
|
191
|
+
if (!faces || faces.length === 0) {
|
|
192
|
+
warnings.push('No face found. Try retaking.');
|
|
193
|
+
return warnings;
|
|
194
|
+
}
|
|
195
|
+
const face = faces[0];
|
|
196
|
+
const box = face.boundingBox;
|
|
197
|
+
const faceArea = box.width * box.height;
|
|
198
|
+
const imageArea = imageWidth * imageHeight;
|
|
199
|
+
const faceRatio = faceArea / imageArea;
|
|
200
|
+
if (faceRatio < 0.03) {
|
|
201
|
+
warnings.push('Face too small. Hold phone closer.');
|
|
202
|
+
}
|
|
203
|
+
const faceCenterX = box.x + box.width / 2;
|
|
204
|
+
const faceCenterY = box.y + box.height / 2;
|
|
205
|
+
const imageCenterX = imageWidth / 2;
|
|
206
|
+
const imageCenterY = imageHeight / 2;
|
|
207
|
+
const xTolerance = imageWidth * 0.25;
|
|
208
|
+
const yTolerance = imageHeight * 0.25;
|
|
209
|
+
if (Math.abs(faceCenterX - imageCenterX) >= xTolerance ||
|
|
210
|
+
Math.abs(faceCenterY - imageCenterY) >= yTolerance) {
|
|
211
|
+
warnings.push('Face is off-center. Try retaking.');
|
|
212
|
+
}
|
|
213
|
+
return warnings;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Check whether a detected face is well-centered and appropriately sized
|
|
217
|
+
* for live capture (stricter than static image checks).
|
|
218
|
+
*
|
|
219
|
+
* @param face - Detected face
|
|
220
|
+
* @param videoWidth - Video frame width
|
|
221
|
+
* @param videoHeight - Video frame height
|
|
222
|
+
*/
|
|
223
|
+
export function isFaceCentered(face, videoWidth, videoHeight) {
|
|
224
|
+
const box = face.boundingBox;
|
|
225
|
+
const faceCenterX = box.x + box.width / 2;
|
|
226
|
+
const faceCenterY = box.y + box.height / 2;
|
|
227
|
+
const videoCenterX = videoWidth / 2;
|
|
228
|
+
const videoCenterY = videoHeight / 2;
|
|
229
|
+
const xTolerance = videoWidth * 0.2;
|
|
230
|
+
const yTolerance = videoHeight * 0.2;
|
|
231
|
+
const centeredX = Math.abs(faceCenterX - videoCenterX) < xTolerance;
|
|
232
|
+
const centeredY = Math.abs(faceCenterY - videoCenterY) < yTolerance;
|
|
233
|
+
const faceArea = box.width * box.height;
|
|
234
|
+
const videoArea = videoWidth * videoHeight;
|
|
235
|
+
const faceRatio = faceArea / videoArea;
|
|
236
|
+
const goodSize = faceRatio > 0.025 && faceRatio < 0.45;
|
|
237
|
+
return centeredX && centeredY && goodSize;
|
|
238
|
+
}
|
|
239
|
+
/* -- Failure evaluation helpers ------------------------------------------- */
|
|
240
|
+
/**
|
|
241
|
+
* Evaluate document quality check results and produce a prioritised failure
|
|
242
|
+
* message. Handles address-proof and ID-document modes.
|
|
243
|
+
*/
|
|
244
|
+
export function evaluateDocumentFailure(checks, isAddressProof) {
|
|
245
|
+
let anyFail = false;
|
|
246
|
+
let failMessage = '';
|
|
247
|
+
if (isAddressProof) {
|
|
248
|
+
if (checks.hasOwnProperty('address_readable') && !checks['address_readable']) {
|
|
249
|
+
anyFail = true;
|
|
250
|
+
failMessage = 'Can\u2019t read the text. Try again in better light.';
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
if (checks.hasOwnProperty('corners_visible') && !checks['corners_visible']) {
|
|
255
|
+
anyFail = true;
|
|
256
|
+
failMessage = 'Part of the ID is cut off. Show all 4 corners.';
|
|
257
|
+
}
|
|
258
|
+
else if (checks.hasOwnProperty('content_valid') && !checks['content_valid']) {
|
|
259
|
+
anyFail = true;
|
|
260
|
+
failMessage = 'Doesn\u2019t look like an ID. Show the full document and try again.';
|
|
261
|
+
}
|
|
262
|
+
else if (checks.hasOwnProperty('good_lighting') && !checks['good_lighting']) {
|
|
263
|
+
anyFail = true;
|
|
264
|
+
failMessage = 'Too dark or blurry. Try again in better light.';
|
|
265
|
+
}
|
|
266
|
+
else if (checks.hasOwnProperty('text_readable') && !checks['text_readable']) {
|
|
267
|
+
anyFail = true;
|
|
268
|
+
failMessage = 'Text is hard to read. Hold steady and try again.';
|
|
269
|
+
}
|
|
270
|
+
else if (checks.hasOwnProperty('glare_free') && !checks['glare_free']) {
|
|
271
|
+
anyFail = true;
|
|
272
|
+
failMessage = 'Glare on the ID. Tilt away from light and try again.';
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return { anyFail, failMessage };
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Evaluate selfie quality check results and produce a prioritised failure
|
|
279
|
+
* message.
|
|
280
|
+
*/
|
|
281
|
+
export function evaluateSelfieFailure(checks) {
|
|
282
|
+
let anyFail = false;
|
|
283
|
+
let failMessage = '';
|
|
284
|
+
if (checks.hasOwnProperty('face_visible') && !checks['face_visible']) {
|
|
285
|
+
anyFail = true;
|
|
286
|
+
failMessage = 'Can\u2019t see your face. Center it and try again.';
|
|
287
|
+
}
|
|
288
|
+
else if (checks.hasOwnProperty('good_lighting') && !checks['good_lighting']) {
|
|
289
|
+
anyFail = true;
|
|
290
|
+
failMessage = 'Too dark. Move to better light and try again.';
|
|
291
|
+
}
|
|
292
|
+
return { anyFail, failMessage };
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Evaluate quality failures for a specific review mode using the same
|
|
296
|
+
* prioritization rules as the SDK review banner.
|
|
297
|
+
*/
|
|
298
|
+
export function evaluateReviewFailure(checks, options) {
|
|
299
|
+
if (options.reviewMode === 'selfie') {
|
|
300
|
+
return evaluateSelfieFailure(checks);
|
|
301
|
+
}
|
|
302
|
+
if (options.reviewMode === 'front-door') {
|
|
303
|
+
if (checks.hasOwnProperty('front_door_clear') && !checks['front_door_clear']) {
|
|
304
|
+
return {
|
|
305
|
+
anyFail: true,
|
|
306
|
+
failMessage: options.failMessage || 'Too dark or blurry. Retake the front of your house.',
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
return { anyFail: false, failMessage: '' };
|
|
310
|
+
}
|
|
311
|
+
if (options.reviewMode === 'address-document') {
|
|
312
|
+
return evaluateDocumentFailure(checks, true);
|
|
313
|
+
}
|
|
314
|
+
return evaluateDocumentFailure(checks, false);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Determine whether the "next" button should be disabled based on the
|
|
318
|
+
* current quality check state.
|
|
319
|
+
*
|
|
320
|
+
* @param checks - Current quality check results
|
|
321
|
+
* @param isSelfie - Whether this is a selfie review
|
|
322
|
+
* @param isAddressProof - Whether this is an address proof review
|
|
323
|
+
* @returns Disabled reason string (empty = should be enabled)
|
|
324
|
+
*/
|
|
325
|
+
export function getNextButtonDisabledReason(checks, isSelfieOrOptions, isAddressProof = false) {
|
|
326
|
+
const options = typeof isSelfieOrOptions === 'boolean'
|
|
327
|
+
? {
|
|
328
|
+
reviewMode: isSelfieOrOptions ? 'selfie' : isAddressProof ? 'address-document' : 'document',
|
|
329
|
+
}
|
|
330
|
+
: isSelfieOrOptions;
|
|
331
|
+
if (options.reviewMode === 'selfie') {
|
|
332
|
+
if (!checks.hasOwnProperty('face_visible')) {
|
|
333
|
+
return 'Reviewing selfie...';
|
|
334
|
+
}
|
|
335
|
+
if (!checks['face_visible']) {
|
|
336
|
+
return 'Please retake this selfie to continue';
|
|
337
|
+
}
|
|
338
|
+
if (checks.hasOwnProperty('good_lighting') && !checks['good_lighting']) {
|
|
339
|
+
return 'Please retake this selfie to continue';
|
|
340
|
+
}
|
|
341
|
+
return '';
|
|
342
|
+
}
|
|
343
|
+
if (options.reviewMode === 'front-door') {
|
|
344
|
+
const pendingMessage = options.pendingMessage || 'Reviewing photo...';
|
|
345
|
+
const failMessage = options.failMessage || 'Please retake this photo to continue';
|
|
346
|
+
if (!checks.hasOwnProperty('front_door_clear')) {
|
|
347
|
+
return pendingMessage;
|
|
348
|
+
}
|
|
349
|
+
if (!checks['front_door_clear']) {
|
|
350
|
+
return failMessage;
|
|
351
|
+
}
|
|
352
|
+
return '';
|
|
353
|
+
}
|
|
354
|
+
if (options.reviewMode === 'address-document') {
|
|
355
|
+
const pendingMessage = options.pendingMessage || 'Reviewing photo...';
|
|
356
|
+
const failMessage = options.failMessage || 'Please retake this photo to continue';
|
|
357
|
+
if (!checks.hasOwnProperty('address_readable')) {
|
|
358
|
+
return pendingMessage;
|
|
359
|
+
}
|
|
360
|
+
if (!checks['address_readable']) {
|
|
361
|
+
return failMessage;
|
|
362
|
+
}
|
|
363
|
+
return '';
|
|
364
|
+
}
|
|
365
|
+
const requiredDocChecks = ['content_valid', 'text_readable', 'good_lighting'];
|
|
366
|
+
for (let ci = 0; ci < requiredDocChecks.length; ci++) {
|
|
367
|
+
if (!checks.hasOwnProperty(requiredDocChecks[ci])) {
|
|
368
|
+
return 'Reviewing photo...';
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
for (let ri = 0; ri < requiredDocChecks.length; ri++) {
|
|
372
|
+
if (!checks[requiredDocChecks[ri]]) {
|
|
373
|
+
return 'Please retake this photo to continue';
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (checks.hasOwnProperty('corners_visible') && !checks['corners_visible']) {
|
|
377
|
+
return 'Please retake this photo to continue';
|
|
378
|
+
}
|
|
379
|
+
return '';
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get the override button text for the submit-anyway flow.
|
|
383
|
+
*/
|
|
384
|
+
export function getOverrideButtonText(stepSig) {
|
|
385
|
+
const bucket = getQualityBucket(stepSig);
|
|
386
|
+
return bucket === 'selfie' ? 'Continue With This Selfie' : 'Continue With This Photo';
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Get the override banner message based on failure count and bucket.
|
|
390
|
+
*/
|
|
391
|
+
export function getOverrideBannerMessage(stepSig, failCount, reviewMode) {
|
|
392
|
+
const bucket = getQualityBucket(stepSig, reviewMode);
|
|
393
|
+
if (failCount >= 5) {
|
|
394
|
+
return 'Having trouble? Continue and our team will check it.';
|
|
395
|
+
}
|
|
396
|
+
return bucket === 'selfie'
|
|
397
|
+
? 'Selfie is unclear. You can continue, but a better photo speeds things up.'
|
|
398
|
+
: 'Photo is unclear. You can continue, but a better photo speeds things up.';
|
|
399
|
+
}
|
|
400
|
+
//# sourceMappingURL=quality-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality-checks.js","sourceRoot":"","sources":["../../src/quality-checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sobel edge detection on raw pixel data.
|
|
3
|
+
*
|
|
4
|
+
* DOM-free: operates on Uint8Array greyscale buffers. No Canvas, no
|
|
5
|
+
* HTMLImageElement. The caller is responsible for rasterising / downscaling
|
|
6
|
+
* the image and converting to greyscale before calling these functions.
|
|
7
|
+
*/
|
|
8
|
+
import type { DocumentCropResult } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Convert an RGBA pixel buffer to greyscale using fixed-point integer math.
|
|
11
|
+
* Uses ITU-R BT.601 approximate weights (77/150/29 out of 256).
|
|
12
|
+
*
|
|
13
|
+
* @param rgba - RGBA pixel buffer (length = width * height * 4)
|
|
14
|
+
* @param pixelCount - Total number of pixels (width * height)
|
|
15
|
+
* @returns Uint8Array of greyscale values, one byte per pixel
|
|
16
|
+
*/
|
|
17
|
+
export declare function rgbaToGreyscale(rgba: Uint8ClampedArray | Uint8Array, pixelCount: number): Uint8Array;
|
|
18
|
+
/**
|
|
19
|
+
* Convert an RGBA pixel buffer to greyscale using the pico.js weighting
|
|
20
|
+
* scheme (2/7/1 out of 10). Used for face-detection compatibility.
|
|
21
|
+
*
|
|
22
|
+
* @param rgba - RGBA pixel buffer
|
|
23
|
+
* @param nrows - Number of rows (height)
|
|
24
|
+
* @param ncols - Number of columns (width)
|
|
25
|
+
* @returns Uint8Array of greyscale values
|
|
26
|
+
*/
|
|
27
|
+
export declare function rgbaToGreyscalePico(rgba: Uint8ClampedArray | Uint8Array, nrows: number, ncols: number): Uint8Array;
|
|
28
|
+
/**
|
|
29
|
+
* Row and column Sobel edge energy profiles for a greyscale image.
|
|
30
|
+
*/
|
|
31
|
+
export interface EdgeEnergyProfile {
|
|
32
|
+
/** Per-row accumulated Sobel magnitude */
|
|
33
|
+
rowEnergy: Float32Array;
|
|
34
|
+
/** Per-column accumulated Sobel magnitude */
|
|
35
|
+
colEnergy: Float32Array;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Compute Sobel edge energy profiles (per-row and per-column) for a
|
|
39
|
+
* greyscale image buffer. Used for document boundary detection.
|
|
40
|
+
*
|
|
41
|
+
* @param grey - Greyscale pixel array (w * h, one byte per pixel)
|
|
42
|
+
* @param w - Image width
|
|
43
|
+
* @param h - Image height
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeEdgeEnergyProfile(grey: Uint8Array, w: number, h: number): EdgeEnergyProfile;
|
|
46
|
+
/**
|
|
47
|
+
* Detect the crop region of a document within a greyscale image using
|
|
48
|
+
* Sobel edge energy profiling. Returns the crop rectangle in the original
|
|
49
|
+
* (pre-scale) coordinate space.
|
|
50
|
+
*
|
|
51
|
+
* This is the DOM-free algorithmic core of `detectDocumentCrop()`. The caller
|
|
52
|
+
* must handle image loading, downscaling, and greyscale conversion.
|
|
53
|
+
*
|
|
54
|
+
* @param grey - Greyscale pixels of the downscaled analysis image
|
|
55
|
+
* @param analysisW - Width of the analysis image
|
|
56
|
+
* @param analysisH - Height of the analysis image
|
|
57
|
+
* @param originalW - Width of the original (full-resolution) image
|
|
58
|
+
* @param originalH - Height of the original (full-resolution) image
|
|
59
|
+
*/
|
|
60
|
+
export declare function detectDocumentCropFromGrey(grey: Uint8Array, analysisW: number, analysisH: number, originalW: number, originalH: number): DocumentCropResult;
|
|
61
|
+
//# sourceMappingURL=sobel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sobel.d.ts","sourceRoot":"","sources":["../../src/sobel.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAY,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAI/D;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,UAAU,CAOpG;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,iBAAiB,GAAG,UAAU,EACpC,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACZ,UAAU,CAUZ;AAID;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,0CAA0C;IAC1C,SAAS,EAAE,YAAY,CAAC;IACxB,6CAA6C;IAC7C,SAAS,EAAE,YAAY,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,UAAU,EAChB,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,GACR,iBAAiB,CA2BnB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,kBAAkB,CAwFpB"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sobel edge detection on raw pixel data.
|
|
3
|
+
*
|
|
4
|
+
* DOM-free: operates on Uint8Array greyscale buffers. No Canvas, no
|
|
5
|
+
* HTMLImageElement. The caller is responsible for rasterising / downscaling
|
|
6
|
+
* the image and converting to greyscale before calling these functions.
|
|
7
|
+
*/
|
|
8
|
+
/* -- Greyscale conversion helpers ----------------------------------------- */
|
|
9
|
+
/**
|
|
10
|
+
* Convert an RGBA pixel buffer to greyscale using fixed-point integer math.
|
|
11
|
+
* Uses ITU-R BT.601 approximate weights (77/150/29 out of 256).
|
|
12
|
+
*
|
|
13
|
+
* @param rgba - RGBA pixel buffer (length = width * height * 4)
|
|
14
|
+
* @param pixelCount - Total number of pixels (width * height)
|
|
15
|
+
* @returns Uint8Array of greyscale values, one byte per pixel
|
|
16
|
+
*/
|
|
17
|
+
export function rgbaToGreyscale(rgba, pixelCount) {
|
|
18
|
+
const grey = new Uint8Array(pixelCount);
|
|
19
|
+
for (let i = 0; i < pixelCount; i++) {
|
|
20
|
+
const p = i * 4;
|
|
21
|
+
grey[i] = (rgba[p] * 77 + rgba[p + 1] * 150 + rgba[p + 2] * 29) >> 8;
|
|
22
|
+
}
|
|
23
|
+
return grey;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Convert an RGBA pixel buffer to greyscale using the pico.js weighting
|
|
27
|
+
* scheme (2/7/1 out of 10). Used for face-detection compatibility.
|
|
28
|
+
*
|
|
29
|
+
* @param rgba - RGBA pixel buffer
|
|
30
|
+
* @param nrows - Number of rows (height)
|
|
31
|
+
* @param ncols - Number of columns (width)
|
|
32
|
+
* @returns Uint8Array of greyscale values
|
|
33
|
+
*/
|
|
34
|
+
export function rgbaToGreyscalePico(rgba, nrows, ncols) {
|
|
35
|
+
const gray = new Uint8Array(nrows * ncols);
|
|
36
|
+
for (let r = 0; r < nrows; ++r)
|
|
37
|
+
for (let c = 0; c < ncols; ++c)
|
|
38
|
+
gray[r * ncols + c] =
|
|
39
|
+
(2 * rgba[r * 4 * ncols + 4 * c + 0] +
|
|
40
|
+
7 * rgba[r * 4 * ncols + 4 * c + 1] +
|
|
41
|
+
1 * rgba[r * 4 * ncols + 4 * c + 2]) /
|
|
42
|
+
10;
|
|
43
|
+
return gray;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compute Sobel edge energy profiles (per-row and per-column) for a
|
|
47
|
+
* greyscale image buffer. Used for document boundary detection.
|
|
48
|
+
*
|
|
49
|
+
* @param grey - Greyscale pixel array (w * h, one byte per pixel)
|
|
50
|
+
* @param w - Image width
|
|
51
|
+
* @param h - Image height
|
|
52
|
+
*/
|
|
53
|
+
export function computeEdgeEnergyProfile(grey, w, h) {
|
|
54
|
+
const rowEnergy = new Float32Array(h);
|
|
55
|
+
const colEnergy = new Float32Array(w);
|
|
56
|
+
for (let y = 1; y < h - 1; y++) {
|
|
57
|
+
for (let x = 1; x < w - 1; x++) {
|
|
58
|
+
const gx = -grey[(y - 1) * w + (x - 1)] +
|
|
59
|
+
grey[(y - 1) * w + (x + 1)] -
|
|
60
|
+
2 * grey[y * w + (x - 1)] +
|
|
61
|
+
2 * grey[y * w + (x + 1)] -
|
|
62
|
+
grey[(y + 1) * w + (x - 1)] +
|
|
63
|
+
grey[(y + 1) * w + (x + 1)];
|
|
64
|
+
const gy = -grey[(y - 1) * w + (x - 1)] -
|
|
65
|
+
2 * grey[(y - 1) * w + x] -
|
|
66
|
+
grey[(y - 1) * w + (x + 1)] +
|
|
67
|
+
grey[(y + 1) * w + (x - 1)] +
|
|
68
|
+
2 * grey[(y + 1) * w + x] +
|
|
69
|
+
grey[(y + 1) * w + (x + 1)];
|
|
70
|
+
const mag = Math.abs(gx) + Math.abs(gy);
|
|
71
|
+
rowEnergy[y] += mag;
|
|
72
|
+
colEnergy[x] += mag;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { rowEnergy, colEnergy };
|
|
76
|
+
}
|
|
77
|
+
/* -- Document crop detection ---------------------------------------------- */
|
|
78
|
+
/**
|
|
79
|
+
* Detect the crop region of a document within a greyscale image using
|
|
80
|
+
* Sobel edge energy profiling. Returns the crop rectangle in the original
|
|
81
|
+
* (pre-scale) coordinate space.
|
|
82
|
+
*
|
|
83
|
+
* This is the DOM-free algorithmic core of `detectDocumentCrop()`. The caller
|
|
84
|
+
* must handle image loading, downscaling, and greyscale conversion.
|
|
85
|
+
*
|
|
86
|
+
* @param grey - Greyscale pixels of the downscaled analysis image
|
|
87
|
+
* @param analysisW - Width of the analysis image
|
|
88
|
+
* @param analysisH - Height of the analysis image
|
|
89
|
+
* @param originalW - Width of the original (full-resolution) image
|
|
90
|
+
* @param originalH - Height of the original (full-resolution) image
|
|
91
|
+
*/
|
|
92
|
+
export function detectDocumentCropFromGrey(grey, analysisW, analysisH, originalW, originalH) {
|
|
93
|
+
try {
|
|
94
|
+
if (analysisW <= 0 || analysisH <= 0)
|
|
95
|
+
return { crop: null, cornersOk: null };
|
|
96
|
+
const sc = Math.min(originalW / analysisW, originalH / analysisH);
|
|
97
|
+
const { rowEnergy, colEnergy } = computeEdgeEnergyProfile(grey, analysisW, analysisH);
|
|
98
|
+
// Find max energy for normalisation
|
|
99
|
+
let maxR = 0, maxC = 0;
|
|
100
|
+
for (let y2 = 0; y2 < analysisH; y2++) {
|
|
101
|
+
if (rowEnergy[y2] > maxR)
|
|
102
|
+
maxR = rowEnergy[y2];
|
|
103
|
+
}
|
|
104
|
+
for (let x2 = 0; x2 < analysisW; x2++) {
|
|
105
|
+
if (colEnergy[x2] > maxC)
|
|
106
|
+
maxC = colEnergy[x2];
|
|
107
|
+
}
|
|
108
|
+
if (maxR === 0 || maxC === 0)
|
|
109
|
+
return { crop: null, cornersOk: false };
|
|
110
|
+
// Scan inward from edges to find first row/col with >12% of max energy
|
|
111
|
+
const thr = 0.12;
|
|
112
|
+
const skip = 0.05;
|
|
113
|
+
let top = 0, bot = analysisH - 1, left = 0, right = analysisW - 1;
|
|
114
|
+
for (let yt = Math.round(analysisH * skip); yt < analysisH / 2; yt++) {
|
|
115
|
+
if (rowEnergy[yt] / maxR > thr) {
|
|
116
|
+
top = yt;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (let yb = Math.round(analysisH * (1 - skip)); yb > analysisH / 2; yb--) {
|
|
121
|
+
if (rowEnergy[yb] / maxR > thr) {
|
|
122
|
+
bot = yb;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (let xl = Math.round(analysisW * skip); xl < analysisW / 2; xl++) {
|
|
127
|
+
if (colEnergy[xl] / maxC > thr) {
|
|
128
|
+
left = xl;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (let xr = Math.round(analysisW * (1 - skip)); xr > analysisW / 2; xr--) {
|
|
133
|
+
if (colEnergy[xr] / maxC > thr) {
|
|
134
|
+
right = xr;
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const cw = right - left;
|
|
139
|
+
const ch = bot - top;
|
|
140
|
+
const edgeMargin = Math.round(Math.min(analysisW, analysisH) * 0.08);
|
|
141
|
+
if (cw < analysisW * 0.25 || ch < analysisH * 0.25)
|
|
142
|
+
return { crop: null, cornersOk: false };
|
|
143
|
+
if (cw > analysisW * 0.88 && ch > analysisH * 0.88)
|
|
144
|
+
return { crop: null, cornersOk: false };
|
|
145
|
+
const rawLeft = left;
|
|
146
|
+
const rawTop = top;
|
|
147
|
+
const rawRight = right;
|
|
148
|
+
const rawBot = bot;
|
|
149
|
+
// 3% padding
|
|
150
|
+
const pad = Math.round(Math.min(analysisW, analysisH) * 0.03);
|
|
151
|
+
left = Math.max(0, left - pad);
|
|
152
|
+
top = Math.max(0, top - pad);
|
|
153
|
+
right = Math.min(analysisW - 1, right + pad);
|
|
154
|
+
bot = Math.min(analysisH - 1, bot + pad);
|
|
155
|
+
const cornersConfident = rawLeft > edgeMargin &&
|
|
156
|
+
rawTop > edgeMargin &&
|
|
157
|
+
rawRight < analysisW - 1 - edgeMargin &&
|
|
158
|
+
rawBot < analysisH - 1 - edgeMargin;
|
|
159
|
+
return {
|
|
160
|
+
crop: {
|
|
161
|
+
x: Math.round(left * sc),
|
|
162
|
+
y: Math.round(top * sc),
|
|
163
|
+
w: Math.round((right - left) * sc),
|
|
164
|
+
h: Math.round((bot - top) * sc),
|
|
165
|
+
},
|
|
166
|
+
cornersOk: cornersConfident,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return { crop: null, cornersOk: null };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=sobel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sobel.js","sourceRoot":"","sources":["../../src/sobel.ts"],"names":[],"mappings":"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"}
|