Coverage Report

Created: 2026-09-14 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/ccmain/tfacepp.cpp
Line
Count
Source
1
/**********************************************************************
2
 * File:        tfacepp.cpp  (Formerly tface++.c)
3
 * Description: C++ side of the C/C++ Tess/Editor interface.
4
 * Author:      Ray Smith
5
 *
6
 * (C) Copyright 1992, Hewlett-Packard Ltd.
7
 ** Licensed under the Apache License, Version 2.0 (the "License");
8
 ** you may not use this file except in compliance with the License.
9
 ** You may obtain a copy of the License at
10
 ** http://www.apache.org/licenses/LICENSE-2.0
11
 ** Unless required by applicable law or agreed to in writing, software
12
 ** distributed under the License is distributed on an "AS IS" BASIS,
13
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 ** See the License for the specific language governing permissions and
15
 ** limitations under the License.
16
 *
17
 **********************************************************************/
18
19
#include <cmath>
20
21
#include "blamer.h"
22
#include "errcode.h"
23
#include "ratngs.h"
24
#include "reject.h"
25
#include "tesseractclass.h"
26
#include "werd.h"
27
28
142k
#define MAX_UNDIVIDED_LENGTH 24
29
30
/**********************************************************************
31
 * recog_word
32
 *
33
 * Convert the word to tess form and pass it to the tess segmenter.
34
 * Convert the output back to editor form.
35
 **********************************************************************/
36
namespace tesseract {
37
104k
void Tesseract::recog_word(WERD_RES *word) {
38
104k
  if (wordrec_skip_no_truth_words &&
39
0
      (word->blamer_bundle == nullptr ||
40
0
       word->blamer_bundle->incorrect_result_reason() == IRR_NO_TRUTH)) {
41
0
    if (classify_debug_level) {
42
0
      tprintf("No truth for word - skipping\n");
43
0
    }
44
0
    word->tess_failed = true;
45
0
    return;
46
0
  }
47
104k
  ASSERT_HOST(!word->chopped_word->blobs.empty());
48
104k
  recog_word_recursive(word);
49
104k
  word->SetupBoxWord();
50
104k
  ASSERT_HOST(static_cast<unsigned>(word->best_choice->length()) == word->box_word->length());
51
  // Check that the ratings matrix size matches the sum of all the
52
  // segmentation states.
53
104k
  if (!word->StatesAllValid()) {
54
0
    tprintf("Not all words have valid states relative to ratings matrix!!");
55
0
    word->DebugWordChoices(true, nullptr);
56
0
    ASSERT_HOST(word->StatesAllValid());
57
0
  }
58
104k
  if (tessedit_override_permuter) {
59
    /* Override the permuter type if a straight dictionary check disagrees. */
60
104k
    uint8_t perm_type = word->best_choice->permuter();
61
104k
    if ((perm_type != SYSTEM_DAWG_PERM) && (perm_type != FREQ_DAWG_PERM) &&
62
104k
        (perm_type != USER_DAWG_PERM)) {
63
104k
      uint8_t real_dict_perm_type = dict_word(*word->best_choice);
64
104k
      if (((real_dict_perm_type == SYSTEM_DAWG_PERM) || (real_dict_perm_type == FREQ_DAWG_PERM) ||
65
64.0k
           (real_dict_perm_type == USER_DAWG_PERM)) &&
66
40.7k
          (alpha_count(word->best_choice->unichar_string().c_str(),
67
40.7k
                       word->best_choice->unichar_lengths().c_str()) > 0)) {
68
40.7k
        word->best_choice->set_permuter(real_dict_perm_type); // use dict perm
69
40.7k
      }
70
104k
    }
71
104k
    if (tessedit_rejection_debug && perm_type != word->best_choice->permuter()) {
72
0
      tprintf("Permuter Type Flipped from %d to %d\n", perm_type, word->best_choice->permuter());
73
0
    }
74
104k
  }
75
  // Factored out from control.cpp
76
104k
  ASSERT_HOST((word->best_choice == nullptr) == (word->raw_choice == nullptr));
77
104k
  if (word->best_choice == nullptr || word->best_choice->empty() ||
78
104k
      strspn(word->best_choice->unichar_string().c_str(), " ") ==
79
104k
          word->best_choice->length()) {
80
21
    word->tess_failed = true;
81
21
    word->reject_map.initialise(word->box_word->length());
82
21
    word->reject_map.rej_word_tess_failure();
83
104k
  } else {
84
104k
    word->tess_failed = false;
85
104k
  }
86
104k
}
87
88
/**********************************************************************
89
 * recog_word_recursive
90
 *
91
 * Convert the word to tess form and pass it to the tess segmenter.
92
 * Convert the output back to editor form.
93
 **********************************************************************/
94
142k
void Tesseract::recog_word_recursive(WERD_RES *word) {
95
142k
  auto word_length = word->chopped_word->NumBlobs(); // no of blobs
96
142k
  if (word_length > MAX_UNDIVIDED_LENGTH) {
97
17.0k
    return split_and_recog_word(word);
98
17.0k
  }
99
125k
  cc_recog(word);
100
125k
  word_length = word->rebuild_word->NumBlobs(); // No of blobs in output.
101
102
  // Do sanity checks and minor fixes on best_choice.
103
125k
  if (word->best_choice->length() > word_length) {
104
0
    word->best_choice->make_bad(); // should never happen
105
0
    tprintf(
106
0
        "recog_word: Discarded long string \"%s\""
107
0
        " (%d characters vs %d blobs)\n",
108
0
        word->best_choice->unichar_string().c_str(), word->best_choice->length(), word_length);
109
0
    tprintf("Word is at:");
110
0
    word->word->bounding_box().print();
111
0
  }
112
125k
  if (word->best_choice->length() < word_length) {
113
0
    UNICHAR_ID space_id = unicharset.unichar_to_id(" ");
114
0
    while (word->best_choice->length() < word_length) {
115
0
      word->best_choice->append_unichar_id(space_id, 1, 0.0, word->best_choice->certainty());
116
0
    }
117
0
  }
118
125k
}
119
120
/**********************************************************************
121
 * split_and_recog_word
122
 *
123
 * Split the word into 2 smaller pieces at the largest gap.
124
 * Recognize the pieces and stick the results back together.
125
 **********************************************************************/
126
17.0k
void Tesseract::split_and_recog_word(WERD_RES *word) {
127
  // Find the biggest blob gap in the chopped_word.
128
17.0k
  int bestgap = -INT32_MAX;
129
17.0k
  int split_index = 0;
130
690k
  for (unsigned b = 1; b < word->chopped_word->NumBlobs(); ++b) {
131
673k
    TBOX prev_box = word->chopped_word->blobs[b - 1]->bounding_box();
132
673k
    TBOX blob_box = word->chopped_word->blobs[b]->bounding_box();
133
673k
    int gap = blob_box.left() - prev_box.right();
134
673k
    if (gap > bestgap) {
135
24.9k
      bestgap = gap;
136
24.9k
      split_index = b;
137
24.9k
    }
138
673k
  }
139
17.0k
  ASSERT_HOST(split_index > 0);
140
141
17.0k
  WERD_RES *word2 = nullptr;
142
17.0k
  BlamerBundle *orig_bb = nullptr;
143
17.0k
  split_word(word, split_index, &word2, &orig_bb);
144
145
  // Recognize the first part of the word.
146
17.0k
  recog_word_recursive(word);
147
  // Recognize the second part of the word.
148
17.0k
  recog_word_recursive(word2);
149
150
17.0k
  join_words(word, word2, orig_bb);
151
17.0k
}
152
153
/**********************************************************************
154
 * split_word
155
 *
156
 * Split a given WERD_RES in place into two smaller words for recognition.
157
 * split_pt is the index of the first blob to go in the second word.
158
 * The underlying word is left alone, only the TWERD (and subsequent data)
159
 * are split up.  orig_blamer_bundle is set to the original blamer bundle,
160
 * and will now be owned by the caller.  New blamer bundles are forged for the
161
 * two pieces.
162
 **********************************************************************/
163
void Tesseract::split_word(WERD_RES *word, unsigned split_pt, WERD_RES **right_piece,
164
19.8k
                           BlamerBundle **orig_blamer_bundle) const {
165
19.8k
  ASSERT_HOST(split_pt > 0 && split_pt < word->chopped_word->NumBlobs());
166
167
  // Save a copy of the blamer bundle so we can try to reconstruct it below.
168
19.8k
  BlamerBundle *orig_bb = word->blamer_bundle ? new BlamerBundle(*word->blamer_bundle) : nullptr;
169
170
19.8k
  auto *word2 = new WERD_RES(*word);
171
172
  // blow away the copied chopped_word, as we want to work with
173
  // the blobs from the input chopped_word so seam_arrays can be merged.
174
19.8k
  TWERD *chopped = word->chopped_word;
175
19.8k
  auto *chopped2 = new TWERD;
176
19.8k
  chopped2->blobs.reserve(chopped->NumBlobs() - split_pt);
177
672k
  for (auto i = split_pt; i < chopped->NumBlobs(); ++i) {
178
652k
    chopped2->blobs.push_back(chopped->blobs[i]);
179
652k
  }
180
19.8k
  chopped->blobs.resize(split_pt);
181
19.8k
  word->chopped_word = nullptr;
182
19.8k
  delete word2->chopped_word;
183
19.8k
  word2->chopped_word = nullptr;
184
185
19.8k
  word->ClearResults();
186
19.8k
  word2->ClearResults();
187
19.8k
  word->chopped_word = chopped;
188
19.8k
  word2->chopped_word = chopped2;
189
19.8k
  word->SetupBasicsFromChoppedWord();
190
19.8k
  word2->SetupBasicsFromChoppedWord();
191
192
  // Try to adjust the blamer bundle.
193
19.8k
  if (orig_bb != nullptr) {
194
    // TODO(rays) Looks like a leak to me.
195
    // orig_bb should take, rather than copy.
196
0
    word->blamer_bundle = new BlamerBundle();
197
0
    word2->blamer_bundle = new BlamerBundle();
198
0
    orig_bb->SplitBundle(chopped->blobs.back()->bounding_box().right(),
199
0
                         word2->chopped_word->blobs[0]->bounding_box().left(), wordrec_debug_blamer,
200
0
                         word->blamer_bundle, word2->blamer_bundle);
201
0
  }
202
203
19.8k
  *right_piece = word2;
204
19.8k
  *orig_blamer_bundle = orig_bb;
205
19.8k
}
206
207
/**********************************************************************
208
 * join_words
209
 *
210
 * The opposite of split_word():
211
 *  join word2 (including any recognized data / seam array / etc)
212
 *  onto the right of word and then delete word2.
213
 *  Also, if orig_bb is provided, stitch it back into word.
214
 **********************************************************************/
215
17.7k
void Tesseract::join_words(WERD_RES *word, WERD_RES *word2, BlamerBundle *orig_bb) const {
216
17.7k
  TBOX prev_box = word->chopped_word->blobs.back()->bounding_box();
217
17.7k
  TBOX blob_box = word2->chopped_word->blobs[0]->bounding_box();
218
  // Tack the word2 outputs onto the end of the word outputs.
219
17.7k
  word->chopped_word->blobs.insert(word->chopped_word->blobs.end(), word2->chopped_word->blobs.begin(), word2->chopped_word->blobs.end());
220
17.7k
  word->rebuild_word->blobs.insert(word->rebuild_word->blobs.end(), word2->rebuild_word->blobs.begin(), word2->rebuild_word->blobs.end());
221
17.7k
  word2->chopped_word->blobs.clear();
222
17.7k
  word2->rebuild_word->blobs.clear();
223
17.7k
  TPOINT split_pt;
224
17.7k
  split_pt.x = (prev_box.right() + blob_box.left()) / 2;
225
17.7k
  split_pt.y = (prev_box.top() + prev_box.bottom() + blob_box.top() + blob_box.bottom()) / 4;
226
  // Move the word2 seams onto the end of the word1 seam_array.
227
  // Since the seam list is one element short, an empty seam marking the
228
  // end of the last blob in the first word is needed first.
229
17.7k
  word->seam_array.push_back(new SEAM(0.0f, split_pt));
230
17.7k
  word->seam_array.insert(word->seam_array.end(), word2->seam_array.begin(), word2->seam_array.end());
231
17.7k
  word2->seam_array.clear();
232
  // Fix widths and gaps.
233
17.7k
  word->blob_widths.insert(word->blob_widths.end(), word2->blob_widths.begin(), word2->blob_widths.end());
234
17.7k
  word->blob_gaps.insert(word->blob_gaps.end(), word2->blob_gaps.begin(), word2->blob_gaps.end());
235
  // Fix the ratings matrix.
236
17.7k
  int rat1 = word->ratings->dimension();
237
17.7k
  int rat2 = word2->ratings->dimension();
238
17.7k
  word->ratings->AttachOnCorner(word2->ratings);
239
17.7k
  ASSERT_HOST(word->ratings->dimension() == rat1 + rat2);
240
17.7k
  word->best_state.insert(word->best_state.end(), word2->best_state.begin(), word2->best_state.end());
241
  // Append the word choices.
242
17.7k
  *word->raw_choice += *word2->raw_choice;
243
244
  // How many alt choices from each should we try to get?
245
17.7k
  const int kAltsPerPiece = 2;
246
  // When do we start throwing away extra alt choices?
247
17.7k
  const int kTooManyAltChoices = 100;
248
249
  // Construct the cartesian product of the best_choices of word(1) and word2.
250
17.7k
  WERD_CHOICE_LIST joined_choices;
251
17.7k
  WERD_CHOICE_IT jc_it(&joined_choices);
252
17.7k
  WERD_CHOICE_IT bc1_it(&word->best_choices);
253
17.7k
  WERD_CHOICE_IT bc2_it(&word2->best_choices);
254
17.7k
  int num_word1_choices = word->best_choices.length();
255
17.7k
  int total_joined_choices = num_word1_choices;
256
  // Nota Bene: For the main loop here, we operate only on the 2nd and greater
257
  // word2 choices, and put them in the joined_choices list. The 1st word2
258
  // choice gets added to the original word1 choices in-place after we have
259
  // finished with them.
260
17.7k
  int bc2_index = 1;
261
318k
  for (bc2_it.forward(); !bc2_it.at_first(); bc2_it.forward(), ++bc2_index) {
262
303k
    if (total_joined_choices >= kTooManyAltChoices && bc2_index > kAltsPerPiece) {
263
2.84k
      break;
264
2.84k
    }
265
300k
    int bc1_index = 0;
266
786k
    for (bc1_it.move_to_first(); bc1_index < num_word1_choices; ++bc1_index, bc1_it.forward()) {
267
486k
      if (total_joined_choices >= kTooManyAltChoices && bc1_index > kAltsPerPiece) {
268
177
        break;
269
177
      }
270
486k
      auto *wc = new WERD_CHOICE(*bc1_it.data());
271
486k
      *wc += *bc2_it.data();
272
486k
      jc_it.add_after_then_move(wc);
273
486k
      ++total_joined_choices;
274
486k
    }
275
300k
  }
276
  // Now that we've filled in as many alternates as we want, paste the best
277
  // choice for word2 onto the original word alt_choices.
278
17.7k
  bc1_it.move_to_first();
279
17.7k
  bc2_it.move_to_first();
280
57.2k
  for (bc1_it.mark_cycle_pt(); !bc1_it.cycled_list(); bc1_it.forward()) {
281
39.5k
    *bc1_it.data() += *bc2_it.data();
282
39.5k
  }
283
17.7k
  bc1_it.move_to_last();
284
17.7k
  bc1_it.add_list_after(&joined_choices);
285
286
  // Restore the pointer to original blamer bundle and combine blamer
287
  // information recorded in the splits.
288
17.7k
  if (orig_bb != nullptr) {
289
0
    orig_bb->JoinBlames(*word->blamer_bundle, *word2->blamer_bundle, wordrec_debug_blamer);
290
0
    delete word->blamer_bundle;
291
0
    word->blamer_bundle = orig_bb;
292
0
  }
293
17.7k
  word->SetupBoxWord();
294
17.7k
  word->reject_map.initialise(word->box_word->length());
295
17.7k
  delete word2;
296
17.7k
}
297
298
} // namespace tesseract