@jaak.ai/stamps 2.0.0-dev.23 → 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.
@@ -46,6 +46,8 @@ const JaakStamps = class {
46
46
  animationId;
47
47
  hasScreenshotTaken = false;
48
48
  lastDetectedBox;
49
+ mobileNetSession;
50
+ mobileNetClassMap;
49
51
  // Performance optimization properties
50
52
  frameSkipCounter = 0;
51
53
  FRAME_SKIP = 2; // Process every 3rd frame (reduces CPU by ~66%)
@@ -58,7 +60,9 @@ const JaakStamps = class {
58
60
  preprocessCtx;
59
61
  captureCanvas;
60
62
  captureCtx;
61
- MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/detector-nano.onnx";
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";
62
66
  INPUT_SIZE = 320;
63
67
  CONFIDENCE_THRESHOLD = 0.6;
64
68
  // ISO/IEC 7810 ID-1 standard dimensions (85.60mm x 53.98mm)
@@ -264,25 +268,27 @@ const JaakStamps = class {
264
268
  }
265
269
  try {
266
270
  this.isLoading = true;
267
- this.statusMessage = "Precargando modelo de detección...";
271
+ this.statusMessage = "Precargando modelos...";
268
272
  this.statusColor = "#007bff";
269
273
  const modelPath = this.MODEL_PATH;
270
- this.debugLog('🤖 Preloading model:', modelPath);
274
+ this.debugLog('🤖 Preloading detection model:', modelPath);
271
275
  // Configure ONNX Runtime with device-specific optimizations
272
276
  const sessionOptions = this.getSessionOptions();
273
277
  this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
278
+ // Preload MobileNet model and classes
279
+ await this.loadMobileNetModel();
274
280
  this.isModelPreloaded = true;
275
281
  this.isLoading = false;
276
- this.statusMessage = "Modelo precargado. Listo para comenzar detección";
282
+ this.statusMessage = "Modelos precargados. Listo para comenzar detección";
277
283
  this.statusColor = "#28a745";
278
284
  this.emitReadyEvent();
279
- this.debugLog('✅ Model preloaded successfully');
280
- return { success: true, message: 'Model preloaded successfully' };
285
+ this.debugLog('✅ Models preloaded successfully');
286
+ return { success: true, message: 'Models preloaded successfully' };
281
287
  }
282
288
  catch (error) {
283
- this.debugLog('❌ Error preloading model:', error);
289
+ this.debugLog('❌ Error preloading models:', error);
284
290
  this.isLoading = false;
285
- this.statusMessage = "Error al precargar el modelo";
291
+ this.statusMessage = "Error al precargar los modelos";
286
292
  this.statusColor = "#ff6b6b";
287
293
  return { success: false, error: error.message };
288
294
  }
@@ -292,6 +298,80 @@ const JaakStamps = class {
292
298
  this.completeProcess(true);
293
299
  }
294
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
+ }
295
375
  cleanup() {
296
376
  if (this.animationId) {
297
377
  cancelAnimationFrame(this.animationId);
@@ -319,11 +399,16 @@ const JaakStamps = class {
319
399
  this.captureCtx = undefined;
320
400
  this.captureCanvas = undefined;
321
401
  }
322
- // Disposed ONNX session
402
+ // Disposed ONNX sessions
323
403
  if (this.session) {
324
404
  this.session.release?.();
325
405
  this.session = undefined;
326
406
  }
407
+ if (this.mobileNetSession) {
408
+ this.mobileNetSession.release?.();
409
+ this.mobileNetSession = undefined;
410
+ }
411
+ this.mobileNetClassMap = undefined;
327
412
  this.isModelPreloaded = false;
328
413
  this.debugLog('🧹 Canvas pool cleaned up');
329
414
  }
@@ -454,18 +539,22 @@ const JaakStamps = class {
454
539
  // Check if model is already preloaded
455
540
  if (!this.session) {
456
541
  this.isLoading = true;
457
- this.statusMessage = "Cargando modelo de detección...";
542
+ this.statusMessage = "Cargando modelos...";
458
543
  this.statusColor = "#007bff";
459
544
  const modelPath = this.MODEL_PATH;
460
- this.debugLog('🤖 Loading model:', modelPath);
545
+ this.debugLog('🤖 Loading detection model:', modelPath);
461
546
  const sessionOptions = this.getSessionOptions();
462
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
+ }
463
552
  this.isModelPreloaded = true;
464
553
  this.emitReadyEvent();
465
554
  }
466
555
  else {
467
- this.debugLog('🚀 Using preloaded model');
468
- this.statusMessage = "Usando modelo precargado...";
556
+ this.debugLog('🚀 Using preloaded models');
557
+ this.statusMessage = "Usando modelos precargados...";
469
558
  this.statusColor = "#007bff";
470
559
  }
471
560
  this.statusMessage = "Configurando cámara...";
@@ -684,7 +773,9 @@ const JaakStamps = class {
684
773
  corners?.forEach(corner => corner.classList.add('perfect-match'));
685
774
  if (!this.hasScreenshotTaken) {
686
775
  this.lastDetectedBox = bestBox;
687
- this.takeScreenshot();
776
+ this.takeScreenshot().catch(error => {
777
+ this.debugLog('❌ Error taking screenshot:', error);
778
+ });
688
779
  this.hasScreenshotTaken = true;
689
780
  // Reset para permitir segunda captura
690
781
  setTimeout(() => {
@@ -767,7 +858,7 @@ const JaakStamps = class {
767
858
  ctx.strokeRect(x, y, w, h);
768
859
  });
769
860
  }
770
- takeScreenshot() {
861
+ async takeScreenshot() {
771
862
  if (!this.videoRef || !this.lastDetectedBox)
772
863
  return;
773
864
  // Activar animación
@@ -801,6 +892,15 @@ const JaakStamps = class {
801
892
  // Captura del frente usando canvas reutilizado
802
893
  this.capturedFullFrame = this.captureCanvas.toDataURL('image/png');
803
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)
804
904
  this.captureStep = 'back';
805
905
  this.statusMessage = "Voltee la identificación y muestre la parte trasera";
806
906
  this.statusColor = "#007bff";
@@ -930,7 +1030,7 @@ const JaakStamps = class {
930
1030
  this.cleanup();
931
1031
  }
932
1032
  render() {
933
- return (h("div", { key: '7cd80970b12ba60949ff34b5a9405b9782267918', class: "detector-container" }, h("div", { key: '0e66e46a524bd37f60f30504a270f72b64a97fc1', class: "video-container" }, h("video", { key: '0c1b0719c626659e54bec8ce3383f50d990c32c1', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: this.isVideoActive ? 'block' : 'none' } }), h("canvas", { key: '09fc3a2d511e48186099a0ff5d3d6378f15fe1c4', ref: el => this.canvasRef = el, class: this.shouldMirrorVideo ? 'mirror' : '' }), this.isMaskReady && (h("div", { key: '70a31b04c9543fc0834a848013daf06c654e4194', class: "overlay-mask" }, h("div", { key: '050dcdaa59b028b5c379de310a80ca7191bf8af8', class: "card-outline" }, h("div", { key: 'adef93f2e6b16b308dc5d67469f35c2025ca160f', class: "side side-top" }), h("div", { key: '4900bed8cc2607f92163a25bbf3c0d3a272b9840', class: "side side-right" }), h("div", { key: '11fc9da6cfe04b993bf4a72413da2344122b21e4', class: "side side-bottom" }), h("div", { key: 'ed95ac2578ba045e003d61144aecd09ef4f01561', class: "side side-left" }), h("div", { key: '37eec8aad0a0d449174d52caab7aec5a25d11731', class: "corner corner-tl" }), h("div", { key: 'c7247e71d8936d0a30f415f73377713a8c457420', class: "corner corner-tr" }), h("div", { key: 'e131e0c3bd66068ae1d33cd7211edafd92dfbe62', class: "corner corner-bl" }), h("div", { key: '89234ddcde735501907276e10287af983033ec85', class: "corner corner-br" }), !this.showFlipAnimation && !this.showSuccessAnimation && (h("div", { key: '4b2543dd05f173b3ee4e3053ed7b62d4e7b75e63', class: "guide-text" }, this.statusMessage))), this.captureStep === 'back' && !this.showFlipAnimation && !this.showSuccessAnimation && (h("button", { key: 'e7e2b2b1486f7024fc2d42073313833bca88e315', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button" }, "Saltar reverso")))), this.isCapturing && (h("div", { key: '6ca8283fe3eb1c5b2700c0d313afb51c2db3ea7f', class: "capture-animation" })), this.showFlipAnimation && (h("div", { key: 'eaed1200385a98a8750cdaef2dad9f8ff63914f3', class: "flip-animation" }, h("div", { key: 'dbe9bfc929fa8b13cadc60cdece603c48dbcbd4c', class: "id-card-icon" }), h("div", { key: '82b9de0910a993b6cf3af7ecdf43de41d495c181', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), this.showSuccessAnimation && (h("div", { key: '92ef47686b0fbf91f6a3f8c3c3f23a78a6fc76f8', class: "success-animation" }, h("div", { key: '35aeb41dce144fc844d7447a958473834f6b0843', class: "check-icon" }), h("div", { key: '2ba06161a8d665394803ce8cc90320b154048c0b', class: "success-text" }, "\u00A1Proceso completado!"))), this.isLoading && (h("div", { key: '8abc4f5724ca6543698f7ca9116ccfc9c042edbd', class: "loading-overlay" }, h("div", { key: '5c8f73bb7c30c6a4c304e96e4db2bf75dc89b89e', class: "loading-spinner" }), h("div", { key: '8f5665f21be98ddccb9d9d2c6412e4edf4571a51', class: "loading-text" }, this.statusMessage))), h("div", { key: '00316bee3c56f07b9771c400f5b84b7e14dca4af', class: "watermark" }, h("img", { key: 'd7f58ffa993405912f8aa26386f39caa72e7a000', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
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" })))));
934
1034
  }
935
1035
  };
936
1036
  JaakStamps.style = myComponentCss;