@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.
@@ -38,6 +38,8 @@ export class JaakStamps {
38
38
  animationId;
39
39
  hasScreenshotTaken = false;
40
40
  lastDetectedBox;
41
+ mobileNetSession;
42
+ mobileNetClassMap;
41
43
  // Performance optimization properties
42
44
  frameSkipCounter = 0;
43
45
  FRAME_SKIP = 2; // Process every 3rd frame (reduces CPU by ~66%)
@@ -50,7 +52,9 @@ export class JaakStamps {
50
52
  preprocessCtx;
51
53
  captureCanvas;
52
54
  captureCtx;
53
- MODEL_PATH = "https://storage.googleapis.com/jaak-static/web/component/stamps/detector-nano.onnx";
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";
54
58
  INPUT_SIZE = 320;
55
59
  CONFIDENCE_THRESHOLD = 0.6;
56
60
  // ISO/IEC 7810 ID-1 standard dimensions (85.60mm x 53.98mm)
@@ -256,25 +260,27 @@ export class JaakStamps {
256
260
  }
257
261
  try {
258
262
  this.isLoading = true;
259
- this.statusMessage = "Precargando modelo de detección...";
263
+ this.statusMessage = "Precargando modelos...";
260
264
  this.statusColor = "#007bff";
261
265
  const modelPath = this.MODEL_PATH;
262
- this.debugLog('🤖 Preloading model:', modelPath);
266
+ this.debugLog('🤖 Preloading detection model:', modelPath);
263
267
  // Configure ONNX Runtime with device-specific optimizations
264
268
  const sessionOptions = this.getSessionOptions();
265
269
  this.session = await window.ort.InferenceSession.create(modelPath, sessionOptions);
270
+ // Preload MobileNet model and classes
271
+ await this.loadMobileNetModel();
266
272
  this.isModelPreloaded = true;
267
273
  this.isLoading = false;
268
- this.statusMessage = "Modelo precargado. Listo para comenzar detección";
274
+ this.statusMessage = "Modelos precargados. Listo para comenzar detección";
269
275
  this.statusColor = "#28a745";
270
276
  this.emitReadyEvent();
271
- this.debugLog('✅ Model preloaded successfully');
272
- return { success: true, message: 'Model preloaded successfully' };
277
+ this.debugLog('✅ Models preloaded successfully');
278
+ return { success: true, message: 'Models preloaded successfully' };
273
279
  }
274
280
  catch (error) {
275
- this.debugLog('❌ Error preloading model:', error);
281
+ this.debugLog('❌ Error preloading models:', error);
276
282
  this.isLoading = false;
277
- this.statusMessage = "Error al precargar el modelo";
283
+ this.statusMessage = "Error al precargar los modelos";
278
284
  this.statusColor = "#ff6b6b";
279
285
  return { success: false, error: error.message };
280
286
  }
@@ -284,6 +290,80 @@ export class JaakStamps {
284
290
  this.completeProcess(true);
285
291
  }
286
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
+ }
287
367
  cleanup() {
288
368
  if (this.animationId) {
289
369
  cancelAnimationFrame(this.animationId);
@@ -311,11 +391,16 @@ export class JaakStamps {
311
391
  this.captureCtx = undefined;
312
392
  this.captureCanvas = undefined;
313
393
  }
314
- // Disposed ONNX session
394
+ // Disposed ONNX sessions
315
395
  if (this.session) {
316
396
  this.session.release?.();
317
397
  this.session = undefined;
318
398
  }
399
+ if (this.mobileNetSession) {
400
+ this.mobileNetSession.release?.();
401
+ this.mobileNetSession = undefined;
402
+ }
403
+ this.mobileNetClassMap = undefined;
319
404
  this.isModelPreloaded = false;
320
405
  this.debugLog('🧹 Canvas pool cleaned up');
321
406
  }
@@ -446,18 +531,22 @@ export class JaakStamps {
446
531
  // Check if model is already preloaded
447
532
  if (!this.session) {
448
533
  this.isLoading = true;
449
- this.statusMessage = "Cargando modelo de detección...";
534
+ this.statusMessage = "Cargando modelos...";
450
535
  this.statusColor = "#007bff";
451
536
  const modelPath = this.MODEL_PATH;
452
- this.debugLog('🤖 Loading model:', modelPath);
537
+ this.debugLog('🤖 Loading detection model:', modelPath);
453
538
  const sessionOptions = this.getSessionOptions();
454
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
+ }
455
544
  this.isModelPreloaded = true;
456
545
  this.emitReadyEvent();
457
546
  }
458
547
  else {
459
- this.debugLog('🚀 Using preloaded model');
460
- this.statusMessage = "Usando modelo precargado...";
548
+ this.debugLog('🚀 Using preloaded models');
549
+ this.statusMessage = "Usando modelos precargados...";
461
550
  this.statusColor = "#007bff";
462
551
  }
463
552
  this.statusMessage = "Configurando cámara...";
@@ -676,7 +765,9 @@ export class JaakStamps {
676
765
  corners?.forEach(corner => corner.classList.add('perfect-match'));
677
766
  if (!this.hasScreenshotTaken) {
678
767
  this.lastDetectedBox = bestBox;
679
- this.takeScreenshot();
768
+ this.takeScreenshot().catch(error => {
769
+ this.debugLog('❌ Error taking screenshot:', error);
770
+ });
680
771
  this.hasScreenshotTaken = true;
681
772
  // Reset para permitir segunda captura
682
773
  setTimeout(() => {
@@ -759,7 +850,7 @@ export class JaakStamps {
759
850
  ctx.strokeRect(x, y, w, h);
760
851
  });
761
852
  }
762
- takeScreenshot() {
853
+ async takeScreenshot() {
763
854
  if (!this.videoRef || !this.lastDetectedBox)
764
855
  return;
765
856
  // Activar animación
@@ -793,6 +884,15 @@ export class JaakStamps {
793
884
  // Captura del frente usando canvas reutilizado
794
885
  this.capturedFullFrame = this.captureCanvas.toDataURL('image/png');
795
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)
796
896
  this.captureStep = 'back';
797
897
  this.statusMessage = "Voltee la identificación y muestre la parte trasera";
798
898
  this.statusColor = "#007bff";
@@ -922,7 +1022,7 @@ export class JaakStamps {
922
1022
  this.cleanup();
923
1023
  }
924
1024
  render() {
925
- 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" })))));
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" })))));
926
1026
  }
927
1027
  static get is() { return "jaak-stamps"; }
928
1028
  static get encapsulation() { return "shadow"; }