@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
|
@@ -4,6 +4,7 @@ export class JaakStamps {
|
|
|
4
4
|
debug = false;
|
|
5
5
|
alignmentTolerance = 10; // Tolerancia en píxeles para considerar un lado alineado (escalado para 320x320)
|
|
6
6
|
maskSize = 90; // Porcentaje del video que ocupará la máscara (default 90%)
|
|
7
|
+
cropMargin = 0; // Margen en píxeles para el recorte del documento (default 0)
|
|
7
8
|
captureCompleted;
|
|
8
9
|
isReady;
|
|
9
10
|
isVideoActive = false;
|
|
@@ -37,6 +38,8 @@ export class JaakStamps {
|
|
|
37
38
|
animationId;
|
|
38
39
|
hasScreenshotTaken = false;
|
|
39
40
|
lastDetectedBox;
|
|
41
|
+
mobileNetSession;
|
|
42
|
+
mobileNetClassMap;
|
|
40
43
|
// Performance optimization properties
|
|
41
44
|
frameSkipCounter = 0;
|
|
42
45
|
FRAME_SKIP = 2; // Process every 3rd frame (reduces CPU by ~66%)
|
|
@@ -49,7 +52,9 @@ export class JaakStamps {
|
|
|
49
52
|
preprocessCtx;
|
|
50
53
|
captureCanvas;
|
|
51
54
|
captureCtx;
|
|
52
|
-
MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/
|
|
55
|
+
MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/ddmyp-v1.onnx";
|
|
56
|
+
MOBILENET_MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.onnx";
|
|
57
|
+
MOBILENET_CLASSES_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/cdmmp-v1.json";
|
|
53
58
|
INPUT_SIZE = 320;
|
|
54
59
|
CONFIDENCE_THRESHOLD = 0.6;
|
|
55
60
|
// ISO/IEC 7810 ID-1 standard dimensions (85.60mm x 53.98mm)
|
|
@@ -65,6 +70,12 @@ export class JaakStamps {
|
|
|
65
70
|
this.maskSize = 90;
|
|
66
71
|
}
|
|
67
72
|
}
|
|
73
|
+
validateCropMargin() {
|
|
74
|
+
if (this.cropMargin < 0 || this.cropMargin > 100) {
|
|
75
|
+
console.warn(`cropMargin debe estar entre 0 y 100. Valor actual: ${this.cropMargin}. Usando valor por defecto: 0`);
|
|
76
|
+
this.cropMargin = 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
68
79
|
emitReadyEvent() {
|
|
69
80
|
const isDocumentReady = !!window.ort && this.isModelPreloaded;
|
|
70
81
|
this.isReady.emit(isDocumentReady);
|
|
@@ -79,6 +90,7 @@ export class JaakStamps {
|
|
|
79
90
|
}
|
|
80
91
|
async componentDidLoad() {
|
|
81
92
|
this.validateMaskSize();
|
|
93
|
+
this.validateCropMargin();
|
|
82
94
|
if (!window.ort) {
|
|
83
95
|
const script = document.createElement('script');
|
|
84
96
|
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
|
|
@@ -248,25 +260,27 @@ export class JaakStamps {
|
|
|
248
260
|
}
|
|
249
261
|
try {
|
|
250
262
|
this.isLoading = true;
|
|
251
|
-
this.statusMessage = "Precargando
|
|
263
|
+
this.statusMessage = "Precargando modelos...";
|
|
252
264
|
this.statusColor = "#007bff";
|
|
253
265
|
const modelPath = this.MODEL_PATH;
|
|
254
|
-
this.debugLog('🤖 Preloading model:', modelPath);
|
|
266
|
+
this.debugLog('🤖 Preloading detection model:', modelPath);
|
|
255
267
|
// Configure ONNX Runtime with device-specific optimizations
|
|
256
268
|
const sessionOptions = this.getSessionOptions();
|
|
257
269
|
this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
|
|
270
|
+
// Preload MobileNet model and classes
|
|
271
|
+
await this.loadMobileNetModel();
|
|
258
272
|
this.isModelPreloaded = true;
|
|
259
273
|
this.isLoading = false;
|
|
260
|
-
this.statusMessage = "
|
|
274
|
+
this.statusMessage = "Modelos precargados. Listo para comenzar detección";
|
|
261
275
|
this.statusColor = "#28a745";
|
|
262
276
|
this.emitReadyEvent();
|
|
263
|
-
this.debugLog('✅
|
|
264
|
-
return { success: true, message: '
|
|
277
|
+
this.debugLog('✅ Models preloaded successfully');
|
|
278
|
+
return { success: true, message: 'Models preloaded successfully' };
|
|
265
279
|
}
|
|
266
280
|
catch (error) {
|
|
267
|
-
this.debugLog('❌ Error preloading
|
|
281
|
+
this.debugLog('❌ Error preloading models:', error);
|
|
268
282
|
this.isLoading = false;
|
|
269
|
-
this.statusMessage = "Error al precargar
|
|
283
|
+
this.statusMessage = "Error al precargar los modelos";
|
|
270
284
|
this.statusColor = "#ff6b6b";
|
|
271
285
|
return { success: false, error: error.message };
|
|
272
286
|
}
|
|
@@ -276,6 +290,80 @@ export class JaakStamps {
|
|
|
276
290
|
this.completeProcess(true);
|
|
277
291
|
}
|
|
278
292
|
}
|
|
293
|
+
async loadMobileNetModel() {
|
|
294
|
+
try {
|
|
295
|
+
this.debugLog('🤖 Loading MobileNet model...');
|
|
296
|
+
// Load class map
|
|
297
|
+
const classResponse = await fetch(this.MOBILENET_CLASSES_PATH);
|
|
298
|
+
if (!classResponse.ok) {
|
|
299
|
+
throw new Error(`Failed to load class map: ${this.MOBILENET_CLASSES_PATH}`);
|
|
300
|
+
}
|
|
301
|
+
this.mobileNetClassMap = await classResponse.json();
|
|
302
|
+
this.debugLog('📋 MobileNet classes loaded:', this.mobileNetClassMap);
|
|
303
|
+
// Load model
|
|
304
|
+
const sessionOptions = this.getSessionOptions();
|
|
305
|
+
this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
|
|
306
|
+
this.debugLog('✅ MobileNet model loaded successfully');
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.debugLog('❌ Error loading MobileNet model:', error);
|
|
310
|
+
throw error;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
preprocessMobileNet(canvas) {
|
|
314
|
+
// Create a temporary canvas for MobileNet preprocessing (224x224)
|
|
315
|
+
const tempCanvas = document.createElement('canvas');
|
|
316
|
+
tempCanvas.width = 224;
|
|
317
|
+
tempCanvas.height = 224;
|
|
318
|
+
const tempCtx = tempCanvas.getContext('2d');
|
|
319
|
+
// Draw the cropped image onto the 224x224 canvas
|
|
320
|
+
tempCtx.drawImage(canvas, 0, 0, 224, 224);
|
|
321
|
+
// Get image data and preprocess for MobileNet
|
|
322
|
+
const imageData = tempCtx.getImageData(0, 0, 224, 224);
|
|
323
|
+
const data = imageData.data;
|
|
324
|
+
const hw = 224 * 224;
|
|
325
|
+
const arr = new Float32Array(3 * hw);
|
|
326
|
+
// Normalize pixels: (pixel/255 - 0.5) / 0.5 = (pixel - 127.5) / 127.5
|
|
327
|
+
for (let i = 0; i < hw; i++) {
|
|
328
|
+
arr[i] = (data[i * 4 + 0] / 255 - 0.5) / 0.5; // R
|
|
329
|
+
arr[hw + i] = (data[i * 4 + 1] / 255 - 0.5) / 0.5; // G
|
|
330
|
+
arr[2 * hw + i] = (data[i * 4 + 2] / 255 - 0.5) / 0.5; // B
|
|
331
|
+
}
|
|
332
|
+
return new window.ort.Tensor('float32', arr, [1, 3, 224, 224]);
|
|
333
|
+
}
|
|
334
|
+
async classifyDocument(canvas) {
|
|
335
|
+
if (!this.mobileNetSession || !this.mobileNetClassMap) {
|
|
336
|
+
this.debugLog('⚠️ MobileNet model not loaded');
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
this.debugLog('🔍 Classifying document...');
|
|
341
|
+
// Preprocess image for MobileNet
|
|
342
|
+
const inputTensor = this.preprocessMobileNet(canvas);
|
|
343
|
+
// Run inference
|
|
344
|
+
const feeds = { input: inputTensor };
|
|
345
|
+
const results = await this.mobileNetSession.run(feeds);
|
|
346
|
+
const output = results[Object.keys(results)[0]].data;
|
|
347
|
+
// Find the class with highest confidence
|
|
348
|
+
const maxIdx = output.reduce((bestIdx, val, idx, arr) => val > arr[bestIdx] ? idx : bestIdx, 0);
|
|
349
|
+
const confidence = output[maxIdx];
|
|
350
|
+
const className = this.mobileNetClassMap[maxIdx.toString()] || "unknown";
|
|
351
|
+
this.debugLog('📄 Document classification result:', {
|
|
352
|
+
class: className,
|
|
353
|
+
confidence: confidence.toFixed(3),
|
|
354
|
+
classIndex: maxIdx
|
|
355
|
+
});
|
|
356
|
+
return {
|
|
357
|
+
class: className,
|
|
358
|
+
confidence: confidence,
|
|
359
|
+
classIndex: maxIdx
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
this.debugLog('❌ Error classifying document:', error);
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
279
367
|
cleanup() {
|
|
280
368
|
if (this.animationId) {
|
|
281
369
|
cancelAnimationFrame(this.animationId);
|
|
@@ -303,11 +391,16 @@ export class JaakStamps {
|
|
|
303
391
|
this.captureCtx = undefined;
|
|
304
392
|
this.captureCanvas = undefined;
|
|
305
393
|
}
|
|
306
|
-
// Disposed ONNX
|
|
394
|
+
// Disposed ONNX sessions
|
|
307
395
|
if (this.session) {
|
|
308
396
|
this.session.release?.();
|
|
309
397
|
this.session = undefined;
|
|
310
398
|
}
|
|
399
|
+
if (this.mobileNetSession) {
|
|
400
|
+
this.mobileNetSession.release?.();
|
|
401
|
+
this.mobileNetSession = undefined;
|
|
402
|
+
}
|
|
403
|
+
this.mobileNetClassMap = undefined;
|
|
311
404
|
this.isModelPreloaded = false;
|
|
312
405
|
this.debugLog('🧹 Canvas pool cleaned up');
|
|
313
406
|
}
|
|
@@ -438,18 +531,22 @@ export class JaakStamps {
|
|
|
438
531
|
// Check if model is already preloaded
|
|
439
532
|
if (!this.session) {
|
|
440
533
|
this.isLoading = true;
|
|
441
|
-
this.statusMessage = "Cargando
|
|
534
|
+
this.statusMessage = "Cargando modelos...";
|
|
442
535
|
this.statusColor = "#007bff";
|
|
443
536
|
const modelPath = this.MODEL_PATH;
|
|
444
|
-
this.debugLog('🤖 Loading model:', modelPath);
|
|
537
|
+
this.debugLog('🤖 Loading detection model:', modelPath);
|
|
445
538
|
const sessionOptions = this.getSessionOptions();
|
|
446
539
|
this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
|
|
540
|
+
// Load MobileNet model if not already loaded
|
|
541
|
+
if (!this.mobileNetSession) {
|
|
542
|
+
await this.loadMobileNetModel();
|
|
543
|
+
}
|
|
447
544
|
this.isModelPreloaded = true;
|
|
448
545
|
this.emitReadyEvent();
|
|
449
546
|
}
|
|
450
547
|
else {
|
|
451
|
-
this.debugLog('🚀 Using preloaded
|
|
452
|
-
this.statusMessage = "Usando
|
|
548
|
+
this.debugLog('🚀 Using preloaded models');
|
|
549
|
+
this.statusMessage = "Usando modelos precargados...";
|
|
453
550
|
this.statusColor = "#007bff";
|
|
454
551
|
}
|
|
455
552
|
this.statusMessage = "Configurando cámara...";
|
|
@@ -668,7 +765,9 @@ export class JaakStamps {
|
|
|
668
765
|
corners?.forEach(corner => corner.classList.add('perfect-match'));
|
|
669
766
|
if (!this.hasScreenshotTaken) {
|
|
670
767
|
this.lastDetectedBox = bestBox;
|
|
671
|
-
this.takeScreenshot()
|
|
768
|
+
this.takeScreenshot().catch(error => {
|
|
769
|
+
this.debugLog('❌ Error taking screenshot:', error);
|
|
770
|
+
});
|
|
672
771
|
this.hasScreenshotTaken = true;
|
|
673
772
|
// Reset para permitir segunda captura
|
|
674
773
|
setTimeout(() => {
|
|
@@ -751,7 +850,7 @@ export class JaakStamps {
|
|
|
751
850
|
ctx.strokeRect(x, y, w, h);
|
|
752
851
|
});
|
|
753
852
|
}
|
|
754
|
-
takeScreenshot() {
|
|
853
|
+
async takeScreenshot() {
|
|
755
854
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
756
855
|
return;
|
|
757
856
|
// Activar animación
|
|
@@ -767,10 +866,10 @@ export class JaakStamps {
|
|
|
767
866
|
// Calcular las coordenadas de recorte basadas en la detección
|
|
768
867
|
const scaleX = this.videoRef.videoWidth / this.INPUT_SIZE;
|
|
769
868
|
const scaleY = this.videoRef.videoHeight / this.INPUT_SIZE;
|
|
770
|
-
const cropX = Math.max(0, this.lastDetectedBox.x * scaleX);
|
|
771
|
-
const cropY = Math.max(0, this.lastDetectedBox.y * scaleY);
|
|
772
|
-
const cropWidth = Math.min(this.lastDetectedBox.w * scaleX, this.videoRef.videoWidth - cropX);
|
|
773
|
-
const cropHeight = Math.min(this.lastDetectedBox.h * scaleY, this.videoRef.videoHeight - cropY);
|
|
869
|
+
const cropX = Math.max(0, (this.lastDetectedBox.x * scaleX) - this.cropMargin);
|
|
870
|
+
const cropY = Math.max(0, (this.lastDetectedBox.y * scaleY) - this.cropMargin);
|
|
871
|
+
const cropWidth = Math.min((this.lastDetectedBox.w * scaleX) + (2 * this.cropMargin), this.videoRef.videoWidth - cropX);
|
|
872
|
+
const cropHeight = Math.min((this.lastDetectedBox.h * scaleY) + (2 * this.cropMargin), this.videoRef.videoHeight - cropY);
|
|
774
873
|
// OPTIMIZATION: Create temporary canvas only for cropped version
|
|
775
874
|
// (We reuse main capture canvas for full frame)
|
|
776
875
|
const croppedCanvas = document.createElement('canvas');
|
|
@@ -785,6 +884,15 @@ export class JaakStamps {
|
|
|
785
884
|
// Captura del frente usando canvas reutilizado
|
|
786
885
|
this.capturedFullFrame = this.captureCanvas.toDataURL('image/png');
|
|
787
886
|
this.capturedCroppedId = croppedCanvas.toDataURL('image/png');
|
|
887
|
+
// Classify the cropped document
|
|
888
|
+
const classification = await this.classifyDocument(croppedCanvas);
|
|
889
|
+
if (classification && classification.class === 'passport') {
|
|
890
|
+
// If it's a passport, skip back capture since passports don't have a back side
|
|
891
|
+
this.debugLog('📄 Passport detected - skipping back capture');
|
|
892
|
+
this.completeProcess(true);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
// For other IDs, continue with normal flow (back capture)
|
|
788
896
|
this.captureStep = 'back';
|
|
789
897
|
this.statusMessage = "Voltee la identificación y muestre la parte trasera";
|
|
790
898
|
this.statusColor = "#007bff";
|
|
@@ -914,7 +1022,7 @@ export class JaakStamps {
|
|
|
914
1022
|
this.cleanup();
|
|
915
1023
|
}
|
|
916
1024
|
render() {
|
|
917
|
-
return (h("div", { key: '
|
|
1025
|
+
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" })))));
|
|
918
1026
|
}
|
|
919
1027
|
static get is() { return "jaak-stamps"; }
|
|
920
1028
|
static get encapsulation() { return "shadow"; }
|
|
@@ -989,6 +1097,26 @@ export class JaakStamps {
|
|
|
989
1097
|
"setter": false,
|
|
990
1098
|
"reflect": false,
|
|
991
1099
|
"defaultValue": "90"
|
|
1100
|
+
},
|
|
1101
|
+
"cropMargin": {
|
|
1102
|
+
"type": "number",
|
|
1103
|
+
"attribute": "crop-margin",
|
|
1104
|
+
"mutable": false,
|
|
1105
|
+
"complexType": {
|
|
1106
|
+
"original": "number",
|
|
1107
|
+
"resolved": "number",
|
|
1108
|
+
"references": {}
|
|
1109
|
+
},
|
|
1110
|
+
"required": false,
|
|
1111
|
+
"optional": false,
|
|
1112
|
+
"docs": {
|
|
1113
|
+
"tags": [],
|
|
1114
|
+
"text": ""
|
|
1115
|
+
},
|
|
1116
|
+
"getter": false,
|
|
1117
|
+
"setter": false,
|
|
1118
|
+
"reflect": false,
|
|
1119
|
+
"defaultValue": "0"
|
|
992
1120
|
}
|
|
993
1121
|
};
|
|
994
1122
|
}
|