Coverage Report

Created: 2026-10-28 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/ccmain/tessedit.cpp
Line
Count
Source
1
/**********************************************************************
2
 * File:        tessedit.cpp  (Formerly tessedit.c)
3
 * Description: (Previously) Main program for merge of tess and editor.
4
 *              Now just code to load the language model and various
5
 *              engine-specific data files.
6
 * Author:      Ray Smith
7
 *
8
 * (C) Copyright 1992, Hewlett-Packard Ltd.
9
 ** Licensed under the Apache License, Version 2.0 (the "License");
10
 ** you may not use this file except in compliance with the License.
11
 ** You may obtain a copy of the License at
12
 ** http://www.apache.org/licenses/LICENSE-2.0
13
 ** Unless required by applicable law or agreed to in writing, software
14
 ** distributed under the License is distributed on an "AS IS" BASIS,
15
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 ** See the License for the specific language governing permissions and
17
 ** limitations under the License.
18
 *
19
 **********************************************************************/
20
21
// Include automatically generated configuration file if running autoconf.
22
#ifdef HAVE_CONFIG_H
23
#  include "config_auto.h"
24
#endif
25
26
#include "control.h"
27
#include "matchdefs.h"
28
#include "pageres.h"
29
#include "params.h"
30
#include "stopper.h"
31
#include "tesseractclass.h"
32
#include "tessvars.h"
33
#include "tprintf.h"
34
#ifndef DISABLED_LEGACY_ENGINE
35
#  include "chop.h"
36
#  include "intmatcher.h"
37
#  include "reject.h"
38
#endif
39
#include "lstmrecognizer.h"
40
41
namespace tesseract {
42
43
// Read a "config" file containing a set of variable, value pairs.
44
// Searches the standard places: tessdata/configs, tessdata/tessconfigs
45
// and also accepts a relative or absolute path name.
46
0
void Tesseract::read_config_file(const char *filename, SetParamConstraint constraint) {
47
0
  std::string path = datadir;
48
0
  path += "configs/";
49
0
  path += filename;
50
0
  FILE *fp;
51
0
  if ((fp = fopen(path.c_str(), "rb")) != nullptr) {
52
0
    fclose(fp);
53
0
  } else {
54
0
    path = datadir;
55
0
    path += "tessconfigs/";
56
0
    path += filename;
57
0
    if ((fp = fopen(path.c_str(), "rb")) != nullptr) {
58
0
      fclose(fp);
59
0
    } else {
60
0
      path = filename;
61
0
    }
62
0
  }
63
0
  ParamUtils::ReadParamsFile(path.c_str(), constraint, this->params());
64
0
}
65
66
// Returns false if a unicharset file for the specified language was not found
67
// or was invalid.
68
// This function initializes TessdataManager. After TessdataManager is
69
// no longer needed, TessdataManager::End() should be called.
70
//
71
// This function sets tessedit_oem_mode to the given OcrEngineMode oem, unless
72
// it is OEM_DEFAULT, in which case the value of the variable will be obtained
73
// from the language-specific config file (stored in [lang].traineddata), from
74
// the config files specified on the command line or left as the default
75
// OEM_TESSERACT_ONLY if none of the configs specify this variable.
76
bool Tesseract::init_tesseract_lang_data(const std::string &language, OcrEngineMode oem,
77
                                         char **configs, int configs_size,
78
                                         const std::vector<std::string> *vars_vec,
79
                                         const std::vector<std::string> *vars_values,
80
4
                                         bool set_only_non_debug_params, TessdataManager *mgr) {
81
  // Set the language data path prefix
82
4
  lang = !language.empty() ? language : "eng";
83
4
  language_data_path_prefix = datadir;
84
4
  language_data_path_prefix += lang;
85
4
  language_data_path_prefix += ".";
86
87
  // Initialize TessdataManager.
88
4
  std::string tessdata_path = language_data_path_prefix + kTrainedDataSuffix;
89
4
  if (!mgr->is_loaded() && !mgr->Init(tessdata_path.c_str())) {
90
0
    tprintf("Error opening data file %s\n", tessdata_path.c_str());
91
0
    tprintf(
92
0
        "Please make sure the TESSDATA_PREFIX environment variable is set"
93
0
        " to your \"tessdata\" directory.\n");
94
0
    return false;
95
0
  }
96
#ifdef DISABLED_LEGACY_ENGINE
97
  tessedit_ocr_engine_mode.set_value(OEM_LSTM_ONLY);
98
#else
99
4
  if (oem == OEM_DEFAULT) {
100
    // Set the engine mode from availability, which can then be overridden by
101
    // the config file when we read it below.
102
4
    if (!mgr->IsLSTMAvailable()) {
103
0
      tessedit_ocr_engine_mode.set_value(OEM_TESSERACT_ONLY);
104
4
    } else if (!mgr->IsBaseAvailable()) {
105
0
      tessedit_ocr_engine_mode.set_value(OEM_LSTM_ONLY);
106
4
    } else {
107
4
      tessedit_ocr_engine_mode.set_value(OEM_TESSERACT_LSTM_COMBINED);
108
4
    }
109
4
  }
110
4
#endif // ndef DISABLED_LEGACY_ENGINE
111
112
  // If a language specific config file (lang.config) exists, load it in.
113
4
  TFile fp;
114
4
  if (mgr->GetComponent(TESSDATA_LANG_CONFIG, &fp)) {
115
0
    ParamUtils::ReadParamsFromFp(SET_PARAM_CONSTRAINT_NONE, &fp, this->params());
116
0
  }
117
118
4
  SetParamConstraint set_params_constraint =
119
4
      set_only_non_debug_params ? SET_PARAM_CONSTRAINT_NON_DEBUG_ONLY : SET_PARAM_CONSTRAINT_NONE;
120
  // Load tesseract variables from config files. This is done after loading
121
  // language-specific variables from [lang].traineddata file, so that custom
122
  // config files can override values in [lang].traineddata file.
123
4
  for (int i = 0; i < configs_size; ++i) {
124
0
    read_config_file(configs[i], set_params_constraint);
125
0
  }
126
127
  // Set params specified in vars_vec (done after setting params from config
128
  // files, so that params in vars_vec can override those from files).
129
4
  if (vars_vec != nullptr && vars_values != nullptr) {
130
0
    for (unsigned i = 0; i < vars_vec->size(); ++i) {
131
0
      if (!ParamUtils::SetParam((*vars_vec)[i].c_str(), (*vars_values)[i].c_str(),
132
0
                                set_params_constraint, this->params())) {
133
0
        tprintf("Warning: The parameter '%s' was not found.\n", (*vars_vec)[i].c_str());
134
0
      }
135
0
    }
136
0
  }
137
138
4
  if (!tessedit_write_params_to_file.empty()) {
139
0
    FILE *params_file = fopen(tessedit_write_params_to_file.c_str(), "wb");
140
0
    if (params_file != nullptr) {
141
0
      ParamUtils::PrintParams(params_file, this->params());
142
0
      fclose(params_file);
143
0
    } else {
144
0
      tprintf("Failed to open %s for writing params.\n", tessedit_write_params_to_file.c_str());
145
0
    }
146
0
  }
147
148
4
#ifndef DISABLED_LEGACY_ENGINE
149
  // Determine which ocr engine(s) should be loaded and used for recognition.
150
4
  if (oem != OEM_DEFAULT) {
151
0
    tessedit_ocr_engine_mode.set_value(oem);
152
0
  }
153
4
#endif
154
155
  // If we are only loading the config file (and so not planning on doing any
156
  // recognition) then there's nothing else do here.
157
4
  if (tessedit_init_config_only) {
158
0
    return true;
159
0
  }
160
161
// The various OcrEngineMode settings (see tesseract/publictypes.h) determine
162
// which engine-specific data files need to be loaded. If LSTM_ONLY is
163
// requested, the base Tesseract files are *Not* required.
164
#ifdef DISABLED_LEGACY_ENGINE
165
  if (tessedit_ocr_engine_mode == OEM_LSTM_ONLY) {
166
#else
167
4
  if (tessedit_ocr_engine_mode == OEM_LSTM_ONLY ||
168
4
      tessedit_ocr_engine_mode == OEM_TESSERACT_LSTM_COMBINED) {
169
4
#endif // ndef DISABLED_LEGACY_ENGINE
170
4
    if (mgr->IsComponentAvailable(TESSDATA_LSTM)) {
171
4
      lstm_recognizer_ = new LSTMRecognizer(language_data_path_prefix.c_str());
172
4
      if (!lstm_recognizer_->Load(this->params(), lstm_use_matrix ? language : "", mgr)) {
173
0
        delete lstm_recognizer_;
174
0
        lstm_recognizer_ = nullptr;
175
0
        tprintf("Error: Failed to load the LSTM model from %s\n", tessdata_path.c_str());
176
0
        return false;
177
0
      }
178
4
    } else {
179
#ifdef DISABLED_LEGACY_ENGINE
180
      // The legacy engine is compiled out, so we cannot fall back to it.
181
      // Returning false here propagates a load failure to init_tesseract,
182
      // which logs "Failed loading language '%s'" and skips this language.
183
      tprintf(
184
          "Error: LSTM requested, but not present in %s; the legacy "
185
          "engine is disabled in this build, so there is no fallback.\n",
186
          tessdata_path.c_str());
187
      return false;
188
#else
189
0
      tprintf("Error: LSTM requested, but not present!! Loading tesseract.\n");
190
0
      tessedit_ocr_engine_mode.set_value(OEM_TESSERACT_ONLY);
191
0
#endif // DISABLED_LEGACY_ENGINE
192
0
    }
193
4
  }
194
195
  // Load the unicharset
196
4
  if (tessedit_ocr_engine_mode == OEM_LSTM_ONLY) {
197
    // Avoid requiring a unicharset when we aren't running base tesseract.
198
0
    unicharset.CopyFrom(lstm_recognizer_->GetUnicharset());
199
0
  }
200
4
#ifndef DISABLED_LEGACY_ENGINE
201
4
  else if (!mgr->GetComponent(TESSDATA_UNICHARSET, &fp) || !unicharset.load_from_file(&fp, false)) {
202
0
    tprintf(
203
0
        "Error: Tesseract (legacy) engine requested, but components are "
204
0
        "not present in %s!!\n",
205
0
        tessdata_path.c_str());
206
0
    return false;
207
0
  }
208
4
#endif // ndef DISABLED_LEGACY_ENGINE
209
4
  if (unicharset.size() > MAX_NUM_CLASSES) {
210
0
    tprintf("Error: Size of unicharset is greater than MAX_NUM_CLASSES\n");
211
0
    return false;
212
0
  }
213
4
  right_to_left_ = unicharset.major_right_to_left();
214
215
4
#ifndef DISABLED_LEGACY_ENGINE
216
217
  // Setup initial unichar ambigs table and read universal ambigs.
218
4
  UNICHARSET encoder_unicharset;
219
4
  encoder_unicharset.CopyFrom(unicharset);
220
4
  unichar_ambigs.InitUnicharAmbigs(unicharset, use_ambigs_for_adaption);
221
4
  unichar_ambigs.LoadUniversal(encoder_unicharset, &unicharset);
222
223
4
  if (!tessedit_ambigs_training && mgr->GetComponent(TESSDATA_AMBIGS, &fp)) {
224
4
    unichar_ambigs.LoadUnicharAmbigs(encoder_unicharset, &fp, ambigs_debug_level,
225
4
                                     use_ambigs_for_adaption, &unicharset);
226
4
  }
227
228
  // Loading the ambiguities inserts the correct n-grams and their fragments
229
  // into the unicharset, so it may have grown beyond the limit checked above.
230
  // A unichar id indexes fixed-size arrays of MAX_NUM_CLASSES entries in the
231
  // legacy classifier, so reject it here rather than failing later.
232
4
  if (unicharset.size() > MAX_NUM_CLASSES) {
233
0
    tprintf("Error: Size of unicharset is greater than MAX_NUM_CLASSES\n");
234
0
    return false;
235
0
  }
236
237
  // Init ParamsModel.
238
  // Load pass1 and pass2 weights (for now these two sets are the same, but in
239
  // the future separate sets of weights can be generated).
240
12
  for (int p = ParamsModel::PTRAIN_PASS1; p < ParamsModel::PTRAIN_NUM_PASSES; ++p) {
241
8
    language_model_->getParamsModel().SetPass(static_cast<ParamsModel::PassEnum>(p));
242
8
    if (mgr->GetComponent(TESSDATA_PARAMS_MODEL, &fp)) {
243
0
      if (!language_model_->getParamsModel().LoadFromFp(lang.c_str(), &fp)) {
244
0
        return false;
245
0
      }
246
0
    }
247
8
  }
248
4
#endif // ndef DISABLED_LEGACY_ENGINE
249
250
4
  return true;
251
4
}
252
253
// Helper returns true if the given string is in the vector of strings.
254
8
static bool IsStrInList(const std::string &str, const std::vector<std::string> &str_list) {
255
8
  for (const auto &i : str_list) {
256
0
    if (i == str) {
257
0
      return true;
258
0
    }
259
0
  }
260
8
  return false;
261
8
}
262
263
// Parse a string of the form [~]<lang>[+[~]<lang>]*.
264
// Langs with no prefix get appended to to_load, provided they
265
// are not in there already.
266
// Langs with ~ prefix get appended to not_to_load, provided they are not in
267
// there already.
268
void Tesseract::ParseLanguageString(const std::string &lang_str, std::vector<std::string> *to_load,
269
8
                                    std::vector<std::string> *not_to_load) {
270
8
  std::string remains(lang_str);
271
  // Look whether the model file uses a prefix which must be applied to
272
  // included model files as well.
273
8
  std::string prefix;
274
8
  size_t found = lang.find_last_of('/');
275
8
  if (found != std::string::npos) {
276
    // A prefix was found.
277
0
    prefix = lang.substr(0, found + 1);
278
0
  }
279
12
  while (!remains.empty()) {
280
    // Find the start of the lang code and which vector to add to.
281
4
    const char *start = remains.c_str();
282
4
    while (*start == '+') {
283
0
      ++start;
284
0
    }
285
4
    std::vector<std::string> *target = to_load;
286
4
    if (*start == '~') {
287
0
      target = not_to_load;
288
0
      ++start;
289
0
    }
290
    // Find the index of the end of the lang code in string start.
291
4
    int end = strlen(start);
292
4
    const char *plus = strchr(start, '+');
293
4
    if (plus != nullptr && plus - start < end) {
294
0
      end = plus - start;
295
0
    }
296
4
    std::string lang_code(start);
297
4
    lang_code.resize(end);
298
4
    std::string next(start + end);
299
4
    remains = std::move(next);
300
4
    lang_code = prefix + lang_code;
301
    // Check whether lang_code is already in the target vector and add.
302
4
    if (!IsStrInList(lang_code, *target)) {
303
4
      target->push_back(std::move(lang_code));
304
4
    }
305
4
  }
306
8
}
307
308
// Initialize for potentially a set of languages defined by the language
309
// string and recursively any additional languages required by any language
310
// traineddata file (via tessedit_load_sublangs in its config) that is loaded.
311
// arg0 is the datapath for the tessdata directory, which could be the
312
// path of the tessdata directory with no trailing /, or (if tessdata
313
// lives in the same directory as the executable, the path of the executable,
314
// hence the name arg0.
315
// See init_tesseract_internal for remaining args.
316
int Tesseract::init_tesseract(const std::string &arg0, const std::string &textbase,
317
                              const std::string &language, OcrEngineMode oem, char **configs,
318
                              int configs_size, const std::vector<std::string> *vars_vec,
319
                              const std::vector<std::string> *vars_values,
320
4
                              bool set_only_non_debug_params, TessdataManager *mgr) {
321
4
  std::vector<std::string> langs_to_load;
322
4
  std::vector<std::string> langs_not_to_load;
323
4
  ParseLanguageString(language, &langs_to_load, &langs_not_to_load);
324
325
4
  for (auto *lang : sub_langs_) {
326
0
    delete lang;
327
0
  }
328
329
  // Set the basename, compute the data directory.
330
4
  main_setup(arg0, textbase);
331
332
4
  sub_langs_.clear();
333
  // Find the first loadable lang and load into this.
334
  // Add any languages that this language requires
335
4
  bool loaded_primary = false;
336
  // Load the rest into sub_langs_.
337
  // WARNING: A range based for loop does not work here because langs_to_load
338
  // might be changed in the loop when a new submodel is found.
339
8
  for (size_t lang_index = 0; lang_index < langs_to_load.size(); ++lang_index) {
340
4
    auto &lang_to_load = langs_to_load[lang_index];
341
4
    if (!IsStrInList(lang_to_load, langs_not_to_load)) {
342
4
      const char *lang_str = lang_to_load.c_str();
343
4
      Tesseract *tess_to_init;
344
4
      if (!loaded_primary) {
345
4
        tess_to_init = this;
346
4
      } else {
347
0
        tess_to_init = new Tesseract;
348
0
        tess_to_init->main_setup(arg0, textbase);
349
0
      }
350
351
4
      int result = tess_to_init->init_tesseract_internal(textbase, lang_str, oem, configs,
352
4
                                                         configs_size, vars_vec, vars_values,
353
4
                                                         set_only_non_debug_params, mgr);
354
      // Forget that language, but keep any reader we were given.
355
4
      mgr->Clear();
356
357
4
      if (!loaded_primary) {
358
4
        if (result < 0) {
359
0
          tprintf("Failed loading language '%s'\n", lang_str);
360
4
        } else {
361
4
          ParseLanguageString(tess_to_init->tessedit_load_sublangs, &langs_to_load,
362
4
                              &langs_not_to_load);
363
4
          loaded_primary = true;
364
4
        }
365
4
      } else {
366
0
        if (result < 0) {
367
0
          tprintf("Failed loading language '%s'\n", lang_str);
368
0
          delete tess_to_init;
369
0
        } else {
370
0
          sub_langs_.push_back(tess_to_init);
371
          // Add any languages that this language requires
372
0
          ParseLanguageString(tess_to_init->tessedit_load_sublangs, &langs_to_load,
373
0
                              &langs_not_to_load);
374
0
        }
375
0
      }
376
4
    }
377
4
  }
378
4
  if (!loaded_primary && !langs_to_load.empty()) {
379
0
    tprintf("Tesseract couldn't load any languages!\n");
380
0
    return -1; // Couldn't load any language!
381
0
  }
382
4
#ifndef DISABLED_LEGACY_ENGINE
383
4
  if (!sub_langs_.empty()) {
384
    // In multilingual mode word ratings have to be directly comparable,
385
    // so use the same language model weights for all languages:
386
    // use the primary language's params model if
387
    // tessedit_use_primary_params_model is set,
388
    // otherwise use default language model weights.
389
0
    if (tessedit_use_primary_params_model) {
390
0
      for (auto &sub_lang : sub_langs_) {
391
0
        sub_lang->language_model_->getParamsModel().Copy(this->language_model_->getParamsModel());
392
0
      }
393
0
      tprintf("Using params model of the primary language\n");
394
0
    } else {
395
0
      this->language_model_->getParamsModel().Clear();
396
0
      for (auto &sub_lang : sub_langs_) {
397
0
        sub_lang->language_model_->getParamsModel().Clear();
398
0
      }
399
0
    }
400
0
  }
401
402
4
  SetupUniversalFontIds();
403
4
#endif // ndef DISABLED_LEGACY_ENGINE
404
4
  return 0;
405
4
}
406
407
// Common initialization for a single language.
408
// textbase is an optional output file basename (used only for training)
409
// language is the language code to load.
410
// oem controls which engine(s) will operate on the image
411
// configs (argv) is an array of config filenames to load variables from.
412
// May be nullptr.
413
// configs_size (argc) is the number of elements in configs.
414
// vars_vec is an optional vector of variables to set.
415
// vars_values is an optional corresponding vector of values for the variables
416
// in vars_vec.
417
// If set_only_non_debug_params is true, only params that do not contain
418
// "debug" in the name will be set.
419
int Tesseract::init_tesseract_internal(const std::string &textbase,
420
                                       const std::string &language, OcrEngineMode oem,
421
                                       char **configs, int configs_size,
422
                                       const std::vector<std::string> *vars_vec,
423
                                       const std::vector<std::string> *vars_values,
424
4
                                       bool set_only_non_debug_params, TessdataManager *mgr) {
425
4
  if (!init_tesseract_lang_data(language, oem, configs, configs_size, vars_vec,
426
4
                                vars_values, set_only_non_debug_params, mgr)) {
427
0
    return -1;
428
0
  }
429
4
  if (tessedit_init_config_only) {
430
0
    return 0;
431
0
  }
432
  // If only LSTM will be used, skip loading Tesseract classifier's
433
  // pre-trained templates and dictionary.
434
4
  bool init_tesseract = tessedit_ocr_engine_mode != OEM_LSTM_ONLY;
435
4
  if (!program_editup(textbase, init_tesseract ? mgr : nullptr, init_tesseract ? mgr : nullptr)) {
436
0
    return -1;
437
0
  }
438
4
  return 0; // Normal exit
439
4
}
440
441
#ifndef DISABLED_LEGACY_ENGINE
442
443
// Helper builds the all_fonts table by adding new fonts from new_fonts.
444
static void CollectFonts(const UnicityTable<FontInfo> &new_fonts,
445
4
                         UnicityTable<FontInfo> *all_fonts) {
446
1.71k
  for (int i = 0; i < new_fonts.size(); ++i) {
447
    // UnicityTable uniques as we go.
448
1.70k
    all_fonts->push_back(new_fonts.at(i));
449
1.70k
  }
450
4
}
451
452
// Helper assigns an id to lang_fonts using the index in all_fonts table.
453
4
static void AssignIds(const UnicityTable<FontInfo> &all_fonts, UnicityTable<FontInfo> *lang_fonts) {
454
1.71k
  for (int i = 0; i < lang_fonts->size(); ++i) {
455
1.70k
    auto index = all_fonts.get_index(lang_fonts->at(i));
456
1.70k
    lang_fonts->at(i).universal_id = index;
457
1.70k
  }
458
4
}
459
460
// Set the universal_id member of each font to be unique among all
461
// instances of the same font loaded.
462
4
void Tesseract::SetupUniversalFontIds() {
463
  // Note that we can get away with bitwise copying FontInfo in
464
  // all_fonts, as it is a temporary structure and we avoid setting the
465
  // delete callback.
466
4
  UnicityTable<FontInfo> all_fonts;
467
468
  // Create the universal ID table.
469
4
  CollectFonts(get_fontinfo_table(), &all_fonts);
470
4
  for (auto &sub_lang : sub_langs_) {
471
0
    CollectFonts(sub_lang->get_fontinfo_table(), &all_fonts);
472
0
  }
473
  // Assign ids from the table to each font table.
474
4
  AssignIds(all_fonts, &get_fontinfo_table());
475
4
  for (auto &sub_lang : sub_langs_) {
476
0
    AssignIds(all_fonts, &sub_lang->get_fontinfo_table());
477
0
  }
478
4
  font_table_size_ = all_fonts.size();
479
4
}
480
481
#endif // ndef DISABLED_LEGACY_ENGINE
482
483
0
void Tesseract::end_tesseract() {
484
0
  end_recog();
485
0
}
486
487
/* Define command type identifiers */
488
489
enum CMD_EVENTS { ACTION_1_CMD_EVENT, RECOG_WERDS, RECOG_PSEUDO, ACTION_2_CMD_EVENT };
490
} // namespace tesseract