tesseract v5.3.3.20231005
ambigs.cpp
Go to the documentation of this file.
1
2// File: ambigs.cpp
3// Description: Functions for dealing with ambiguities
4// (training and recognition).
5// Author: Daria Antonova
6//
7// (C) Copyright 2008, 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//
19
20#include "ambigs.h"
21
22#include "helpers.h"
23#include "universalambigs.h"
24
25#include <cstdio>
26
27#if defined(_WIN32) && !defined(__GNUC__)
28# define strtok_r(str, delim, saveptr) strtok_s(str, delim, saveptr)
29#endif /* _WIN32 && !__GNUC__ */
30
31namespace tesseract {
32
33static const char kAmbigDelimiters[] = "\t ";
34static const char kIllegalMsg[] = "Illegal ambiguity specification on line %d\n";
35static const char kIllegalUnicharMsg[] = "Illegal unichar %s in ambiguity specification\n";
36
37// Maximum line size:
38// 10 for sizes of ambigs, tabs, abmig type and newline
39// UNICHAR_LEN * (MAX_AMBIG_SIZE + 1) for each part of the ambig
41
43 wrong_ngram[0] = INVALID_UNICHAR_ID;
44 correct_fragments[0] = INVALID_UNICHAR_ID;
45 correct_ngram_id = INVALID_UNICHAR_ID;
48}
49
50// Initializes the ambigs by adding a nullptr pointer to each table.
51void UnicharAmbigs::InitUnicharAmbigs(const UNICHARSET &unicharset, bool use_ambigs_for_adaption) {
52 for (unsigned i = 0; i < unicharset.size(); ++i) {
53 replace_ambigs_.push_back(nullptr);
54 dang_ambigs_.push_back(nullptr);
55 one_to_one_definite_ambigs_.push_back(nullptr);
56 if (use_ambigs_for_adaption) {
57 ambigs_for_adaption_.push_back(nullptr);
58 reverse_ambigs_for_adaption_.push_back(nullptr);
59 }
60 }
61}
62
63// Loads the universal ambigs that are useful for any language.
64void UnicharAmbigs::LoadUniversal(const UNICHARSET &encoder_set, UNICHARSET *unicharset) {
65 TFile file;
67 return;
68 }
69 LoadUnicharAmbigs(encoder_set, &file, 0, false, unicharset);
70}
71
72void UnicharAmbigs::LoadUnicharAmbigs(const UNICHARSET &encoder_set, TFile *ambig_file,
73 int debug_level, bool use_ambigs_for_adaption,
74 UNICHARSET *unicharset) {
75 UnicharIdVector *adaption_ambigs_entry;
76 if (debug_level) {
77 tprintf("Reading ambiguities\n");
78 }
79
80 int test_ambig_part_size;
81 int replacement_ambig_part_size;
82 // The space for buffer is allocated on the heap to avoid
83 // GCC frame size warning.
84 const int kBufferSize = 10 + 2 * kMaxAmbigStringSize;
85 char *buffer = new char[kBufferSize];
86 char replacement_string[kMaxAmbigStringSize];
87 UNICHAR_ID test_unichar_ids[MAX_AMBIG_SIZE + 1];
88 int line_num = 0;
89 int type = NOT_AMBIG;
90
91 // Determine the version of the ambigs file.
92 int version = 0;
93 ASSERT_HOST(ambig_file->FGets(buffer, kBufferSize) != nullptr && buffer[0] != '\0');
94 if (*buffer == 'v') {
95 version = static_cast<int>(strtol(buffer + 1, nullptr, 10));
96 ++line_num;
97 } else {
98 ambig_file->Rewind();
99 }
100 while (ambig_file->FGets(buffer, kBufferSize) != nullptr) {
101 chomp_string(buffer);
102 if (debug_level > 2) {
103 tprintf("read line %s\n", buffer);
104 }
105 ++line_num;
106 if (!ParseAmbiguityLine(line_num, version, debug_level, encoder_set, buffer,
107 &test_ambig_part_size, test_unichar_ids, &replacement_ambig_part_size,
108 replacement_string, &type)) {
109 continue;
110 }
111 // Construct AmbigSpec and add it to the appropriate AmbigSpec_LIST.
112 auto *ambig_spec = new AmbigSpec();
113 if (!InsertIntoTable((type == REPLACE_AMBIG) ? replace_ambigs_ : dang_ambigs_,
114 test_ambig_part_size, test_unichar_ids, replacement_ambig_part_size,
115 replacement_string, type, ambig_spec, unicharset)) {
116 continue;
117 }
118
119 // Update one_to_one_definite_ambigs_.
120 if (test_ambig_part_size == 1 && replacement_ambig_part_size == 1 && type == DEFINITE_AMBIG) {
121 if (one_to_one_definite_ambigs_[test_unichar_ids[0]] == nullptr) {
122 one_to_one_definite_ambigs_[test_unichar_ids[0]] = new UnicharIdVector();
123 }
124 one_to_one_definite_ambigs_[test_unichar_ids[0]]->push_back(ambig_spec->correct_ngram_id);
125 }
126 // Update ambigs_for_adaption_.
127 if (use_ambigs_for_adaption) {
128 std::vector<UNICHAR_ID> encoding;
129 // Silently ignore invalid strings, as before, so it is safe to use a
130 // universal ambigs file.
131 if (unicharset->encode_string(replacement_string, true, &encoding, nullptr, nullptr)) {
132 for (int i = 0; i < test_ambig_part_size; ++i) {
133 if (ambigs_for_adaption_[test_unichar_ids[i]] == nullptr) {
134 ambigs_for_adaption_[test_unichar_ids[i]] = new UnicharIdVector();
135 }
136 adaption_ambigs_entry = ambigs_for_adaption_[test_unichar_ids[i]];
137 for (int id_to_insert : encoding) {
138 ASSERT_HOST(id_to_insert != INVALID_UNICHAR_ID);
139 // Add the new unichar id to adaption_ambigs_entry (only if the
140 // vector does not already contain it) keeping it in sorted order.
141 size_t j;
142 for (j = 0;
143 j < adaption_ambigs_entry->size() && (*adaption_ambigs_entry)[j] > id_to_insert;
144 ++j) {
145 }
146 if (j < adaption_ambigs_entry->size()) {
147 if ((*adaption_ambigs_entry)[j] != id_to_insert) {
148 adaption_ambigs_entry->insert(adaption_ambigs_entry->begin() + j, id_to_insert);
149 }
150 } else {
151 adaption_ambigs_entry->push_back(id_to_insert);
152 }
153 }
154 }
155 }
156 }
157 }
158 delete[] buffer;
159
160 // Fill in reverse_ambigs_for_adaption from ambigs_for_adaption vector.
161 if (use_ambigs_for_adaption) {
162 for (size_t i = 0; i < ambigs_for_adaption_.size(); ++i) {
163 adaption_ambigs_entry = ambigs_for_adaption_[i];
164 if (adaption_ambigs_entry == nullptr) {
165 continue;
166 }
167 for (size_t j = 0; j < adaption_ambigs_entry->size(); ++j) {
168 UNICHAR_ID ambig_id = (*adaption_ambigs_entry)[j];
169 if (reverse_ambigs_for_adaption_[ambig_id] == nullptr) {
170 reverse_ambigs_for_adaption_[ambig_id] = new UnicharIdVector();
171 }
172 reverse_ambigs_for_adaption_[ambig_id]->push_back(i);
173 }
174 }
175 }
176
177 // Print what was read from the input file.
178 if (debug_level > 1) {
179 for (int tbl = 0; tbl < 2; ++tbl) {
180 const UnicharAmbigsVector &print_table = (tbl == 0) ? replace_ambigs_ : dang_ambigs_;
181 for (size_t i = 0; i < print_table.size(); ++i) {
182 AmbigSpec_LIST *lst = print_table[i];
183 if (lst == nullptr) {
184 continue;
185 }
186 if (!lst->empty()) {
187 tprintf("%s Ambiguities for %s:\n", (tbl == 0) ? "Replaceable" : "Dangerous",
188 unicharset->debug_str(i).c_str());
189 }
190 AmbigSpec_IT lst_it(lst);
191 for (lst_it.mark_cycle_pt(); !lst_it.cycled_list(); lst_it.forward()) {
192 AmbigSpec *ambig_spec = lst_it.data();
193 tprintf("wrong_ngram:");
194 UnicharIdArrayUtils::print(ambig_spec->wrong_ngram, *unicharset);
195 tprintf("correct_fragments:");
196 UnicharIdArrayUtils::print(ambig_spec->correct_fragments, *unicharset);
197 }
198 }
199 }
200 if (use_ambigs_for_adaption) {
201 for (int vec_id = 0; vec_id < 2; ++vec_id) {
202 const std::vector<UnicharIdVector *> &vec =
203 (vec_id == 0) ? ambigs_for_adaption_ : reverse_ambigs_for_adaption_;
204 for (size_t i = 0; i < vec.size(); ++i) {
205 adaption_ambigs_entry = vec[i];
206 if (adaption_ambigs_entry != nullptr) {
207 tprintf("%sAmbigs for adaption for %s:\n", (vec_id == 0) ? "" : "Reverse ",
208 unicharset->debug_str(i).c_str());
209 for (size_t j = 0; j < adaption_ambigs_entry->size(); ++j) {
210 tprintf("%s ", unicharset->debug_str((*adaption_ambigs_entry)[j]).c_str());
211 }
212 tprintf("\n");
213 }
214 }
215 }
216 }
217 }
218}
219
220bool UnicharAmbigs::ParseAmbiguityLine(int line_num, int version, int debug_level,
221 const UNICHARSET &unicharset, char *buffer,
222 int *test_ambig_part_size, UNICHAR_ID *test_unichar_ids,
223 int *replacement_ambig_part_size, char *replacement_string,
224 int *type) {
225 if (version > 1) {
226 // Simpler format is just wrong-string correct-string type\n.
227 std::string input(buffer);
228 std::vector<std::string> fields = split(input, ' ');
229 if (fields.size() != 3) {
230 if (debug_level) {
231 tprintf(kIllegalMsg, line_num);
232 }
233 return false;
234 }
235 // Encode wrong-string.
236 std::vector<UNICHAR_ID> unichars;
237 if (!unicharset.encode_string(fields[0].c_str(), true, &unichars, nullptr, nullptr)) {
238 return false;
239 }
240 *test_ambig_part_size = unichars.size();
241 if (*test_ambig_part_size > MAX_AMBIG_SIZE) {
242 if (debug_level) {
243 tprintf("Too many unichars in ambiguity on line %d\n", line_num);
244 }
245 return false;
246 }
247 // Copy encoded string to output.
248 for (size_t i = 0; i < unichars.size(); ++i) {
249 test_unichar_ids[i] = unichars[i];
250 }
251 test_unichar_ids[unichars.size()] = INVALID_UNICHAR_ID;
252 // Encode replacement-string to check validity.
253 if (!unicharset.encode_string(fields[1].c_str(), true, &unichars, nullptr, nullptr)) {
254 return false;
255 }
256 *replacement_ambig_part_size = unichars.size();
257 if (*replacement_ambig_part_size > MAX_AMBIG_SIZE) {
258 if (debug_level) {
259 tprintf("Too many unichars in ambiguity on line %d\n", line_num);
260 }
261 return false;
262 }
263 if (sscanf(fields[2].c_str(), "%d", type) != 1) {
264 if (debug_level) {
265 tprintf(kIllegalMsg, line_num);
266 }
267 return false;
268 }
269 snprintf(replacement_string, kMaxAmbigStringSize, "%s", fields[1].c_str());
270 return true;
271 }
272 int i;
273 char *token;
274 char *next_token;
275 if (!(token = strtok_r(buffer, kAmbigDelimiters, &next_token)) ||
276 !sscanf(token, "%d", test_ambig_part_size) || *test_ambig_part_size <= 0) {
277 if (debug_level) {
278 tprintf(kIllegalMsg, line_num);
279 }
280 return false;
281 }
282 if (*test_ambig_part_size > MAX_AMBIG_SIZE) {
283 if (debug_level) {
284 tprintf("Too many unichars in ambiguity on line %d\n", line_num);
285 }
286 return false;
287 }
288 for (i = 0; i < *test_ambig_part_size; ++i) {
289 if (!(token = strtok_r(nullptr, kAmbigDelimiters, &next_token))) {
290 break;
291 }
292 if (!unicharset.contains_unichar(token)) {
293 if (debug_level) {
294 tprintf(kIllegalUnicharMsg, token);
295 }
296 break;
297 }
298 test_unichar_ids[i] = unicharset.unichar_to_id(token);
299 }
300 test_unichar_ids[i] = INVALID_UNICHAR_ID;
301
302 if (i != *test_ambig_part_size || !(token = strtok_r(nullptr, kAmbigDelimiters, &next_token)) ||
303 !sscanf(token, "%d", replacement_ambig_part_size) || *replacement_ambig_part_size <= 0) {
304 if (debug_level) {
305 tprintf(kIllegalMsg, line_num);
306 }
307 return false;
308 }
309 if (*replacement_ambig_part_size > MAX_AMBIG_SIZE) {
310 if (debug_level) {
311 tprintf("Too many unichars in ambiguity on line %d\n", line_num);
312 }
313 return false;
314 }
315 replacement_string[0] = '\0';
316 for (i = 0; i < *replacement_ambig_part_size; ++i) {
317 if (!(token = strtok_r(nullptr, kAmbigDelimiters, &next_token))) {
318 break;
319 }
320 strcat(replacement_string, token);
321 if (!unicharset.contains_unichar(token)) {
322 if (debug_level) {
323 tprintf(kIllegalUnicharMsg, token);
324 }
325 break;
326 }
327 }
328 if (i != *replacement_ambig_part_size) {
329 if (debug_level) {
330 tprintf(kIllegalMsg, line_num);
331 }
332 return false;
333 }
334 if (version > 0) {
335 // The next field being true indicates that the ambiguity should
336 // always be substituted (e.g. '' should always be changed to ").
337 // For such "certain" n -> m ambigs tesseract will insert character
338 // fragments for the n pieces in the unicharset. AmbigsFound()
339 // will then replace the incorrect ngram with the character
340 // fragments of the correct character (or ngram if m > 1).
341 // Note that if m > 1, an ngram will be inserted into the
342 // modified word, not the individual unigrams. Tesseract
343 // has limited support for ngram unichar (e.g. dawg permuter).
344 if (!(token = strtok_r(nullptr, kAmbigDelimiters, &next_token)) || !sscanf(token, "%d", type)) {
345 if (debug_level) {
346 tprintf(kIllegalMsg, line_num);
347 }
348 return false;
349 }
350 }
351 return true;
352}
353
354bool UnicharAmbigs::InsertIntoTable(UnicharAmbigsVector &table, int test_ambig_part_size,
355 UNICHAR_ID *test_unichar_ids, int replacement_ambig_part_size,
356 const char *replacement_string, int type, AmbigSpec *ambig_spec,
357 UNICHARSET *unicharset) {
358 ambig_spec->type = static_cast<AmbigType>(type);
359 if (test_ambig_part_size == 1 && replacement_ambig_part_size == 1 &&
360 unicharset->to_lower(test_unichar_ids[0]) ==
361 unicharset->to_lower(unicharset->unichar_to_id(replacement_string))) {
362 ambig_spec->type = CASE_AMBIG;
363 }
364
365 ambig_spec->wrong_ngram_size =
366 UnicharIdArrayUtils::copy(test_unichar_ids, ambig_spec->wrong_ngram);
367
368 // Since we need to maintain a constant number of unichar positions in
369 // order to construct ambig_blob_choices vector in NoDangerousAmbig(), for
370 // each n->m ambiguity we will have to place n character fragments of the
371 // correct ngram into the corresponding positions in the vector (e.g. given
372 // "vvvvw" and vvvv->ww we will place v and |ww|0|4 into position 0, v and
373 // |ww|1|4 into position 1 and so on. The correct ngram is reconstructed
374 // from fragments by dawg_permute_and_select().
375
376 // Insert the corresponding correct ngram into the unicharset.
377 // Unicharset code assumes that the "base" ngram is inserted into
378 // the unicharset before fragments of this ngram are inserted.
379 unicharset->unichar_insert(replacement_string, OldUncleanUnichars::kTrue);
380 ambig_spec->correct_ngram_id = unicharset->unichar_to_id(replacement_string);
381 if (replacement_ambig_part_size > 1) {
382 unicharset->set_isngram(ambig_spec->correct_ngram_id, true);
383 }
384 // Add the corresponding fragments of the wrong ngram to unicharset.
385 int i;
386 for (i = 0; i < test_ambig_part_size; ++i) {
387 UNICHAR_ID unichar_id;
388 if (test_ambig_part_size == 1) {
389 unichar_id = ambig_spec->correct_ngram_id;
390 } else {
391 std::string frag_str =
392 CHAR_FRAGMENT::to_string(replacement_string, i, test_ambig_part_size, false);
393 unicharset->unichar_insert(frag_str.c_str(), OldUncleanUnichars::kTrue);
394 unichar_id = unicharset->unichar_to_id(frag_str.c_str());
395 }
396 ambig_spec->correct_fragments[i] = unichar_id;
397 }
398 ambig_spec->correct_fragments[i] = INVALID_UNICHAR_ID;
399
400 // Add AmbigSpec for this ambiguity to the corresponding AmbigSpec_LIST.
401 // Keep AmbigSpec_LISTs sorted by AmbigSpec.wrong_ngram.
402 if (table[test_unichar_ids[0]] == nullptr) {
403 table[test_unichar_ids[0]] = new AmbigSpec_LIST();
404 }
405 if (table[test_unichar_ids[0]]->add_sorted(AmbigSpec::compare_ambig_specs, true, ambig_spec)) {
406 return true;
407 }
408 delete ambig_spec;
409 return false;
410}
411
412} // namespace tesseract
#define UNICHAR_LEN
Definition: unichar.h:31
#define MAX_AMBIG_SIZE
Definition: ambigs.h:34
#define ASSERT_HOST(x)
Definition: errcode.h:54
const char kUniversalAmbigsFile[]
std::vector< AmbigSpec_LIST * > UnicharAmbigsVector
Definition: ambigs.h:140
void tprintf(const char *format,...)
Definition: tprintf.cpp:41
void chomp_string(char *str)
Definition: helpers.h:91
const int ksizeofUniversalAmbigsFile
int UNICHAR_ID
Definition: unichar.h:34
AmbigType
Definition: ambigs.h:40
@ CASE_AMBIG
Definition: ambigs.h:45
@ DEFINITE_AMBIG
Definition: ambigs.h:43
@ REPLACE_AMBIG
Definition: ambigs.h:42
@ NOT_AMBIG
Definition: ambigs.h:41
const int kMaxAmbigStringSize
Definition: ambigs.cpp:40
std::vector< UNICHAR_ID > UnicharIdVector
Definition: ambigs.h:38
const std::vector< std::string > split(const std::string &s, char c)
Definition: helpers.h:43
type
Definition: upload.py:458
static void print(const UNICHAR_ID array[], const UNICHARSET &unicharset)
Definition: ambigs.h:93
static int copy(const UNICHAR_ID src[], UNICHAR_ID dst[])
Definition: ambigs.h:83
UNICHAR_ID correct_ngram_id
Definition: ambigs.h:132
UNICHAR_ID wrong_ngram[MAX_AMBIG_SIZE+1]
Definition: ambigs.h:130
static int compare_ambig_specs(const void *spec1, const void *spec2)
Definition: ambigs.h:120
UNICHAR_ID correct_fragments[MAX_AMBIG_SIZE+1]
Definition: ambigs.h:131
AmbigType type
Definition: ambigs.h:133
void LoadUniversal(const UNICHARSET &encoder_set, UNICHARSET *unicharset)
Definition: ambigs.cpp:64
void InitUnicharAmbigs(const UNICHARSET &unicharset, bool use_ambigs_for_adaption)
Definition: ambigs.cpp:51
void LoadUnicharAmbigs(const UNICHARSET &encoder_set, TFile *ambigs_file, int debug_level, bool use_ambigs_for_adaption, UNICHARSET *unicharset)
Definition: ambigs.cpp:72
char * FGets(char *buffer, int buffer_size)
Definition: serialis.cpp:195
std::string to_string() const
Definition: unicharset.h:91
bool encode_string(const char *str, bool give_up_on_failure, std::vector< UNICHAR_ID > *encoding, std::vector< char > *lengths, unsigned *encoded_length) const
Definition: unicharset.cpp:239
bool contains_unichar(const char *const unichar_repr) const
Definition: unicharset.cpp:695
UNICHAR_ID unichar_to_id(const char *const unichar_repr) const
Definition: unicharset.cpp:186
size_t size() const
Definition: unicharset.h:355
std::string debug_str(UNICHAR_ID id) const
Definition: unicharset.cpp:331
static FILE * Open(const std::string &filename, const std::string &mode)
Definition: fileio.cpp:41