tesseract/
lib.rs

1pub extern crate tesseract_plumbing as plumbing;
2extern crate tesseract_sys;
3extern crate thiserror;
4
5use self::thiserror::Error;
6use std::ffi::CString;
7use std::ffi::NulError;
8use std::os::raw::c_int;
9use std::str;
10mod page_seg_mode;
11
12pub use page_seg_mode::PageSegMode;
13
14use self::tesseract_sys::{
15    TessOcrEngineMode, TessOcrEngineMode_OEM_DEFAULT, TessOcrEngineMode_OEM_LSTM_ONLY,
16    TessOcrEngineMode_OEM_TESSERACT_LSTM_COMBINED, TessOcrEngineMode_OEM_TESSERACT_ONLY,
17};
18
19#[derive(Debug, Error)]
20pub enum InitializeError {
21    #[error("Conversion to CString failed")]
22    CStringError(#[from] NulError),
23    #[error("TessBaseApi failed to initialize")]
24    TessBaseAPIInitError(#[from] plumbing::TessBaseApiInitError),
25}
26
27#[derive(Debug, Error)]
28pub enum SetImageError {
29    #[error("Conversion to CString failed")]
30    CStringError(#[from] NulError),
31    #[error("Failed to read image")]
32    PixReadError(#[from] plumbing::leptonica_plumbing::PixReadError),
33}
34
35#[derive(Debug, Error)]
36pub enum SetVariableError {
37    #[error("Conversion to CString failed")]
38    CStringError(#[from] NulError),
39    #[error("TessBaseApi failed to set variable")]
40    TessBaseAPISetVariableError(#[from] plumbing::TessBaseApiSetVariableError),
41}
42
43#[derive(Debug, Error)]
44pub enum TesseractError {
45    #[error("Failed to set language")]
46    InitializeError(#[from] InitializeError),
47    #[error("Failed to set image")]
48    SetImageError(#[from] SetImageError),
49    #[error("Errored whilst recognizing")]
50    RecognizeError(#[from] plumbing::TessBaseApiRecogniseError),
51    #[error("Errored whilst getting text")]
52    GetTextError(#[from] plumbing::TessBaseApiGetUtf8TextError),
53    #[error("Errored whilst getting HOCR text")]
54    GetHOCRTextError(#[from] plumbing::TessBaseApiGetHocrTextError),
55    #[error("Errored whilst getting TSV text")]
56    GetTsvTextError(#[from] plumbing::TessBaseApiGetTsvTextError),
57    #[error("Errored whilst setting frame")]
58    SetFrameError(#[from] plumbing::TessBaseApiSetImageSafetyError),
59    #[error("Errored whilst setting image from mem")]
60    SetImgFromMemError(#[from] plumbing::leptonica_plumbing::PixReadMemError),
61    #[error("Errored whilst setting variable")]
62    SetVariableError(#[from] SetVariableError),
63}
64
65/// https://tesseract-ocr.github.io/tessapi/5.x/a01818.html#a04550a0ed1279562027bf2fc92c421aead84e1ef94e50df1622b4fcd189c6c00b
66pub enum OcrEngineMode {
67    /// Run Tesseract only - fastest; deprecated
68    Default,
69    /// Run just the LSTM line recognizer.
70    LstmOnly,
71    /// Run the LSTM recognizer, but allow fallback
72    /// to Tesseract when things get difficult.
73    /// deprecated
74    TesseractLstmCombined,
75    /// Specify this mode,
76    /// to indicate that any of the above modes
77    /// should be automatically inferred from the
78    /// variables in the language-specific config,
79    /// command-line configs, or if not specified
80    /// in any of the above should be set to the
81    /// default OEM_TESSERACT_ONLY.
82    TesseractOnly,
83}
84
85impl OcrEngineMode {
86    fn to_value(&self) -> TessOcrEngineMode {
87        match *self {
88            OcrEngineMode::Default => TessOcrEngineMode_OEM_DEFAULT,
89            OcrEngineMode::LstmOnly => TessOcrEngineMode_OEM_LSTM_ONLY,
90            OcrEngineMode::TesseractLstmCombined => TessOcrEngineMode_OEM_TESSERACT_LSTM_COMBINED,
91            OcrEngineMode::TesseractOnly => TessOcrEngineMode_OEM_TESSERACT_ONLY,
92        }
93    }
94}
95
96pub struct Tesseract(plumbing::TessBaseApi);
97
98impl Tesseract {
99    pub fn new(datapath: Option<&str>, language: Option<&str>) -> Result<Self, InitializeError> {
100        let mut tess = Tesseract(plumbing::TessBaseApi::create());
101        let datapath = match datapath {
102            Some(i) => Some(CString::new(i)?),
103            None => None,
104        };
105        let language = match language {
106            Some(i) => Some(CString::new(i)?),
107            None => None,
108        };
109
110        tess.0.init_2(datapath.as_deref(), language.as_deref())?;
111        Ok(tess)
112    }
113
114    pub fn new_with_oem(
115        datapath: Option<&str>,
116        language: Option<&str>,
117        oem: OcrEngineMode,
118    ) -> Result<Self, InitializeError> {
119        let mut tess = Tesseract(plumbing::TessBaseApi::create());
120        let datapath = match datapath {
121            Some(i) => Some(CString::new(i)?),
122            None => None,
123        };
124        let language = match language {
125            Some(i) => Some(CString::new(i)?),
126            None => None,
127        };
128
129        tess.0
130            .init_4(datapath.as_deref(), language.as_deref(), oem.to_value())?;
131        Ok(tess)
132    }
133
134    #[cfg(feature = "tesseract_5_2")]
135    pub fn new_with_data(
136        data: &[u8],
137        language: Option<&str>,
138        oem: OcrEngineMode,
139    ) -> Result<Self, InitializeError> {
140        let mut tess = Tesseract(plumbing::TessBaseApi::create());
141        let language = match language {
142            Some(i) => Some(CString::new(i)?),
143            None => None,
144        };
145
146        tess.0.init_1(data, language.as_deref(), oem.to_value())?;
147        Ok(tess)
148    }
149
150    pub fn set_image(mut self, filename: &str) -> Result<Self, SetImageError> {
151        let pix = plumbing::leptonica_plumbing::Pix::read(&CString::new(filename)?)?;
152        self.0.set_image_2(&pix);
153        Ok(self)
154    }
155    pub fn set_frame(
156        mut self,
157        frame_data: &[u8],
158        width: i32,
159        height: i32,
160        bytes_per_pixel: i32,
161        bytes_per_line: i32,
162    ) -> Result<Self, plumbing::TessBaseApiSetImageSafetyError> {
163        self.0
164            .set_image(frame_data, width, height, bytes_per_pixel, bytes_per_line)?;
165        Ok(self)
166    }
167    pub fn set_image_from_mem(
168        mut self,
169        img: &[u8],
170    ) -> Result<Self, plumbing::leptonica_plumbing::PixReadMemError> {
171        let pix = plumbing::leptonica_plumbing::Pix::read_mem(img)?;
172        self.0.set_image_2(&pix);
173        Ok(self)
174    }
175
176    pub fn set_rectangle(mut self, left: i32, top: i32, width: i32, height: i32) -> Self {
177        self.0.set_rectangle(left, top, width, height);
178        self
179    }
180
181    pub fn set_source_resolution(mut self, ppi: i32) -> Self {
182        self.0.set_source_resolution(ppi);
183        self
184    }
185
186    pub fn set_variable(mut self, name: &str, value: &str) -> Result<Self, SetVariableError> {
187        self.0
188            .set_variable(&CString::new(name)?, &CString::new(value)?)?;
189        Ok(self)
190    }
191    pub fn recognize(mut self) -> Result<Self, plumbing::TessBaseApiRecogniseError> {
192        self.0.recognize()?;
193        Ok(self)
194    }
195    pub fn get_text(&mut self) -> Result<String, plumbing::TessBaseApiGetUtf8TextError> {
196        Ok(self
197            .0
198            .get_utf8_text()?
199            .as_ref()
200            .to_string_lossy()
201            .into_owned())
202    }
203    pub fn mean_text_conf(&mut self) -> i32 {
204        self.0.mean_text_conf()
205    }
206
207    /// Get the text encoded as HTML with bounding box tags
208    ///
209    /// See [img.html](../img.html) for an example.
210    pub fn get_hocr_text(
211        &mut self,
212        page: c_int,
213    ) -> Result<String, plumbing::TessBaseApiGetHocrTextError> {
214        Ok(self
215            .0
216            .get_hocr_text(page)?
217            .as_ref()
218            .to_string_lossy()
219            .into_owned())
220    }
221
222    /// Get the text encoded as TSV, including bounding boxes, confidence
223    ///
224    /// See [char* TessBaseAPI::GetTSVText](https://github.com/tesseract-ocr/tesseract/blob/cdebe13d81e2ad2a83be533886750f5491b25262/src/api/baseapi.cpp#L1398)
225    pub fn get_tsv_text(
226        &mut self,
227        page: c_int,
228    ) -> Result<String, plumbing::TessBaseApiGetTsvTextError> {
229        Ok(self
230            .0
231            .get_tsv_text(page)?
232            .as_ref()
233            .to_string_lossy()
234            .into_owned())
235    }
236
237    pub fn set_page_seg_mode(&mut self, mode: PageSegMode) {
238        self.0.set_page_seg_mode(mode.as_tess_page_seg_mode());
239    }
240}
241
242pub fn ocr(filename: &str, language: &str) -> Result<String, TesseractError> {
243    Ok(Tesseract::new(None, Some(language))?
244        .set_image(filename)?
245        .recognize()?
246        .get_text()?)
247}
248
249pub fn ocr_from_frame(
250    frame_data: &[u8],
251    width: i32,
252    height: i32,
253    bytes_per_pixel: i32,
254    bytes_per_line: i32,
255    language: &str,
256) -> Result<String, TesseractError> {
257    Ok(Tesseract::new(None, Some(language))?
258        .set_frame(frame_data, width, height, bytes_per_pixel, bytes_per_line)?
259        .recognize()?
260        .get_text()?)
261}
262
263#[test]
264fn ocr_test() -> Result<(), TesseractError> {
265    assert_eq!(
266        ocr("img.png", "eng")?,
267        include_str!("../img.txt").to_string()
268    );
269    Ok(())
270}
271
272#[test]
273fn ocr_from_frame_test() -> Result<(), TesseractError> {
274    assert_eq!(
275        ocr_from_frame(include_bytes!("../img.tiff"), 2256, 324, 3, 2256 * 3, "eng")?,
276        include_str!("../img.txt").to_string()
277    );
278    Ok(())
279}
280
281#[test]
282fn ocr_from_mem_with_ppi() -> Result<(), TesseractError> {
283    let mut cube = Tesseract::new(None, Some("eng"))?
284        .set_image_from_mem(include_bytes!("../img.tiff"))?
285        .set_source_resolution(70);
286    assert_eq!(&cube.get_text()?, include_str!("../img.txt"));
287    Ok(())
288}
289
290#[test]
291fn expanded_test() -> Result<(), TesseractError> {
292    let mut cube = Tesseract::new(None, Some("eng"))?
293        .set_image("img.png")?
294        .set_variable("tessedit_char_blacklist", "z")?
295        .recognize()?;
296    assert_eq!(&cube.get_text()?, include_str!("../img.txt"));
297    Ok(())
298}
299
300#[test]
301fn hocr_test() -> Result<(), TesseractError> {
302    let mut cube = Tesseract::new(None, Some("eng"))?.set_image("img.png")?;
303    assert!(&cube.get_hocr_text(0)?.contains("<div class='ocr_page'"));
304    Ok(())
305}
306
307#[test]
308#[ignore] // Many systems do not have legacy Tesseract data available
309fn oem_test() -> Result<(), TesseractError> {
310    let only_tesseract_str =
311        Tesseract::new_with_oem(None, Some("eng"), OcrEngineMode::TesseractOnly)?
312            .set_image("img.png")?
313            .recognize()?
314            .get_text()?;
315
316    let only_lstm_str = Tesseract::new_with_oem(None, Some("eng"), OcrEngineMode::LstmOnly)?
317        .set_image("img.png")?
318        .recognize()?
319        .get_text()?;
320
321    assert_ne!(only_tesseract_str, only_lstm_str);
322    Ok(())
323}
324
325#[test]
326fn oem_ltsm_only_test() -> Result<(), TesseractError> {
327    let only_lstm_str = Tesseract::new_with_oem(None, Some("eng"), OcrEngineMode::LstmOnly)?
328        .set_image("img.png")?
329        .recognize()?
330        .get_text()?;
331
332    assert_eq!(only_lstm_str, include_str!("../img.txt"));
333    Ok(())
334}
335
336#[test]
337fn initialize_with_none() {
338    assert!(Tesseract::new(None, None).is_ok());
339}