mrz-genius 2.0.0

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.
package/src/index.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * mrz-genius
3
+ * Node.js library for MRZ (Machine Readable Zone) detection, OCR, and parsing
4
+ *
5
+ * Supports: TD1 (ID Cards), TD2, TD3 (Passports), MRVA (Visa type A), MRVB (Visa type B)
6
+ *
7
+ * Features:
8
+ * - MRZ region detection on document images
9
+ * - OCR (Optical Character Recognition) for MRZ text extraction
10
+ * - Full MRZ parsing with check digit validation
11
+ * - OCR error correction
12
+ * - BAC key generation for e-Passports
13
+ *
14
+ * @module mrz-genius
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const { parse, detectFormat, parseName, parseDate, formatDate, parseSex, parseDocumentType } = require('./parser/mrzParser');
20
+ const { calculateCheckDigit, isCheckDigitValid, isCompositeValid } = require('./parser/checkDigit');
21
+ const { correctOCR, findMatchingStrings } = require('./parser/ocrCorrector');
22
+ const { MRZ_FORMATS, getFieldPositions } = require('./parser/fieldPositions');
23
+ const { detectMRZRegion, optimizeForOCR, preprocessForOCR } = require('./detector/mrzDetector');
24
+ const { performOCR, hasMRZ, postProcessOCR, extractMRZFromFullText } = require('./ocr/mrzOCR');
25
+ const { extractWithLLM } = require('./ocr/llmExtractor');
26
+
27
+ // ──────────────────────────────────────────────────────────
28
+ // High-level API
29
+ // ──────────────────────────────────────────────────────────
30
+
31
+ /**
32
+ * Complete MRZ processing pipeline:
33
+ * 1. Detect MRZ region in image
34
+ * 2. Perform OCR on the detected region
35
+ * 3. Parse the extracted MRZ text
36
+ *
37
+ * @param {Buffer|string} image - Image buffer or file path
38
+ * @param {Object} [options] - Processing options
39
+ * @param {boolean} [options.ocrCorrection=true] - Enable OCR error correction in parser
40
+ * @param {boolean} [options.detectRegion=true] - Auto-detect MRZ region in image
41
+ * @param {string} [options.lang='eng'] - Tesseract language code
42
+ * @returns {Promise<Object>} Complete result with OCR data and parsed MRZ
43
+ */
44
+ async function processImage(image, options = {}) {
45
+ const {
46
+ ocrCorrection = true,
47
+ detectRegion = true,
48
+ lang = 'eng',
49
+ llm = null // LLM configuration object
50
+ } = options;
51
+
52
+ let ocrResult;
53
+ let useLLM = !!llm;
54
+
55
+ if (useLLM) {
56
+ try {
57
+ // Ensure image is a buffer for LLM consumption
58
+ const fs = require('fs');
59
+ const imageBuffer = Buffer.isBuffer(image) ? image : fs.readFileSync(image);
60
+ const rawLLMText = await extractWithLLM(imageBuffer, llm);
61
+
62
+ const extractedLines = extractMRZFromFullText(rawLLMText);
63
+ ocrResult = {
64
+ lines: extractedLines || rawLLMText.split('\n').filter(l => l.length > 5),
65
+ rawText: rawLLMText,
66
+ confidence: 99, // LLMs don't typically return confidence, assume high if parsable
67
+ method: `llm_${llm.provider}`
68
+ };
69
+ } catch (err) {
70
+ console.warn(`LLM extraction failed: ${err.message}. Falling back to Tesseract OCR.`);
71
+ useLLM = false;
72
+ }
73
+ }
74
+
75
+ // Fallback or Primary OCR
76
+ if (!useLLM) {
77
+ ocrResult = await performOCR(image, {
78
+ detectRegion,
79
+ lang,
80
+ });
81
+ }
82
+
83
+ // Step 3: Parse
84
+ const parsed = parse(ocrResult.lines, { ocrCorrection });
85
+
86
+ return {
87
+ success: !!parsed && parsed.valid,
88
+ ocr: {
89
+ lines: ocrResult.lines,
90
+ rawText: ocrResult.rawText,
91
+ confidence: ocrResult.confidence,
92
+ method: ocrResult.method,
93
+ },
94
+ parsed,
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Parse an MRZ string directly (no image processing)
100
+ * @param {string|string[]} mrzText - MRZ text (multi-line string or array of lines)
101
+ * @param {Object} [options] - Parser options
102
+ * @param {boolean} [options.ocrCorrection=false] - Enable OCR error correction
103
+ * @returns {Object|null} Parsed MRZ result or null
104
+ */
105
+ function parseMRZ(mrzText, options = {}) {
106
+ return parse(mrzText, options);
107
+ }
108
+
109
+ // ──────────────────────────────────────────────────────────
110
+ // Exports
111
+ // ──────────────────────────────────────────────────────────
112
+
113
+ module.exports = {
114
+ // High-level API
115
+ processImage,
116
+ parseMRZ,
117
+
118
+ // Detection
119
+ detectMRZRegion,
120
+ optimizeForOCR,
121
+ preprocessForOCR,
122
+
123
+ // OCR
124
+ performOCR,
125
+ hasMRZ,
126
+ postProcessOCR,
127
+ extractMRZFromFullText,
128
+
129
+ // Parser
130
+ parse,
131
+ detectFormat,
132
+ parseName,
133
+ parseDate,
134
+ formatDate,
135
+ parseSex,
136
+ parseDocumentType,
137
+
138
+ // Validation
139
+ calculateCheckDigit,
140
+ isCheckDigitValid,
141
+ isCompositeValid,
142
+
143
+ // OCR Correction
144
+ correctOCR,
145
+ findMatchingStrings,
146
+
147
+ // Constants
148
+ MRZ_FORMATS,
149
+ getFieldPositions,
150
+ };
@@ -0,0 +1,146 @@
1
+ 'use strict';
2
+
3
+ const https = require('https');
4
+ const http = require('http');
5
+
6
+ /**
7
+ * Perform a generic HTTP/HTTPS JSON post request
8
+ */
9
+ function jsonPost(urlStr, headers, body) {
10
+ return new Promise((resolve, reject) => {
11
+ const url = new URL(urlStr);
12
+ const lib = url.protocol === 'http:' ? http : https;
13
+
14
+ const options = {
15
+ method: 'POST',
16
+ headers: {
17
+ 'Content-Type': 'application/json',
18
+ ...headers
19
+ }
20
+ };
21
+
22
+ const req = lib.request(url, options, (res) => {
23
+ let data = '';
24
+ res.on('data', chunk => data += chunk);
25
+ res.on('end', () => {
26
+ try {
27
+ const json = JSON.parse(data);
28
+ if (res.statusCode >= 400) {
29
+ reject(new Error(`API Error ${res.statusCode}: ${JSON.stringify(json)}`));
30
+ } else {
31
+ resolve(json);
32
+ }
33
+ } catch (e) {
34
+ reject(new Error('Invalid JSON response: ' + data));
35
+ }
36
+ });
37
+ });
38
+
39
+ req.on('error', reject);
40
+ req.write(JSON.stringify(body));
41
+ req.end();
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Extract MRZ using LLM Vision APIs
47
+ * @param {Buffer} imageBuffer - Image containing MRZ
48
+ * @param {Object} llmConfig - LLM Configuration
49
+ * @returns {Promise<string>} - Extracted MRZ text
50
+ */
51
+ async function extractWithLLM(imageBuffer, llmConfig) {
52
+ if (!llmConfig || !llmConfig.provider || !llmConfig.apiKey) {
53
+ throw new Error('LLM config requires provider and apiKey');
54
+ }
55
+
56
+ const base64Image = imageBuffer.toString('base64');
57
+ const prompt = `Extract only the raw MRZ (Machine Readable Zone) text from this identity document image.
58
+ Output nothing else but the exact MRZ text lines.
59
+ Do not add markdown formatting, do not add introductions. Only the raw uppercase letters, numbers, and '<' characters.
60
+ Preserve exact spacing and newlines.`;
61
+
62
+ const provider = llmConfig.provider.toLowerCase();
63
+
64
+ // OpenAI / ChatGPT / LiteLLM Compatible
65
+ if (provider === 'chatgpt' || provider === 'openai' || provider === 'litellm') {
66
+ const baseUrl = llmConfig.baseUrl || 'https://api.openai.com/v1';
67
+ const url = `${baseUrl.replace(/\/$/, '')}/chat/completions`;
68
+ const model = llmConfig.model || 'gpt-4o-mini';
69
+
70
+ const response = await jsonPost(url, {
71
+ 'Authorization': `Bearer ${llmConfig.apiKey}`
72
+ }, {
73
+ model: model,
74
+ messages: [
75
+ {
76
+ role: 'user',
77
+ content: [
78
+ { type: 'text', text: prompt },
79
+ { type: 'image_url', image_url: { url: `data:image/jpeg;base64,${base64Image}` } }
80
+ ]
81
+ }
82
+ ],
83
+ max_tokens: 300,
84
+ temperature: 0.1
85
+ });
86
+
87
+ return response.choices[0].message.content.trim();
88
+ }
89
+
90
+ // Anthropic Claude
91
+ if (provider === 'anthropic') {
92
+ const url = 'https://api.anthropic.com/v1/messages';
93
+ const model = llmConfig.model || 'claude-3-haiku-20240307';
94
+
95
+ const response = await jsonPost(url, {
96
+ 'x-api-key': llmConfig.apiKey,
97
+ 'anthropic-version': '2023-06-01'
98
+ }, {
99
+ model: model,
100
+ messages: [
101
+ {
102
+ role: 'user',
103
+ content: [
104
+ {
105
+ type: 'image',
106
+ source: { type: 'base64', media_type: 'image/jpeg', data: base64Image }
107
+ },
108
+ { type: 'text', text: prompt }
109
+ ]
110
+ }
111
+ ],
112
+ max_tokens: 300,
113
+ temperature: 0.1
114
+ });
115
+
116
+ return response.content[0].text.trim();
117
+ }
118
+
119
+ // Google Gemini
120
+ if (provider === 'gemini') {
121
+ const model = llmConfig.model || 'gemini-1.5-flash';
122
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${llmConfig.apiKey}`;
123
+
124
+ const response = await jsonPost(url, {}, {
125
+ contents: [{
126
+ parts: [
127
+ { text: prompt },
128
+ { inline_data: { mime_type: 'image/jpeg', data: base64Image } }
129
+ ]
130
+ }],
131
+ generationConfig: {
132
+ temperature: 0.1,
133
+ maxOutputTokens: 300
134
+ }
135
+ });
136
+
137
+ const candidate = response.candidates[0].content.parts[0].text;
138
+ return candidate.trim();
139
+ }
140
+
141
+ throw new Error(`Unsupported LLM provider: ${provider}`);
142
+ }
143
+
144
+ module.exports = {
145
+ extractWithLLM
146
+ };