npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tuchsoft/ocrideu

v2.0.0

Published

Optical Character Recognition for Identity Document of the European Union

Readme

License: Apache-2.0 Copyright: Tuchsoft

Ocrideu (Optical Character Recognition for Identity Document of the European Union)

ocrideu is a JavaScript/Node.js library that extracts machine-readable information from European (EU/EEA) identity documents. It performs OCR on the Machine-Readable Zone (MRZ) and reads QR codes and 1D barcodes, following the ICAO 9303 standard (TD1 ID cards, TD2 and TD3 passports).

From the same image it also derives some properties of the document: the side (front/back), an is_document verdict with a confidence, the country (ISO 3166-1 alpha-3) and a matching per-country profile. It can optionally return a crop of the detected portrait and the processed image. Both sides of a document are supported: the image is rotated upright and cropped to the document boundaries before the data is read.

Key features include:

  • MRZ parsing: reads the ICAO 9303 Machine-Readable Zone in the TD1, TD2 and TD3 formats.
  • QR code & barcode scanning: decodes QR codes and 1D barcodes (ZXing, with Quagga2 for additional coverage), from both the optimized and the original image.
  • Side & is_document: infers the document side and whether the image is a document, each with a confidence. The side is determined from a plausible portrait (a face in the left portrait zone), not from any face.
  • Country & profiles: resolves the ISO 3166-1 alpha-3 country from the MRZ and matches a per-country document profile.
  • Portrait extraction: optionally returns the detected face photo (base64).
  • Automatic image processing: rotates the image upright, optimizes brightness/contrast, and crops to the document boundaries (only when a card-like contour is found).
  • OCR correction: applies correction logic to the raw MRZ OCR text to fix common scanning errors.
  • Robustness: a failing detector does not abort the call, and barcode decoding is protected against the underlying decoder's known failure modes.
  • Configurable: all of the above is gated by options (src/options.js), merged with the defaults.

This library does not parse human-readable information and does not perform any validation or verification of the document's authenticity. Its sole purpose is to parse the existing machine-readable data.

Table of Contents

Install

This project is available on npm.

npm i @tuchsoft/ocrideu

Usage

Import the parse function and pass it a Buffer containing the image of the identity document.

import  {parse}  from '@tuchsoft/ocrideu';
//const { parse } = require('@tuchsoft/ocrideu');
import { readFileSync } from 'fs';

async function parseDocument(imagePath) {
  try {
    // Load the image file into a Buffer
    const imageBuffer = readFileSync(imagePath);
    
    // Pass the buffer to the parse function
    const result = await parse(imageBuffer, {
      // Optional configuration options
      // See "Configuration Options" section below
    });
    
    console.log(result);
  
  } catch (error) {
    console.error('Failed to parse document:', error);
  }
}

// Example: Parse the back of an identity card
parseDocument('ocrideu/test/data/est.jpg');

The parse function automatically handles:

  • Image type detection
  • Image rotation (autorotate)
  • Image optimization (brightness, contrast)
  • Image cropping (autocrop)
  • MRZ OCR and parsing (via tesseract.js and mrz), in TD1/TD2/TD3
  • Barcode and QR code scanning (ZXing + Quagga2)
  • MRZ OCR correction (attempts to fix common OCR errors)
  • Document side + is_document inference (from the combined evidence)
  • Country + document profile resolution
  • Portrait (face photo) extraction (if enabled)

Configuration Options

You can pass an options object as the second argument to the parse function. Any options you provide will be merged with the defaults.

Default Configuration

export const defaultOptions = {
    mrz: {
        fixLines: true, //Attempt to fix the parsed lines by adding or removing "<"
        fixChar: true, //Attempt to fix char in wrong place (@see https://www.npmjs.com/package/mrz)
        fixPatterns: true, //Attempt to fix known difficul pattern (Es. "<<<k<<<")
    },
    image: {
        optimize: { //Optimization options, use false to disable
           ocr: {
            medianBlurSize: 1, //Kernel size for median blur, or false to disable.
            adaptiveThresholdBlockSize: 201, //Block size for adaptive thresholding.
            adaptiveThresholdC: 25, //Constant subtracted from the mean or weighted mean.
           },
           code: {
            medianBlurSize: false, //Kernel size for median blur, or false to disable.
            adaptiveThresholdBlockSize: 201, //Block size for adaptive thresholding.
            adaptiveThresholdC: 5 //Constant subtracted from the mean or weighted mean.
           }
        },
        autorotate: true, // Rotates the image upright (OCR will fail if not upright).
        autocrop: true,  // Autocrop the image (only when a card-like contour is found)
        addBorder: 0.05 //Add a white border of x px to the image (better ocr result)
    },
    barcode: {
        multiple: true, // Searches for multiple barcodes (slower).
        tryHard: true, // Tries the best to scan barcodes, requires `multiple` (very slow).
    },
    qr: true,
    detect: {
        face: true, // Use face detection (pico.js) as side evidence (a face implies the front).
        faceThreshold: 80, // Minimum pico score to consider a face present.
        portraitThreshold: 5, // Minimum pico score to accept a face as the document portrait (side evidence).
        portraitTrustScore: 150, // A pico score this high is trusted without the position check (ghosts stay below it).
        portraitZone: 0.55, // The portrait must sit in the left fraction of the image (right-side faces are usually hologram ghosts).
        document: true, // Compute an is_document confidence from the combined evidence.
        documentThreshold: 0.5 // Minimum confidence to report is_document.value = true.
    },
    portrait: {
        expand: 1.6 // Crop the detected face box expanded by this factor.
    },
    output: {
        hashes: true, // Include computed file hashes.
        execution: true, // Include execution time metrics.
        side: true, // Include detected document side + confidence.
        isDocument: true, // Include the is_document verdict + confidence.
        codes: true, // Include decoded QR/barcodes (grouped under `codes`).
        country: true, // Include the inferred country code (from MRZ).
        profile: true, // Include the matched document profile (country/name/matched).
        image: false, // Include the processed image (base64).
        portrait: false, // Include the detected portrait (face photo) as base64.
        detections: false // Include raw detector evidence (face/card/MRZ geometry).
    },
    debug: false /*{
        path: './debug' // Output directory for debug files.
        ocr: false //Print tesseract debug
    }*/
}

Output Format

The parse function returns a promise that resolves to a comprehensive result object.

{
  "mrz": {
    "documentCode": "ID",
    "issuingState": "EST",
    "documentNumber": "AS0002261",
    "documentNumberCheckDigit": "9",
    "birthDate": "1980-01-08T00:00:00.000Z",
    "expirationDate": "2026-06-28T00:00:00.000Z",
    "sex": "male",
    "nationality": "EST",
    "lastName": "JOEORG",
    "firstName": "JAAK KRISTJAN",
    "valid": true,
    "raw": "IDESTAS0002261938001085718<<<<\n8001081M2606288EST<<<<<<<<<<<1\nJOEORG<<JAAK<KRISTJAN<<<<<<<<<"
  },
  "country": "EST",
  "profile": { "country": "EST", "name": "Estonia", "matched": true },
  "side": { "value": "back", "confidence": 0.98 },
  "codes": {
    "qr": ["https://www2.politsei.ee/qr/?qr=AS0002261"],
    "barcode": ["38001085718"]
  },
  "is_document": {
    "value": true,
    "confidence": 0.99,
    "signals": { "mrzValid": 0.95, "card": 0.75, "codes": 0.5, "text": 0.35 }
  },
  "portrait": {
    "image": "/9j/4AAQSkZJRg...",
    "mime": "image/jpeg",
    "ext": "jpg",
    "box": { "x": 39, "y": 107, "w": 127, "h": 127 }
  },
  "image": {
    "processed": "/9j/4AAQSkZJRg...",
    "mime": "image/jpeg",
    "ext": "jpg",
    "width": 1920,
    "height": 1210
  },
  "hashes": {
    "image": { "md5": "cbbb0d0087ef5d505c049fd937e7aa2e", "sha1": "30cddf06e09868dd748e51b48c278f4cad65aeca", "crc32": "879451e2" },
    "document": { "md5": "c99cf2ff79191281b18900df672aa636", "sha1": "303e908ec6ca129b236a41d614681cacbec73af4", "crc32": "99f6eea4" }
  },
  "expired": false,
  "execution": { "duration": 1381, "start": 1763344610408, "end": 1763344611789, "id": "6d05d12a-04da-4f21-af98-e6813a007c7f" }
}
  • mrz: The parsed MRZ data from the mrz package, or null if not found.
  • country: ISO 3166-1 alpha-3 country code inferred from the MRZ, or null. Included if output.country.
  • side: { value, confidence } — detected document side. Included if output.side.
  • codes: { qr, barcode } — decoded QR codes and 1D barcodes. Included if output.codes.
  • is_document: { value, confidence, signals } — whether the image looks like an ID document. Included if output.isDocument.
  • portrait: { image (base64), mime, ext, box } — the detected face photo. Included if output.portrait.
  • image: { processed (base64), mime, ext, width, height } — the processed document. Included if output.image.
  • hashes: Hashes of the original image and the raw MRZ string. Included if output.hashes.
  • expired: true/false (or null) based on the MRZ expiration date.
  • execution: Timing and job ID. Included if output.execution.

API

parse(image: Buffer, options?: Partial<ParseOptions>): Promise<object>

The primary function exported by the library.

  • image: A Buffer containing the raw image data (e.g., from fs.readFileSync).
  • options: (Optional) A partial ParseOptions object to override default settings. See Configuration Options for details.

Returns a Promise that resolves to the Output Format object.

Maintainers

Contributing

We welcome contributions and pull requests!

For questions, bug reports, or feature requests, please open an issue on the project's GitHub repository. You can also contact the maintainers directly.

Areas for Improvement

The barcode and QR code parsing still has room to grow (recall is not 100%, and the Quagga2 pass is slow). It has been made considerably more robust and accurate in 2.0 (see CHANGELOG.md), but a faster/cleaner implementation is welcome. Contributions and pull requests are appreciated.

Known limitations

  • Document side, is_document and the portrait are heuristics built from independent evidence (MRZ, QR/barcodes, card contour, face detection) — not a trained model. They are reported with a confidence, and can be wrong on unusual documents.
  • is_document precision is limited: weak signals (a card-like contour, OCR text) can also fire on non-documents (e.g. food or landscape photos), and are hard to tell apart from genuinely weak documents. A confidence is always reported.
  • Face detection uses a small Haar-like cascade (pico.js) and can miss faces on low-quality or stylised photos; a missing face can flip the inferred side. The side logic only trusts a face as the portrait when it is high-confidence or sits in the left portrait zone.
  • Barcode and QR Code Reliability: There is no guarantee that all QR codes and barcodes will be read correctly.
  • Performance: face detection and the "try hard" barcode option add noticeable processing time (roughly 3.3 s mean per image on the benchmark machine).

Quality & benchmarks

The test/ folder contains a measure-first benchmark: a main suite of EU/EEA facsimiles, real phone photos and negatives, plus an edge-case suite of hard real-world frames (MIDV) and synthetic passports. Every change is validated against it before being kept. See docs/index.md for the current headline numbers.

Dependencies

This library relies on Tesseract.js for Optical Character Recognition (OCR), along with libraries for image manipulation (Open Cv), QR/barcode scanning (@zxing/library and @ericblade/quagga2), face detection (picojs, with a bundled cascade), MRZ parsing (mrz), among others. All required dependencies are listed in the package.json and should be installed automatically.

Jscanify

This library include a slightly modified version of Jscanify.

All credits for the Jscanify code goes to the original authors.

License

© 2024 Mattia Bonzi, TuchSoft

Licensed under the Apache License 2.0 (see LICENSE).