1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//! MonOcr - Mon language OCR library using ONNX models
//!
//! This library provides OCR (Optical Character Recognition) functionality for Mon text
//! using deep learning models. It supports reading text from images and PDFs, with optional
//! accuracy measurement against ground truth text.
//!
//! Mon (`mnw`) is a Mon-Khmer language of Myanmar and Thailand, written in a
//! Myanmar-script orthography. It is unrelated to Mongolian.
//!
//! # The model
//!
//! Weights are downloaded from [janakhpon/monocr](https://huggingface.co/janakhpon/monocr),
//! pinned to revision [`model_manager::MODEL_REVISION`]. That artifact takes a
//! `[1, 1, 160, 1024]` input and emits `[1, sequence, 277]` logits: 276
//! characters plus the CTC blank. The width is static: v3.5 accepts 1024 and
//! nothing else, where v2 accepted any width.
//!
//! The charset, the input height and the classifier width are one contract. If
//! they drift apart the model still runs and still returns text — it is just the
//! wrong text, with no error anywhere. So the graph is read on load and a
//! disagreement yields [`ModelContractError`] instead of a result.
//!
//! # Quick Start
//!
//! ```no_run
//! use monocr_onnx::read_image;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let text = read_image("path/to/image.png").await?;
//! println!("Recognized text: {}", text);
//! Ok(())
//! }
//! ```
//!
//! # Features
//!
//! - Read text from single images (PNG, JPG, etc.)
//! - Read text from multiple images in batch
//! - Read text from PDF files (requires poppler-utils)
//! - Measure OCR accuracy against ground truth
//! - Customizable model paths and character sets
//! - Line segmentation for full page OCR
//! - Lines too wide for the 1024px model window are tiled at whitespace columns
//! rather than squeezed into it; see [`MonOcr::predict_page`] for the measured
//! reason
use Result;
use Path;
pub use ModelManager;
pub use ;
pub use ;
pub use calculate_accuracy;
/// Read text from a single image file
///
/// This function initializes a new MonOcr instance with default settings and performs
/// OCR on the given image. The image is automatically segmented into lines, and each
/// line is recognized using the ONNX model.
///
/// # Arguments
///
/// * `image_path` - Path to the image file (PNG, JPG, BMP, etc.)
///
/// # Returns
///
/// Returns a `Result<String>` containing the recognized text, with lines separated by newlines.
///
/// # Example
///
/// ```no_run
/// use monocr_onnx::read_image;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let text = read_image("document.png").await?;
/// println!("Recognized: {}", text);
/// Ok(())
/// }
/// ```
pub async
/// Read text from multiple image files
///
/// This function processes multiple images in sequence, returning a vector of recognized texts.
/// Each image is segmented into lines and processed individually.
///
/// # Arguments
///
/// * `image_paths` - A slice of paths to image files
///
/// # Returns
///
/// Returns a `Result<Vec<String>>` where each element contains the recognized text
/// from the corresponding image. Lines within each text are separated by newlines.
///
/// # Example
///
/// ```no_run
/// use monocr_onnx::read_images;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let paths = vec!["page1.png", "page2.png", "page3.png"];
/// let results = read_images(&paths).await?;
/// for (i, text) in results.iter().enumerate() {
/// println!("Page {}: {}", i + 1, text);
/// }
/// Ok(())
/// }
/// ```
pub async
/// Read text from a PDF file
///
/// This function converts a PDF document to images (using pdftoppm from poppler-utils)
/// and performs OCR on each page. Each page is treated as a separate image.
///
/// # Arguments
///
/// * `pdf_path` - Path to the PDF file
///
/// # Returns
///
/// Returns a `Result<Vec<String>>` where each element contains the recognized text
/// from the corresponding page.
///
/// # Requirements
///
/// This function requires `pdftoppm` from the poppler-utils package to be installed:
/// - Ubuntu/Debian: `sudo apt-get install poppler-utils`
/// - macOS: `brew install poppler`
/// - Fedora/RHEL: `sudo dnf install poppler-utils`
///
/// # Example
///
/// ```no_run
/// use monocr_onnx::read_pdf;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let pages = read_pdf("document.pdf").await?;
/// for (i, text) in pages.iter().enumerate() {
/// println!("=== Page {} ===\n{}", i + 1, text);
/// }
/// Ok(())
/// }
/// ```
pub async
/// Read text from an image with accuracy measurement
///
/// This function performs OCR on an image and calculates the accuracy by comparing
/// the recognized text against the ground truth using Levenshtein distance.
///
/// # Arguments
///
/// * `image_path` - Path to the image file
/// * `ground_truth` - The expected/ground truth text to compare against
///
/// # Returns
///
/// Returns a `Result<OcrResult>` containing:
/// - `text`: The recognized text from the image
/// - `accuracy`: A percentage (0-100) representing how close the recognized text is
/// to the ground truth
///
/// # Accuracy Calculation
///
/// Accuracy is calculated as: `(1 - CER) * 100` where CER is the Character Error Rate
/// (Levenshtein distance divided by the maximum length of the two strings).
/// This gives a percentage score where 100% means perfect recognition.
///
/// # Example
///
/// ```no_run
/// use monocr_onnx::read_image_with_accuracy;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let result = read_image_with_accuracy("image.png", "ဘာသာမန်").await?;
/// println!("Recognized: {}", result.text);
/// println!("Accuracy: {:.2}%", result.accuracy);
/// Ok(())
/// }
/// ```
pub async
/// OCR result containing recognized text and accuracy measurement
///
/// This struct is returned by [`read_image_with_accuracy`] and contains both
/// the recognized text and the accuracy score when compared against ground truth.
///
/// # Fields
///
/// * `text` - The recognized text from the OCR process
/// * `accuracy` - A percentage value (0-100) indicating how closely the recognized
/// text matches the ground truth
///
/// # Example
///
/// ```no_run
/// use monocr_onnx::read_image_with_accuracy;
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let result = read_image_with_accuracy("test.png", "Hello World").await?;
/// if result.accuracy >= 90.0 {
/// println!("Good recognition: {}", result.text);
/// } else {
/// println!("Poor recognition: {} ({}% accuracy)", result.text, result.accuracy);
/// }
/// Ok(())
/// }
/// ```