monocr 0.1.2 → 0.1.4
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/README.md +33 -75
- package/bin/monocr.js +4 -4
- package/package.json +1 -1
- package/src/charset.txt +1 -0
- package/src/model-manager.js +52 -67
- package/src/monocr.js +10 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# monocr
|
|
1
|
+
# monocr
|
|
2
2
|
|
|
3
|
-
Mon language OCR
|
|
3
|
+
Mon language (mnw) OCR for Node.js.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -13,107 +13,65 @@ npm install monocr
|
|
|
13
13
|
```javascript
|
|
14
14
|
const { read_image } = require("monocr");
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
const text = await read_image("
|
|
16
|
+
// Automatically downloads model on first run
|
|
17
|
+
const text = await read_image("image.jpg");
|
|
18
18
|
console.log(text);
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## API
|
|
22
22
|
|
|
23
|
-
### read_image(imagePath, [modelPath], [charsetPath])
|
|
23
|
+
### `read_image(imagePath, [modelPath], [charsetPath])`
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Recognizes text from an image file.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
- `imagePath` (string): Path to image file.
|
|
28
|
+
- `modelPath` (string, optional): Path to ONNX model. Defaults to `~/.monocr/models/monocr.onnx`.
|
|
29
|
+
- `charsetPath` (string, optional): Path to charset file. Defaults to bundled charset.
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
- `modelPath` - (Optional) Path to ONNX model. Defaults to auto-downloaded model.
|
|
31
|
-
- `charsetPath` - (Optional) Path to charset file. Defaults to auto-downloaded file.
|
|
31
|
+
Returns: `Promise<string>`
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
### `read_pdf(pdfPath, [modelPath], [charsetPath])`
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
Recognizes text from a PDF file.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
- `pdfPath` (string): Path to PDF file.
|
|
38
|
+
- `modelPath` (string, optional): As above.
|
|
39
|
+
- `charsetPath` (string, optional): As above.
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
Returns: `Promise<string[]>` (Array of text per page)
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
- `modelPath` - Path to ONNX model (optional)
|
|
43
|
-
- `charsetPath` - Path to charset file (optional)
|
|
43
|
+
### `read_image_with_accuracy(imagePath, groundTruth, [modelPath], [charsetPath])`
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Recognizes text and calculates accuracy against ground truth.
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
- `imagePath` (string): Path to image file.
|
|
48
|
+
- `groundTruth` (string): Expected text.
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
Returns: `Promise<{text: string, accuracy: number}>`
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
## CLI Usage
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
- `groundTruth` - Expected text for accuracy calculation
|
|
55
|
-
- `modelPath` - Path to ONNX model (optional)
|
|
56
|
-
- `charsetPath` - Path to charset file (optional)
|
|
57
|
-
|
|
58
|
-
**Returns:** `Promise<{text: string, accuracy: number}>` - Text and accuracy percentage
|
|
59
|
-
|
|
60
|
-
### MonOCR Class
|
|
61
|
-
|
|
62
|
-
For advanced usage, use the `MonOCR` class directly:
|
|
63
|
-
|
|
64
|
-
```javascript
|
|
65
|
-
const { MonOCR } = require("monocr");
|
|
66
|
-
|
|
67
|
-
const ocr = new MonOCR("model.onnx", "charset.txt");
|
|
68
|
-
await ocr.init();
|
|
69
|
-
|
|
70
|
-
// Single line
|
|
71
|
-
const text = await ocr.predictLine(imageSource);
|
|
72
|
-
|
|
73
|
-
// Full page (with line segmentation)
|
|
74
|
-
const results = await ocr.predictPage(imagePath);
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## CLI
|
|
78
|
-
|
|
79
|
-
The package includes a command-line tool:
|
|
54
|
+
The package includes a `monocr` command-line tool.
|
|
80
55
|
|
|
81
56
|
```bash
|
|
82
|
-
#
|
|
83
|
-
monocr
|
|
84
|
-
|
|
85
|
-
# PDF файл
|
|
86
|
-
monocr pdf path/to/document.pdf
|
|
87
|
-
|
|
88
|
-
# Batch processing
|
|
89
|
-
monocr batch path/to/images/ -o results.json
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Examples
|
|
93
|
-
|
|
94
|
-
See the `examples/` directory for detailed usage examples:
|
|
57
|
+
# Download model to cache (optional, happens automatically on first use)
|
|
58
|
+
monocr download
|
|
95
59
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
- `batch.js` - Batch processing
|
|
60
|
+
# Recognize single image
|
|
61
|
+
monocr image input.jpg
|
|
99
62
|
|
|
100
|
-
|
|
63
|
+
# Recognize PDF
|
|
64
|
+
monocr pdf document.pdf
|
|
101
65
|
|
|
102
|
-
|
|
103
|
-
|
|
66
|
+
# Batch process directory
|
|
67
|
+
monocr batch ./images -o results.json
|
|
104
68
|
```
|
|
105
69
|
|
|
106
70
|
## Model Files
|
|
107
71
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
You can also trigger a manual download:
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
monocr download
|
|
114
|
-
```
|
|
72
|
+
The ONNX model (`monocr.onnx`) is downloaded automatically to `~/.monocr/models/` on first use. The charset file is bundled with the package.
|
|
115
73
|
|
|
116
|
-
|
|
74
|
+
To use a custom model, provide the `modelPath` argument to the API functions or CLI.
|
|
117
75
|
|
|
118
76
|
## License
|
|
119
77
|
|
package/bin/monocr.js
CHANGED
|
@@ -14,7 +14,7 @@ program
|
|
|
14
14
|
.command('image <path>')
|
|
15
15
|
.description('Recognize text from an image file')
|
|
16
16
|
.option('-m, --model <path>', 'Path to ONNX model (optional, auto-downloads)')
|
|
17
|
-
.option('-c, --charset <path>', 'Path to charset file (optional
|
|
17
|
+
.option('-c, --charset <path>', 'Path to charset file (optional)')
|
|
18
18
|
.action(async (imagePath, options) => {
|
|
19
19
|
try {
|
|
20
20
|
const text = await read_image(imagePath, options.model, options.charset);
|
|
@@ -29,7 +29,7 @@ program
|
|
|
29
29
|
.command('pdf <path>')
|
|
30
30
|
.description('Recognize text from a PDF file')
|
|
31
31
|
.option('-m, --model <path>', 'Path to ONNX model (optional, auto-downloads)')
|
|
32
|
-
.option('-c, --charset <path>', 'Path to charset file (optional
|
|
32
|
+
.option('-c, --charset <path>', 'Path to charset file (optional)')
|
|
33
33
|
.action(async (pdfPath, options) => {
|
|
34
34
|
try {
|
|
35
35
|
const pages = await read_pdf(pdfPath, options.model, options.charset);
|
|
@@ -48,7 +48,7 @@ program
|
|
|
48
48
|
.command('batch <directory>')
|
|
49
49
|
.description('Process all images in a directory')
|
|
50
50
|
.option('-m, --model <path>', 'Path to ONNX model (optional, auto-downloads)')
|
|
51
|
-
.option('-c, --charset <path>', 'Path to charset file (optional
|
|
51
|
+
.option('-c, --charset <path>', 'Path to charset file (optional)')
|
|
52
52
|
.option('-o, --output <path>', 'Output file for results (optional)')
|
|
53
53
|
.action(async (directory, options) => {
|
|
54
54
|
try {
|
|
@@ -89,7 +89,7 @@ program
|
|
|
89
89
|
try {
|
|
90
90
|
const { MonOCR } = require('../src/index');
|
|
91
91
|
const ocr = new MonOCR();
|
|
92
|
-
await ocr.modelManager.
|
|
92
|
+
await ocr.modelManager.downloadModel();
|
|
93
93
|
} catch (err) {
|
|
94
94
|
console.error('Error:', err.message);
|
|
95
95
|
process.exit(1);
|
package/package.json
CHANGED
package/src/charset.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ကခဂဃငစဆဇဈဉညဋဌဍဎဏတထဒဓနပဖဗဘမယရလဝသဟဠအဢဣဤဥဦဧဨဩဪါာိီုူေဲဳဴဵံ့း္်ျြွှဿ၀၁၂၃၄၅၆၇၈၉၊။၌၍၎၏ၐၑၓၚၛၜၝၞၟၠၡၢၣၤၥၨၪၰၱၲၳၴၵၷၸၹၺၻၼၾၿႀႄႅႆႇႈႉႊႏ႐႒႓႔႕႘႙ႜႝ႟
|
package/src/model-manager.js
CHANGED
|
@@ -9,12 +9,10 @@ class ModelManager {
|
|
|
9
9
|
// Default cache directory in user's home
|
|
10
10
|
this.cacheDir = path.join(os.homedir(), '.monocr', 'models');
|
|
11
11
|
|
|
12
|
-
// HuggingFace model
|
|
12
|
+
// HuggingFace model URL
|
|
13
13
|
this.baseUrl = 'https://huggingface.co/janakhpon/monocr/resolve/main';
|
|
14
|
-
this.
|
|
15
|
-
|
|
16
|
-
charset: 'charset.txt'
|
|
17
|
-
};
|
|
14
|
+
this.modelFileName = 'monocr.onnx';
|
|
15
|
+
this.hfModelPath = 'onnx/monocr.onnx';
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
/**
|
|
@@ -27,19 +25,17 @@ class ModelManager {
|
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
/**
|
|
30
|
-
* Get local path for
|
|
28
|
+
* Get local path for the model
|
|
31
29
|
*/
|
|
32
|
-
|
|
33
|
-
return path.join(this.cacheDir,
|
|
30
|
+
getModelPath() {
|
|
31
|
+
return path.join(this.cacheDir, this.modelFileName);
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
/**
|
|
37
|
-
* Check if model
|
|
35
|
+
* Check if model exists locally
|
|
38
36
|
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const charsetPath = this.getLocalPath('charset');
|
|
42
|
-
return fs.existsSync(modelPath) && fs.existsSync(charsetPath);
|
|
37
|
+
hasModel() {
|
|
38
|
+
return fs.existsSync(this.getModelPath());
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
/**
|
|
@@ -49,43 +45,44 @@ class ModelManager {
|
|
|
49
45
|
return new Promise((resolve, reject) => {
|
|
50
46
|
const file = fs.createWriteStream(destPath);
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
const request = (requestUrl) => {
|
|
49
|
+
https.get(requestUrl, { headers: { 'User-Agent': 'monocr-npm' } }, (response) => {
|
|
50
|
+
if ([301, 302, 307, 308].includes(response.statusCode)) {
|
|
51
|
+
let redirectUrl = response.headers.location;
|
|
52
|
+
if (!redirectUrl.startsWith('http')) {
|
|
53
|
+
const originalUrl = new URL(requestUrl);
|
|
54
|
+
redirectUrl = `${originalUrl.protocol}//${originalUrl.host}${redirectUrl}`;
|
|
55
|
+
}
|
|
56
|
+
request(redirectUrl);
|
|
57
|
+
} else if (response.statusCode === 200) {
|
|
58
|
+
const totalSize = parseInt(response.headers['content-length'], 10);
|
|
57
59
|
let downloadedSize = 0;
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
response.on('data', (chunk) => {
|
|
60
62
|
downloadedSize += chunk.length;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
if (totalSize) {
|
|
64
|
+
const progress = ((downloadedSize / totalSize) * 100).toFixed(1);
|
|
65
|
+
process.stdout.write(`\r Downloading model: ${progress}% (${(downloadedSize / 1024 / 1024).toFixed(2)} MB)`);
|
|
66
|
+
}
|
|
63
67
|
});
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
|
|
69
|
+
response.pipe(file);
|
|
66
70
|
|
|
67
71
|
file.on('finish', () => {
|
|
68
72
|
file.close();
|
|
69
73
|
process.stdout.write('\n');
|
|
70
74
|
resolve();
|
|
71
75
|
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} else {
|
|
83
|
-
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
84
|
-
}
|
|
85
|
-
}).on('error', (err) => {
|
|
86
|
-
fs.unlink(destPath, () => {});
|
|
87
|
-
reject(err);
|
|
88
|
-
});
|
|
76
|
+
} else {
|
|
77
|
+
reject(new Error(`Failed to download: ${response.statusCode}`));
|
|
78
|
+
}
|
|
79
|
+
}).on('error', (err) => {
|
|
80
|
+
fs.unlink(destPath, () => {});
|
|
81
|
+
reject(err);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
request(url);
|
|
89
86
|
|
|
90
87
|
file.on('error', (err) => {
|
|
91
88
|
fs.unlink(destPath, () => {});
|
|
@@ -95,44 +92,32 @@ class ModelManager {
|
|
|
95
92
|
}
|
|
96
93
|
|
|
97
94
|
/**
|
|
98
|
-
* Download
|
|
95
|
+
* Download model file
|
|
99
96
|
*/
|
|
100
|
-
async
|
|
97
|
+
async downloadModel() {
|
|
101
98
|
this.ensureCacheDir();
|
|
102
99
|
|
|
103
|
-
console.log('Downloading monocr
|
|
104
|
-
console.log(`Cache directory: ${this.cacheDir}
|
|
105
|
-
|
|
106
|
-
// Download model
|
|
107
|
-
const modelUrl = `${this.baseUrl}/${this.modelFiles.model}`;
|
|
108
|
-
const modelPath = this.getLocalPath('model');
|
|
109
|
-
console.log('Downloading monocr.onnx...');
|
|
110
|
-
await this.downloadFile(modelUrl, modelPath);
|
|
111
|
-
console.log('✓ Model downloaded\n');
|
|
100
|
+
console.log('Downloading monocr model from HuggingFace...');
|
|
101
|
+
console.log(`Cache directory: ${this.cacheDir}`);
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
const charsetPath = this.getLocalPath('charset');
|
|
116
|
-
console.log('Downloading charset.txt...');
|
|
117
|
-
await this.downloadFile(charsetUrl, charsetPath);
|
|
118
|
-
console.log('✓ Charset downloaded\n');
|
|
103
|
+
const modelUrl = `${this.baseUrl}/${this.hfModelPath}`;
|
|
104
|
+
const destPath = this.getModelPath();
|
|
119
105
|
|
|
120
|
-
|
|
106
|
+
await this.downloadFile(modelUrl, destPath);
|
|
107
|
+
console.log('✓ Model downloaded successfully!');
|
|
121
108
|
}
|
|
122
109
|
|
|
123
110
|
/**
|
|
124
|
-
* Get model
|
|
111
|
+
* Get model path, downloading if needed
|
|
125
112
|
*/
|
|
126
|
-
async
|
|
127
|
-
if (!this.
|
|
128
|
-
await this.
|
|
113
|
+
async ensureModel() {
|
|
114
|
+
if (!this.hasModel()) {
|
|
115
|
+
await this.downloadModel();
|
|
129
116
|
}
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
modelPath: this.getLocalPath('model'),
|
|
133
|
-
charsetPath: this.getLocalPath('charset')
|
|
134
|
-
};
|
|
117
|
+
return this.getModelPath();
|
|
135
118
|
}
|
|
136
119
|
}
|
|
137
120
|
|
|
138
121
|
module.exports = ModelManager;
|
|
122
|
+
|
|
123
|
+
module.exports = ModelManager;
|
package/src/monocr.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const ort = require('onnxruntime-node');
|
|
2
2
|
const sharp = require('sharp');
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
4
5
|
const LineSegmenter = require('./segmenter');
|
|
5
6
|
const ModelManager = require('./model-manager');
|
|
6
7
|
|
|
@@ -21,17 +22,18 @@ class MonOCR {
|
|
|
21
22
|
async init() {
|
|
22
23
|
if (this.session) return;
|
|
23
24
|
|
|
24
|
-
//
|
|
25
|
-
if (!this.modelPath
|
|
26
|
-
|
|
27
|
-
this.modelPath = this.modelPath || paths.modelPath;
|
|
28
|
-
this.charsetPath = this.charsetPath || paths.charsetPath;
|
|
25
|
+
// Ensure model exists
|
|
26
|
+
if (!this.modelPath) {
|
|
27
|
+
this.modelPath = await this.modelManager.ensureModel();
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
|
|
32
|
-
if (this.charsetPath) {
|
|
33
|
-
this.
|
|
30
|
+
// Use bundled charset if not provided
|
|
31
|
+
if (!this.charsetPath) {
|
|
32
|
+
this.charsetPath = path.join(__dirname, 'charset.txt');
|
|
34
33
|
}
|
|
34
|
+
|
|
35
|
+
this.session = await ort.InferenceSession.create(this.modelPath);
|
|
36
|
+
this.charset = fs.readFileSync(this.charsetPath, 'utf-8').trim();
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/**
|