@jaak.ai/stamps 2.0.0-dev.22 → 2.0.0-dev.24
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 +29 -13
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +128 -20
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/my-component/my-component.js +148 -20
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/components/jaak-stamps.js +129 -20
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/jaak-stamps-webcomponent.js +1 -1
- package/dist/esm/jaak-stamps.entry.js +128 -20
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-0521d8f5.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-0521d8f5.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +9 -0
- package/dist/types/components.d.ts +8 -0
- package/package.json +1 -1
- package/dist/jaak-stamps-webcomponent/p-5bdd3e25.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-5bdd3e25.entry.js.map +0 -1
|
@@ -12,6 +12,7 @@ const JaakStamps = class {
|
|
|
12
12
|
debug = false;
|
|
13
13
|
alignmentTolerance = 10; // Tolerancia en píxeles para considerar un lado alineado (escalado para 320x320)
|
|
14
14
|
maskSize = 90; // Porcentaje del video que ocupará la máscara (default 90%)
|
|
15
|
+
cropMargin = 0; // Margen en píxeles para el recorte del documento (default 0)
|
|
15
16
|
captureCompleted;
|
|
16
17
|
isReady;
|
|
17
18
|
isVideoActive = false;
|
|
@@ -45,6 +46,8 @@ const JaakStamps = class {
|
|
|
45
46
|
animationId;
|
|
46
47
|
hasScreenshotTaken = false;
|
|
47
48
|
lastDetectedBox;
|
|
49
|
+
mobileNetSession;
|
|
50
|
+
mobileNetClassMap;
|
|
48
51
|
// Performance optimization properties
|
|
49
52
|
frameSkipCounter = 0;
|
|
50
53
|
FRAME_SKIP = 2; // Process every 3rd frame (reduces CPU by ~66%)
|
|
@@ -57,7 +60,9 @@ const JaakStamps = class {
|
|
|
57
60
|
preprocessCtx;
|
|
58
61
|
captureCanvas;
|
|
59
62
|
captureCtx;
|
|
60
|
-
MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/
|
|
63
|
+
MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/ddmyp-v1.onnx";
|
|
64
|
+
MOBILENET_MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.onnx";
|
|
65
|
+
MOBILENET_CLASSES_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.json";
|
|
61
66
|
INPUT_SIZE = 320;
|
|
62
67
|
CONFIDENCE_THRESHOLD = 0.6;
|
|
63
68
|
// ISO/IEC 7810 ID-1 standard dimensions (85.60mm x 53.98mm)
|
|
@@ -73,6 +78,12 @@ const JaakStamps = class {
|
|
|
73
78
|
this.maskSize = 90;
|
|
74
79
|
}
|
|
75
80
|
}
|
|
81
|
+
validateCropMargin() {
|
|
82
|
+
if (this.cropMargin < 0 || this.cropMargin > 100) {
|
|
83
|
+
console.warn(`cropMargin debe estar entre 0 y 100. Valor actual: ${this.cropMargin}. Usando valor por defecto: 0`);
|
|
84
|
+
this.cropMargin = 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
76
87
|
emitReadyEvent() {
|
|
77
88
|
const isDocumentReady = !!window.ort && this.isModelPreloaded;
|
|
78
89
|
this.isReady.emit(isDocumentReady);
|
|
@@ -87,6 +98,7 @@ const JaakStamps = class {
|
|
|
87
98
|
}
|
|
88
99
|
async componentDidLoad() {
|
|
89
100
|
this.validateMaskSize();
|
|
101
|
+
this.validateCropMargin();
|
|
90
102
|
if (!window.ort) {
|
|
91
103
|
const script = document.createElement('script');
|
|
92
104
|
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
|
|
@@ -256,25 +268,27 @@ const JaakStamps = class {
|
|
|
256
268
|
}
|
|
257
269
|
try {
|
|
258
270
|
this.isLoading = true;
|
|
259
|
-
this.statusMessage = "Precargando
|
|
271
|
+
this.statusMessage = "Precargando modelos...";
|
|
260
272
|
this.statusColor = "#007bff";
|
|
261
273
|
const modelPath = this.MODEL_PATH;
|
|
262
|
-
this.debugLog('🤖 Preloading model:', modelPath);
|
|
274
|
+
this.debugLog('🤖 Preloading detection model:', modelPath);
|
|
263
275
|
// Configure ONNX Runtime with device-specific optimizations
|
|
264
276
|
const sessionOptions = this.getSessionOptions();
|
|
265
277
|
this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
|
|
278
|
+
// Preload MobileNet model and classes
|
|
279
|
+
await this.loadMobileNetModel();
|
|
266
280
|
this.isModelPreloaded = true;
|
|
267
281
|
this.isLoading = false;
|
|
268
|
-
this.statusMessage = "
|
|
282
|
+
this.statusMessage = "Modelos precargados. Listo para comenzar detección";
|
|
269
283
|
this.statusColor = "#28a745";
|
|
270
284
|
this.emitReadyEvent();
|
|
271
|
-
this.debugLog('✅
|
|
272
|
-
return { success: true, message: '
|
|
285
|
+
this.debugLog('✅ Models preloaded successfully');
|
|
286
|
+
return { success: true, message: 'Models preloaded successfully' };
|
|
273
287
|
}
|
|
274
288
|
catch (error) {
|
|
275
|
-
this.debugLog('❌ Error preloading
|
|
289
|
+
this.debugLog('❌ Error preloading models:', error);
|
|
276
290
|
this.isLoading = false;
|
|
277
|
-
this.statusMessage = "Error al precargar
|
|
291
|
+
this.statusMessage = "Error al precargar los modelos";
|
|
278
292
|
this.statusColor = "#ff6b6b";
|
|
279
293
|
return { success: false, error: error.message };
|
|
280
294
|
}
|
|
@@ -284,6 +298,80 @@ const JaakStamps = class {
|
|
|
284
298
|
this.completeProcess(true);
|
|
285
299
|
}
|
|
286
300
|
}
|
|
301
|
+
async loadMobileNetModel() {
|
|
302
|
+
try {
|
|
303
|
+
this.debugLog('🤖 Loading MobileNet model...');
|
|
304
|
+
// Load class map
|
|
305
|
+
const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
|
|
306
|
+
if (!classResponse.ok) {
|
|
307
|
+
throw new Error(`Failed to load class map: ${this.MOBILENET_CLASSES_PATH}`);
|
|
308
|
+
}
|
|
309
|
+
this.mobileNetClassMap = await classResponse.json();
|
|
310
|
+
this.debugLog('📋 MobileNet classes loaded:', this.mobileNetClassMap);
|
|
311
|
+
// Load model
|
|
312
|
+
const sessionOptions = this.getSessionOptions();
|
|
313
|
+
this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
|
|
314
|
+
this.debugLog('✅ MobileNet model loaded successfully');
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
this.debugLog('❌ Error loading MobileNet model:', error);
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
preprocessMobileNet(canvas) {
|
|
322
|
+
// Create a temporary canvas for MobileNet preprocessing (224x224)
|
|
323
|
+
const tempCanvas = document.createElement('canvas');
|
|
324
|
+
tempCanvas.width = 224;
|
|
325
|
+
tempCanvas.height = 224;
|
|
326
|
+
const tempCtx = tempCanvas.getContext('2d');
|
|
327
|
+
// Draw the cropped image onto the 224x224 canvas
|
|
328
|
+
tempCtx.drawImage(canvas, 0, 0, 224, 224);
|
|
329
|
+
// Get image data and preprocess for MobileNet
|
|
330
|
+
const imageData = tempCtx.getImageData(0, 0, 224, 224);
|
|
331
|
+
const data = imageData.data;
|
|
332
|
+
const hw = 224 * 224;
|
|
333
|
+
const arr = new Float32Array(3 * hw);
|
|
334
|
+
// Normalize pixels: (pixel/255 - 0.5) / 0.5 = (pixel - 127.5) / 127.5
|
|
335
|
+
for (let i = 0; i < hw; i++) {
|
|
336
|
+
arr[i] = (data[i * 4 + 0] / 255 - 0.5) / 0.5; // R
|
|
337
|
+
arr[hw + i] = (data[i * 4 + 1] / 255 - 0.5) / 0.5; // G
|
|
338
|
+
arr[2 * hw + i] = (data[i * 4 + 2] / 255 - 0.5) / 0.5; // B
|
|
339
|
+
}
|
|
340
|
+
return new window.ort.Tensor('float32', arr, [1, 3, 224, 224]);
|
|
341
|
+
}
|
|
342
|
+
async classifyDocument(canvas) {
|
|
343
|
+
if (!this.mobileNetSession || !this.mobileNetClassMap) {
|
|
344
|
+
this.debugLog('⚠️ MobileNet model not loaded');
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
this.debugLog('🔍 Classifying document...');
|
|
349
|
+
// Preprocess image for MobileNet
|
|
350
|
+
const inputTensor = this.preprocessMobileNet(canvas);
|
|
351
|
+
// Run inference
|
|
352
|
+
const feeds = { input: inputTensor };
|
|
353
|
+
const results = await this.mobileNetSession.run(feeds);
|
|
354
|
+
const output = results[Object.keys(results)[0]].data;
|
|
355
|
+
// Find the class with highest confidence
|
|
356
|
+
const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
|
|
357
|
+
const confidence = output[maxIdx];
|
|
358
|
+
const className = this.mobileNetClassMap[maxIdx.toString()] || "unknown";
|
|
359
|
+
this.debugLog('📄 Document classification result:', {
|
|
360
|
+
class: className,
|
|
361
|
+
confidence: confidence.toFixed(3),
|
|
362
|
+
classIndex: maxIdx
|
|
363
|
+
});
|
|
364
|
+
return {
|
|
365
|
+
class: className,
|
|
366
|
+
confidence: confidence,
|
|
367
|
+
classIndex: maxIdx
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
this.debugLog('❌ Error classifying document:', error);
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
287
375
|
cleanup() {
|
|
288
376
|
if (this.animationId) {
|
|
289
377
|
cancelAnimationFrame(this.animationId);
|
|
@@ -311,11 +399,16 @@ const JaakStamps = class {
|
|
|
311
399
|
this.captureCtx = undefined;
|
|
312
400
|
this.captureCanvas = undefined;
|
|
313
401
|
}
|
|
314
|
-
// Disposed ONNX
|
|
402
|
+
// Disposed ONNX sessions
|
|
315
403
|
if (this.session) {
|
|
316
404
|
this.session.release?.();
|
|
317
405
|
this.session = undefined;
|
|
318
406
|
}
|
|
407
|
+
if (this.mobileNetSession) {
|
|
408
|
+
this.mobileNetSession.release?.();
|
|
409
|
+
this.mobileNetSession = undefined;
|
|
410
|
+
}
|
|
411
|
+
this.mobileNetClassMap = undefined;
|
|
319
412
|
this.isModelPreloaded = false;
|
|
320
413
|
this.debugLog('🧹 Canvas pool cleaned up');
|
|
321
414
|
}
|
|
@@ -446,18 +539,22 @@ const JaakStamps = class {
|
|
|
446
539
|
// Check if model is already preloaded
|
|
447
540
|
if (!this.session) {
|
|
448
541
|
this.isLoading = true;
|
|
449
|
-
this.statusMessage = "Cargando
|
|
542
|
+
this.statusMessage = "Cargando modelos...";
|
|
450
543
|
this.statusColor = "#007bff";
|
|
451
544
|
const modelPath = this.MODEL_PATH;
|
|
452
|
-
this.debugLog('🤖 Loading model:', modelPath);
|
|
545
|
+
this.debugLog('🤖 Loading detection model:', modelPath);
|
|
453
546
|
const sessionOptions = this.getSessionOptions();
|
|
454
547
|
this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
|
|
548
|
+
// Load MobileNet model if not already loaded
|
|
549
|
+
if (!this.mobileNetSession) {
|
|
550
|
+
await this.loadMobileNetModel();
|
|
551
|
+
}
|
|
455
552
|
this.isModelPreloaded = true;
|
|
456
553
|
this.emitReadyEvent();
|
|
457
554
|
}
|
|
458
555
|
else {
|
|
459
|
-
this.debugLog('🚀 Using preloaded
|
|
460
|
-
this.statusMessage = "Usando
|
|
556
|
+
this.debugLog('🚀 Using preloaded models');
|
|
557
|
+
this.statusMessage = "Usando modelos precargados...";
|
|
461
558
|
this.statusColor = "#007bff";
|
|
462
559
|
}
|
|
463
560
|
this.statusMessage = "Configurando cámara...";
|
|
@@ -676,7 +773,9 @@ const JaakStamps = class {
|
|
|
676
773
|
corners?.forEach(corner => corner.classList.add('perfect-match'));
|
|
677
774
|
if (!this.hasScreenshotTaken) {
|
|
678
775
|
this.lastDetectedBox = bestBox;
|
|
679
|
-
this.takeScreenshot()
|
|
776
|
+
this.takeScreenshot().catch(error => {
|
|
777
|
+
this.debugLog('❌ Error taking screenshot:', error);
|
|
778
|
+
});
|
|
680
779
|
this.hasScreenshotTaken = true;
|
|
681
780
|
// Reset para permitir segunda captura
|
|
682
781
|
setTimeout(() => {
|
|
@@ -759,7 +858,7 @@ const JaakStamps = class {
|
|
|
759
858
|
ctx.strokeRect(x, y, w, h);
|
|
760
859
|
});
|
|
761
860
|
}
|
|
762
|
-
takeScreenshot() {
|
|
861
|
+
async takeScreenshot() {
|
|
763
862
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
764
863
|
return;
|
|
765
864
|
// Activar animación
|
|
@@ -775,10 +874,10 @@ const JaakStamps = class {
|
|
|
775
874
|
// Calcular las coordenadas de recorte basadas en la detección
|
|
776
875
|
const scaleX = this.videoRef.videoWidth / this.INPUT_SIZE;
|
|
777
876
|
const scaleY = this.videoRef.videoHeight / this.INPUT_SIZE;
|
|
778
|
-
const cropX = Math.max(0, this.lastDetectedBox.x * scaleX);
|
|
779
|
-
const cropY = Math.max(0, this.lastDetectedBox.y * scaleY);
|
|
780
|
-
const cropWidth = Math.min(this.lastDetectedBox.w * scaleX, this.videoRef.videoWidth - cropX);
|
|
781
|
-
const cropHeight = Math.min(this.lastDetectedBox.h * scaleY, this.videoRef.videoHeight - cropY);
|
|
877
|
+
const cropX = Math.max(0, (this.lastDetectedBox.x * scaleX) - this.cropMargin);
|
|
878
|
+
const cropY = Math.max(0, (this.lastDetectedBox.y * scaleY) - this.cropMargin);
|
|
879
|
+
const cropWidth = Math.min((this.lastDetectedBox.w * scaleX) + (2 * this.cropMargin), this.videoRef.videoWidth - cropX);
|
|
880
|
+
const cropHeight = Math.min((this.lastDetectedBox.h * scaleY) + (2 * this.cropMargin), this.videoRef.videoHeight - cropY);
|
|
782
881
|
// OPTIMIZATION: Create temporary canvas only for cropped version
|
|
783
882
|
// (We reuse main capture canvas for full frame)
|
|
784
883
|
const croppedCanvas = document.createElement('canvas');
|
|
@@ -793,6 +892,15 @@ const JaakStamps = class {
|
|
|
793
892
|
// Captura del frente usando canvas reutilizado
|
|
794
893
|
this.capturedFullFrame = this.captureCanvas.toDataURL('image/png');
|
|
795
894
|
this.capturedCroppedId = croppedCanvas.toDataURL('image/png');
|
|
895
|
+
// Classify the cropped document
|
|
896
|
+
const classification = await this.classifyDocument(croppedCanvas);
|
|
897
|
+
if (classification && classification.class === 'passport') {
|
|
898
|
+
// If it's a passport, skip back capture since passports don't have a back side
|
|
899
|
+
this.debugLog('📄 Passport detected - skipping back capture');
|
|
900
|
+
this.completeProcess(true);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
// For other IDs, continue with normal flow (back capture)
|
|
796
904
|
this.captureStep = 'back';
|
|
797
905
|
this.statusMessage = "Voltee la identificación y muestre la parte trasera";
|
|
798
906
|
this.statusColor = "#007bff";
|
|
@@ -922,7 +1030,7 @@ const JaakStamps = class {
|
|
|
922
1030
|
this.cleanup();
|
|
923
1031
|
}
|
|
924
1032
|
render() {
|
|
925
|
-
return (h("div", { key: '
|
|
1033
|
+
return (h("div", { key: 'd6cd4225500c44e5c7fb4f2b3dc453a055467066', class: "detector-container" }, h("div", { key: '6bd49b0bcd686648aeffb8fb95203dc0779f1d8a', class: "video-container" }, h("video", { key: 'd2666ce9d1f5b84703ac86fd4eb901f4298ed7d5', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), h("canvas", { key: 'e997f063450d81d2a15398f0761c9d06c28a85c9', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (h("div", { key: '4ccb8bf05091a7173255181eae1a8699fe512b56', class: "overlay-mask" }, h("div", { key: 'b35defa8c207b05e41d64c8e1eefc905d31fad55', class: "card-outline" }, h("div", { key: '6ed8dad63d6d79c1bb4a64f0b28467f5b29d778a', class: "side side-top" }), h("div", { key: '403d4a5ba681a58ad1870a4339e19b4f037a8795', class: "side side-right" }), h("div", { key: '3cb3276bb184a4b76419b4cb0b4fcb151a4df631', class: "side side-bottom" }), h("div", { key: 'ed7c9bec0e708ab282ba8b24a08d21b8126af27e', class: "side side-left" }), h("div", { key: '157be211355bf9260dc7a73d03831dbb1a2945e3', class: "corner corner-tl" }), h("div", { key: 'b19af5f319333ff05f981063af939d2c4cfc9b5f', class: "corner corner-tr" }), h("div", { key: '6fcd3e2c8842261dcd32c36e6df5b2f3dd0c8fc8', class: "corner corner-bl" }), h("div", { key: '7caab5eaee62f3f9039075b3748083c89c8127e0', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (h("div", { key: 'e3dd42ddb75b22aeef438d8651955f3dc039f00c', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (h("button", { key: '54b8651817cd52bad2f34ac1413a4b1e21166cee', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")))), this.isCapturing && (h("div", { key: '853707a4fa71eeacbee7985e619fca80d6522cbd', class: "capture-animation" })), this.showFlipAnimation && (h("div", { key: '246ed3f5dc57ee57345105cdebe5f950d5ff4c90', class: "flip-animation" }, h("div", { key: 'f03c6c87c8f5e98a32176cca061d369a521da2e8', class: "id-card-icon" }), h("div", { key: 'e357785b076a9e41ea4f9b3d9c14e847bacc9604', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (h("div", { key: 'df3adaaecd3d547938638c58ccc0e89f8325fc23', class: "success-animation" }, h("div", { key: 'a023985b8afd6ed72f9e1c2ee63e2c7ba9fb960b', class: "check-icon" }), h("div", { key: '739ff7f329f2131061a6860529c07c8819800aab', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (h("div", { key: '9b02df640528a19cb15e5dbabf9ecb43afbceffa', class: "loading-overlay" }, h("div", { key: '3652c8e355ea7fe623c478229bf548bdf30e399a', class: "loading-spinner" }), h("div", { key: '334f4469b5672f6af78b8679c15ed8c59ecefa85', class: "loading-text" }, this.statusMessage))), h("div", { key: 'a0b7c9b92771cff96e97bd67da2fd3090b080f8a', class: "watermark" }, h("img", { key: 'b5c8f0de1d8138d4a0c80526fd33b2daefa86b16', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
926
1034
|
}
|
|
927
1035
|
};
|
|
928
1036
|
JaakStamps.style = myComponentCss;
|