Coverage Report

Created: 2026-10-28 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tesseract/src/wordrec/language_model.h
Line
Count
Source
1
///////////////////////////////////////////////////////////////////////
2
// File:        language_model.h
3
// Description: Functions that utilize the knowledge about the properties,
4
//              structure and statistics of the language to help segmentation
5
//              search.
6
// Author:      Daria Antonova
7
//
8
// (C) Copyright 2009, Google Inc.
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
#ifndef TESSERACT_WORDREC_LANGUAGE_MODEL_H_
22
#define TESSERACT_WORDREC_LANGUAGE_MODEL_H_
23
24
#include "associate.h"      // for AssociateStats (ptr only), AssociateUtils
25
#include "dawg.h"           // for DawgPositionVector
26
#include "dict.h"           // for DawgArgs, Dict
27
#include "lm_consistency.h" // for LMConsistencyInfo
28
#include "lm_state.h"       // for ViterbiStateEntry, LanguageModelFlagsType
29
#include "params.h"         // for DoubleParam, double_VAR_H, IntParam, Boo...
30
#include "params_model.h"   // for ParamsModel
31
#include "ratngs.h"         // for BLOB_CHOICE (ptr only), BLOB_CHOICE_LIST...
32
#include "stopper.h"        // for DANGERR
33
34
#include <cmath> // for exp
35
36
namespace tesseract {
37
38
class UNICHARSET;
39
class WERD_RES;
40
41
struct BlamerBundle;
42
43
template <typename T>
44
class UnicityTable;
45
46
class LMPainPoints;
47
struct FontInfo;
48
49
// This class that contains the data structures and functions necessary
50
// to represent and use the knowledge about the language.
51
class LanguageModel {
52
public:
53
  // Masks for keeping track of top choices that should not be pruned out.
54
  static const LanguageModelFlagsType kSmallestRatingFlag = 0x1;
55
  static const LanguageModelFlagsType kLowerCaseFlag = 0x2;
56
  static const LanguageModelFlagsType kUpperCaseFlag = 0x4;
57
  static const LanguageModelFlagsType kDigitFlag = 0x8;
58
  static const LanguageModelFlagsType kXhtConsistentFlag = 0x10;
59
60
  // Denominator for normalizing per-letter ngram cost when deriving
61
  // penalty adjustments.
62
  static const float kMaxAvgNgramCost;
63
64
  LanguageModel(const UnicityTable<FontInfo> *fontinfo_table, Dict *dict);
65
  ~LanguageModel();
66
67
  // Fills the given floats array with features extracted from path represented
68
  // by the given ViterbiStateEntry. See ccstruct/params_training_featdef.h
69
  // for feature information.
70
  // Note: the function assumes that features points to an array of size
71
  // PTRAIN_NUM_FEATURE_TYPES.
72
  static void ExtractFeaturesFromPath(const ViterbiStateEntry &vse, float features[]);
73
74
  // Updates data structures that are used for the duration of the segmentation
75
  // search on the current word;
76
  void InitForWord(const WERD_CHOICE *prev_word, bool fixed_pitch, float max_char_wh_ratio,
77
                   float rating_cert_scale);
78
79
  // Updates language model state of the given BLOB_CHOICE_LIST (from
80
  // the ratings matrix) and its parent.
81
  //
82
  // At most language_model_viterbi_list_size are kept in each
83
  // LanguageModelState.viterbi_state_entries list.
84
  // At most language_model_viterbi_list_max_num_prunable of those are prunable
85
  // (non-dictionary) paths.
86
  // The entries that represent dictionary word paths are kept at the front
87
  // of the list.
88
  // The list ordered by cost that is computed collectively by several
89
  // language model components (currently dawg and ngram components).
90
  bool UpdateState(bool just_classified, int curr_col, int curr_row, BLOB_CHOICE_LIST *curr_list,
91
                   LanguageModelState *parent_node, WERD_RES *word_res,
92
                   BestChoiceBundle *best_choice_bundle, BlamerBundle *blamer_bundle);
93
94
  // Returns true if an acceptable best choice was discovered.
95
2.49M
  inline bool AcceptableChoiceFound() {
96
2.49M
    return acceptable_choice_found_;
97
2.49M
  }
98
0
  inline void SetAcceptableChoiceFound(bool val) {
99
0
    acceptable_choice_found_ = val;
100
0
  }
101
  // Returns the reference to ParamsModel.
102
112k
  inline ParamsModel &getParamsModel() {
103
112k
    return params_model_;
104
112k
  }
105
106
protected:
107
0
  inline float CertaintyScore(float cert) {
108
0
    if (language_model_use_sigmoidal_certainty) {
109
      // cert is assumed to be between 0 and -dict_->certainty_scale.
110
      // If you enable language_model_use_sigmoidal_certainty, you
111
      // need to adjust language_model_ngram_nonmatch_score as well.
112
0
      cert = -cert / dict_->certainty_scale;
113
0
      return 1.0f / (1.0f + exp(10.0f * cert));
114
0
    } else {
115
0
      return (-1.0f / cert);
116
0
    }
117
0
  }
118
119
95.4M
  inline float ComputeAdjustment(int num_problems, float penalty) {
120
95.4M
    if (num_problems == 0) {
121
72.0M
      return 0.0f;
122
72.0M
    }
123
23.4M
    if (num_problems == 1) {
124
12.0M
      return penalty;
125
12.0M
    }
126
11.3M
    return (penalty + (language_model_penalty_increment * static_cast<float>(num_problems - 1)));
127
23.4M
  }
128
129
  // Computes the adjustment to the ratings sum based on the given
130
  // consistency_info. The paths with invalid punctuation, inconsistent
131
  // case and character type are penalized proportionally to the number
132
  // of inconsistencies on the path.
133
  inline float ComputeConsistencyAdjustment(const LanguageModelDawgInfo *dawg_info,
134
23.8M
                                            const LMConsistencyInfo &consistency_info) {
135
23.8M
    if (dawg_info != nullptr) {
136
0
      return ComputeAdjustment(consistency_info.NumInconsistentCase(),
137
0
                               language_model_penalty_case) +
138
0
             (consistency_info.inconsistent_script ? language_model_penalty_script : 0.0f);
139
0
    }
140
23.8M
    return (ComputeAdjustment(consistency_info.NumInconsistentPunc(), language_model_penalty_punc) +
141
23.8M
            ComputeAdjustment(consistency_info.NumInconsistentCase(), language_model_penalty_case) +
142
23.8M
            ComputeAdjustment(consistency_info.NumInconsistentChartype(),
143
23.8M
                              language_model_penalty_chartype) +
144
23.8M
            ComputeAdjustment(consistency_info.NumInconsistentSpaces(),
145
23.8M
                              language_model_penalty_spacing) +
146
23.8M
            (consistency_info.inconsistent_script ? language_model_penalty_script : 0.0f) +
147
23.8M
            (consistency_info.inconsistent_font ? language_model_penalty_font : 0.0f));
148
23.8M
  }
149
150
  // Returns an adjusted ratings sum that includes inconsistency penalties,
151
  // penalties for non-dictionary paths and paths with dips in ngram
152
  // probability.
153
  float ComputeAdjustedPathCost(ViterbiStateEntry *vse);
154
155
  // Finds the first lower and upper case letter and first digit in curr_list.
156
  // Uses the first character in the list in place of empty results.
157
  // Returns true if both alpha and digits are found.
158
  bool GetTopLowerUpperDigit(BLOB_CHOICE_LIST *curr_list, BLOB_CHOICE **first_lower,
159
                             BLOB_CHOICE **first_upper, BLOB_CHOICE **first_digit) const;
160
  // Forces there to be at least one entry in the overall set of the
161
  // viterbi_state_entries of each element of parent_node that has the
162
  // top_choice_flag set for lower, upper and digit using the same rules as
163
  // GetTopLowerUpperDigit, setting the flag on the first found suitable
164
  // candidate, whether or not the flag is set on some other parent.
165
  // Returns 1 if both alpha and digits are found among the parents, -1 if no
166
  // parents are found at all (a legitimate case), and 0 otherwise.
167
  int SetTopParentLowerUpperDigit(LanguageModelState *parent_node) const;
168
169
  // Finds the next ViterbiStateEntry with which the given unichar_id can
170
  // combine sensibly, taking into account any mixed alnum/mixed case
171
  // situation, and whether this combination has been inspected before.
172
  ViterbiStateEntry *GetNextParentVSE(bool just_classified, bool mixed_alnum, const BLOB_CHOICE *bc,
173
                                      LanguageModelFlagsType blob_choice_flags,
174
                                      const UNICHARSET &unicharset, WERD_RES *word_res,
175
                                      ViterbiStateEntry_IT *vse_it,
176
                                      LanguageModelFlagsType *top_choice_flags) const;
177
  // Helper function that computes the cost of the path composed of the
178
  // path in the given parent ViterbiStateEntry and the given BLOB_CHOICE.
179
  // If the new path looks good enough, adds a new ViterbiStateEntry to the
180
  // list of viterbi entries in the given BLOB_CHOICE and returns true.
181
  bool AddViterbiStateEntry(LanguageModelFlagsType top_choice_flags, float denom, bool word_end,
182
                            int curr_col, int curr_row, BLOB_CHOICE *b,
183
                            LanguageModelState *curr_state, ViterbiStateEntry *parent_vse,
184
                            WERD_RES *word_res,
185
                            BestChoiceBundle *best_choice_bundle, BlamerBundle *blamer_bundle);
186
187
  // Determines whether a potential entry is a true top choice and
188
  // updates changed accordingly.
189
  //
190
  // Note: The function assumes that b, top_choice_flags and changed
191
  // are not nullptr.
192
  void GenerateTopChoiceInfo(ViterbiStateEntry *new_vse,
193
                             LanguageModelState *lms);
194
195
  // Calls dict_->LetterIsOk() with DawgArgs initialized from parent_vse and
196
  // unichar from b.unichar_id(). Constructs and returns LanguageModelDawgInfo
197
  // with updated active dawgs, constraints and permuter.
198
  //
199
  // Note: the caller is responsible for deleting the returned pointer.
200
  LanguageModelDawgInfo *GenerateDawgInfo(bool word_end, int curr_col, const BLOB_CHOICE &b,
201
                                          const ViterbiStateEntry *parent_vse);
202
203
  // Computes p(unichar | parent context) and records it in ngram_cost.
204
  // If b.unichar_id() is an unlikely continuation of the parent context
205
  // sets found_small_prob to true and returns nullptr.
206
  // Otherwise creates a new LanguageModelNgramInfo entry containing the
207
  // updated context (that includes b.unichar_id() at the end) and returns it.
208
  //
209
  // Note: the caller is responsible for deleting the returned pointer.
210
  LanguageModelNgramInfo *GenerateNgramInfo(const char *unichar, float certainty, float denom,
211
                                            float outline_length,
212
                                            const ViterbiStateEntry *parent_vse);
213
214
  // Computes -(log(prob(classifier)) + log(prob(ngram model)))
215
  // for the given unichar in the given context. If there are multiple
216
  // unichars at one position - takes the average of their probabilities.
217
  // UNICHAR::utf8_step() is used to separate out individual UTF8 characters,
218
  // since probability_in_context() can only handle one at a time (while
219
  // unicharset might contain ngrams and glyphs composed from multiple UTF8
220
  // characters).
221
  float ComputeNgramCost(const char *unichar, float certainty, float denom, const char *context,
222
                         int *unichar_step_len, bool *found_small_prob, float *ngram_prob);
223
224
  // Computes the normalization factors for the classifier confidences
225
  // (used by ComputeNgramCost()).
226
  float ComputeDenom(BLOB_CHOICE_LIST *curr_list);
227
228
  // Fills the given consistency_info based on parent_vse.consistency_info
229
  // and on the consistency of the given unichar_id with parent_vse.
230
  void FillConsistencyInfo(int curr_col, bool word_end, BLOB_CHOICE *b,
231
                           ViterbiStateEntry *parent_vse, WERD_RES *word_res,
232
                           LMConsistencyInfo *consistency_info);
233
234
  // Constructs WERD_CHOICE by recording unichar_ids of the BLOB_CHOICEs
235
  // on the path represented by the given BLOB_CHOICE and language model
236
  // state entries (lmse, dse). The path is re-constructed by following
237
  // the parent pointers in the lang model state entries). If the
238
  // constructed WERD_CHOICE is better than the best/raw choice recorded
239
  // in the best_choice_bundle, this function updates the corresponding
240
  // fields and sets best_choice_bunldle->updated to true.
241
  void UpdateBestChoice(ViterbiStateEntry *vse, WERD_RES *word_res, BestChoiceBundle *best_choice_bundle,
242
                        BlamerBundle *blamer_bundle);
243
244
  // Constructs a WERD_CHOICE by tracing parent pointers starting with
245
  // the given LanguageModelStateEntry. Returns the constructed word.
246
  // Updates best_char_choices, certainties and state if they are not
247
  // nullptr (best_char_choices and certainties are assumed to have the
248
  // length equal to lmse->length).
249
  // The caller is responsible for freeing memory associated with the
250
  // returned WERD_CHOICE.
251
  WERD_CHOICE *ConstructWord(ViterbiStateEntry *vse, WERD_RES *word_res, DANGERR *fixpt,
252
                             BlamerBundle *blamer_bundle, bool *truth_path);
253
254
  // Wrapper around AssociateUtils::ComputeStats().
255
  inline void ComputeAssociateStats(int col, int row, float max_char_wh_ratio,
256
                                    ViterbiStateEntry *parent_vse, WERD_RES *word_res,
257
23.8M
                                    AssociateStats *associate_stats) {
258
23.8M
    AssociateUtils::ComputeStats(
259
23.8M
        col, row, (parent_vse != nullptr) ? &(parent_vse->associate_stats) : nullptr,
260
23.8M
        (parent_vse != nullptr) ? parent_vse->length : 0, fixed_pitch_, max_char_wh_ratio, word_res,
261
23.8M
        language_model_debug_level > 2, associate_stats);
262
23.8M
  }
263
264
  // Returns true if the path with such top_choice_flags and dawg_info
265
  // could be pruned out (i.e. is neither a system/user/frequent dictionary
266
  // nor a top choice path).
267
  // In non-space delimited languages all paths can be "somewhat" dictionary
268
  // words. In such languages we cannot do dictionary-driven path pruning,
269
  // so paths with non-empty dawg_info are considered prunable.
270
115M
  inline bool PrunablePath(const ViterbiStateEntry &vse) {
271
115M
    if (vse.top_choice_flags) {
272
76.3M
      return false;
273
76.3M
    }
274
39.4M
    if (vse.dawg_info != nullptr &&
275
0
        (vse.dawg_info->permuter == SYSTEM_DAWG_PERM || vse.dawg_info->permuter == USER_DAWG_PERM ||
276
0
         vse.dawg_info->permuter == FREQ_DAWG_PERM)) {
277
0
      return false;
278
0
    }
279
39.4M
    return true;
280
39.4M
  }
281
282
  // Returns true if the given ViterbiStateEntry represents an acceptable path.
283
0
  inline bool AcceptablePath(const ViterbiStateEntry &vse) {
284
0
    return (vse.dawg_info != nullptr || vse.Consistent() ||
285
0
            (vse.ngram_info != nullptr && !vse.ngram_info->pruned));
286
0
  }
287
288
public:
289
  // Parameters.
290
  INT_VAR_H(language_model_debug_level);
291
  BOOL_VAR_H(language_model_ngram_on);
292
  INT_VAR_H(language_model_ngram_order);
293
  INT_VAR_H(language_model_viterbi_list_max_num_prunable);
294
  INT_VAR_H(language_model_viterbi_list_max_size);
295
  double_VAR_H(language_model_ngram_small_prob);
296
  double_VAR_H(language_model_ngram_nonmatch_score);
297
  BOOL_VAR_H(language_model_ngram_use_only_first_uft8_step);
298
  double_VAR_H(language_model_ngram_scale_factor);
299
  double_VAR_H(language_model_ngram_rating_factor);
300
  BOOL_VAR_H(language_model_ngram_space_delimited_language);
301
  INT_VAR_H(language_model_min_compound_length);
302
  // Penalties used for adjusting path costs and final word rating.
303
  double_VAR_H(language_model_penalty_non_freq_dict_word);
304
  double_VAR_H(language_model_penalty_non_dict_word);
305
  double_VAR_H(language_model_penalty_punc);
306
  double_VAR_H(language_model_penalty_case);
307
  double_VAR_H(language_model_penalty_script);
308
  double_VAR_H(language_model_penalty_chartype);
309
  double_VAR_H(language_model_penalty_font);
310
  double_VAR_H(language_model_penalty_spacing);
311
  double_VAR_H(language_model_penalty_increment);
312
  INT_VAR_H(wordrec_display_segmentations);
313
  BOOL_VAR_H(language_model_use_sigmoidal_certainty);
314
315
protected:
316
  // Member Variables.
317
318
  // Temporary DawgArgs struct that is re-used across different words to
319
  // avoid dynamic memory re-allocation (should be cleared before each use).
320
  DawgArgs dawg_args_;
321
  // Scaling for recovering blob outline length from rating and certainty.
322
  float rating_cert_scale_ = 0.0f;
323
324
  // The following variables are set at construction time.
325
326
  // Pointer to fontinfo table (not owned by LanguageModel).
327
  const UnicityTable<FontInfo> *fontinfo_table_ = nullptr;
328
329
  // Pointer to Dict class, that is used for querying the dictionaries
330
  // (the pointer is not owned by LanguageModel).
331
  Dict *dict_ = nullptr;
332
333
  // TODO(daria): the following variables should become LanguageModel params
334
  // when the old code in bestfirst.cpp and heuristic.cpp is deprecated.
335
  //
336
  // Set to true if we are dealing with fixed pitch text
337
  // (set to assume_fixed_pitch_char_segment).
338
  bool fixed_pitch_ = false;
339
  // Max char width-to-height ratio allowed
340
  // (set to segsearch_max_char_wh_ratio).
341
  float max_char_wh_ratio_ = 0.0f;
342
343
  // The following variables are initialized with InitForWord().
344
345
  // String representation of the classification of the previous word
346
  // (since this is only used by the character ngram model component,
347
  // only the last language_model_ngram_order of the word are stored).
348
  std::string prev_word_str_;
349
  int prev_word_unichar_step_len_ = 0;
350
  // Active dawg vector.
351
  DawgPositionVector very_beginning_active_dawgs_; // includes continuation
352
  DawgPositionVector beginning_active_dawgs_;
353
  // Set to true if acceptable choice was discovered.
354
  // Note: it would be nice to use this to terminate the search once an
355
  // acceptable choices is found. However we do not do that and once an
356
  // acceptable choice is found we finish looking for alternative choices
357
  // in the current segmentation graph and then exit the search (no more
358
  // classifications are done after an acceptable choice is found).
359
  // This is needed in order to let the search find the words very close to
360
  // the best choice in rating (e.g. what/What, Cat/cat, etc) and log these
361
  // choices. This way the stopper will know that the best choice is not
362
  // ambiguous (i.e. there are best choices in the best choice list that have
363
  // ratings close to the very best one) and will be less likely to mis-adapt.
364
  bool acceptable_choice_found_ = false;
365
  // Set to true if a choice representing correct segmentation was explored.
366
  bool correct_segmentation_explored_ = false;
367
368
  // Params models containing weights for computing ViterbiStateEntry costs.
369
  ParamsModel params_model_;
370
};
371
372
} // namespace tesseract
373
374
#endif // TESSERACT_WORDREC_LANGUAGE_MODEL_H_