/src/tesseract/src/ccmain/reject.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * File: reject.cpp (Formerly reject.c) |
3 | | * Description: Rejection functions used in tessedit |
4 | | * Author: Phil Cheatle |
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 automatically generated configuration file if running autoconf. |
20 | | #ifdef HAVE_CONFIG_H |
21 | | # include "config_auto.h" |
22 | | #endif |
23 | | |
24 | | #ifdef DISABLED_LEGACY_ENGINE |
25 | | |
26 | | # include "tesseractclass.h" |
27 | | |
28 | | namespace tesseract { |
29 | | |
30 | | int16_t Tesseract::safe_dict_word(const WERD_RES *werd_res) { |
31 | | const WERD_CHOICE &word = *werd_res->best_choice; |
32 | | int dict_word_type = werd_res->tesseract->dict_word(word); |
33 | | return dict_word_type == DOC_DAWG_PERM ? 0 : dict_word_type; |
34 | | } |
35 | | } // namespace tesseract |
36 | | |
37 | | #else |
38 | | |
39 | | # include "reject.h" |
40 | | |
41 | | # include "control.h" |
42 | | # include "docqual.h" |
43 | | # include "tesseractclass.h" |
44 | | # include "tessvars.h" |
45 | | |
46 | | # include "helpers.h" |
47 | | |
48 | | # include <algorithm> // for std::sort |
49 | | # include <cctype> |
50 | | # include <cerrno> |
51 | | # include <cstring> |
52 | | # include <vector> // for std::vector |
53 | | |
54 | | namespace tesseract { |
55 | | |
56 | | /************************************************************************* |
57 | | * set_done() |
58 | | * |
59 | | * Set the done flag based on the word acceptability criteria |
60 | | *************************************************************************/ |
61 | | |
62 | 104k | void Tesseract::set_done(WERD_RES *word, int16_t pass) { |
63 | 104k | word->done = |
64 | 104k | word->tess_accepted && (strchr(word->best_choice->unichar_string().c_str(), ' ') == nullptr); |
65 | 104k | bool word_is_ambig = word->best_choice->dangerous_ambig_found(); |
66 | 104k | bool word_from_dict = word->best_choice->permuter() == SYSTEM_DAWG_PERM || |
67 | 73.9k | word->best_choice->permuter() == FREQ_DAWG_PERM || |
68 | 64.0k | word->best_choice->permuter() == USER_DAWG_PERM; |
69 | 104k | if (word->done && (pass == 1) && (!word_from_dict || word_is_ambig) && |
70 | 286 | one_ell_conflict(word, false)) { |
71 | 3 | if (tessedit_rejection_debug) { |
72 | 0 | tprintf("one_ell_conflict detected\n"); |
73 | 0 | } |
74 | 3 | word->done = false; |
75 | 3 | } |
76 | 104k | if (word->done && |
77 | 7.61k | ((!word_from_dict && word->best_choice->permuter() != NUMBER_PERM) || word_is_ambig)) { |
78 | 752 | if (tessedit_rejection_debug) { |
79 | 0 | tprintf("non-dict or ambig word detected\n"); |
80 | 0 | } |
81 | 752 | word->done = false; |
82 | 752 | } |
83 | 104k | if (tessedit_rejection_debug) { |
84 | 0 | tprintf("set_done(): done=%d\n", word->done); |
85 | 0 | word->best_choice->print(""); |
86 | 0 | } |
87 | 104k | } |
88 | | |
89 | | /************************************************************************* |
90 | | * make_reject_map() |
91 | | * |
92 | | * Sets the done flag to indicate whether the resylt is acceptable. |
93 | | * |
94 | | * Sets a reject map for the word. |
95 | | *************************************************************************/ |
96 | 104k | void Tesseract::make_reject_map(WERD_RES *word, int16_t pass) { |
97 | 104k | flip_0O(word); |
98 | 104k | check_debug_pt(word, -1); // For trap only |
99 | 104k | set_done(word, pass); // Set acceptance |
100 | 104k | word->reject_map.initialise(word->best_choice->unichar_lengths().length()); |
101 | 104k | reject_blanks(word); |
102 | | /* |
103 | | 0: Rays original heuristic - the baseline |
104 | | */ |
105 | 104k | if (tessedit_reject_mode == 0) { |
106 | 104k | if (!word->done) { |
107 | 97.9k | reject_poor_matches(word); |
108 | 97.9k | } |
109 | 104k | } else if (tessedit_reject_mode == 5) { |
110 | | /* |
111 | | 5: Reject I/1/l from words where there is no strong contextual confirmation; |
112 | | the whole of any unacceptable words (incl PERM rej of dubious 1/I/ls); |
113 | | and the whole of any words which are very small |
114 | | */ |
115 | 0 | if (kBlnXHeight / word->denorm.y_scale() <= min_sane_x_ht_pixels) { |
116 | 0 | word->reject_map.rej_word_small_xht(); |
117 | 0 | } else { |
118 | 0 | one_ell_conflict(word, true); |
119 | | /* |
120 | | Originally the code here just used the done flag. Now I have duplicated |
121 | | and unpacked the conditions for setting the done flag so that each |
122 | | mechanism can be turned on or off independently. This works WITHOUT |
123 | | affecting the done flag setting. |
124 | | */ |
125 | 0 | if (rej_use_tess_accepted && !word->tess_accepted) { |
126 | 0 | word->reject_map.rej_word_not_tess_accepted(); |
127 | 0 | } |
128 | |
|
129 | 0 | if (rej_use_tess_blanks && |
130 | 0 | (strchr(word->best_choice->unichar_string().c_str(), ' ') != nullptr)) { |
131 | 0 | word->reject_map.rej_word_contains_blanks(); |
132 | 0 | } |
133 | |
|
134 | 0 | WERD_CHOICE *best_choice = word->best_choice; |
135 | 0 | if (rej_use_good_perm) { |
136 | 0 | if ((best_choice->permuter() == SYSTEM_DAWG_PERM || |
137 | 0 | best_choice->permuter() == FREQ_DAWG_PERM || |
138 | 0 | best_choice->permuter() == USER_DAWG_PERM) && |
139 | 0 | (!rej_use_sensible_wd || |
140 | 0 | acceptable_word_string(*word->uch_set, best_choice->unichar_string().c_str(), |
141 | 0 | best_choice->unichar_lengths().c_str()) != AC_UNACCEPTABLE)) { |
142 | | // PASSED TEST |
143 | 0 | } else if (best_choice->permuter() == NUMBER_PERM) { |
144 | 0 | if (rej_alphas_in_number_perm) { |
145 | 0 | for (int i = 0, offset = 0; best_choice->unichar_string()[offset] != '\0'; |
146 | 0 | offset += best_choice->unichar_lengths()[i++]) { |
147 | 0 | if (word->reject_map[i].accepted() && |
148 | 0 | word->uch_set->get_isalpha(best_choice->unichar_string().c_str() + offset, |
149 | 0 | best_choice->unichar_lengths()[i])) { |
150 | 0 | word->reject_map[i].setrej_bad_permuter(); |
151 | 0 | } |
152 | | // rej alpha |
153 | 0 | } |
154 | 0 | } |
155 | 0 | } else { |
156 | 0 | word->reject_map.rej_word_bad_permuter(); |
157 | 0 | } |
158 | 0 | } |
159 | | /* Ambig word rejection was here once !!*/ |
160 | 0 | } |
161 | 0 | } else { |
162 | 0 | tprintf("BAD tessedit_reject_mode\n"); |
163 | 0 | ASSERT_HOST("Fatal error encountered!" == nullptr); |
164 | 0 | } |
165 | | |
166 | 104k | if (tessedit_image_border > -1) { |
167 | 104k | reject_edge_blobs(word); |
168 | 104k | } |
169 | | |
170 | 104k | check_debug_pt(word, 10); |
171 | 104k | if (tessedit_rejection_debug) { |
172 | 0 | tprintf("Permuter Type = %d\n", word->best_choice->permuter()); |
173 | 0 | tprintf("Certainty: %f Rating: %f\n", word->best_choice->certainty(), |
174 | 0 | word->best_choice->rating()); |
175 | 0 | tprintf("Dict word: %d\n", dict_word(*(word->best_choice))); |
176 | 0 | } |
177 | | |
178 | 104k | flip_hyphens(word); |
179 | 104k | check_debug_pt(word, 20); |
180 | 104k | } |
181 | | |
182 | 104k | void reject_blanks(WERD_RES *word) { |
183 | 104k | int16_t i; |
184 | 104k | int16_t offset; |
185 | | |
186 | 519k | for (i = 0, offset = 0; word->best_choice->unichar_string()[offset] != '\0'; |
187 | 414k | offset += word->best_choice->unichar_lengths()[i], i += 1) { |
188 | 414k | if (word->best_choice->unichar_string()[offset] == ' ') { |
189 | | // rej unrecognised blobs |
190 | 27 | word->reject_map[i].setrej_tess_failure(); |
191 | 27 | } |
192 | 414k | } |
193 | 104k | } |
194 | | |
195 | 0 | void Tesseract::reject_I_1_L(WERD_RES *word) { |
196 | 0 | int16_t i; |
197 | 0 | int16_t offset; |
198 | |
|
199 | 0 | for (i = 0, offset = 0; word->best_choice->unichar_string()[offset] != '\0'; |
200 | 0 | offset += word->best_choice->unichar_lengths()[i], i += 1) { |
201 | 0 | if (conflict_set_I_l_1.contains(word->best_choice->unichar_string()[offset])) { |
202 | | // rej 1Il conflict |
203 | 0 | word->reject_map[i].setrej_1Il_conflict(); |
204 | 0 | } |
205 | 0 | } |
206 | 0 | } |
207 | | |
208 | 97.9k | void reject_poor_matches(WERD_RES *word) { |
209 | 97.9k | float threshold = compute_reject_threshold(word->best_choice); |
210 | 505k | for (unsigned i = 0; i < word->best_choice->length(); ++i) { |
211 | 407k | if (word->best_choice->unichar_id(i) == UNICHAR_SPACE) { |
212 | 27 | word->reject_map[i].setrej_tess_failure(); |
213 | 407k | } else if (word->best_choice->certainty(i) < threshold) { |
214 | 179k | word->reject_map[i].setrej_poor_match(); |
215 | 179k | } |
216 | 407k | } |
217 | 97.9k | } |
218 | | |
219 | | /********************************************************************** |
220 | | * compute_reject_threshold |
221 | | * |
222 | | * Set a rejection threshold for this word. |
223 | | * Initially this is a trivial function which looks for the largest |
224 | | * gap in the certainty value. |
225 | | **********************************************************************/ |
226 | | |
227 | 97.9k | float compute_reject_threshold(WERD_CHOICE *word) { |
228 | 97.9k | float threshold; // rejection threshold |
229 | 97.9k | float bestgap = 0.0f; // biggest gap |
230 | 97.9k | float gapstart; // bottom of gap |
231 | | |
232 | 97.9k | auto blob_count = word->length(); |
233 | 97.9k | std::vector<float> ratings; |
234 | 97.9k | ratings.reserve(blob_count); |
235 | 505k | for (unsigned i = 0; i < blob_count; ++i) { |
236 | 407k | ratings.push_back(word->certainty(i)); |
237 | 407k | } |
238 | 97.9k | std::sort(ratings.begin(), ratings.end()); |
239 | 97.9k | gapstart = ratings[0] - 1; // all reject if none better |
240 | 97.9k | if (blob_count >= 3) { |
241 | 342k | for (unsigned index = 0; index < blob_count - 1; index++) { |
242 | 300k | if (ratings[index + 1] - ratings[index] > bestgap) { |
243 | 96.1k | bestgap = ratings[index + 1] - ratings[index]; |
244 | | // find biggest |
245 | 96.1k | gapstart = ratings[index]; |
246 | 96.1k | } |
247 | 300k | } |
248 | 42.2k | } |
249 | 97.9k | threshold = gapstart + bestgap / 2; |
250 | | |
251 | 97.9k | return threshold; |
252 | 97.9k | } |
253 | | |
254 | | /************************************************************************* |
255 | | * reject_edge_blobs() |
256 | | * |
257 | | * If the word is perilously close to the edge of the image, reject those blobs |
258 | | * in the word which are too close to the edge as they could be clipped. |
259 | | *************************************************************************/ |
260 | 104k | void Tesseract::reject_edge_blobs(WERD_RES *word) { |
261 | 104k | TBOX word_box = word->word->bounding_box(); |
262 | | // Use the box_word as it is already denormed back to image coordinates. |
263 | 104k | int blobcount = word->box_word->length(); |
264 | | |
265 | 104k | if (word_box.left() < tessedit_image_border || word_box.bottom() < tessedit_image_border || |
266 | 27.8k | word_box.right() + tessedit_image_border > ImageWidth() - 1 || |
267 | 81.7k | word_box.top() + tessedit_image_border > ImageHeight() - 1) { |
268 | 81.7k | ASSERT_HOST(word->reject_map.length() == blobcount); |
269 | 377k | for (int blobindex = 0; blobindex < blobcount; blobindex++) { |
270 | 296k | TBOX blob_box = word->box_word->BlobBox(blobindex); |
271 | 296k | if (blob_box.left() < tessedit_image_border || blob_box.bottom() < tessedit_image_border || |
272 | 203k | blob_box.right() + tessedit_image_border > ImageWidth() - 1 || |
273 | 202k | blob_box.top() + tessedit_image_border > ImageHeight() - 1) { |
274 | 106k | word->reject_map[blobindex].setrej_edge_char(); |
275 | | // Close to edge |
276 | 106k | } |
277 | 296k | } |
278 | 81.7k | } |
279 | 104k | } |
280 | | |
281 | | /********************************************************************** |
282 | | * one_ell_conflict() |
283 | | * |
284 | | * Identify words where there is a potential I/l/1 error. |
285 | | * - A bundle of contextual heuristics! |
286 | | **********************************************************************/ |
287 | 286 | bool Tesseract::one_ell_conflict(WERD_RES *word_res, bool update_map) { |
288 | 286 | int16_t word_len; // its length |
289 | 286 | int16_t first_alphanum_index_; |
290 | 286 | int16_t first_alphanum_offset_; |
291 | 286 | int16_t i; |
292 | 286 | int16_t offset; |
293 | 286 | bool non_conflict_set_char; // non conf set a/n? |
294 | 286 | ACCEPTABLE_WERD_TYPE word_type; |
295 | 286 | bool dict_perm_type; |
296 | 286 | bool dict_word_ok; |
297 | 286 | int dict_word_type; |
298 | | |
299 | | // unichar_string() and unichar_lengths() rebuild their internal strings, |
300 | | // so copy them here to keep stable pointers for the rest of the function. |
301 | 286 | std::string word_str = word_res->best_choice->unichar_string(); |
302 | 286 | std::string lengths_str = word_res->best_choice->unichar_lengths(); |
303 | 286 | const char *word = word_str.c_str(); |
304 | 286 | const char *lengths = lengths_str.c_str(); |
305 | 286 | word_len = strlen(lengths); |
306 | | /* |
307 | | If there are no occurrences of the conflict set characters then the word |
308 | | is OK. |
309 | | */ |
310 | 286 | if (strpbrk(word, conflict_set_I_l_1.c_str()) == nullptr) { |
311 | 283 | return false; |
312 | 283 | } |
313 | | |
314 | | /* |
315 | | There is a conflict if there are NO other (confirmed) alphanumerics apart |
316 | | from those in the conflict set. |
317 | | */ |
318 | | |
319 | 21 | for (i = 0, offset = 0, non_conflict_set_char = false; (i < word_len) && !non_conflict_set_char; |
320 | 18 | offset += lengths[i++]) { |
321 | 18 | non_conflict_set_char = (word_res->uch_set->get_isalpha(word + offset, lengths[i]) || |
322 | 16 | word_res->uch_set->get_isdigit(word + offset, lengths[i])) && |
323 | 3 | !conflict_set_I_l_1.contains(word[offset]); |
324 | 18 | } |
325 | 3 | if (!non_conflict_set_char) { |
326 | 3 | if (update_map) { |
327 | 0 | reject_I_1_L(word_res); |
328 | 0 | } |
329 | 3 | return true; |
330 | 3 | } |
331 | | |
332 | | /* |
333 | | If the word is accepted by a dawg permuter, and the first alpha character |
334 | | is "I" or "l", check to see if the alternative is also a dawg word. If it |
335 | | is, then there is a potential error otherwise the word is ok. |
336 | | */ |
337 | | |
338 | 0 | dict_perm_type = (word_res->best_choice->permuter() == SYSTEM_DAWG_PERM) || |
339 | 0 | (word_res->best_choice->permuter() == USER_DAWG_PERM) || |
340 | 0 | (rej_trust_doc_dawg && (word_res->best_choice->permuter() == DOC_DAWG_PERM)) || |
341 | 0 | (word_res->best_choice->permuter() == FREQ_DAWG_PERM); |
342 | 0 | dict_word_type = dict_word(*(word_res->best_choice)); |
343 | 0 | dict_word_ok = (dict_word_type > 0) && (rej_trust_doc_dawg || (dict_word_type != DOC_DAWG_PERM)); |
344 | |
|
345 | 0 | if ((rej_1Il_use_dict_word && dict_word_ok) || (rej_1Il_trust_permuter_type && dict_perm_type) || |
346 | 0 | (dict_perm_type && dict_word_ok)) { |
347 | 0 | first_alphanum_index_ = first_alphanum_index(word, lengths); |
348 | 0 | first_alphanum_offset_ = first_alphanum_offset(word, lengths); |
349 | 0 | if (lengths[first_alphanum_index_] == 1 && word[first_alphanum_offset_] == 'I') { |
350 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l'; |
351 | 0 | if (safe_dict_word(word_res) > 0) { |
352 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I'; |
353 | 0 | if (update_map) { |
354 | 0 | word_res->reject_map[first_alphanum_index_].setrej_1Il_conflict(); |
355 | 0 | } |
356 | 0 | return true; |
357 | 0 | } else { |
358 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I'; |
359 | 0 | return false; |
360 | 0 | } |
361 | 0 | } |
362 | | |
363 | 0 | if (lengths[first_alphanum_index_] == 1 && word[first_alphanum_offset_] == 'l') { |
364 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I'; |
365 | 0 | if (safe_dict_word(word_res) > 0) { |
366 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l'; |
367 | 0 | if (update_map) { |
368 | 0 | word_res->reject_map[first_alphanum_index_].setrej_1Il_conflict(); |
369 | 0 | } |
370 | 0 | return true; |
371 | 0 | } else { |
372 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l'; |
373 | 0 | return false; |
374 | 0 | } |
375 | 0 | } |
376 | 0 | return false; |
377 | 0 | } |
378 | | |
379 | | /* |
380 | | NEW 1Il code. The old code relied on permuter types too much. In fact, |
381 | | tess will use TOP_CHOICE permute for good things like "palette". |
382 | | In this code the string is examined independently to see if it looks like |
383 | | a well formed word. |
384 | | */ |
385 | | |
386 | | /* |
387 | | REGARDLESS OF PERMUTER, see if flipping a leading I/l generates a |
388 | | dictionary word. |
389 | | */ |
390 | 0 | first_alphanum_index_ = first_alphanum_index(word, lengths); |
391 | 0 | first_alphanum_offset_ = first_alphanum_offset(word, lengths); |
392 | 0 | if (lengths[first_alphanum_index_] == 1 && word[first_alphanum_offset_] == 'l') { |
393 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I'; |
394 | 0 | if (safe_dict_word(word_res) > 0) { |
395 | 0 | return false; |
396 | 0 | } else { |
397 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l'; |
398 | 0 | } |
399 | 0 | } else if (lengths[first_alphanum_index_] == 1 && word[first_alphanum_offset_] == 'I') { |
400 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'l'; |
401 | 0 | if (safe_dict_word(word_res) > 0) { |
402 | 0 | return false; |
403 | 0 | } else { |
404 | 0 | word_res->best_choice->unichar_string()[first_alphanum_offset_] = 'I'; |
405 | 0 | } |
406 | 0 | } |
407 | | /* |
408 | | For strings containing digits: |
409 | | If there are no alphas OR the numeric permuter liked the word, |
410 | | reject any non 1 conflict chs |
411 | | Else reject all conflict chs |
412 | | */ |
413 | 0 | if (word_contains_non_1_digit(word, lengths)) { |
414 | 0 | bool allow_1s = |
415 | 0 | (alpha_count(word, lengths) == 0) || (word_res->best_choice->permuter() == NUMBER_PERM); |
416 | |
|
417 | 0 | int16_t offset; |
418 | 0 | bool conflict = false; |
419 | 0 | for (i = 0, offset = 0; word[offset] != '\0'; |
420 | 0 | offset += word_res->best_choice->unichar_lengths()[i++]) { |
421 | 0 | if ((!allow_1s || (word[offset] != '1')) && |
422 | 0 | conflict_set_I_l_1.contains(word[offset])) { |
423 | 0 | if (update_map) { |
424 | 0 | word_res->reject_map[i].setrej_1Il_conflict(); |
425 | 0 | } |
426 | 0 | conflict = true; |
427 | 0 | } |
428 | 0 | } |
429 | 0 | return conflict; |
430 | 0 | } |
431 | | /* |
432 | | For anything else. See if it conforms to an acceptable word type. If so, |
433 | | treat accordingly. |
434 | | */ |
435 | 0 | word_type = acceptable_word_string(*word_res->uch_set, word, lengths); |
436 | 0 | if ((word_type == AC_LOWER_CASE) || (word_type == AC_INITIAL_CAP)) { |
437 | 0 | first_alphanum_index_ = first_alphanum_index(word, lengths); |
438 | 0 | first_alphanum_offset_ = first_alphanum_offset(word, lengths); |
439 | 0 | if (conflict_set_I_l_1.contains(word[first_alphanum_offset_])) { |
440 | 0 | if (update_map) { |
441 | 0 | word_res->reject_map[first_alphanum_index_].setrej_1Il_conflict(); |
442 | 0 | } |
443 | 0 | return true; |
444 | 0 | } else { |
445 | 0 | return false; |
446 | 0 | } |
447 | 0 | } else if (word_type == AC_UPPER_CASE) { |
448 | 0 | return false; |
449 | 0 | } else { |
450 | 0 | if (update_map) { |
451 | 0 | reject_I_1_L(word_res); |
452 | 0 | } |
453 | 0 | return true; |
454 | 0 | } |
455 | 0 | } |
456 | | |
457 | 0 | int16_t Tesseract::first_alphanum_index(const char *word, const char *word_lengths) { |
458 | 0 | int16_t i; |
459 | 0 | int16_t offset; |
460 | |
|
461 | 0 | for (i = 0, offset = 0; word[offset] != '\0'; offset += word_lengths[i++]) { |
462 | 0 | if (unicharset.get_isalpha(word + offset, word_lengths[i]) || |
463 | 0 | unicharset.get_isdigit(word + offset, word_lengths[i])) { |
464 | 0 | return i; |
465 | 0 | } |
466 | 0 | } |
467 | 0 | return -1; |
468 | 0 | } |
469 | | |
470 | 0 | int16_t Tesseract::first_alphanum_offset(const char *word, const char *word_lengths) { |
471 | 0 | int16_t i; |
472 | 0 | int16_t offset; |
473 | |
|
474 | 0 | for (i = 0, offset = 0; word[offset] != '\0'; offset += word_lengths[i++]) { |
475 | 0 | if (unicharset.get_isalpha(word + offset, word_lengths[i]) || |
476 | 0 | unicharset.get_isdigit(word + offset, word_lengths[i])) { |
477 | 0 | return offset; |
478 | 0 | } |
479 | 0 | } |
480 | 0 | return -1; |
481 | 0 | } |
482 | | |
483 | 40.7k | int16_t Tesseract::alpha_count(const char *word, const char *word_lengths) { |
484 | 40.7k | int16_t i; |
485 | 40.7k | int16_t offset; |
486 | 40.7k | int16_t count = 0; |
487 | | |
488 | 86.0k | for (i = 0, offset = 0; word[offset] != '\0'; offset += word_lengths[i++]) { |
489 | 45.3k | if (unicharset.get_isalpha(word + offset, word_lengths[i])) { |
490 | 42.5k | count++; |
491 | 42.5k | } |
492 | 45.3k | } |
493 | 40.7k | return count; |
494 | 40.7k | } |
495 | | |
496 | 0 | bool Tesseract::word_contains_non_1_digit(const char *word, const char *word_lengths) { |
497 | 0 | int16_t i; |
498 | 0 | int16_t offset; |
499 | |
|
500 | 0 | for (i = 0, offset = 0; word[offset] != '\0'; offset += word_lengths[i++]) { |
501 | 0 | if (unicharset.get_isdigit(word + offset, word_lengths[i]) && |
502 | 0 | (word_lengths[i] != 1 || word[offset] != '1')) { |
503 | 0 | return true; |
504 | 0 | } |
505 | 0 | } |
506 | 0 | return false; |
507 | 0 | } |
508 | | |
509 | | /************************************************************************* |
510 | | * dont_allow_1Il() |
511 | | * Don't unreject LONE accepted 1Il conflict set chars |
512 | | *************************************************************************/ |
513 | 0 | void Tesseract::dont_allow_1Il(WERD_RES *word) { |
514 | 0 | int word_len = word->reject_map.length(); |
515 | 0 | const char *s = word->best_choice->unichar_string().c_str(); |
516 | 0 | const char *lengths = word->best_choice->unichar_lengths().c_str(); |
517 | 0 | bool accepted_1Il = false; |
518 | |
|
519 | 0 | for (int i = 0, offset = 0; i < word_len; offset += word->best_choice->unichar_lengths()[i++]) { |
520 | 0 | if (word->reject_map[i].accepted()) { |
521 | 0 | if (conflict_set_I_l_1.contains(s[offset])) { |
522 | 0 | accepted_1Il = true; |
523 | 0 | } else { |
524 | 0 | if (word->uch_set->get_isalpha(s + offset, lengths[i]) || |
525 | 0 | word->uch_set->get_isdigit(s + offset, lengths[i])) { |
526 | 0 | return; // >=1 non 1Il ch accepted |
527 | 0 | } |
528 | 0 | } |
529 | 0 | } |
530 | 0 | } |
531 | 0 | if (!accepted_1Il) { |
532 | 0 | return; // Nothing to worry about |
533 | 0 | } |
534 | | |
535 | 0 | for (int i = 0, offset = 0; i < word_len; offset += word->best_choice->unichar_lengths()[i++]) { |
536 | 0 | if (conflict_set_I_l_1.contains(s[offset]) && word->reject_map[i].accepted()) { |
537 | 0 | word->reject_map[i].setrej_postNN_1Il(); |
538 | 0 | } |
539 | 0 | } |
540 | 0 | } |
541 | | |
542 | 0 | int16_t Tesseract::count_alphanums(WERD_RES *word_res) { |
543 | 0 | int count = 0; |
544 | 0 | const WERD_CHOICE *best_choice = word_res->best_choice; |
545 | 0 | for (unsigned i = 0; i < word_res->reject_map.length(); ++i) { |
546 | 0 | if ((word_res->reject_map[i].accepted()) && |
547 | 0 | (word_res->uch_set->get_isalpha(best_choice->unichar_id(i)) || |
548 | 0 | word_res->uch_set->get_isdigit(best_choice->unichar_id(i)))) { |
549 | 0 | count++; |
550 | 0 | } |
551 | 0 | } |
552 | 0 | return count; |
553 | 0 | } |
554 | | |
555 | | // reject all if most rejected. |
556 | 0 | void Tesseract::reject_mostly_rejects(WERD_RES *word) { |
557 | | /* Reject the whole of the word if the fraction of rejects exceeds a limit */ |
558 | |
|
559 | 0 | if (static_cast<float>(word->reject_map.reject_count()) / word->reject_map.length() >= |
560 | 0 | rej_whole_of_mostly_reject_word_fract) { |
561 | 0 | word->reject_map.rej_word_mostly_rej(); |
562 | 0 | } |
563 | 0 | } |
564 | | |
565 | 0 | bool Tesseract::repeated_nonalphanum_wd(WERD_RES *word) { |
566 | 0 | if (word->best_choice->unichar_lengths().length() <= 1) { |
567 | 0 | return false; |
568 | 0 | } |
569 | | |
570 | 0 | if (!ok_repeated_ch_non_alphanum_wds.contains(word->best_choice->unichar_string()[0])) { |
571 | 0 | return false; |
572 | 0 | } |
573 | | |
574 | 0 | UNICHAR_ID uch_id = word->best_choice->unichar_id(0); |
575 | 0 | for (unsigned i = 1; i < word->best_choice->length(); ++i) { |
576 | 0 | if (word->best_choice->unichar_id(i) != uch_id) { |
577 | 0 | return false; |
578 | 0 | } |
579 | 0 | } |
580 | | |
581 | 0 | int16_t char_quality; |
582 | 0 | int16_t accepted_char_quality; |
583 | 0 | word_char_quality(word, &char_quality, &accepted_char_quality); |
584 | |
|
585 | 0 | if ((word->best_choice->unichar_lengths().length() == static_cast<size_t>(char_quality)) && |
586 | 0 | (char_quality == accepted_char_quality)) { |
587 | 0 | return true; |
588 | 0 | } else { |
589 | 0 | return false; |
590 | 0 | } |
591 | 0 | } |
592 | | |
593 | 0 | int16_t Tesseract::safe_dict_word(const WERD_RES *werd_res) { |
594 | 0 | const WERD_CHOICE &word = *werd_res->best_choice; |
595 | 0 | int dict_word_type = werd_res->tesseract->dict_word(word); |
596 | 0 | return dict_word_type == DOC_DAWG_PERM ? 0 : dict_word_type; |
597 | 0 | } |
598 | | |
599 | | // Note: After running this function word_res->ratings |
600 | | // might not contain the right BLOB_CHOICE corresponding to each character |
601 | | // in word_res->best_choice. |
602 | 104k | void Tesseract::flip_hyphens(WERD_RES *word_res) { |
603 | 104k | WERD_CHOICE *best_choice = word_res->best_choice; |
604 | 104k | int prev_right = -9999; |
605 | 104k | int next_left; |
606 | 104k | TBOX out_box; |
607 | 104k | float aspect_ratio; |
608 | | |
609 | 104k | if (tessedit_lower_flip_hyphen <= 1) { |
610 | 0 | return; |
611 | 0 | } |
612 | | |
613 | 104k | auto num_blobs = word_res->rebuild_word->NumBlobs(); |
614 | 104k | UNICHAR_ID unichar_dash = word_res->uch_set->unichar_to_id("-"); |
615 | 519k | for (unsigned i = 0; i < best_choice->length() && i < num_blobs; ++i) { |
616 | 414k | TBLOB *blob = word_res->rebuild_word->blobs[i]; |
617 | 414k | out_box = blob->bounding_box(); |
618 | 414k | if (i + 1 == num_blobs) { |
619 | 104k | next_left = 9999; |
620 | 309k | } else { |
621 | 309k | next_left = word_res->rebuild_word->blobs[i + 1]->bounding_box().left(); |
622 | 309k | } |
623 | | // Don't touch small or touching blobs - it is too dangerous. |
624 | 414k | if ((out_box.width() > 8 * word_res->denorm.x_scale()) && (out_box.left() > prev_right) && |
625 | 38.8k | (out_box.right() < next_left)) { |
626 | 32.1k | aspect_ratio = out_box.width() / static_cast<float>(out_box.height()); |
627 | 32.1k | if (word_res->uch_set->eq(best_choice->unichar_id(i), ".")) { |
628 | 330 | if (aspect_ratio >= tessedit_upper_flip_hyphen && |
629 | 49 | word_res->uch_set->contains_unichar_id(unichar_dash) && |
630 | 49 | word_res->uch_set->get_enabled(unichar_dash)) { |
631 | | /* Certain HYPHEN */ |
632 | 49 | best_choice->set_unichar_id(unichar_dash, i); |
633 | 49 | if (word_res->reject_map[i].rejected()) { |
634 | 18 | word_res->reject_map[i].setrej_hyphen_accept(); |
635 | 18 | } |
636 | 49 | } |
637 | 330 | if ((aspect_ratio > tessedit_lower_flip_hyphen) && word_res->reject_map[i].accepted()) { |
638 | | // Suspected HYPHEN |
639 | 41 | word_res->reject_map[i].setrej_hyphen(); |
640 | 41 | } |
641 | 31.8k | } else if (best_choice->unichar_id(i) == unichar_dash) { |
642 | 348 | if ((aspect_ratio >= tessedit_upper_flip_hyphen) && (word_res->reject_map[i].rejected())) { |
643 | 60 | word_res->reject_map[i].setrej_hyphen_accept(); |
644 | 60 | } |
645 | | // Certain HYPHEN |
646 | | |
647 | 348 | if ((aspect_ratio <= tessedit_lower_flip_hyphen) && (word_res->reject_map[i].accepted())) { |
648 | | // Suspected HYPHEN |
649 | 84 | word_res->reject_map[i].setrej_hyphen(); |
650 | 84 | } |
651 | 348 | } |
652 | 32.1k | } |
653 | 414k | prev_right = out_box.right(); |
654 | 414k | } |
655 | 104k | } |
656 | | |
657 | | // Note: After running this function word_res->ratings |
658 | | // might not contain the right BLOB_CHOICE corresponding to each character |
659 | | // in word_res->best_choice. |
660 | 104k | void Tesseract::flip_0O(WERD_RES *word_res) { |
661 | 104k | WERD_CHOICE *best_choice = word_res->best_choice; |
662 | 104k | TBOX out_box; |
663 | | |
664 | 104k | if (!tessedit_flip_0O) { |
665 | 0 | return; |
666 | 0 | } |
667 | | |
668 | 104k | auto num_blobs = word_res->rebuild_word->NumBlobs(); |
669 | 440k | for (unsigned i = 0; i < best_choice->length() && i < num_blobs; ++i) { |
670 | 351k | TBLOB *blob = word_res->rebuild_word->blobs[i]; |
671 | 351k | if (word_res->uch_set->get_isupper(best_choice->unichar_id(i)) || |
672 | 318k | word_res->uch_set->get_isdigit(best_choice->unichar_id(i))) { |
673 | 40.0k | out_box = blob->bounding_box(); |
674 | 40.0k | if ((out_box.top() < kBlnBaselineOffset + kBlnXHeight) || |
675 | 28.7k | (out_box.bottom() > kBlnBaselineOffset + kBlnXHeight / 4)) { |
676 | 15.7k | return; // Beware words with sub/superscripts |
677 | 15.7k | } |
678 | 40.0k | } |
679 | 351k | } |
680 | 89.0k | UNICHAR_ID unichar_0 = word_res->uch_set->unichar_to_id("0"); |
681 | 89.0k | UNICHAR_ID unichar_O = word_res->uch_set->unichar_to_id("O"); |
682 | 89.0k | if (unichar_0 == INVALID_UNICHAR_ID || !word_res->uch_set->get_enabled(unichar_0) || |
683 | 89.0k | unichar_O == INVALID_UNICHAR_ID || !word_res->uch_set->get_enabled(unichar_O)) { |
684 | 0 | return; // 0 or O are not present/enabled in unicharset |
685 | 0 | } |
686 | 312k | for (unsigned i = 1; i < best_choice->length(); ++i) { |
687 | 223k | if (best_choice->unichar_id(i) == unichar_0 || best_choice->unichar_id(i) == unichar_O) { |
688 | | /* A0A */ |
689 | 462 | if ((i + 1) < best_choice->length() && |
690 | 431 | non_O_upper(*word_res->uch_set, best_choice->unichar_id(i - 1)) && |
691 | 16 | non_O_upper(*word_res->uch_set, best_choice->unichar_id(i + 1))) { |
692 | 15 | best_choice->set_unichar_id(unichar_O, i); |
693 | 15 | } |
694 | | /* A00A */ |
695 | 462 | if (non_O_upper(*word_res->uch_set, best_choice->unichar_id(i - 1)) && |
696 | 16 | (i + 1) < best_choice->length() && |
697 | 16 | (best_choice->unichar_id(i + 1) == unichar_0 || |
698 | 16 | best_choice->unichar_id(i + 1) == unichar_O) && |
699 | 1 | (i + 2) < best_choice->length() && |
700 | 1 | non_O_upper(*word_res->uch_set, best_choice->unichar_id(i + 2))) { |
701 | 1 | best_choice->set_unichar_id(unichar_O, i); |
702 | 1 | i++; |
703 | 1 | } |
704 | | /* AA0<non digit or end of word> */ |
705 | 462 | if ((i > 1) && non_O_upper(*word_res->uch_set, best_choice->unichar_id(i - 2)) && |
706 | 20 | non_O_upper(*word_res->uch_set, best_choice->unichar_id(i - 1)) && |
707 | 10 | (((i + 1) < best_choice->length() && |
708 | 10 | !word_res->uch_set->get_isdigit(best_choice->unichar_id(i + 1)) && |
709 | 10 | !word_res->uch_set->eq(best_choice->unichar_id(i + 1), "l") && |
710 | 10 | !word_res->uch_set->eq(best_choice->unichar_id(i + 1), "I")) || |
711 | 6 | (i == best_choice->length() - 1))) { |
712 | 4 | best_choice->set_unichar_id(unichar_O, i); |
713 | 4 | } |
714 | | /* 9O9 */ |
715 | 462 | if (non_0_digit(*word_res->uch_set, best_choice->unichar_id(i - 1)) && |
716 | 60 | (i + 1) < best_choice->length() && |
717 | 56 | non_0_digit(*word_res->uch_set, best_choice->unichar_id(i + 1))) { |
718 | 13 | best_choice->set_unichar_id(unichar_0, i); |
719 | 13 | } |
720 | | /* 9OOO */ |
721 | 462 | if (non_0_digit(*word_res->uch_set, best_choice->unichar_id(i - 1)) && |
722 | 60 | (i + 2) < best_choice->length() && |
723 | 49 | (best_choice->unichar_id(i + 1) == unichar_0 || |
724 | 24 | best_choice->unichar_id(i + 1) == unichar_O) && |
725 | 25 | (best_choice->unichar_id(i + 2) == unichar_0 || |
726 | 21 | best_choice->unichar_id(i + 2) == unichar_O)) { |
727 | 4 | best_choice->set_unichar_id(unichar_0, i); |
728 | 4 | best_choice->set_unichar_id(unichar_0, i + 1); |
729 | 4 | best_choice->set_unichar_id(unichar_0, i + 2); |
730 | 4 | i += 2; |
731 | 4 | } |
732 | | /* 9OO<non upper> */ |
733 | 462 | if (non_0_digit(*word_res->uch_set, best_choice->unichar_id(i - 1)) && |
734 | 56 | (i + 2) < best_choice->length() && |
735 | 45 | (best_choice->unichar_id(i + 1) == unichar_0 || |
736 | 24 | best_choice->unichar_id(i + 1) == unichar_O) && |
737 | 21 | !word_res->uch_set->get_isupper(best_choice->unichar_id(i + 2))) { |
738 | 21 | best_choice->set_unichar_id(unichar_0, i); |
739 | 21 | best_choice->set_unichar_id(unichar_0, i + 1); |
740 | 21 | i++; |
741 | 21 | } |
742 | | /* 9O<non upper> */ |
743 | 462 | if (non_0_digit(*word_res->uch_set, best_choice->unichar_id(i - 1)) && |
744 | 35 | (i + 1) < best_choice->length() && |
745 | 31 | !word_res->uch_set->get_isupper(best_choice->unichar_id(i + 1))) { |
746 | 31 | best_choice->set_unichar_id(unichar_0, i); |
747 | 31 | } |
748 | | /* 9[.,]OOO.. */ |
749 | 462 | if ((i > 1) && |
750 | 423 | (word_res->uch_set->eq(best_choice->unichar_id(i - 1), ".") || |
751 | 419 | word_res->uch_set->eq(best_choice->unichar_id(i - 1), ",")) && |
752 | 7 | (word_res->uch_set->get_isdigit(best_choice->unichar_id(i - 2)) || |
753 | 5 | best_choice->unichar_id(i - 2) == unichar_O)) { |
754 | 2 | if (best_choice->unichar_id(i - 2) == unichar_O) { |
755 | 0 | best_choice->set_unichar_id(unichar_0, i - 2); |
756 | 0 | } |
757 | 4 | while (i < best_choice->length() && (best_choice->unichar_id(i) == unichar_O || |
758 | 4 | best_choice->unichar_id(i) == unichar_0)) { |
759 | 2 | best_choice->set_unichar_id(unichar_0, i); |
760 | 2 | i++; |
761 | 2 | } |
762 | 2 | i--; |
763 | 2 | } |
764 | 462 | } |
765 | 223k | } |
766 | 89.0k | } |
767 | | |
768 | 1.34k | bool Tesseract::non_O_upper(const UNICHARSET &ch_set, UNICHAR_ID unichar_id) { |
769 | 1.34k | return ch_set.get_isupper(unichar_id) && !ch_set.eq(unichar_id, "O"); |
770 | 1.34k | } |
771 | | |
772 | 1.90k | bool Tesseract::non_0_digit(const UNICHARSET &ch_set, UNICHAR_ID unichar_id) { |
773 | 1.90k | return ch_set.get_isdigit(unichar_id) && !ch_set.eq(unichar_id, "0"); |
774 | 1.90k | } |
775 | | } // namespace tesseract |
776 | | |
777 | | #endif // def DISABLED_LEGACY_ENGINE |