Tesseract 3.02

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
tfacepp.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 * File: tfacepp.cpp (Formerly tface++.c)
3 * Description: C++ side of the C/C++ Tess/Editor interface.
4 * Author: Ray Smith
5 * Created: Thu Apr 23 15:39:23 BST 1992
6 *
7 * (C) Copyright 1992, Hewlett-Packard Ltd.
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 #ifdef _MSC_VER
21 #pragma warning(disable:4244) // Conversion warnings
22 #pragma warning(disable:4305) // int/float warnings
23 #pragma warning(disable:4800) // int/bool warnings
24 #endif
25
26 #include <math.h>
27
28 #include "mfcpch.h"
29 #ifdef __UNIX__
30 #include <assert.h>
31 #endif
32 #include "errcode.h"
33 #include "ratngs.h"
34 #include "reject.h"
35 #include "werd.h"
36 #include "tfacep.h"
37 #include "tfacepp.h"
38 #include "tessvars.h"
39 #include "globals.h"
40 #include "reject.h"
41 #include "tesseractclass.h"
42
43 #define MAX_UNDIVIDED_LENGTH 24
44
45
46
47 /**********************************************************************
48 * recog_word
49 *
50 * Convert the word to tess form and pass it to the tess segmenter.
51 * Convert the output back to editor form.
52 **********************************************************************/
53 namespace tesseract {
54 void Tesseract::recog_word(WERD_RES *word,
55 BLOB_CHOICE_LIST_CLIST *blob_choices) {
56 ASSERT_HOST(word->chopped_word->blobs != NULL);
57 recog_word_recursive(word, blob_choices);
58 word->SetupBoxWord();
59 if ((word->best_choice->length() != word->box_word->length()) ||
60 (word->best_choice->length() != blob_choices->length())) {
61 tprintf("recog_word ASSERT FAIL String:\"%s\"; "
62 "Strlen=%d; #Blobs=%d; #Choices=%d\n",
63 word->best_choice->debug_string().string(),
64 word->best_choice->length(), word->box_word->length(),
65 blob_choices->length());
66 }
67 ASSERT_HOST(word->best_choice->length() == word->box_word->length());
68 ASSERT_HOST(word->best_choice->length() == blob_choices->length());
69 if (tessedit_override_permuter) {
70 /* Override the permuter type if a straight dictionary check disagrees. */
71 uinT8 perm_type = word->best_choice->permuter();
72 if ((perm_type != SYSTEM_DAWG_PERM) &&
73 (perm_type != FREQ_DAWG_PERM) && (perm_type != USER_DAWG_PERM)) {
74 uinT8 real_dict_perm_type = dict_word(*word->best_choice);
75 if (((real_dict_perm_type == SYSTEM_DAWG_PERM) ||
76 (real_dict_perm_type == FREQ_DAWG_PERM) ||
77 (real_dict_perm_type == USER_DAWG_PERM)) &&
78 (alpha_count(word->best_choice->unichar_string().string(),
79 word->best_choice->unichar_lengths().string()) > 0)) {
80 word->best_choice->set_permuter(real_dict_perm_type); // use dict perm
81 }
82 }
83 if (tessedit_rejection_debug &&
84 perm_type != word->best_choice->permuter()) {
85 tprintf("Permuter Type Flipped from %d to %d\n",
86 perm_type, word->best_choice->permuter());
87 }
88 }
89 // Factored out from control.cpp
90 ASSERT_HOST((word->best_choice == NULL) == (word->raw_choice == NULL));
91 if (word->best_choice == NULL || word->best_choice->length() == 0 ||
92 strspn(word->best_choice->unichar_string().string(), " ") ==
93 word->best_choice->length()) {
94 word->tess_failed = true;
95 word->reject_map.initialise(word->box_word->length());
96 word->reject_map.rej_word_tess_failure();
97 } else {
98 word->tess_failed = false;
99 }
100 }
101
102
103 /**********************************************************************
104 * recog_word_recursive
105 *
106 * Convert the word to tess form and pass it to the tess segmenter.
107 * Convert the output back to editor form.
108 **********************************************************************/
109 void Tesseract::recog_word_recursive(WERD_RES *word,
110 BLOB_CHOICE_LIST_CLIST *blob_choices) {
111 int word_length = word->chopped_word->NumBlobs(); // no of blobs
112 if (word_length > MAX_UNDIVIDED_LENGTH) {
113 return split_and_recog_word(word, blob_choices);
114 }
115 int initial_blob_choice_len = blob_choices->length();
116 BLOB_CHOICE_LIST_VECTOR* tess_ratings = cc_recog(word);
117
118 // Put BLOB_CHOICE_LISTs from tess_ratings into blob_choices.
119 BLOB_CHOICE_LIST_C_IT blob_choices_it(blob_choices);
120 for (int i = 0; i < tess_ratings->length(); ++i) {
121 blob_choices_it.add_to_end(tess_ratings->get(i));
122 }
123 delete tess_ratings;
124
125 word_length = word->rebuild_word->NumBlobs(); // No of blobs in output.
126 // Pad raw_choice with spaces if needed.
127 if (word->raw_choice->length() < word_length) {
128 UNICHAR_ID space_id = unicharset.unichar_to_id(" ");
129 while (word->raw_choice->length() < word_length) {
130 word->raw_choice->append_unichar_id(space_id, 1, 0.0,
131 word->raw_choice->certainty());
132 }
133 }
134
135 // Do sanity checks and minor fixes on best_choice.
136 if (word->best_choice->length() > word_length) {
137 word->best_choice->make_bad(); // should never happen
138 tprintf("recog_word: Discarded long string \"%s\""
139 " (%d characters vs %d blobs)\n",
140 word->best_choice->unichar_string().string(),
141 word->best_choice->length(), word_length);
142 tprintf("Word is at:");
143 word->word->bounding_box().print();
144 }
145 if (blob_choices->length() - initial_blob_choice_len != word_length) {
146 word->best_choice->make_bad(); // force rejection
147 tprintf("recog_word: Choices list len:%d; blob lists len:%d\n",
148 blob_choices->length(), word_length);
149 blob_choices_it.set_to_list(blob_choices); // list of lists
150 while (blob_choices->length() - initial_blob_choice_len < word_length) {
151 blob_choices_it.add_to_end(new BLOB_CHOICE_LIST()); // add a fake one
152 tprintf("recog_word: Added dummy choice list\n");
153 }
154 while (blob_choices->length() - initial_blob_choice_len > word_length) {
155 blob_choices_it.move_to_last(); // should never happen
156 delete blob_choices_it.extract();
157 tprintf("recog_word: Deleted choice list\n");
158 }
159 }
160 if (word->best_choice->length() < word_length) {
161 UNICHAR_ID space_id = unicharset.unichar_to_id(" ");
162 while (word->best_choice->length() < word_length) {
163 word->best_choice->append_unichar_id(space_id, 1, 0.0,
164 word->best_choice->certainty());
165 }
166 }
167 }
168
169
170 /**********************************************************************
171 * split_and_recog_word
172 *
173 * Split the word into 2 smaller pieces at the largest gap.
174 * Recognize the pieces and stick the results back together.
175 **********************************************************************/
176
177 void Tesseract::split_and_recog_word(WERD_RES *word,
178 BLOB_CHOICE_LIST_CLIST *blob_choices) {
179 // Find the biggest blob gap in the chopped_word.
180 int bestgap = -MAX_INT32;
181 TPOINT best_split_pt;
182 TBLOB* best_end = NULL;
183 TBLOB* prev_blob = NULL;
184 for (TBLOB* blob = word->chopped_word->blobs; blob != NULL;
185 blob = blob->next) {
186 if (prev_blob != NULL) {
187 TBOX prev_box = prev_blob->bounding_box();
188 TBOX blob_box = blob->bounding_box();
189 int gap = blob_box.left() - prev_box.right();
190 if (gap > bestgap) {
191 bestgap = gap;
192 best_end = prev_blob;
193 best_split_pt.x = (prev_box.right() + blob_box.left()) / 2;
194 best_split_pt.y = (prev_box.top() + prev_box.bottom() +
195 blob_box.top() + blob_box.bottom()) / 4;
196 }
197 }
198 prev_blob = blob;
199 }
200 ASSERT_HOST(best_end != NULL);
201 ASSERT_HOST(best_end->next != NULL);
202
203 // Make a copy of the word to put the 2nd half in.
204 WERD_RES* word2 = new WERD_RES(*word);
205 // Blow away the copied chopped_word, as we want to work with the blobs
206 // from the input chopped_word so the seam_arrays can be merged.
207 delete word2->chopped_word;
208 word2->chopped_word = new TWERD;
209 word2->chopped_word->blobs = best_end->next;
210 best_end->next = NULL;
211 // Make a new seamarray on both words.
212 free_seam_list(word->seam_array);
213 word->seam_array = start_seam_list(word->chopped_word->blobs);
214 word2->seam_array = start_seam_list(word2->chopped_word->blobs);
215 BlamerBundle *orig_bb = word->blamer_bundle;
216 STRING blamer_debug;
217 // Try to adjust truth information.
218 if (orig_bb != NULL) {
219 // Find truth boxes that correspond to the split in the blobs.
220 int b;
221 int begin2_truth_index = -1;
222 if (orig_bb->incorrect_result_reason != IRR_NO_TRUTH &&
223 orig_bb->truth_has_char_boxes) {
224 int end1_x = best_end->bounding_box().right();
225 int begin2_x = word2->chopped_word->blobs->bounding_box().left();
226 blamer_debug = "Looking for truth split at";
227 blamer_debug.add_str_int(" end1_x ", end1_x);
228 blamer_debug.add_str_int(" begin2_x ", begin2_x);
229 blamer_debug += "\nnorm_truth_word boxes:\n";
230 if (orig_bb->norm_truth_word.length() > 1) {
231 orig_bb->norm_truth_word.BlobBox(0).append_debug(&blamer_debug);
232 for (b = 1; b < orig_bb->norm_truth_word.length(); ++b) {
233 orig_bb->norm_truth_word.BlobBox(b).append_debug(&blamer_debug);
234 if ((abs(end1_x - orig_bb->norm_truth_word.BlobBox(b-1).right()) <
235 orig_bb->norm_box_tolerance) &&
236 (abs(begin2_x - orig_bb->norm_truth_word.BlobBox(b).left()) <
237 orig_bb->norm_box_tolerance)) {
238 begin2_truth_index = b;
239 blamer_debug += "Split found\n";
240 break;
241 }
242 }
243 }
244 }
245 // Populate truth information in word and word2 with the first and second
246 // part of the original truth.
247 word->blamer_bundle = new BlamerBundle();
248 word2->blamer_bundle = new BlamerBundle();
249 if (begin2_truth_index > 0) {
250 word->blamer_bundle->truth_has_char_boxes = true;
251 word->blamer_bundle->norm_box_tolerance = orig_bb->norm_box_tolerance;
252 word2->blamer_bundle->truth_has_char_boxes = true;
253 word2->blamer_bundle->norm_box_tolerance = orig_bb->norm_box_tolerance;
254 BlamerBundle *curr_bb = word->blamer_bundle;
255 for (b = 0; b < orig_bb->norm_truth_word.length(); ++b) {
256 if (b == begin2_truth_index) curr_bb = word2->blamer_bundle;
257 curr_bb->norm_truth_word.InsertBox(
258 b, orig_bb->norm_truth_word.BlobBox(b));
259 curr_bb->truth_word.InsertBox(b, orig_bb->truth_word.BlobBox(b));
260 curr_bb->truth_text.push_back(orig_bb->truth_text[b]);
261 }
262 } else if (orig_bb->incorrect_result_reason == IRR_NO_TRUTH) {
263 word->blamer_bundle->incorrect_result_reason = IRR_NO_TRUTH;
264 word2->blamer_bundle->incorrect_result_reason = IRR_NO_TRUTH;
265 } else {
266 blamer_debug += "Truth split not found";
267 blamer_debug += orig_bb->truth_has_char_boxes ?
268 "\n" : " (no truth char boxes)\n";
269 word->blamer_bundle->SetBlame(IRR_NO_TRUTH_SPLIT, blamer_debug,
270 NULL, wordrec_debug_blamer);
271 word2->blamer_bundle->SetBlame(IRR_NO_TRUTH_SPLIT, blamer_debug,
272 NULL, wordrec_debug_blamer);
273 }
274 }
275
276 // Recognize the first part of the word.
277 recog_word_recursive(word, blob_choices);
278 // Recognize the second part of the word.
279 recog_word_recursive(word2, blob_choices);
280 // Tack the word2 outputs onto the end of the word outputs.
281 // New blobs might have appeared on the end of word1.
282 for (best_end = word->chopped_word->blobs; best_end->next != NULL;
283 best_end = best_end->next);
284 best_end->next = word2->chopped_word->blobs;
285 TBLOB* blob;
286 for (blob = word->rebuild_word->blobs; blob->next != NULL; blob = blob->next);
287 blob->next = word2->rebuild_word->blobs;
288 word2->chopped_word->blobs = NULL;
289 word2->rebuild_word->blobs = NULL;
290 // Copy the seams onto the end of the word1 seam_array.
291 // Since the seam list is one element short, an empty seam marking the
292 // end of the last blob in the first word is needed first.
293 word->seam_array = add_seam(word->seam_array,
294 new_seam(0.0, best_split_pt, NULL, NULL, NULL));
295 for (int i = 0; i < array_count(word2->seam_array); ++i) {
296 SEAM* seam = reinterpret_cast< SEAM*>(array_value(word2->seam_array, i));
297 array_value(word2->seam_array, i) = NULL;
298 word->seam_array = add_seam(word->seam_array, seam);
299 }
300 word->best_state += word2->best_state;
301 // Append the word choices.
302 *word->best_choice += *word2->best_choice;
303 *word->raw_choice += *word2->raw_choice;
304
305 // How many alt choices from each should we try to get?
306 const int kAltsPerPiece = 2;
307 // When do we start throwing away extra alt choices?
308 const int kTooManyAltChoices = 100;
309
310 if (word->alt_choices.size() > 0 && word2->alt_choices.size() > 0) {
311 // Construct the cartesian product of the alt choices of word(1) and word2.
312 int num_first_alt_choices = word->alt_choices.size();
313 // Nota Bene: For the main loop here, we leave in place word1-only
314 // alt_choices in
315 // word->alt_choices[0] .. word_alt_choices[num_first_alt_choices - 1]
316 // These will get fused with the best choices for word2 below.
317 for (int j = 1; j < word2->alt_choices.size() &&
318 (j <= kAltsPerPiece || word->alt_choices.size() < kTooManyAltChoices);
319 j++) {
320 for (int i = 0; i < num_first_alt_choices &&
321 (i <= kAltsPerPiece ||
322 word->alt_choices.size() < kTooManyAltChoices);
323 i++) {
324 WERD_CHOICE *wc = new WERD_CHOICE(*word->alt_choices[i]);
325 *wc += *word2->alt_choices[j];
326 word->alt_choices.push_back(wc);
327
328 word->alt_states.push_back(GenericVector<int>());
329 GenericVector<int> &alt_state = word->alt_states.back();
330 alt_state += word->alt_states[i];
331 alt_state += word2->alt_states[j];
332 }
333 }
334 // Now that we've filled in as many alternates as we want, paste the best
335 // choice for word2 onto the original word alt_choices.
336 for (int i = 0; i < num_first_alt_choices; i++) {
337 *word->alt_choices[i] += *word2->alt_choices[0];
338 word->alt_states[i] += word2->alt_states[0];
339 }
340 }
341
342 // Restore the pointer to original blamer bundle and combine blamer
343 // information recorded in the splits.
344 if (orig_bb != NULL) {
345 IncorrectResultReason irr = orig_bb->incorrect_result_reason;
346 if (irr != IRR_NO_TRUTH_SPLIT) blamer_debug = "";
347 if (word->blamer_bundle->incorrect_result_reason != IRR_CORRECT &&
348 word->blamer_bundle->incorrect_result_reason != IRR_NO_TRUTH &&
349 word->blamer_bundle->incorrect_result_reason != IRR_NO_TRUTH_SPLIT) {
350 blamer_debug += "Blame from part 1: ";
351 blamer_debug += word->blamer_bundle->debug;
352 irr = word->blamer_bundle->incorrect_result_reason;
353 }
354 if (word2->blamer_bundle->incorrect_result_reason != IRR_CORRECT &&
355 word2->blamer_bundle->incorrect_result_reason != IRR_NO_TRUTH &&
356 word2->blamer_bundle->incorrect_result_reason != IRR_NO_TRUTH_SPLIT) {
357 blamer_debug += "Blame from part 2: ";
358 blamer_debug += word2->blamer_bundle->debug;
359 if (irr == IRR_CORRECT) {
360 irr = word2->blamer_bundle->incorrect_result_reason;
361 } else if (irr != word2->blamer_bundle->incorrect_result_reason) {
362 irr = IRR_UNKNOWN;
363 }
364 }
365 delete word->blamer_bundle;
366 word->blamer_bundle = orig_bb;
367 word->blamer_bundle->incorrect_result_reason = irr;
368 if (irr != IRR_CORRECT && irr != IRR_NO_TRUTH) {
369 word->blamer_bundle->SetBlame(irr, blamer_debug, NULL,
370 wordrec_debug_blamer);
371 }
372 }
373 delete word2;
374 }
375
376 } // namespace tesseract