How to Implement an Angular MRZ Recognition Module from Scratch

Dynamsoft Label Recognizer is a JavaScript MRZ SDK, which allows developers to build web applications to recognize passport, Visa, ID card and travel documents. By combining Dynamsoft Label Recognizer and Dynamsoft Camera Enhancer, it is easy to build real-time MRZ recognition applications with a camera. This article aims to wrap Dynamsoft Label Recognizer and Dynamsoft Camera Enhancer into an Angular module, which saves time for developers to build MRZ recognition applications using Angular.

NPM Package

https://www.npmjs.com/package/ngx-mrz-sdk

Pre-requisites

Steps to Build an Angular MRZ Recognition Module

Scaffold an Angular Library Project

We use Angular CLI to create a new Angular project and add a library project to it:

ng new angular-mrz-scanner
cd angular-mrz-scanner
ng generate library ngx-mrz-sdk

The Angular app project can be used to test and debug the library project. Run the following commands respectively in two terminals to make them work together:

ng build ngx-mrz-sdk --watch
ng serve --ssl

If both of them are compiled successfully, we can change directory to the library project and start to implement the MRZ recognition module.

cd projects/ngx-mrz-sdk/src/lib

Install Dynamsoft Label Recognizer and Dynamsoft Camera Enhancer

Add dynamsoft-label-recognizer and dynamsoft-camera-enhancer to peerDependencies in the package.json file:

"peerDependencies": {
    "dynamsoft-camera-enhancer": "^3.0.1",
    "dynamsoft-label-recognizer": "^2.2.11"
  },

Then run npm install to download the two packages.

Configure License Key and Resource Path

The ngx-mrz-sdk.service.ts file exports a global accessible service, in which we can set the license key of Dynamsoft Label Recognizer and resource paths of Dynamsoft Camera Enhancer and Dynamsoft Label Recognizer.

import { Injectable, Optional } from '@angular/core';
import { LabelRecognizer } from 'dynamsoft-label-recognizer';
import { CameraEnhancer } from 'dynamsoft-camera-enhancer';

export class MrzSdkServiceConfig {
  licenseKey = '';
  dceResourcePath = '';
  dlrResourcePath = '';
}

@Injectable({
  providedIn: 'root'
})

export class NgxMrzSdkService {

  constructor(@Optional() config?: MrzSdkServiceConfig) { 
    if (config) { 
      LabelRecognizer.license = config.licenseKey;
      LabelRecognizer.engineResourcePath = config.dlrResourcePath;
      CameraEnhancer.engineResourcePath = config.dceResourcePath;
    }
  }
}

Parse MRZ Data

As MRZ strings are recognized, we need to parse them and extract detailed information. Since Dynamsoft online demo has contained the parsing logic, we don’t need to re-invent the wheel. Create a parser.ts file containing the following code:

export class MrzParser {

    static parseTwoLines(line1: string, line2: string): any {
        let mrzInfo: any = {};
        let type = line1.substring(0, 1);
        if (!(/[I|P|V]/.test(type))) return false;
        if (type === 'P') {
            mrzInfo.type = 'PASSPORT (TD-3)';
        } 
        ...
        return mrzInfo;
    };

    static parseThreeLines(line1: string, line2: string, line3: string): any {
        let mrzInfo: any = {};
        let type = line1.substring(0, 1);
        if (!(/[I|P|V]/.test(type))) return false;
        mrzInfo.type = 'ID CARD (TD-1)';
        ...
        return mrzInfo;
    }

}

Show Results on Overlay

To provider a better user experience, we can show the results on an overlay. Create an overlay.ts file to draw MRZ recognition results:

export class OverlayManager {
    overlay: HTMLCanvasElement | undefined;
    context: CanvasRenderingContext2D | undefined;

    initOverlay(overlay: HTMLCanvasElement): void {
        this.overlay = overlay;
        this.context = this.overlay.getContext('2d') as CanvasRenderingContext2D;
    }

    updateOverlay(width: number, height: number): void {
        if (this.overlay) {
            this.overlay.width = width;
            this.overlay.height = height;
            this.clearOverlay();
        }
    }

    clearOverlay(): void {
        if (this.context && this.overlay) {
            this.context.clearRect(0, 0, this.overlay.width, this.overlay.height);
            this.context.strokeStyle = '#ff0000';
            this.context.lineWidth = 5;
        }
    }

    drawOverlay(points: any, text: any): void {
        if (this.context) {
            this.context.beginPath();
            this.context.moveTo(points[0].x, points[0].y);
            this.context.lineTo(points[1].x, points[1].y);
            this.context.lineTo(points[2].x, points[2].y);
            this.context.lineTo(points[3].x, points[3].y);
            this.context.lineTo(points[0].x, points[0].y);
            this.context.stroke();
    
            this.context.font = '18px Verdana';
            this.context.fillStyle = '#ff0000';
            let x = [points[0].x, points[1].x, points[0].x, points[0].x];
            let y = [points[0].y, points[1].y, points[0].y, points[0].y];
            x.sort(function (a, b) {
                return a - b;
            });
            y.sort(function (a, b) {
                return b - a;
            });
            let left = x[0];
            let top = y[0];
    
            this.context.fillText(text, left, top);
        }
    }
}

Generate MRZ Reader and MRZ Scanner Components

We run the following commands to generate two components: ngx-mrz-reader and ngx-mrz-scanner:

ng generate component ngx-mrz-reader --skip-import
ng generate component ngx-mrz-scanner --skip-import
  • ngx-mrz-reader is used to scan MRZ from image files.

      <span id="loading-status" style="font-size:x-large" [hidden]="isLoaded">Loading Library...</span>
      <br />
    
      <input type="file" title="file" id="file" accept="image/*" (change)="onChange($event)" />
    
      <div id="imageview">
        <img id="image" alt=""/>
        <canvas id="overlay"></canvas>
      </div>
    
  • ngx-mrz-scanner is used to scan MRZ from camera.

      <div id="mrz-scanner">
        <span id="loading-status" style="font-size:x-large" [hidden]="isLoaded">Loading Library...</span>
        <br />
    
        <div>
            <label for="videoSource">Video Source</label>
            <select id="videoSource" (change)="openCamera()"></select>
        </div>
    
        <div id="videoview">
            <div class="dce-video-container" id="videoContainer"></div>
            <canvas id="overlay"></canvas>
        </div>
    </div>
    

Both of them contain @Input and @Output properties. The @Input properties are used to pass configuration to the components. The @Output properties are used to emit events to the parent component.

@Input() showOverlay: boolean;
@Output() result = new EventEmitter<any>();

constructor() {
  this.showOverlay = true;
}

Initialize Dynamsoft Label Recognizer in ngOnInit method:

ngOnInit(): void {
    this.overlayManager.initOverlay(document.getElementById('overlay') as HTMLCanvasElement);
    (async () => {
      LabelRecognizer.onResourcesLoaded = (resourcePath) => {
        this.isLoaded = true;
      };
      this.reader = await LabelRecognizer.createInstance();
      await this.reader.updateRuntimeSettingsFromString("MRZ");
      
    })();
  }

To recognize MRZ from image files, we invoke recognize() method.

this.reader.recognize(file).then((results: any) => {
  let txts: any = [];
  try {
    if (results.length > 0) {
      for (let result of results) {
        for (let line of result.lineResults) {
            txts.push(line.text);
            if (this.showOverlay) this.overlayManager.drawOverlay(line.location.points, line.text);
        }
      }
      
      let parsedResults = "";
      if (txts.length == 2) {
        parsedResults = MrzParser.parseTwoLines(txts[0], txts[1]);
      }
      else if (txts.length == 3) {
        parsedResults = MrzParser.parseThreeLines(txts[0], txts[1], txts[2]);
      }
      this.result.emit([txts.join('\n'), parsedResults]);
    } else {
      this.result.emit(txts.join(''));
    }
  } catch (e) {
    alert(e);
  }
});

Scanning MRZ from camera is also a piece of cake. What we need to do is to bind Dynamsoft Label Recognizer to Dynamsoft Camera Enhancer and thereafter receive the MRZ recognition results from a registered callback.

this.cameraEnhancer = await CameraEnhancer.createInstance();
let uiElement = document.getElementById('videoContainer');
if (uiElement) {
  await this.cameraEnhancer.setUIElement(uiElement);
}

if (this.showOverlay) {
  await this.scanner.setImageSource(this.cameraEnhancer, { resultsHighlightBaseShapes: DrawingItem });
}
else {
  await this.scanner.setImageSource(this.cameraEnhancer, {});
}

this.scanner.onImageRead = (results: any) => {
  this.overlayManager.clearOverlay();
  let txts: any = [];
  try {
    if (results.length > 0) {
      for (let result of results) {
        for (let line of result.lineResults) {
          txts.push(line.text);
          if (this.showOverlay) this.overlayManager.drawOverlay(line.location.points, line.text);
        }
      }

      let parsedResults = "";
      if (txts.length == 2) {
        parsedResults = MrzParser.parseTwoLines(txts[0], txts[1]);
      }
      else if (txts.length == 3) {
        parsedResults = MrzParser.parseThreeLines(txts[0], txts[1], txts[2]);
      }
      this.result.emit([txts.join('\n'), parsedResults]);
    } else {
      this.result.emit(txts.join(''));
    }
  } catch (e) {
    alert(e);
  }
};
this.cameraEnhancer.on("played", (playCallBackInfo: any) => {
  this.updateResolution();
});
await this.scanner.startScanning(true);

Configure *.module.ts

The ngx-mrz-sdk.module.ts file is used to export the Angular components and initialize the service.

import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { NgxMrzReaderComponent } from './ngx-mrz-reader/ngx-mrz-reader.component';
import { NgxMrzScannerComponent } from './ngx-mrz-scanner/ngx-mrz-scanner.component';
import { MrzSdkServiceConfig } from './ngx-mrz-sdk.service';

@NgModule({
  declarations: [
    NgxMrzReaderComponent,
    NgxMrzScannerComponent
  ],
  imports: [
  ],
  exports: [
    NgxMrzReaderComponent,
    NgxMrzScannerComponent
  ]
})
export class NgxMrzSdkModule { 
  constructor(@Optional() @SkipSelf() parentModule?: NgxMrzSdkModule) {
    if (parentModule) {
      throw new Error(
        'GreetingModule is already loaded. Import it in the AppModule only');
    }
  }

  static forRoot(config: MrzSdkServiceConfig): ModuleWithProviders<NgxMrzSdkModule> {
    return {
      ngModule: NgxMrzSdkModule,
      providers: [
        { provide: MrzSdkServiceConfig, useValue: config }
      ]
    };
  }
}

Test the Angular MRZ Recognition Module

  1. Install ngx-mrz-sdk package.

     npm install ngx-mrz-sdk
    
  2. Create a new Angular component in the Angular project:

     ng generate component mrz-scanner
    
  3. Include <ngx-mrz-scanner> in the mrz-scanner.component.html file.

     <div id="mrzResult">
          
     </div>
    
     <ngx-mrz-scanner
     (result)="onResultReady($event)"
     [showOverlay]="true"
     ></ngx-mrz-scanner>
    

    Inject the service and implement the MRZ callback function in mrz-scanner.component.ts:

     import { Component, OnInit } from '@angular/core';
     import { NgxMrzSdkService } from 'ngx-mrz-sdk';
    
     @Component({
       selector: 'app-mrz-scanner',
       templateUrl: './mrz-scanner.component.html',
       styleUrls: ['./mrz-scanner.component.css']
     })
     export class MrzScannerComponent implements OnInit {
       mrzResult: string = '';
       constructor(private mrzSdkService: NgxMrzSdkService) {
       }
    
       ngOnInit(): void {
       }
    
       // result = [originalValue, parsedValue]
       onResultReady(result: any): void {
         this.mrzResult = "";
         for (let i in result[1]) {
           this.mrzResult += i + ": " + result[1][i] + '\n';
         }
         // this.mrzResult = result[0];
       }
     }
    
  4. Set the license key and the resource path in app.module.ts:
     import { NgModule } from '@angular/core';
     import { BrowserModule } from '@angular/platform-browser';
    
     import { AppRoutingModule } from './app-routing.module';
     import { AppComponent } from './app.component';
     import { MrzReaderComponent } from './mrz-reader/mrz-reader.component';
     import { MrzScannerComponent } from './mrz-scanner/mrz-scanner.component';
     import { ProductListComponent } from './product-list/product-list.component';
     import { TopBarComponent } from './top-bar/top-bar.component';
     import { NgxMrzSdkModule } from 'ngx-mrz-sdk';
    
     @NgModule({
       declarations: [
         AppComponent,
         MrzReaderComponent,
         MrzScannerComponent,
         ProductListComponent,
         TopBarComponent,
       ],
       imports: [
         BrowserModule,
         AppRoutingModule,
         NgxMrzSdkModule.forRoot({ 
           licenseKey: "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ==", 
           dceResourcePath: "assets/dynamsoft-camera-enhancer", 
           dlrResourcePath: "assets/dynamsoft-label-recognizer"}),
       ],
       providers: [],
       bootstrap: [AppComponent]
     })
     export class AppModule { }
    
    
  5. Configure the asset path in angular.json:

     "assets": [
         "src/favicon.ico",
         "src/assets",
         {
           "glob": "**/*",
           "input": "./node_modules/dynamsoft-label-recognizer/dist",
           "output": "assets/dynamsoft-label-recognizer"
         },
         {
           "glob": "**/*",
           "input": "./node_modules/dynamsoft-camera-enhancer/dist",
           "output": "assets/dynamsoft-camera-enhancer"
         }
       ],
    
  6. Run the Angular MRZ scanner application:

     ng serve
    

    Angular MRZ scanner

Source Code

https://github.com/yushulx/ngx-mrz-sdk