1 ///////////////////////////////////////////////////////////////////////
2 // File:        ltrresultiterator.cpp
3 // Description: Iterator for tesseract results in strict left-to-right
4 //              order that avoids using tesseract internal data structures.
5 // Author:      Ray Smith
6 //
7 // (C) Copyright 2010, Google Inc.
8 // Licensed under the Apache License, Version 2.0 (the "License");
9 // you may not use this file except in compliance with the License.
10 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
12 // Unless required by applicable law or agreed to in writing, software
13 // distributed under the License is distributed on an "AS IS" BASIS,
14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 // See the License for the specific language governing permissions and
16 // limitations under the License.
17 //
18 ///////////////////////////////////////////////////////////////////////
19 
20 #include <tesseract/ltrresultiterator.h>
21 
22 #include "pageres.h"
23 #include "tesseractclass.h"
24 
25 #include <allheaders.h>
26 
27 namespace tesseract {
28 
LTRResultIterator(PAGE_RES * page_res,Tesseract * tesseract,int scale,int scaled_yres,int rect_left,int rect_top,int rect_width,int rect_height)29 LTRResultIterator::LTRResultIterator(PAGE_RES *page_res, Tesseract *tesseract, int scale,
30                                      int scaled_yres, int rect_left, int rect_top, int rect_width,
31                                      int rect_height)
32     : PageIterator(page_res, tesseract, scale, scaled_yres, rect_left, rect_top, rect_width,
33                    rect_height)
34     , line_separator_("\n")
35     , paragraph_separator_("\n") {}
36 
37 // Destructor.
38 // It is defined here, so the compiler can create a single vtable
39 // instead of weak vtables in every compilation unit.
40 LTRResultIterator::~LTRResultIterator() = default;
41 
42 // Returns the null terminated UTF-8 encoded text string for the current
43 // object at the given level. Use delete [] to free after use.
GetUTF8Text(PageIteratorLevel level) const44 char *LTRResultIterator::GetUTF8Text(PageIteratorLevel level) const {
45   if (it_->word() == nullptr) {
46     return nullptr; // Already at the end!
47   }
48   std::string text;
49   PAGE_RES_IT res_it(*it_);
50   WERD_CHOICE *best_choice = res_it.word()->best_choice;
51   ASSERT_HOST(best_choice != nullptr);
52   if (level == RIL_SYMBOL) {
53     text = res_it.word()->BestUTF8(blob_index_, false);
54   } else if (level == RIL_WORD) {
55     text = best_choice->unichar_string();
56   } else {
57     bool eol = false; // end of line?
58     bool eop = false; // end of paragraph?
59     do {              // for each paragraph in a block
60       do {            // for each text line in a paragraph
61         do {          // for each word in a text line
62           best_choice = res_it.word()->best_choice;
63           ASSERT_HOST(best_choice != nullptr);
64           text += best_choice->unichar_string();
65           text += " ";
66           res_it.forward();
67           eol = res_it.row() != res_it.prev_row();
68         } while (!eol);
69         text.resize(text.length() - 1);
70         text += line_separator_;
71         eop = res_it.block() != res_it.prev_block() ||
72               res_it.row()->row->para() != res_it.prev_row()->row->para();
73       } while (level != RIL_TEXTLINE && !eop);
74       if (eop) {
75         text += paragraph_separator_;
76       }
77     } while (level == RIL_BLOCK && res_it.block() == res_it.prev_block());
78   }
79   int length = text.length() + 1;
80   char *result = new char[length];
81   strncpy(result, text.c_str(), length);
82   return result;
83 }
84 
85 // Set the string inserted at the end of each text line. "\n" by default.
SetLineSeparator(const char * new_line)86 void LTRResultIterator::SetLineSeparator(const char *new_line) {
87   line_separator_ = new_line;
88 }
89 
90 // Set the string inserted at the end of each paragraph. "\n" by default.
SetParagraphSeparator(const char * new_para)91 void LTRResultIterator::SetParagraphSeparator(const char *new_para) {
92   paragraph_separator_ = new_para;
93 }
94 
95 // Returns the mean confidence of the current object at the given level.
96 // The number should be interpreted as a percent probability. (0.0f-100.0f)
Confidence(PageIteratorLevel level) const97 float LTRResultIterator::Confidence(PageIteratorLevel level) const {
98   if (it_->word() == nullptr) {
99     return 0.0f; // Already at the end!
100   }
101   float mean_certainty = 0.0f;
102   int certainty_count = 0;
103   PAGE_RES_IT res_it(*it_);
104   WERD_CHOICE *best_choice = res_it.word()->best_choice;
105   ASSERT_HOST(best_choice != nullptr);
106   switch (level) {
107     case RIL_BLOCK:
108       do {
109         best_choice = res_it.word()->best_choice;
110         ASSERT_HOST(best_choice != nullptr);
111         mean_certainty += best_choice->certainty();
112         ++certainty_count;
113         res_it.forward();
114       } while (res_it.block() == res_it.prev_block());
115       break;
116     case RIL_PARA:
117       do {
118         best_choice = res_it.word()->best_choice;
119         ASSERT_HOST(best_choice != nullptr);
120         mean_certainty += best_choice->certainty();
121         ++certainty_count;
122         res_it.forward();
123       } while (res_it.block() == res_it.prev_block() &&
124                res_it.row()->row->para() == res_it.prev_row()->row->para());
125       break;
126     case RIL_TEXTLINE:
127       do {
128         best_choice = res_it.word()->best_choice;
129         ASSERT_HOST(best_choice != nullptr);
130         mean_certainty += best_choice->certainty();
131         ++certainty_count;
132         res_it.forward();
133       } while (res_it.row() == res_it.prev_row());
134       break;
135     case RIL_WORD:
136       mean_certainty += best_choice->certainty();
137       ++certainty_count;
138       break;
139     case RIL_SYMBOL:
140       mean_certainty += best_choice->certainty(blob_index_);
141       ++certainty_count;
142   }
143   if (certainty_count > 0) {
144     mean_certainty /= certainty_count;
145     return ClipToRange(100 + 5 * mean_certainty, 0.0f, 100.0f);
146   }
147   return 0.0f;
148 }
149 
150 // Returns the font attributes of the current word. If iterating at a higher
151 // level object than words, eg textlines, then this will return the
152 // attributes of the first word in that textline.
153 // The actual return value is a string representing a font name. It points
154 // to an internal table and SHOULD NOT BE DELETED. Lifespan is the same as
155 // the iterator itself, ie rendered invalid by various members of
156 // TessBaseAPI, including Init, SetImage, End or deleting the TessBaseAPI.
157 // Pointsize is returned in printers points (1/72 inch.)
WordFontAttributes(bool * is_bold,bool * is_italic,bool * is_underlined,bool * is_monospace,bool * is_serif,bool * is_smallcaps,int * pointsize,int * font_id) const158 const char *LTRResultIterator::WordFontAttributes(bool *is_bold, bool *is_italic,
159                                                   bool *is_underlined, bool *is_monospace,
160                                                   bool *is_serif, bool *is_smallcaps,
161                                                   int *pointsize, int *font_id) const {
162   const char *result = nullptr;
163 
164   if (it_->word() == nullptr) {
165     // Already at the end!
166     *pointsize = 0;
167   } else {
168     float row_height =
169         it_->row()->row->x_height() + it_->row()->row->ascenders() - it_->row()->row->descenders();
170     // Convert from pixels to printers points.
171     *pointsize =
172         scaled_yres_ > 0 ? static_cast<int>(row_height * kPointsPerInch / scaled_yres_ + 0.5) : 0;
173 
174 #ifndef DISABLED_LEGACY_ENGINE
175     const FontInfo *font_info = it_->word()->fontinfo;
176     if (font_info) {
177       // Font information available.
178       *font_id = font_info->universal_id;
179       *is_bold = font_info->is_bold();
180       *is_italic = font_info->is_italic();
181       *is_underlined = false; // TODO(rays) fix this!
182       *is_monospace = font_info->is_fixed_pitch();
183       *is_serif = font_info->is_serif();
184       result = font_info->name;
185     }
186 #endif // ndef DISABLED_LEGACY_ENGINE
187 
188     *is_smallcaps = it_->word()->small_caps;
189   }
190 
191   if (!result) {
192     *is_bold = false;
193     *is_italic = false;
194     *is_underlined = false;
195     *is_monospace = false;
196     *is_serif = false;
197     *is_smallcaps = false;
198     *font_id = -1;
199   }
200 
201   return result;
202 }
203 
204 // Returns the name of the language used to recognize this word.
WordRecognitionLanguage() const205 const char *LTRResultIterator::WordRecognitionLanguage() const {
206   if (it_->word() == nullptr || it_->word()->tesseract == nullptr) {
207     return nullptr;
208   }
209   return it_->word()->tesseract->lang.c_str();
210 }
211 
212 // Return the overall directionality of this word.
WordDirection() const213 StrongScriptDirection LTRResultIterator::WordDirection() const {
214   if (it_->word() == nullptr) {
215     return DIR_NEUTRAL;
216   }
217   bool has_rtl = it_->word()->AnyRtlCharsInWord();
218   bool has_ltr = it_->word()->AnyLtrCharsInWord();
219   if (has_rtl && !has_ltr) {
220     return DIR_RIGHT_TO_LEFT;
221   }
222   if (has_ltr && !has_rtl) {
223     return DIR_LEFT_TO_RIGHT;
224   }
225   if (!has_ltr && !has_rtl) {
226     return DIR_NEUTRAL;
227   }
228   return DIR_MIX;
229 }
230 
231 // Returns true if the current word was found in a dictionary.
WordIsFromDictionary() const232 bool LTRResultIterator::WordIsFromDictionary() const {
233   if (it_->word() == nullptr) {
234     return false; // Already at the end!
235   }
236   int permuter = it_->word()->best_choice->permuter();
237   return permuter == SYSTEM_DAWG_PERM || permuter == FREQ_DAWG_PERM || permuter == USER_DAWG_PERM;
238 }
239 
240 // Returns the number of blanks before the current word.
BlanksBeforeWord() const241 int LTRResultIterator::BlanksBeforeWord() const {
242   if (it_->word() == nullptr) {
243     return 1;
244   }
245   return it_->word()->word->space();
246 }
247 
248 // Returns true if the current word is numeric.
WordIsNumeric() const249 bool LTRResultIterator::WordIsNumeric() const {
250   if (it_->word() == nullptr) {
251     return false; // Already at the end!
252   }
253   int permuter = it_->word()->best_choice->permuter();
254   return permuter == NUMBER_PERM;
255 }
256 
257 // Returns true if the word contains blamer information.
HasBlamerInfo() const258 bool LTRResultIterator::HasBlamerInfo() const {
259   return it_->word() != nullptr && it_->word()->blamer_bundle != nullptr &&
260          it_->word()->blamer_bundle->HasDebugInfo();
261 }
262 
263 #ifndef DISABLED_LEGACY_ENGINE
264 // Returns the pointer to ParamsTrainingBundle stored in the BlamerBundle
265 // of the current word.
GetParamsTrainingBundle() const266 const void *LTRResultIterator::GetParamsTrainingBundle() const {
267   return (it_->word() != nullptr && it_->word()->blamer_bundle != nullptr)
268              ? &(it_->word()->blamer_bundle->params_training_bundle())
269              : nullptr;
270 }
271 #endif // ndef DISABLED_LEGACY_ENGINE
272 
273 // Returns the pointer to the string with blamer information for this word.
274 // Assumes that the word's blamer_bundle is not nullptr.
GetBlamerDebug() const275 const char *LTRResultIterator::GetBlamerDebug() const {
276   return it_->word()->blamer_bundle->debug().c_str();
277 }
278 
279 // Returns the pointer to the string with misadaption information for this word.
280 // Assumes that the word's blamer_bundle is not nullptr.
GetBlamerMisadaptionDebug() const281 const char *LTRResultIterator::GetBlamerMisadaptionDebug() const {
282   return it_->word()->blamer_bundle->misadaption_debug().c_str();
283 }
284 
285 // Returns true if a truth string was recorded for the current word.
HasTruthString() const286 bool LTRResultIterator::HasTruthString() const {
287   if (it_->word() == nullptr) {
288     return false; // Already at the end!
289   }
290   if (it_->word()->blamer_bundle == nullptr || it_->word()->blamer_bundle->NoTruth()) {
291     return false; // no truth information for this word
292   }
293   return true;
294 }
295 
296 // Returns true if the given string is equivalent to the truth string for
297 // the current word.
EquivalentToTruth(const char * str) const298 bool LTRResultIterator::EquivalentToTruth(const char *str) const {
299   if (!HasTruthString()) {
300     return false;
301   }
302   ASSERT_HOST(it_->word()->uch_set != nullptr);
303   WERD_CHOICE str_wd(str, *(it_->word()->uch_set));
304   return it_->word()->blamer_bundle->ChoiceIsCorrect(&str_wd);
305 }
306 
307 // Returns the null terminated UTF-8 encoded truth string for the current word.
308 // Use delete [] to free after use.
WordTruthUTF8Text() const309 char *LTRResultIterator::WordTruthUTF8Text() const {
310   if (!HasTruthString()) {
311     return nullptr;
312   }
313   std::string truth_text = it_->word()->blamer_bundle->TruthString();
314   int length = truth_text.length() + 1;
315   char *result = new char[length];
316   strncpy(result, truth_text.c_str(), length);
317   return result;
318 }
319 
320 // Returns the null terminated UTF-8 encoded normalized OCR string for the
321 // current word. Use delete [] to free after use.
WordNormedUTF8Text() const322 char *LTRResultIterator::WordNormedUTF8Text() const {
323   if (it_->word() == nullptr) {
324     return nullptr; // Already at the end!
325   }
326   std::string ocr_text;
327   WERD_CHOICE *best_choice = it_->word()->best_choice;
328   const UNICHARSET *unicharset = it_->word()->uch_set;
329   ASSERT_HOST(best_choice != nullptr);
330   for (unsigned i = 0; i < best_choice->length(); ++i) {
331     ocr_text += unicharset->get_normed_unichar(best_choice->unichar_id(i));
332   }
333   auto length = ocr_text.length() + 1;
334   char *result = new char[length];
335   strncpy(result, ocr_text.c_str(), length);
336   return result;
337 }
338 
339 // Returns a pointer to serialized choice lattice.
340 // Fills lattice_size with the number of bytes in lattice data.
WordLattice(int * lattice_size) const341 const char *LTRResultIterator::WordLattice(int *lattice_size) const {
342   if (it_->word() == nullptr) {
343     return nullptr; // Already at the end!
344   }
345   if (it_->word()->blamer_bundle == nullptr) {
346     return nullptr;
347   }
348   *lattice_size = it_->word()->blamer_bundle->lattice_size();
349   return it_->word()->blamer_bundle->lattice_data();
350 }
351 
352 // Returns true if the current symbol is a superscript.
353 // If iterating at a higher level object than symbols, eg words, then
354 // this will return the attributes of the first symbol in that word.
SymbolIsSuperscript() const355 bool LTRResultIterator::SymbolIsSuperscript() const {
356   if (cblob_it_ == nullptr && it_->word() != nullptr) {
357     return it_->word()->best_choice->BlobPosition(blob_index_) == SP_SUPERSCRIPT;
358   }
359   return false;
360 }
361 
362 // Returns true if the current symbol is a subscript.
363 // If iterating at a higher level object than symbols, eg words, then
364 // this will return the attributes of the first symbol in that word.
SymbolIsSubscript() const365 bool LTRResultIterator::SymbolIsSubscript() const {
366   if (cblob_it_ == nullptr && it_->word() != nullptr) {
367     return it_->word()->best_choice->BlobPosition(blob_index_) == SP_SUBSCRIPT;
368   }
369   return false;
370 }
371 
372 // Returns true if the current symbol is a dropcap.
373 // If iterating at a higher level object than symbols, eg words, then
374 // this will return the attributes of the first symbol in that word.
SymbolIsDropcap() const375 bool LTRResultIterator::SymbolIsDropcap() const {
376   if (cblob_it_ == nullptr && it_->word() != nullptr) {
377     return it_->word()->best_choice->BlobPosition(blob_index_) == SP_DROPCAP;
378   }
379   return false;
380 }
381 
ChoiceIterator(const LTRResultIterator & result_it)382 ChoiceIterator::ChoiceIterator(const LTRResultIterator &result_it) {
383   ASSERT_HOST(result_it.it_->word() != nullptr);
384   word_res_ = result_it.it_->word();
385   oemLSTM_ = word_res_->tesseract->AnyLSTMLang();
386   // Is there legacy engine related trained data?
387   bool oemLegacy = word_res_->tesseract->AnyTessLang();
388   // Is lstm_choice_mode activated?
389   bool lstm_choice_mode = word_res_->tesseract->lstm_choice_mode;
390   rating_coefficient_ = word_res_->tesseract->lstm_rating_coefficient;
391   blanks_before_word_ = result_it.BlanksBeforeWord();
392   BLOB_CHOICE_LIST *choices = nullptr;
393   tstep_index_ = &result_it.blob_index_;
394   if (oemLSTM_ && !word_res_->CTC_symbol_choices.empty()) {
395     if (!word_res_->CTC_symbol_choices[0].empty() &&
396         strcmp(word_res_->CTC_symbol_choices[0][0].first, " ")) {
397       blanks_before_word_ = 0;
398     }
399     unsigned index = *tstep_index_;
400     index += blanks_before_word_;
401     if (index < word_res_->CTC_symbol_choices.size()) {
402       LSTM_choices_ = &word_res_->CTC_symbol_choices[index];
403       filterSpaces();
404     }
405   }
406   if ((oemLegacy || !lstm_choice_mode) && word_res_->ratings != nullptr) {
407     choices = word_res_->GetBlobChoices(result_it.blob_index_);
408   }
409   if (choices != nullptr && !choices->empty()) {
410     choice_it_ = new BLOB_CHOICE_IT(choices);
411     choice_it_->mark_cycle_pt();
412   } else {
413     choice_it_ = nullptr;
414   }
415   if (LSTM_choices_ != nullptr && !LSTM_choices_->empty()) {
416     LSTM_choice_it_ = LSTM_choices_->begin();
417   }
418 }
~ChoiceIterator()419 ChoiceIterator::~ChoiceIterator() {
420   delete choice_it_;
421 }
422 
423 // Moves to the next choice for the symbol and returns false if there
424 // are none left.
Next()425 bool ChoiceIterator::Next() {
426   if (oemLSTM_ && LSTM_choices_ != nullptr && !LSTM_choices_->empty()) {
427     if (LSTM_choice_it_ == LSTM_choices_->end() ||
428         next(LSTM_choice_it_) == LSTM_choices_->end()) {
429       return false;
430     } else {
431       ++LSTM_choice_it_;
432       return true;
433     }
434   } else {
435     if (choice_it_ == nullptr) {
436       return false;
437     }
438     choice_it_->forward();
439     return !choice_it_->cycled_list();
440   }
441 }
442 
443 // Returns the null terminated UTF-8 encoded text string for the current
444 // choice. Do NOT use delete [] to free after use.
GetUTF8Text() const445 const char *ChoiceIterator::GetUTF8Text() const {
446   if (oemLSTM_ && LSTM_choices_ != nullptr && !LSTM_choices_->empty()) {
447     std::pair<const char *, float> choice = *LSTM_choice_it_;
448     return choice.first;
449   } else {
450     if (choice_it_ == nullptr) {
451       return nullptr;
452     }
453     UNICHAR_ID id = choice_it_->data()->unichar_id();
454     return word_res_->uch_set->id_to_unichar_ext(id);
455   }
456 }
457 
458 // Returns the confidence of the current choice depending on the used language
459 // data. If only LSTM traineddata is used the value range is 0.0f - 1.0f. All
460 // choices for one symbol should roughly add up to 1.0f.
461 // If only traineddata of the legacy engine is used, the number should be
462 // interpreted as a percent probability. (0.0f-100.0f) In this case
463 // probabilities won't add up to 100. Each one stands on its own.
Confidence() const464 float ChoiceIterator::Confidence() const {
465   float confidence;
466   if (oemLSTM_ && LSTM_choices_ != nullptr && !LSTM_choices_->empty()) {
467     std::pair<const char *, float> choice = *LSTM_choice_it_;
468     confidence = 100 - rating_coefficient_ * choice.second;
469   } else {
470     if (choice_it_ == nullptr) {
471       return 0.0f;
472     }
473     confidence = 100 + 5 * choice_it_->data()->certainty();
474   }
475   return ClipToRange(confidence, 0.0f, 100.0f);
476 }
477 
478 // Returns the set of timesteps which belong to the current symbol
Timesteps() const479 std::vector<std::vector<std::pair<const char *, float>>> *ChoiceIterator::Timesteps() const {
480   unsigned offset = *tstep_index_ + blanks_before_word_;
481   if (offset >= word_res_->segmented_timesteps.size() || !oemLSTM_) {
482     return nullptr;
483   }
484   return &word_res_->segmented_timesteps[offset];
485 }
486 
filterSpaces()487 void ChoiceIterator::filterSpaces() {
488   if (LSTM_choices_->empty()) {
489     return;
490   }
491   std::vector<std::pair<const char *, float>>::iterator it;
492   for (it = LSTM_choices_->begin(); it != LSTM_choices_->end();) {
493     if (!strcmp(it->first, " ")) {
494       it = LSTM_choices_->erase(it);
495     } else {
496       ++it;
497     }
498   }
499 }
500 } // namespace tesseract.
501