From ee864d7a09b1bb40892afebd310882d73666abda Mon Sep 17 00:00:00 2001 From: felixindrawan Date: Wed, 28 Aug 2024 09:27:22 -0700 Subject: [PATCH] feat: General MRZ --- README.md | 10 +- assets/Music-selected.svg | 20 +- .../{passport frame.svg => mrz-guide-box.svg} | 2 +- assets/music-unselected.svg | 17 +- assets/torch-icon-close.svg | 14 + assets/torch-icon-open.svg | 16 + css/index.css | 155 ++++++-- index.html | 345 ++++++++++++------ js/const.js | 51 +++ js/define.js | 29 -- js/index.js | 149 ++++---- js/init.js | 116 +++--- js/util.js | 208 +++++++---- template.json | 287 +++++++++++---- 14 files changed, 959 insertions(+), 460 deletions(-) rename assets/{passport frame.svg => mrz-guide-box.svg} (99%) create mode 100644 assets/torch-icon-close.svg create mode 100644 assets/torch-icon-open.svg create mode 100644 js/const.js delete mode 100644 js/define.js diff --git a/README.md b/README.md index 244cff1..2af013a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # About the solution -Passport MRZ Scanner enables camera to scan the MRZ code of a passport. It will extract all data like firstname, lastname, passport number, nationality, date of birth, expiration date and personal number from the MRZ string, and converts the encoded string into human-readable fields. Welcome to visit dynamsoft's [official website](https://dynamsoft.com/capture-vision/docs/web/programming/javascript/user-guide/passport-mrz-scanner.html) to learn more about this solution. +General MRZ Scanner enables camera to scan the MRZ code of ID-cards, passports, and visas. Currently, the General MRZ Scanner supports TD-1, TD-2, TD-3, MRV-A, and MRV-B standards. It will extract all data like first name, last name, document number, nationality, date of birth, expiration date and more from the MRZ string, and converts the encoded string into human-readable fields. ## Web demo @@ -30,12 +30,12 @@ cd passport-MRZ-scanner-javascript ## Request a Trial License -The key "DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9" used in this solution serves as a test license valid for 24 hours, applicable to any newly authorized browser. To test the SDK further, you can request a 30-day free trial license via the Request a Trial License link. +The key "DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9" used in this solution (in the file `js/init.js`) serves as a test license valid for 24 hours, applicable to any newly authorized browser. To test the SDK further, you can request a 30-day free trial license via the Request a Trial License link. ## Project Structure ```text -Passport MRZ Scanner +General MRZ Scanner ├── assets │ ├── ... │ ├── ... @@ -47,7 +47,7 @@ Passport MRZ Scanner │ ├── ... │ └── ... ├── js -│ ├── define.js +│ ├── const.js │ ├── index.js │ ├── init.js │ └── util.js @@ -59,7 +59,7 @@ Passport MRZ Scanner * `/css` : This directory contains the CSS file(s) used for styling the project. * `/font` : This directory contains the font files used in the project. * `/js` : This directory contains all the JavaScript files used in the project. - * `define.js` : This file contains definitions of certain constants or variables used across the project. + * `const.js` : This file contains definitions of certain constants or variables used across the project. * `index.js`: This is the main JavaScript file where the core logic of the project is implemented. * `init.js` : This file is used for initialization purposes, such as initializing license, load resources, etc. * `util.js` : This file contains utility functions that are used across the project. diff --git a/assets/Music-selected.svg b/assets/Music-selected.svg index ff3d7a2..f440979 100644 --- a/assets/Music-selected.svg +++ b/assets/Music-selected.svg @@ -1,14 +1,6 @@ - - - - - - - - - - - - - - + + + + + + \ No newline at end of file diff --git a/assets/passport frame.svg b/assets/mrz-guide-box.svg similarity index 99% rename from assets/passport frame.svg rename to assets/mrz-guide-box.svg index caa616c..06cdc9c 100644 --- a/assets/passport frame.svg +++ b/assets/mrz-guide-box.svg @@ -133,4 +133,4 @@ - + \ No newline at end of file diff --git a/assets/music-unselected.svg b/assets/music-unselected.svg index 4b5f4be..0edab1f 100644 --- a/assets/music-unselected.svg +++ b/assets/music-unselected.svg @@ -1,10 +1,7 @@ - - - - - - - - - - + + + + + + + \ No newline at end of file diff --git a/assets/torch-icon-close.svg b/assets/torch-icon-close.svg new file mode 100644 index 0000000..6b47cce --- /dev/null +++ b/assets/torch-icon-close.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/torch-icon-open.svg b/assets/torch-icon-open.svg new file mode 100644 index 0000000..e8c0013 --- /dev/null +++ b/assets/torch-icon-open.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/css/index.css b/css/index.css index 77bbbe9..e6f065c 100644 --- a/css/index.css +++ b/css/index.css @@ -25,6 +25,18 @@ body { height: 100%; -webkit-text-size-adjust: 100%; /* Prevent font scaling in landscape while allowing user zoom */ + background-color: #2b2b2b; +} + +button { + border: none; + cursor: pointer; + color: #ffffff; + border: 0; +} + +img { + user-select: none; } .home-page { @@ -39,8 +51,9 @@ body { justify-content: space-between; align-items: center; color: #ffffff; - background-color: #323234; + background-color: #2b2b2b; padding: 30px 0; + gap: 20px; } .home-page .logo { @@ -49,8 +62,12 @@ body { } .home-page .description { - width: 80%; + width: 50%; text-align: center; + background-color: #323234; + margin: 0 auto; + padding: 2rem; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .home-page .description .title { @@ -62,7 +79,7 @@ body { font-family: OpenSans-Regular; font-size: 16px; line-height: 26px; - margin: 16px 0 25px 0; + margin: 16px 0 25px; } .home-page .description .start-btn { @@ -70,16 +87,21 @@ body { height: 6vh; min-height: 40px; max-height: 60px; - background-color: #FE8E14; + background-color: #fe8e14; font-family: Oswald-Regular; font-size: 20px; margin: 0 auto; display: flex; justify-content: center; align-items: center; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + +.home-page .description .start-btn:hover { + background-color: #fea543; } -.home-page .power { +.home-page .powered-by-msg { font-size: 16px; font-family: Oswald-Light; } @@ -97,6 +119,8 @@ body { background-color: rgb(55, 55, 55); display: flex; align-items: center; + gap: 15px; + position: relative; } .scanner-container .header .camera-selector { @@ -107,7 +131,11 @@ body { justify-content: space-around; align-items: center; padding: 0 10px; - margin-right: 15px; + cursor: pointer; +} + +.scanner-container .header .camera-selector:hover { + opacity: 0.8; } .scanner-container .header .camera-selector .camera-svg { @@ -120,27 +148,32 @@ body { } .scanner-container .header .camera-list { - width: 165px; position: absolute; top: 46px; left: 0; background-color: #000000; z-index: 1; display: none; + border-right: #aaaaaa; } .scanner-container .header .camera-list .camera-item { width: 100%; height: 40px; - color: #AAAAAA; + color: #aaaaaa; border-bottom: 1px solid rgb(50, 50, 52); font-size: 12px; - font-family: "OpenSans-Regular"; + font-family: OpenSans-Regular; line-height: 40px; padding: 0 10px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + user-select: none; + cursor: pointer; +} +.scanner-container .header .camera-list .camera-item:hover { + opacity: 0.8; } .scanner-container .header .camera-list .camera-selected { @@ -151,14 +184,27 @@ body { border: unset; } -.scanner-container .header .music { +.scanner-container .header .music-container { width: 30px; height: 30px; + cursor: pointer; + display: flex; + align-items: center; + cursor: pointer; +} + +.scanner-container .header .music-container:hover { + opacity: 0.8; +} + +.scanner-container .header .music { + width: 24px; + height: 24px; } .scanner-container .header .no-music { - width: 22px; - height: 22px; + width: 24px; + height: 24px; display: none; } @@ -181,19 +227,15 @@ body { height: 6%; min-height: 35px; max-height: 50px; - padding: 0 15px 0 30px; - background-color: #2B2B2B; + background-color: #2b2b2b; display: flex; justify-content: space-between; align-items: center; + padding: 0 15px 0 30px; } .result-container .result-header .result-title { - color: #AAAAAA; -} - -.result-container .result-header .result-restart { - color: #FE8E14; + color: #aaaaaa; } .result-container .parsed-result-area { @@ -203,11 +245,6 @@ body { overflow: auto; } -.result-container .parsed-result-area .parsed-result-header { - font-size: 18px; - margin-bottom: 30px; -} - .result-container .parsed-result-area .parsed-filed { display: flex; flex-direction: column; @@ -215,7 +252,13 @@ body { } .result-container .parsed-result-area .parsed-filed .field-name { - color: #AAAAAA; + color: #aaaaaa; +} +.result-container .parsed-result-area .parsed-filed .field-value { + word-wrap: break-word; +} +.result-container .parsed-result-area .code .field-value { + font-family: monospace; } .result-container .restart-video { @@ -226,6 +269,7 @@ body { display: flex; justify-content: center; align-items: center; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } .result-container .restart-video .btn-restart-video { @@ -238,6 +282,46 @@ body { font-family: "Oswald-Regular"; } +.scan-mode-container { + display: none; + justify-content: center; + align-items: center; + position: fixed; + bottom: 15%; + left: 5%; + right: 5%; + z-index: 2; +} + +.scan-mode-container .scan-mode-content { + position: relative; + display: flex; + justify-content: center; + align-items: center; + background-color: rgb(34, 34, 34); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + border-radius: 8px; + padding: 0.5rem; + width: max-content; + opacity: 0.8; +} + +.scan-option-btn { + background-color: transparent; + padding: 0.5rem; + font-family: OpenSans-Regular; + color: white; + flex: 1; + width: 5rem; +} + +.selected { + background-color: #fe8e14; + color: white; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} + @keyframes dce-rotate { from { transform: rotate(0turn); @@ -258,8 +342,25 @@ body { } } -@media screen and (max-width: 780px) and (orientation: landscape) { +@media screen and (max-width: 800px) { + html, + body, + .home-page { + background-color: #323234; + } + + .home-page .description { + width: 80%; + box-shadow: none; + } + + .home-page .description .start-btn { + font-size: 20px; + } +} + +@media screen and (max-width: 800px) and (orientation: landscape) { .result-container .parsed-result-area .parsed-filed { font-size: 14px; } -} \ No newline at end of file +} diff --git a/index.html b/index.html index 58596a3..a590649 100644 --- a/index.html +++ b/index.html @@ -1,160 +1,279 @@ - - - - - Passport MRZ Scanner - - - - - - - - - - - -
- -
-
Passport MRZ Scanner
-
Dynamsoft Passport MRZ Scanner recognizes the Machine-Readable Zone (MRZ) on the biographical page of a passport and converts the encoded strings into human-readable fields.
-
Scan a Passport
+ + + + Dynamsoft MRZ Scanner + + + + + + +
+ +
+
MRZ Scanner
+
+ Dynamsoft MRZ Scanner recognizes the Machine-Readable Zone (MRZ) on a passport or ID card and converts the + encoded strings into human-readable fields +
+ +
+
Powered by Dynamsoft
-
Powered by Dynamsoft
-
-
-
-
- - - - - - +
+
+
+ + + + + + + - - - down - up + + down + up +
+
+
+ music + no-music +
-
- music - no-music -
-
- - - - -
-
- -
- -
- - - + + + + + + + + +
+
+ mrz-guide +
+ +
+ + + - + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - - + C92.9,14.1,92.8,14.1,92.7,14.1z" + /> + + +
-
-
-
-
Passport Scan Results:
-
Restart >
-
-
-
-
-
+
+
+
Passport MRZ Scan Results
+
+
+
+
-
-
- +
+
+ + + +
-
- - - - - - \ No newline at end of file + + + + + diff --git a/js/const.js b/js/const.js new file mode 100644 index 0000000..915cfd1 --- /dev/null +++ b/js/const.js @@ -0,0 +1,51 @@ +// Define some global variables that will be used +let cameraList = []; +let cameraView = null; +let cameraEnhancer = null; +let cvRouter = null; +let pInit = null; // Promise of init +let isSoundOn = true; +let timer = null; + +const SCAN_MODES = ["id", "passport", "both"]; +const SCAN_TEMPLATES = { + id: "ReadId", + passport: "ReadPassport", + both: "ReadPassportAndId", +}; +let currentMode = SCAN_MODES[2]; // Set scan mode as "Scan Both" by default + +const resolutions = { + "Full HD": [1920, 1080], // Full HD + HD: [1280, 720], // HD +}; + +// Aspect Ratio of MRZ Guide Box +const MRZ_GUIDEBOX_ASPECT_RATIO = 6.73; + +// Get the UI element +const homePage = document.querySelector(".home-page"); + +const cameraViewContainer = document.querySelector(".camera-view-container"); + +const cameraListContainer = document.querySelector(".camera-list"); +const cameraSelector = document.querySelector(".camera-selector"); + +const scannerContainer = document.querySelector(".scanner-container"); + +const mrzGuideFrame = document.querySelector(".mrz-frame"); + +const resultContainer = document.querySelector(".result-container"); +const parsedResultArea = document.querySelector(".parsed-result-area"); + +const startScaningBtn = document.querySelector(".start-btn"); + +const scanModeContainer = document.querySelector(".scan-mode-container"); +const scanBothBtn = document.querySelector("#scan-both-btn"); + +const restartVideoBtn = document.querySelector(".btn-restart-video"); + +const playSoundBtn = document.querySelector(".music"); +const closeSoundBtn = document.querySelector(".no-music"); +const down = document.querySelector(".down"); +const up = document.querySelector(".up"); diff --git a/js/define.js b/js/define.js deleted file mode 100644 index e9781f3..0000000 --- a/js/define.js +++ /dev/null @@ -1,29 +0,0 @@ -// Define some global variables that will be used -let cameraList = []; -let cameraView = null; -let cvRouter = null; -let cameraEnhancer = null; -let promiseCVRReady = null; -let isPlaySound = true; -let timer = null; - -// Get the UI element -const homePage = document.querySelector(".home-page"); -const scannerContainer = document.querySelector(".scanner-container"); -const startScaningBtn = document.querySelector(".start-btn"); -const resultRestartBtn = document.querySelector(".result-restart"); -const passportFrame = document.querySelector(".passport-frame"); -const restartVideoBtn = document.querySelector(".btn-restart-video"); -const resultContainer = document.querySelector(".result-container"); -const cameraViewContainer = document.querySelector(".div-ui-container"); -const parsedResultArea = document.querySelector(".parsed-result-area"); -const parsedResultHeader = document.querySelector(".parsed-result-header"); -const parsedResultName = document.querySelector(".name"); -const parsedResultSexAndAge = document.querySelector(".sex-and-age"); -const parsedResultMain = document.querySelector(".parsed-result-main"); -const cameraListDiv = document.querySelector(".camera-list") -const cameraSelector = document.querySelector(".camera-selector"); -const playSoundBtn = document.querySelector(".music"); -const closeSoundBtn = document.querySelector(".no-music"); -const down = document.querySelector(".down"); -const up = document.querySelector(".up"); \ No newline at end of file diff --git a/js/index.js b/js/index.js index 6c2b1d7..f2f0023 100644 --- a/js/index.js +++ b/js/index.js @@ -1,29 +1,50 @@ -import { pDataLoad, cvrReady } from "./init.js"; -import { checkOrientation, getVisibleRegionOfVideo } from "./util.js" +import { init, pDataLoad } from "./init.js"; +import { judgeCurResolution, shouldShowScanModeContainer } from "./util.js"; +import { checkOrientation, getVisibleRegionOfVideo } from "./util.js"; -async function startCapturing() { +function startCapturing(mode) { try { - await (promiseCVRReady = promiseCVRReady || (async () => { + (async () => { homePage.style.display = "none"; scannerContainer.style.display = "block"; - // Open the camera after the model and .wasm files have loaded - await cvrReady; + // Open the camera after the model and .wasm files have loaded + pInit = pInit || (await init); await pDataLoad.promise; // Starts streaming the video - await cameraEnhancer.open(); + if (cameraEnhancer.isOpen()) { + await cvRouter.stopCapturing(); + await cameraView.clearAllInnerDrawingItems(); + } else { + await cameraEnhancer.open(); + } + + // Highlight the selected camera in the camera list container const currentCamera = cameraEnhancer.getSelectedCamera(); - for (let child of cameraListDiv.childNodes) { - if (currentCamera.deviceId === child.deviceId) { + const currentResolution = judgeCurResolution(cameraEnhancer.getResolution()); + cameraListContainer.childNodes.forEach((child) => { + if (currentCamera.deviceId === child.deviceId && currentResolution === child.resolution) { child.className = "camera-item camera-selected"; } - } - passportFrame.style.display = "inline-block"; + }); cameraEnhancer.setScanRegion(region()); cameraView.setScanRegionMaskVisible(false); - await cvRouter.startCapturing("ReadPassport"); - })()); + + // Show MRZ guide frame + mrzGuideFrame.style.display = "inline-block"; + + await cvRouter.startCapturing(SCAN_TEMPLATES[mode]); + + // Update button styles to show selected scan mode + document.querySelectorAll(".scan-option-btn").forEach((button) => { + button.classList.remove("selected"); + }); + document.querySelector(`#scan-${mode}-btn`).classList.add("selected"); + + currentMode = mode; + scanModeContainer.style.display = "flex"; + })(); } catch (ex) { let errMsg = ex.message || ex; console.error(errMsg); @@ -31,60 +52,55 @@ async function startCapturing() { } } -// -----------Logic for calculating scan region ↓------------ -const regionEdgeLength = () => { - if (!cameraEnhancer || !cameraEnhancer.isOpen()) return 0; - const visibleRegionInPixels = getVisibleRegionOfVideo(); - const visibleRegionWidth = visibleRegionInPixels.width; - const visibleRegionHeight = visibleRegionInPixels.height; - const regionEdgeLength = 0.4 * Math.min(visibleRegionWidth, visibleRegionHeight); - return Math.round(regionEdgeLength); -}; +SCAN_MODES.forEach((mode) => + document.querySelector(`#scan-${mode}-btn`).addEventListener("click", () => startCapturing(mode)) +); +// -----------Logic for calculating scan region ↓------------ const regionLeft = () => { if (!cameraEnhancer || !cameraEnhancer.isOpen()) return 0; const visibleRegionInPixels = getVisibleRegionOfVideo(); const currentResolution = cameraEnhancer.getResolution(); - let vw = currentResolution.width; - if (checkOrientation() === "portrait") { - vw = Math.min(currentResolution.width, currentResolution.height); - } else { - vw = Math.max(currentResolution.width, currentResolution.height); - } + + const vw = + checkOrientation() === "portrait" + ? Math.min(currentResolution.width, currentResolution.height) + : Math.max(currentResolution.width, currentResolution.height); const visibleRegionWidth = visibleRegionInPixels.width; - let left = 0.5 - regionEdgeLength() / vw / 2; + let regionCssW; - if (document.body.clientWidth > document.body.clientHeight * 6.73) { + if (document.body.clientWidth > document.body.clientHeight * MRZ_GUIDEBOX_ASPECT_RATIO) { let regionCssH = document.body.clientHeight * 0.75; - regionCssW = regionCssH * 6.73; + regionCssW = regionCssH * MRZ_GUIDEBOX_ASPECT_RATIO; } else { regionCssW = document.body.clientWidth * 0.9; } regionCssW = Math.min(regionCssW, 600); + const regionWidthInPixel = (visibleRegionWidth / document.body.clientWidth) * regionCssW; - left = ((vw - regionWidthInPixel) / 2 / vw) * 100; - left = Math.round(left); - return left; + const left = ((vw - regionWidthInPixel) / 2 / vw) * 100; + + return Math.round(left); }; const regionTop = () => { if (!cameraEnhancer || !cameraEnhancer.isOpen()) return 0; + const currentResolution = cameraEnhancer.getResolution(); - let vw = currentResolution.width; - let vh = currentResolution.height; - if (checkOrientation() === "portrait") { - vw = Math.min(currentResolution.width, currentResolution.height); - vh = Math.max(currentResolution.width, currentResolution.height); - } else { - vw = Math.max(currentResolution.width, currentResolution.height); - vh = Math.min(currentResolution.width, currentResolution.height); - } - let top = 0.5 - regionEdgeLength() / vh / 2; + + const vw = + checkOrientation() === "portrait" + ? Math.min(currentResolution.width, currentResolution.height) + : Math.max(currentResolution.width, currentResolution.height); + const vh = + checkOrientation() === "portrait" + ? Math.max(currentResolution.width, currentResolution.height) + : Math.min(currentResolution.width, currentResolution.height); + const regionWidthInPixel = vw - (regionLeft() * 2 * vw) / 100; const regionHeightInPixel = regionWidthInPixel / 4; - top = ((vh - regionHeightInPixel) / 2 / vh) * 100; - top = Math.round(top); - return top; + const top = ((vh - regionHeightInPixel) / 2 / vh) * 100; + return Math.round(top); }; const region = () => { @@ -96,52 +112,51 @@ const region = () => { isMeasuredInPercentage: true, }; return region; -} +}; // -----------Logic for calculating scan region ↑------------ -const restartVideo = async () => { - resultContainer.style.display = "none"; - await cvRouter.startCapturing("ReadPassport"); -} - window.addEventListener("click", () => { - cameraListDiv.style.display = "none"; + cameraListContainer.style.display = "none"; up.style.display = "none"; down.style.display = "inline-block"; -}) +}); // Recalculate the scan region when the window size changes window.addEventListener("resize", () => { - passportFrame.style.display = "none"; + mrzGuideFrame.style.display = "none"; timer && clearTimeout(timer); timer = setTimeout(() => { - passportFrame.style.display = "inline-block"; + shouldShowScanModeContainer(); + mrzGuideFrame.style.display = "inline-block"; cameraEnhancer.setScanRegion(region()); cameraView.setScanRegionMaskVisible(false); }, 500); -}) +}); // Add click events to buttons -startScaningBtn.addEventListener("click", startCapturing); +startScaningBtn.addEventListener("click", () => scanBothBtn.click()); +const restartVideo = async () => { + resultContainer.style.display = "none"; + document.querySelector(`#scan-${currentMode}-btn`).click(); +}; restartVideoBtn.addEventListener("click", restartVideo); -resultRestartBtn.addEventListener("click", restartVideo); cameraSelector.addEventListener("click", (e) => { e.stopPropagation(); - const isShow = cameraListDiv.style.display === "block"; - cameraListDiv.style.display = isShow ? "none" : "block"; + const isShow = cameraListContainer.style.display === "block"; + cameraListContainer.style.display = isShow ? "none" : "block"; up.style.display = !isShow ? "inline-block" : "none"; down.style.display = isShow ? "inline-block" : "none"; -}) +}); playSoundBtn.addEventListener("click", () => { playSoundBtn.style.display = "none"; closeSoundBtn.style.display = "block"; - isPlaySound = false; -}) + isSoundOn = false; +}); closeSoundBtn.addEventListener("click", () => { playSoundBtn.style.display = "block"; closeSoundBtn.style.display = "none"; - isPlaySound = true; -}) \ No newline at end of file + isSoundOn = true; +}); diff --git a/js/init.js b/js/init.js index 4508f10..271301e 100644 --- a/js/init.js +++ b/js/init.js @@ -1,25 +1,25 @@ - -import { createPendingPromise, getNeedShowFields } from "./util.js"; +import { judgeCurResolution } from "./util.js"; +import { createPendingPromise, extractDocumentFields, resultToHTMLElement, formatMRZ } from "./util.js"; // Promise variable used to control model loading state -let pDataLoad = createPendingPromise(); +const pDataLoad = createPendingPromise(); /** LICENSE ALERT - README * To use the library, you need to first specify a license key using the API "initLicense" as shown below. */ Dynamsoft.License.LicenseManager.initLicense("DLS2eyJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSJ9"); -/** - * You can visit https://www.dynamsoft.com/customer/license/trialLicense/?product=passport&utm_source=docs&package=js to get your own trial license good for 30 days. +/** + * You can visit https://www.dynamsoft.com/customer/license/trialLicense/?product=passport&utm_source=docs&package=js to get your own trial license good for 30 days. * Note that if you downloaded this sample from Dynamsoft while logged in, the above license key may already be your own 30-day trial license. * For more information, see https://www.dynamsoft.com/label-recognition/programming/javascript/user-guide.html?ver=latest#specify-the-license or contact support@dynamsoft.com. - * LICENSE ALERT - THE END + * LICENSE ALERT - THE END */ Dynamsoft.DLR.LabelRecognizerModule.onDataLoadProgressChanged = (modelPath, tag, progress) => { if (tag === "completed") { pDataLoad.resolve(); } -} +}; /** * Preloads the resources @@ -38,29 +38,50 @@ async function initDCE() { // Get the camera information of the device and render the camera list cameraList = await cameraEnhancer.getAllCameras(); for (let camera of cameraList) { - const cameraItem = document.createElement("div"); - cameraItem.className = "camera-item"; - cameraItem.innerText = camera.label; - cameraItem.deviceId = camera.deviceId; - // - cameraItem.addEventListener("click", (e) => { - e.stopPropagation(); - for (let child of cameraListDiv.childNodes) { - child.className = "camera-item"; - } - cameraItem.className = "camera-item camera-selected"; - cameraEnhancer.selectCamera(camera); - }) - cameraListDiv.appendChild(cameraItem); + for (let res of Object.keys(resolutions)) { + const cameraItem = document.createElement("div"); + cameraItem.className = "camera-item"; + cameraItem.innerText = `${camera.label} (${res})`; + cameraItem.deviceId = camera.deviceId; + cameraItem.resolution = res; + + cameraItem.addEventListener("click", async (e) => { + e.stopPropagation(); + for (let child of cameraListContainer.childNodes) { + child.className = "camera-item"; + } + cameraItem.className = "camera-item camera-selected"; + await cameraEnhancer.selectCamera(camera); + await cameraEnhancer.setResolution({ + width: resolutions[res][0], + height: resolutions[res][1], + }); + + const currentCamera = await cameraEnhancer.getSelectedCamera(); + const currentResolution = judgeCurResolution(await cameraEnhancer.getResolution()); + if (judgeCurResolution(currentResolution) !== res) { + // Update resolution to the current resolution that is supported + for (let child of cameraListContainer.childNodes) { + child.className = "camera-item"; + if (currentCamera.deviceId === child.deviceId && currentResolution === child.resolution) { + child.className = "camera-item camera-selected"; + } + } + } + // Hide options after user clicks an option + cameraSelector.click(); + }); + cameraListContainer.appendChild(cameraItem); + } } cameraView.setVideoFit("cover"); await cameraEnhancer.setResolution({ width: 1920, height: 1080 }); } /** - * Creates a CaptureVisionRouter instance and configure the task setting. + * Initialize CaptureVisionRouter, CameraEnhancer, and CameraView instance */ -let cvrReady = (async function initCVR() { +let init = (async () => { await initDCE(); cvRouter = await Dynamsoft.CVR.CaptureVisionRouter.createInstance(); await cvRouter.initSettings("./template.json"); @@ -72,39 +93,32 @@ let cvrReady = (async function initCVR() { const recognizedResults = result.textLineResultItems; const parsedResults = result.parsedResultItems; - if (recognizedResults) { - parsedResultName.innerText = ""; - parsedResultSexAndAge.innerText = ""; - parsedResultMain.innerText = ""; + if (recognizedResults?.length) { + // Play sound feedback if enabled + isSoundOn ? Dynamsoft.DCE.Feedback.beep() : null; + + parsedResultArea.innerText = ""; + + // Add MRZ Text to Result + const mrzElement = resultToHTMLElement("MRZ String", formatMRZ(recognizedResults[0]?.text)); + mrzElement.classList.add("code"); + parsedResultArea.appendChild(mrzElement); + // If a parsed result is obtained, use it to render the result page if (parsedResults) { - const parseResultInfo = getNeedShowFields(parsedResults[0]); - parsedResultName.innerText = parseResultInfo["Name"] || "Name not detected"; - const sex = parseResultInfo["Gender"] || "Sex not detected"; - const age = parseResultInfo["Age"] || "Age not detected"; - parsedResultSexAndAge.innerText = sex + ", Age: " + age; - - for (let field in parseResultInfo) { - if(["Name", "Gender", "Age"].includes(field)) continue; - const p = document.createElement("p"); - p.className = "parsed-filed"; - const spanFieldName = document.createElement("span"); - spanFieldName.className = "field-name"; - const spanValue = document.createElement("span"); - spanValue.className = "field-value"; - spanFieldName.innerText = `${field} : `; - spanValue.innerText = `${parseResultInfo[field] || 'not detected'}`; - p.appendChild(spanFieldName); - p.appendChild(spanValue); - parsedResultMain.appendChild(p); - } + const parseResultInfo = extractDocumentFields(parsedResults[0]); + Object.entries(parseResultInfo).map(([field, value]) => { + const resultElement = resultToHTMLElement(field, value); + parsedResultArea.appendChild(resultElement); + }); } else { - alert(`Failed to parse the content. The MRZ text ${needShowTextLines}.`); + alert(`Failed to parse the content.`); parsedResultArea.style.justifyContent = "flex-start"; } - isPlaySound ? Dynamsoft.DCE.Feedback.beep() : null; resultContainer.style.display = "flex"; - cameraListDiv.style.display = "none"; + cameraListContainer.style.display = "none"; + scanModeContainer.style.display = "none"; + cvRouter.stopCapturing(); cameraView.clearAllInnerDrawingItems(); } @@ -112,4 +126,4 @@ let cvrReady = (async function initCVR() { await cvRouter.addResultReceiver(resultReceiver); })(); -export { pDataLoad, cvrReady } +export { pDataLoad, init }; diff --git a/js/util.js b/js/util.js index bd3e6f2..8dee534 100644 --- a/js/util.js +++ b/js/util.js @@ -1,3 +1,8 @@ +/** + * Creates a pending promise. Used to keep track of library loading progress + * + * @returns {Object} An object containing the promise, resolve, and reject functions. + */ export function createPendingPromise() { let resolve, reject; const promise = new Promise((res, rej) => { @@ -8,101 +13,76 @@ export function createPendingPromise() { return { promise, resolve, reject }; } -export function getNeedShowFields(result) { +/** + * Extracts and returns document fields from the parsed MRZ result + * + * @param {Object} result - The parsed result object containing document fields. + * @returns {Object} An object with key-value pairs of the extracted fields. + */ +export function extractDocumentFields(result) { const parseResultInfo = {}; if (!result.exception) { - let name = result.getFieldValue("name"); - parseResultInfo['Name'] = name; - - let gender = result.getFieldValue("sex"); - parseResultInfo["Gender"] = gender; - - let birthYear = result.getFieldValue("birthYear"); - let birthMonth = result.getFieldValue("birthMonth"); - let birthDay = result.getFieldValue("birthDay"); - if (parseInt(birthYear) > (new Date().getFullYear() % 100)) { - birthYear = "19" + birthYear; - } else { - birthYear = "20" + birthYear; - } - if(isNaN(parseInt(birthYear))) { - parseResultInfo["Age"] = undefined; - } else { - let age = new Date().getUTCFullYear() - parseInt(birthYear); - parseResultInfo["Age"] = age; - } - let documentNumber = result.getFieldValue("passportNumber"); - parseResultInfo['Document Number'] = documentNumber; - - let issuingState = result.getFieldValue("issuingState"); - parseResultInfo['Issuing State'] = issuingState; - - let nationality = result.getFieldValue("nationality"); - parseResultInfo['Nationality'] = nationality; - - parseResultInfo['Date of Birth (YYYY-MM-DD)'] = birthYear + "-" + birthMonth + "-" + birthDay; - - let expiryYear = result.getFieldValue("expiryYear"); - let expiryMonth = result.getFieldValue("expiryMonth"); - let expiryDay = result.getFieldValue("expiryDay"); - if (parseInt(expiryYear) >= 60) { - expiryYear = "19" + expiryYear; - } else { - expiryYear = "20" + expiryYear; - } - parseResultInfo["Date of Expiry (YYYY-MM-DD)"] = expiryYear + "-" + expiryMonth + "-" + expiryDay; - - let personalNumber = result.getFieldValue("personalNumber"); - parseResultInfo["Personal Number"] = personalNumber; - - let primaryIdentifier = result.getFieldValue("primaryIdentifier"); - parseResultInfo["Primary Identifier(s)"] = primaryIdentifier; - - let secondaryIdentifier = result.getFieldValue("secondaryIdentifier"); - parseResultInfo["Secondary Identifier(s)"] = secondaryIdentifier; + const type = result.getFieldValue("documentCode"); + const documentType = JSON.parse(result.jsonString).CodeType; + const birthYear = result.getFieldValue("birthYear"); + const birthYearBase = parseInt(birthYear) > new Date().getFullYear() % 100 ? "19" : "20"; + const fullBirthYear = `${birthYearBase}${birthYear}`; + + const expiryYear = result.getFieldValue("expiryYear"); + const expiryYearBase = parseInt(expiryYear) >= 60 ? "19" : "20"; + const fullExpiryYear = `${expiryYearBase}${expiryYear}`; + + parseResultInfo["Document Type"] = documentType; + parseResultInfo["Issuing State"] = result.getFieldValue("issuingState"); + parseResultInfo["Surname"] = result.getFieldValue("primaryIdentifier"); + parseResultInfo["Given Name"] = result.getFieldValue("secondaryIdentifier"); + parseResultInfo["Document Number"] = + type === "P" ? result.getFieldValue("passportNumber") : result.getFieldValue("documentNumber"); + parseResultInfo["Nationality"] = result.getFieldValue("nationality"); + parseResultInfo["Sex"] = result.getFieldValue("sex"); + parseResultInfo["Date of Birth (YYYY-MM-DD)"] = + fullBirthYear + "-" + result.getFieldValue("birthMonth") + "-" + result.getFieldValue("birthDay"); + parseResultInfo["Date of Expiry (YYYY-MM-DD)"] = + fullExpiryYear + "-" + result.getFieldValue("expiryMonth") + "-" + result.getFieldValue("expiryDay"); } return parseResultInfo; } +/** + * Checks and returns the current screen orientation. + * + * @returns {string} The current screen orientation ('portrait' or 'landscape'). + */ export function checkOrientation() { if (window.matchMedia("(orientation: portrait)").matches) { - return 'portrait'; + return "portrait"; } else if (window.matchMedia("(orientation: landscape)").matches) { - return 'landscape'; + return "landscape"; } } export function getVisibleRegionOfVideo() { - if(!cameraView || !cameraView.getVideoElement()) return; + if (!cameraView || !cameraView.getVideoElement()) return; const video = cameraView.getVideoElement(); - let width = video.videoWidth; - let height = video.videoHeight; - let objectFit = cameraView.getVideoFit(); + const { videoWidth, videoHeight } = video; + const objectFit = cameraView.getVideoFit(); + // Adjust dimensions based on orientation const isPortrait = checkOrientation() === "portrait"; - let _width = width; - let _height = height; - if (isPortrait) { - _width = Math.min(width, height); - _height = Math.max(width, height); - } else { - _width = Math.max(width, height); - _height = Math.min(width, height); - } - width = _width; - height = _height; + const width = isPortrait ? Math.min(videoWidth, videoHeight) : Math.max(videoWidth, videoHeight); + const height = isPortrait ? Math.max(videoWidth, videoHeight) : Math.min(videoWidth, videoHeight); - const { width: videoCSSWidth, height: videoCSSHeight } = - cameraView._innerComponent.getBoundingClientRect(); + // Get the CSS dimensions of the video element + const { width: videoCSSWidth, height: videoCSSHeight } = cameraView._innerComponent.getBoundingClientRect(); if (videoCSSWidth <= 0 || videoCSSHeight <= 0) { - throw new Error( - `Unable to get video dimensions. Video may not be rendered on the page.` - ); + throw new Error(`Unable to get video dimensions. Video may not be rendered on the page.`); } const videoCSSWHRatio = videoCSSWidth / videoCSSHeight, videoWHRatio = width / height; let cssScaleRatio; + + // Set visible region in pixels const regionInPixels = { x: 0, y: 0, @@ -115,9 +95,7 @@ export function getVisibleRegionOfVideo() { if (videoCSSWHRatio < videoWHRatio) { // a part of length is invisible cssScaleRatio = videoCSSHeight / height; - regionInPixels.x = Math.floor( - (width - videoCSSWidth / cssScaleRatio) / 2 - ); + regionInPixels.x = Math.floor((width - videoCSSWidth / cssScaleRatio) / 2); regionInPixels.y = 0; regionInPixels.width = Math.ceil(videoCSSWidth / cssScaleRatio); regionInPixels.height = height; @@ -125,12 +103,84 @@ export function getVisibleRegionOfVideo() { // a part of height is invisible cssScaleRatio = videoCSSWidth / width; regionInPixels.x = 0; - regionInPixels.y = Math.floor( - (height - videoCSSHeight / cssScaleRatio) / 2 - ); + regionInPixels.y = Math.floor((height - videoCSSHeight / cssScaleRatio) / 2); regionInPixels.width = width; regionInPixels.height = Math.ceil(videoCSSHeight / cssScaleRatio); } } return regionInPixels; -} \ No newline at end of file +} + +/** + * Create an HTML paragraph element containing the document field name and value. + * + * @param {string} field - The document field name. + * @param {string} value - The document field value. + * @returns {HTMLElement} The paragraph element containing the formatted document field name and value. + */ +export function resultToHTMLElement(field, value) { + const p = document.createElement("p"); + p.className = "parsed-filed"; + const spanFieldName = document.createElement("span"); + spanFieldName.className = "field-name"; + const spanValue = document.createElement("span"); + spanValue.className = "field-value"; + + spanFieldName.innerText = `${field} : `; + spanValue.innerText = `${value || "Not detected"}`; + + p.appendChild(spanFieldName); + p.appendChild(spanValue); + + return p; +} + +/** + * Formats a Machine Readable Zone (MRZ) string by adding line breaks based on its length. + * + * @param {string} [mrzString=""] - The MRZ string to format. + * @returns {string} The formatted MRZ string with appropriate line breaks or the original string + */ +export function formatMRZ(mrzString = "") { + let formattedMRZ = mrzString; + + // Check if the length matches any known MRZ format + if (mrzString.length === 88) { + // Passport (TD3 format) + formattedMRZ = mrzString.slice(0, 44) + "\n" + mrzString.slice(44); + } else if (mrzString.length === 90) { + // ID card (TD1 format) + formattedMRZ = mrzString.slice(0, 30) + "\n" + mrzString.slice(30, 60) + "\n" + mrzString.slice(60); + } else if (mrzString.length === 72) { + // Visa (TD2 format) + formattedMRZ = mrzString.slice(0, 36) + "\n" + mrzString.slice(36); + } + + return formattedMRZ; +} + +/** Check if current resolution is Full HD or HD + * @params {Object} currentResolution - an object with `width` and `height` to represent the current resolution of the camera + * @returns {string} Either "HD" or "Full HD" depending of the resolution of the screen + */ +export const judgeCurResolution = (currentResolution) => { + const { width, height } = currentResolution; + const minValue = Math.min(width, height); + const maxValue = Math.max(width, height); + + if (minValue > 480 && minValue < 960 && maxValue > 960 && maxValue < 1440) { + return "HD"; + } else if (minValue > 900 && minValue < 1440 && maxValue > 1400 && maxValue < 2160) { + return "Full HD"; + } +}; + +/** + * Checks if we should show the switch scan mode buttons + * @returns true if cameraEnhancer is open, false otherwise + */ +export function shouldShowScanModeContainer() { + const isHomepageClosed = homePage.style.display === "none"; + const isResultClosed = resultContainer.style.display === "none" || resultContainer.style.display === ""; + scanModeContainer.style.display = isHomepageClosed && isResultClosed ? "flex" : "none"; +} diff --git a/template.json b/template.json index 54a174d..2704d9c 100644 --- a/template.json +++ b/template.json @@ -1,79 +1,226 @@ { - "CaptureVisionTemplates" : - [ + "CaptureVisionTemplates": [ { - "Name" : "ReadPassport", - "ImageROIProcessingNameArray" : - [ - "ROI_OriginalImage" + "Name": "ReadPassportAndId", + "OutputOriginalImage": 0, + "ImageROIProcessingNameArray": [ + "roi-passport-and-id" ], - "SemanticProcessingNameArray": [ "SP_Passport" ], - "OutputOriginalImage": 1 + "SemanticProcessingNameArray": ["sp-passport-and-id"], + "Timeout": 2000 + }, + { + "Name": "ReadPassport", + "OutputOriginalImage": 0, + "ImageROIProcessingNameArray": [ + "roi-passport" + ], + "SemanticProcessingNameArray": ["sp-passport"], + "Timeout": 2000 + }, + { + "Name": "ReadId", + "OutputOriginalImage": 0, + "ImageROIProcessingNameArray": [ + "roi-id" + ], + "SemanticProcessingNameArray": ["sp-id"], + "Timeout": 2000 } ], - "LabelRecognizerTaskSettingOptions": [ + "TargetROIDefOptions": [ { - "Name": "Task_RecognizeMRZonPassport", - "TextLineSpecificationNameArray": [ - "TLS_Passport" - ], - "SectionImageParameterArray": [ - { - "Section": "ST_TEXT_LINE_LOCALIZATION", - "ImageParameterName": "IP_RecognizePassport" - } + "Name": "roi-passport-and-id", + "TaskSettingNameArray": [ + "task-passport-and-id" + ] + }, + { + "Name": "roi-passport", + "TaskSettingNameArray": [ + "task-passport" + ] + }, + { + "Name": "roi-id", + "TaskSettingNameArray": [ + "task-id" ] } ], "TextLineSpecificationOptions": [ { - "Name": "TLS_Template", + "Name": "tls-mrz-passport", + "BaseTextLineSpecificationName": "tls-base", + "StringLengthRange": [ 44, 44 ], + "OutputResults": 1, + "ExpectedGroupsCount": 1, + "ConcatResults": 1, + "ConcatSeparator": "", + "SubGroups": [ + { + "StringRegExPattern": "(P[A-Z<][A-Z<]{3}[A-Z<]{39}){(44)}", + "StringLengthRange": [ 44, 44 ], + "BaseTextLineSpecificationName": "tls-base" + }, + { + "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{14}[0-9<][0-9]){(44)}", + "StringLengthRange": [ 44, 44 ], + "BaseTextLineSpecificationName": "tls-base" + } + ] + }, + { + "Name": "tls-mrz-id-td2", + "BaseTextLineSpecificationName": "tls-base", + "StringLengthRange": [ 36, 36 ], + "OutputResults": 1, + "ExpectedGroupsCount": 1, + "ConcatResults": 1, + "ConcatSeparator": "", + "SubGroups": [ + { + "StringRegExPattern": "([ACI][A-Z<][A-Z<]{3}[A-Z<]{31}){(36)}", + "StringLengthRange": [ 36, 36 ], + "BaseTextLineSpecificationName": "tls-base" + }, + { + "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{8}){(36)}", + "StringLengthRange": [ 36, 36 ], + "BaseTextLineSpecificationName": "tls-base" + } + ] + }, + { + "Name": "tls-mrz-id-td1", + "BaseTextLineSpecificationName": "tls-base", + "StringLengthRange": [ 30, 30 ], + "OutputResults": 1, + "ExpectedGroupsCount": 1, + "ConcatResults": 1, + "ConcatSeparator": "", + "SubGroups": [ + { + "StringRegExPattern": "([ACI][A-Z<][A-Z<]{3}[A-Z0-9<]{9}[0-9][A-Z0-9<]{15}){(30)}", + "StringLengthRange": [ 30, 30 ], + "BaseTextLineSpecificationName": "tls-base" + }, + { + "StringRegExPattern": "([0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z<]{3}[A-Z0-9<]{11}[0-9]){(30)}", + "StringLengthRange": [ 30, 30 ], + "BaseTextLineSpecificationName": "tls-base" + }, + { + "StringRegExPattern": "([A-Z<]{30}){(30)}", + "StringLengthRange": [ 30, 30 ], + "BaseTextLineSpecificationName": "tls-base" + } + ] + }, + { + "Name": "tls-base", "CharacterModelName": "MRZ", "CharHeightRange": [ 5, 1000, 1 ], - "ConfusableCharactersCorrection":{ - "ConfusableCharacters":[["0","O"],["1","I"],["5","S"]], - "FontNameArray":["OCR_B"] - }, "BinarizationModes": [ { "BlockSizeX": 30, "BlockSizeY": 30, "Mode": "BM_LOCAL_BLOCK", - "MorphOperation": "Close" + "EnableFillBinaryVacancy": 0, + "ThresholdCompensation": 15 + } + ], + "ConfusableCharactersCorrection": { + "ConfusableCharacters": [ + [ "0", "O" ], + [ "1", "I" ], + [ "5", "S" ] + ], + "FontNameArray": [ "OCR_B" ] + } + } + ], + "LabelRecognizerTaskSettingOptions": [ + { + "Name": "task-passport", + "ConfusableCharactersPath": "ConfusableChars.data", + "TextLineSpecificationNameArray": ["tls-mrz-passport"], + "SectionImageParameterArray": [ + { + "Section": "ST_REGION_PREDETECTION", + "ImageParameterName": "ip-mrz" + }, + { + "Section": "ST_TEXT_LINE_LOCALIZATION", + "ImageParameterName": "ip-mrz" + }, + { + "Section": "ST_TEXT_LINE_RECOGNITION", + "ImageParameterName": "ip-mrz" } ] }, { - "Name": "TLS_Passport", - "BaseTextLineSpecificationName": "TLS_Template", - "OutputResults": 1, - "ConcatResults": 1, - "SubGroups": [ + "Name": "task-id", + "ConfusableCharactersPath": "ConfusableChars.data", + "TextLineSpecificationNameArray": ["tls-mrz-id-td1", "tls-mrz-id-td2"], + "SectionImageParameterArray": [ { - "StringRegExPattern": "(P[A-Z<][A-Z<]{3}[A-Z<]{39}){(44)}", - "StringLengthRange": [ 44, 44 ], - "BaseTextLineSpecificationName": "TLS_Template", - "TextLinesCount": 1 + "Section": "ST_REGION_PREDETECTION", + "ImageParameterName": "ip-mrz" }, { - "StringRegExPattern": "([A-Z0-9<]{9}[0-9][A-Z<]{3}[0-9]{2}[(01-12)][(01-31)][0-9][MF<][0-9]{2}[(01-12)][(01-31)][0-9][A-Z0-9<]{14}[0-9<][0-9]){(44)}", - "StringLengthRange": [ 44, 44 ], - "BaseTextLineSpecificationName": "TLS_Template", - "TextLinesCount": 1 + "Section": "ST_TEXT_LINE_LOCALIZATION", + "ImageParameterName": "ip-mrz" + }, + { + "Section": "ST_TEXT_LINE_RECOGNITION", + "ImageParameterName": "ip-mrz" } ] + }, + { + "Name": "task-passport-and-id", + "ConfusableCharactersPath": "ConfusableChars.data", + "TextLineSpecificationNameArray": ["tls-mrz-passport", "tls-mrz-id-td1", "tls-mrz-id-td2"], + "SectionImageParameterArray": [ + { + "Section": "ST_REGION_PREDETECTION", + "ImageParameterName": "ip-mrz" + }, + { + "Section": "ST_TEXT_LINE_LOCALIZATION", + "ImageParameterName": "ip-mrz" + }, + { + "Section": "ST_TEXT_LINE_RECOGNITION", + "ImageParameterName": "ip-mrz" + } + ] + } + ], + "CharacterModelOptions": [ + { + "DirectoryPath": "", + "Name": "MRZ" } ], - "ImageParameterOptions" : - [ + "ImageParameterOptions": [ { - "Name" : "IP_RecognizePassport", + "Name": "ip-mrz", "TextureDetectionModes": [ { "Mode": "TDM_GENERAL_WIDTH_CONCENTRATION", "Sensitivity": 8 } ], + "BinarizationModes": [ + { + "EnableFillBinaryVacancy": 0, + "ThresholdCompensation": 21, + "Mode": "BM_LOCAL_BLOCK" + } + ], "TextDetectionMode": { "Mode": "TTDM_LINE", "CharHeightRange": [ 5, 1000, 1 ], @@ -82,41 +229,53 @@ } } ], - "TargetROIDefOptions" : - [ + "SemanticProcessingOptions": [ { - "Name" : "ROI_OriginalImage", - "TaskSettingNameArray" : - [ - "Task_RecognizeMRZonPassport" + "Name": "sp-passport-and-id", + "ReferenceObjectFilter": { + "ReferenceTargetROIDefNameArray": [ + "roi-passport-and-id" + ] + }, + "TaskSettingNameArray": [ + "dcp-passport-and-id" ] - } - ], - "CharacterModelOptions": [ + }, { - "Name" : "MRZ" - } - ], - "SemanticProcessingOptions": [ + "Name": "sp-passport", + "ReferenceObjectFilter": { + "ReferenceTargetROIDefNameArray": [ + "roi-passport" + ] + }, + "TaskSettingNameArray": [ + "dcp-passport" + ] + }, { - "Name": "SP_Passport", + "Name": "sp-id", "ReferenceObjectFilter": { - "ReferenceTargetROIDefNameArray": ["ROI_OriginalImage"], - "AtomicResultTypeArray" : ["ART_TEXT_LINE"] + "ReferenceTargetROIDefNameArray": [ + "roi-id" + ] }, "TaskSettingNameArray": [ - "ParsePassport" + "dcp-id" ] } ], "CodeParserTaskSettingOptions": [ { - "Name": "ParsePassport", - "CodeSpecifications": ["MRTD_TD3_PASSPORT"] + "Name": "dcp-passport", + "CodeSpecifications": [ "MRTD_TD3_PASSPORT" ] + }, + { + "Name": "dcp-id", + "CodeSpecifications": [ "MRTD_TD1_ID", "MRTD_TD2_ID" ] + }, + { + "Name": "dcp-passport-and-id", + "CodeSpecifications": [ "MRTD_TD3_PASSPORT", "MRTD_TD1_ID", "MRTD_TD2_ID" ] } - ], - "GlobalParameter" : - { - "MaxTotalImageDimension" : 0 - } + ] } \ No newline at end of file