@tuchsoft/ocrideu
v2.0.0
Published
Optical Character Recognition for Identity Document of the European Union
Maintainers
Readme
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/ocrideuUsage
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.jsandmrz), in TD1/TD2/TD3 - Barcode and QR code scanning (ZXing + Quagga2)
- MRZ OCR correction (attempts to fix common OCR errors)
- Document side +
is_documentinference (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 themrzpackage, ornullif not found.country: ISO 3166-1 alpha-3 country code inferred from the MRZ, ornull. Included ifoutput.country.side:{ value, confidence }— detected document side. Included ifoutput.side.codes:{ qr, barcode }— decoded QR codes and 1D barcodes. Included ifoutput.codes.is_document:{ value, confidence, signals }— whether the image looks like an ID document. Included ifoutput.isDocument.portrait:{ image (base64), mime, ext, box }— the detected face photo. Included ifoutput.portrait.image:{ processed (base64), mime, ext, width, height }— the processed document. Included ifoutput.image.hashes: Hashes of the original image and the raw MRZ string. Included ifoutput.hashes.expired:true/false(ornull) based on the MRZ expiration date.execution: Timing and job ID. Included ifoutput.execution.
API
parse(image: Buffer, options?: Partial<ParseOptions>): Promise<object>
The primary function exported by the library.
image: ABuffercontaining the raw image data (e.g., fromfs.readFileSync).options: (Optional) A partialParseOptionsobject to override default settings. See Configuration Options for details.
Returns a Promise that resolves to the Output Format object.
Maintainers
- Mattia Bonzi ([email protected])
- TuchSoft ([email protected] | tuchsoft.com)
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_documentand 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_documentprecision 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).
