tesseract  3.04.00
adaptmatch.cpp
Go to the documentation of this file.
1 /******************************************************************************
2  ** Filename: adaptmatch.c
3  ** Purpose: High level adaptive matcher.
4  ** Author: Dan Johnson
5  ** History: Mon Mar 11 10:00:10 1991, DSJ, Created.
6  **
7  ** (c) Copyright Hewlett-Packard Company, 1988.
8  ** Licensed under the Apache License, Version 2.0 (the "License");
9  ** you may not use this file except in compliance with the License.
10  ** You may obtain a copy of the License at
11  ** http://www.apache.org/licenses/LICENSE-2.0
12  ** Unless required by applicable law or agreed to in writing, software
13  ** distributed under the License is distributed on an "AS IS" BASIS,
14  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  ** See the License for the specific language governing permissions and
16  ** limitations under the License.
17  ******************************************************************************/
18 
19 /*-----------------------------------------------------------------------------
20  Include Files and Type Defines
21 -----------------------------------------------------------------------------*/
22 #ifdef HAVE_CONFIG_H
23 #include "config_auto.h"
24 #endif
25 
26 #include <ctype.h>
27 #include "shapeclassifier.h"
28 #include "ambigs.h"
29 #include "blobclass.h"
30 #include "blobs.h"
31 #include "callcpp.h"
32 #include "classify.h"
33 #include "const.h"
34 #include "dict.h"
35 #include "efio.h"
36 #include "emalloc.h"
37 #include "featdefs.h"
38 #include "float2int.h"
39 #include "genericvector.h"
40 #include "globals.h"
41 #include "helpers.h"
42 #include "intfx.h"
43 #include "intproto.h"
44 #include "mfoutline.h"
45 #include "ndminx.h"
46 #include "normfeat.h"
47 #include "normmatch.h"
48 #include "outfeat.h"
49 #include "pageres.h"
50 #include "params.h"
51 #include "picofeat.h"
52 #include "shapetable.h"
53 #include "tessclassifier.h"
54 #include "trainingsample.h"
55 #include "unicharset.h"
56 #include "werd.h"
57 
58 #include <stdio.h>
59 #include <string.h>
60 #include <stdlib.h>
61 #include <math.h>
62 #ifdef __UNIX__
63 #include <assert.h>
64 #endif
65 
66 #define ADAPT_TEMPLATE_SUFFIX ".a"
67 
68 #define MAX_MATCHES 10
69 #define UNLIKELY_NUM_FEAT 200
70 #define NO_DEBUG 0
71 #define MAX_ADAPTABLE_WERD_SIZE 40
72 
73 #define ADAPTABLE_WERD_ADJUSTMENT (0.05)
74 
75 #define Y_DIM_OFFSET (Y_SHIFT - BASELINE_Y_SHIFT)
76 
77 #define WORST_POSSIBLE_RATING (0.0f)
78 
81 
82 struct ADAPT_RESULTS {
90 
93  inline void Initialize() {
94  BlobLength = MAX_INT32;
95  HasNonfragment = false;
96  ComputeBest();
97  }
98  // Computes best_unichar_id, best_match_index and best_rating.
99  void ComputeBest() {
100  best_unichar_id = INVALID_UNICHAR_ID;
101  best_match_index = -1;
102  best_rating = WORST_POSSIBLE_RATING;
103  for (int i = 0; i < match.size(); ++i) {
104  if (match[i].rating > best_rating) {
105  best_rating = match[i].rating;
106  best_unichar_id = match[i].unichar_id;
107  best_match_index = i;
108  }
109  }
110  }
111 };
112 
113 struct PROTO_KEY {
116  int ConfigId;
117 };
118 
119 /*-----------------------------------------------------------------------------
120  Private Macros
121 -----------------------------------------------------------------------------*/
122 inline bool MarginalMatch(float confidence, float matcher_great_threshold) {
123  return (1.0f - confidence) > matcher_great_threshold;
124 }
125 
126 /*-----------------------------------------------------------------------------
127  Private Function Prototypes
128 -----------------------------------------------------------------------------*/
129 // Returns the index of the given id in results, if present, or the size of the
130 // vector (index it will go at) if not present.
131 static int FindScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) {
132  for (int i = 0; i < results.match.size(); i++) {
133  if (results.match[i].unichar_id == id)
134  return i;
135  }
136  return results.match.size();
137 }
138 
139 // Returns the current rating for a unichar id if we have rated it, defaulting
140 // to WORST_POSSIBLE_RATING.
141 static float ScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) {
142  int index = FindScoredUnichar(id, results);
143  if (index >= results.match.size()) return WORST_POSSIBLE_RATING;
144  return results.match[index].rating;
145 }
146 
147 void InitMatcherRatings(register FLOAT32 *Rating);
148 
149 int MakeTempProtoPerm(void *item1, void *item2);
150 
151 void SetAdaptiveThreshold(FLOAT32 Threshold);
152 
153 
154 /*-----------------------------------------------------------------------------
155  Public Code
156 -----------------------------------------------------------------------------*/
157 /*---------------------------------------------------------------------------*/
158 namespace tesseract {
185 void Classify::AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices) {
186  assert(Choices != NULL);
187  ADAPT_RESULTS *Results = new ADAPT_RESULTS;
188  Results->Initialize();
189 
190  ASSERT_HOST(AdaptedTemplates != NULL);
191 
192  DoAdaptiveMatch(Blob, Results);
193 
194  RemoveBadMatches(Results);
195  Results->match.sort(&UnicharRating::SortDescendingRating);
196  RemoveExtraPuncs(Results);
197  Results->ComputeBest();
198  ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results,
199  Choices);
200 
201  // TODO(rays) Move to before ConvertMatchesToChoices!
202  if (LargeSpeckle(*Blob) || Choices->length() == 0)
203  AddLargeSpeckleTo(Results->BlobLength, Choices);
204 
205  if (matcher_debug_level >= 1) {
206  tprintf("AD Matches = ");
207  PrintAdaptiveMatchResults(*Results);
208  }
209 
210 #ifndef GRAPHICS_DISABLED
211  if (classify_enable_adaptive_debugger)
212  DebugAdaptiveClassifier(Blob, Results);
213 #endif
214 
215  delete Results;
216 } /* AdaptiveClassifier */
217 
218 // If *win is NULL, sets it to a new ScrollView() object with title msg.
219 // Clears the window and draws baselines.
220 void Classify::RefreshDebugWindow(ScrollView **win, const char *msg,
221  int y_offset, const TBOX &wbox) {
222  #ifndef GRAPHICS_DISABLED
223  const int kSampleSpaceWidth = 500;
224  if (*win == NULL) {
225  *win = new ScrollView(msg, 100, y_offset, kSampleSpaceWidth * 2, 200,
226  kSampleSpaceWidth * 2, 200, true);
227  }
228  (*win)->Clear();
229  (*win)->Pen(64, 64, 64);
230  (*win)->Line(-kSampleSpaceWidth, kBlnBaselineOffset,
231  kSampleSpaceWidth, kBlnBaselineOffset);
232  (*win)->Line(-kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset,
233  kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset);
234  (*win)->ZoomToRectangle(wbox.left(), wbox.top(),
235  wbox.right(), wbox.bottom());
236  #endif // GRAPHICS_DISABLED
237 }
238 
239 // Learns the given word using its chopped_word, seam_array, denorm,
240 // box_word, best_state, and correct_text to learn both correctly and
241 // incorrectly segmented blobs. If fontname is not NULL, then LearnBlob
242 // is called and the data will be saved in an internal buffer.
243 // Otherwise AdaptToBlob is called for adaption within a document.
244 void Classify::LearnWord(const char* fontname, WERD_RES* word) {
245  int word_len = word->correct_text.size();
246  if (word_len == 0) return;
247 
248  float* thresholds = NULL;
249  if (fontname == NULL) {
250  // Adaption mode.
251  if (!EnableLearning || word->best_choice == NULL)
252  return; // Can't or won't adapt.
253 
254  if (classify_learning_debug_level >= 1)
255  tprintf("\n\nAdapting to word = %s\n",
256  word->best_choice->debug_string().string());
257  thresholds = new float[word_len];
258  word->ComputeAdaptionThresholds(certainty_scale,
259  matcher_perfect_threshold,
260  matcher_good_threshold,
261  matcher_rating_margin, thresholds);
262  }
263  int start_blob = 0;
264 
265  #ifndef GRAPHICS_DISABLED
266  if (classify_debug_character_fragments) {
267  if (learn_fragmented_word_debug_win_ != NULL) {
268  window_wait(learn_fragmented_word_debug_win_);
269  }
270  RefreshDebugWindow(&learn_fragments_debug_win_, "LearnPieces", 400,
271  word->chopped_word->bounding_box());
272  RefreshDebugWindow(&learn_fragmented_word_debug_win_, "LearnWord", 200,
273  word->chopped_word->bounding_box());
274  word->chopped_word->plot(learn_fragmented_word_debug_win_);
276  }
277  #endif // GRAPHICS_DISABLED
278 
279  for (int ch = 0; ch < word_len; ++ch) {
280  if (classify_debug_character_fragments) {
281  tprintf("\nLearning %s\n", word->correct_text[ch].string());
282  }
283  if (word->correct_text[ch].length() > 0) {
284  float threshold = thresholds != NULL ? thresholds[ch] : 0.0f;
285 
286  LearnPieces(fontname, start_blob, word->best_state[ch], threshold,
287  CST_WHOLE, word->correct_text[ch].string(), word);
288 
289  if (word->best_state[ch] > 1 && !disable_character_fragments) {
290  // Check that the character breaks into meaningful fragments
291  // that each match a whole character with at least
292  // classify_character_fragments_garbage_certainty_threshold
293  bool garbage = false;
294  int frag;
295  for (frag = 0; frag < word->best_state[ch]; ++frag) {
296  TBLOB* frag_blob = word->chopped_word->blobs[start_blob + frag];
297  if (classify_character_fragments_garbage_certainty_threshold < 0) {
298  garbage |= LooksLikeGarbage(frag_blob);
299  }
300  }
301  // Learn the fragments.
302  if (!garbage) {
303  bool pieces_all_natural = word->PiecesAllNatural(start_blob,
304  word->best_state[ch]);
305  if (pieces_all_natural || !prioritize_division) {
306  for (frag = 0; frag < word->best_state[ch]; ++frag) {
307  GenericVector<STRING> tokens;
308  word->correct_text[ch].split(' ', &tokens);
309 
310  tokens[0] = CHAR_FRAGMENT::to_string(
311  tokens[0].string(), frag, word->best_state[ch],
312  pieces_all_natural);
313 
314  STRING full_string;
315  for (int i = 0; i < tokens.size(); i++) {
316  full_string += tokens[i];
317  if (i != tokens.size() - 1)
318  full_string += ' ';
319  }
320  LearnPieces(fontname, start_blob + frag, 1, threshold,
321  CST_FRAGMENT, full_string.string(), word);
322  }
323  }
324  }
325  }
326 
327  // TODO(rays): re-enable this part of the code when we switch to the
328  // new classifier that needs to see examples of garbage.
329  /*
330  if (word->best_state[ch] > 1) {
331  // If the next blob is good, make junk with the rightmost fragment.
332  if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
333  LearnPieces(fontname, start_blob + word->best_state[ch] - 1,
334  word->best_state[ch + 1] + 1,
335  threshold, CST_IMPROPER, INVALID_UNICHAR, word);
336  }
337  // If the previous blob is good, make junk with the leftmost fragment.
338  if (ch > 0 && word->correct_text[ch - 1].length() > 0) {
339  LearnPieces(fontname, start_blob - word->best_state[ch - 1],
340  word->best_state[ch - 1] + 1,
341  threshold, CST_IMPROPER, INVALID_UNICHAR, word);
342  }
343  }
344  // If the next blob is good, make a join with it.
345  if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
346  STRING joined_text = word->correct_text[ch];
347  joined_text += word->correct_text[ch + 1];
348  LearnPieces(fontname, start_blob,
349  word->best_state[ch] + word->best_state[ch + 1],
350  threshold, CST_NGRAM, joined_text.string(), word);
351  }
352  */
353  }
354  start_blob += word->best_state[ch];
355  }
356  delete [] thresholds;
357 } // LearnWord.
358 
359 // Builds a blob of length fragments, from the word, starting at start,
360 // and then learns it, as having the given correct_text.
361 // If fontname is not NULL, then LearnBlob is called and the data will be
362 // saved in an internal buffer for static training.
363 // Otherwise AdaptToBlob is called for adaption within a document.
364 // threshold is a magic number required by AdaptToChar and generated by
365 // ComputeAdaptionThresholds.
366 // Although it can be partly inferred from the string, segmentation is
367 // provided to explicitly clarify the character segmentation.
368 void Classify::LearnPieces(const char* fontname, int start, int length,
369  float threshold, CharSegmentationType segmentation,
370  const char* correct_text, WERD_RES* word) {
371  // TODO(daria) Remove/modify this if/when we want
372  // to train and/or adapt to n-grams.
373  if (segmentation != CST_WHOLE &&
374  (segmentation != CST_FRAGMENT || disable_character_fragments))
375  return;
376 
377  if (length > 1) {
378  SEAM::JoinPieces(word->seam_array, word->chopped_word->blobs, start,
379  start + length - 1);
380  }
381  TBLOB* blob = word->chopped_word->blobs[start];
382  // Rotate the blob if needed for classification.
383  TBLOB* rotated_blob = blob->ClassifyNormalizeIfNeeded();
384  if (rotated_blob == NULL)
385  rotated_blob = blob;
386 
387  #ifndef GRAPHICS_DISABLED
388  // Draw debug windows showing the blob that is being learned if needed.
389  if (strcmp(classify_learn_debug_str.string(), correct_text) == 0) {
390  RefreshDebugWindow(&learn_debug_win_, "LearnPieces", 600,
391  word->chopped_word->bounding_box());
392  rotated_blob->plot(learn_debug_win_, ScrollView::GREEN, ScrollView::BROWN);
393  learn_debug_win_->Update();
394  window_wait(learn_debug_win_);
395  }
396  if (classify_debug_character_fragments && segmentation == CST_FRAGMENT) {
397  ASSERT_HOST(learn_fragments_debug_win_ != NULL); // set up in LearnWord
398  blob->plot(learn_fragments_debug_win_,
400  learn_fragments_debug_win_->Update();
401  }
402  #endif // GRAPHICS_DISABLED
403 
404  if (fontname != NULL) {
405  classify_norm_method.set_value(character); // force char norm spc 30/11/93
406  tess_bn_matching.set_value(false); // turn it off
407  tess_cn_matching.set_value(false);
408  DENORM bl_denorm, cn_denorm;
409  INT_FX_RESULT_STRUCT fx_info;
410  SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm,
411  &bl_denorm, &cn_denorm, &fx_info);
412  LearnBlob(fontname, rotated_blob, cn_denorm, fx_info, correct_text);
413  } else if (unicharset.contains_unichar(correct_text)) {
414  UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text);
415  int font_id = word->fontinfo != NULL
416  ? fontinfo_table_.get_id(*word->fontinfo)
417  : 0;
418  if (classify_learning_debug_level >= 1)
419  tprintf("Adapting to char = %s, thr= %g font_id= %d\n",
420  unicharset.id_to_unichar(class_id), threshold, font_id);
421  // If filename is not NULL we are doing recognition
422  // (as opposed to training), so we must have already set word fonts.
423  AdaptToChar(rotated_blob, class_id, font_id, threshold, AdaptedTemplates);
424  if (BackupAdaptedTemplates != NULL) {
425  // Adapt the backup templates too. They will be used if the primary gets
426  // too full.
427  AdaptToChar(rotated_blob, class_id, font_id, threshold,
428  BackupAdaptedTemplates);
429  }
430  } else if (classify_debug_level >= 1) {
431  tprintf("Can't adapt to %s not in unicharset\n", correct_text);
432  }
433  if (rotated_blob != blob) {
434  delete rotated_blob;
435  }
436 
437  SEAM::BreakPieces(word->seam_array, word->chopped_word->blobs, start,
438  start + length - 1);
439 } // LearnPieces.
440 
441 /*---------------------------------------------------------------------------*/
456 void Classify::EndAdaptiveClassifier() {
457  STRING Filename;
458  FILE *File;
459 
460  if (AdaptedTemplates != NULL &&
461  classify_enable_adaptive_matcher && classify_save_adapted_templates) {
462  Filename = imagefile + ADAPT_TEMPLATE_SUFFIX;
463  File = fopen (Filename.string(), "wb");
464  if (File == NULL)
465  cprintf ("Unable to save adapted templates to %s!\n", Filename.string());
466  else {
467  cprintf ("\nSaving adapted templates to %s ...", Filename.string());
468  fflush(stdout);
469  WriteAdaptedTemplates(File, AdaptedTemplates);
470  cprintf ("\n");
471  fclose(File);
472  }
473  }
474 
475  if (AdaptedTemplates != NULL) {
476  free_adapted_templates(AdaptedTemplates);
477  AdaptedTemplates = NULL;
478  }
479  if (BackupAdaptedTemplates != NULL) {
480  free_adapted_templates(BackupAdaptedTemplates);
481  BackupAdaptedTemplates = NULL;
482  }
483 
484  if (PreTrainedTemplates != NULL) {
485  free_int_templates(PreTrainedTemplates);
486  PreTrainedTemplates = NULL;
487  }
488  getDict().EndDangerousAmbigs();
489  FreeNormProtos();
490  if (AllProtosOn != NULL) {
491  FreeBitVector(AllProtosOn);
492  FreeBitVector(AllConfigsOn);
493  FreeBitVector(AllConfigsOff);
494  FreeBitVector(TempProtoMask);
495  AllProtosOn = NULL;
496  AllConfigsOn = NULL;
497  AllConfigsOff = NULL;
498  TempProtoMask = NULL;
499  }
500  delete shape_table_;
501  shape_table_ = NULL;
502  if (static_classifier_ != NULL) {
503  delete static_classifier_;
504  static_classifier_ = NULL;
505  }
506 } /* EndAdaptiveClassifier */
507 
508 
509 /*---------------------------------------------------------------------------*/
527 void Classify::InitAdaptiveClassifier(bool load_pre_trained_templates) {
528  if (!classify_enable_adaptive_matcher)
529  return;
530  if (AllProtosOn != NULL)
531  EndAdaptiveClassifier(); // Don't leak with multiple inits.
532 
533  // If there is no language_data_path_prefix, the classifier will be
534  // adaptive only.
535  if (language_data_path_prefix.length() > 0 &&
536  load_pre_trained_templates) {
537  ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_INTTEMP));
538  PreTrainedTemplates =
539  ReadIntTemplates(tessdata_manager.GetDataFilePtr());
540  if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded inttemp\n");
541 
542  if (tessdata_manager.SeekToStart(TESSDATA_SHAPE_TABLE)) {
543  shape_table_ = new ShapeTable(unicharset);
544  if (!shape_table_->DeSerialize(tessdata_manager.swap(),
545  tessdata_manager.GetDataFilePtr())) {
546  tprintf("Error loading shape table!\n");
547  delete shape_table_;
548  shape_table_ = NULL;
549  } else if (tessdata_manager.DebugLevel() > 0) {
550  tprintf("Successfully loaded shape table!\n");
551  }
552  }
553 
554  ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_PFFMTABLE));
555  ReadNewCutoffs(tessdata_manager.GetDataFilePtr(),
556  tessdata_manager.swap(),
557  tessdata_manager.GetEndOffset(TESSDATA_PFFMTABLE),
558  CharNormCutoffs);
559  if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded pffmtable\n");
560 
561  ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_NORMPROTO));
562  NormProtos =
563  ReadNormProtos(tessdata_manager.GetDataFilePtr(),
564  tessdata_manager.GetEndOffset(TESSDATA_NORMPROTO));
565  if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded normproto\n");
566  static_classifier_ = new TessClassifier(false, this);
567  }
568 
569  im_.Init(&classify_debug_level);
570  InitIntegerFX();
571 
572  AllProtosOn = NewBitVector(MAX_NUM_PROTOS);
573  AllConfigsOn = NewBitVector(MAX_NUM_CONFIGS);
574  AllConfigsOff = NewBitVector(MAX_NUM_CONFIGS);
575  TempProtoMask = NewBitVector(MAX_NUM_PROTOS);
579 
580  for (int i = 0; i < MAX_NUM_CLASSES; i++) {
581  BaselineCutoffs[i] = 0;
582  }
583 
584  if (classify_use_pre_adapted_templates) {
585  FILE *File;
586  STRING Filename;
587 
588  Filename = imagefile;
589  Filename += ADAPT_TEMPLATE_SUFFIX;
590  File = fopen(Filename.string(), "rb");
591  if (File == NULL) {
592  AdaptedTemplates = NewAdaptedTemplates(true);
593  } else {
594  cprintf("\nReading pre-adapted templates from %s ...\n",
595  Filename.string());
596  fflush(stdout);
597  AdaptedTemplates = ReadAdaptedTemplates(File);
598  cprintf("\n");
599  fclose(File);
600  PrintAdaptedTemplates(stdout, AdaptedTemplates);
601 
602  for (int i = 0; i < AdaptedTemplates->Templates->NumClasses; i++) {
603  BaselineCutoffs[i] = CharNormCutoffs[i];
604  }
605  }
606  } else {
607  if (AdaptedTemplates != NULL)
608  free_adapted_templates(AdaptedTemplates);
609  AdaptedTemplates = NewAdaptedTemplates(true);
610  }
611 } /* InitAdaptiveClassifier */
612 
613 void Classify::ResetAdaptiveClassifierInternal() {
614  if (classify_learning_debug_level > 0) {
615  tprintf("Resetting adaptive classifier (NumAdaptationsFailed=%d)\n",
616  NumAdaptationsFailed);
617  }
618  free_adapted_templates(AdaptedTemplates);
619  AdaptedTemplates = NewAdaptedTemplates(true);
620  if (BackupAdaptedTemplates != NULL)
621  free_adapted_templates(BackupAdaptedTemplates);
622  BackupAdaptedTemplates = NULL;
623  NumAdaptationsFailed = 0;
624 }
625 
626 // If there are backup adapted templates, switches to those, otherwise resets
627 // the main adaptive classifier (because it is full.)
628 void Classify::SwitchAdaptiveClassifier() {
629  if (BackupAdaptedTemplates == NULL) {
630  ResetAdaptiveClassifierInternal();
631  return;
632  }
633  if (classify_learning_debug_level > 0) {
634  tprintf("Switch to backup adaptive classifier (NumAdaptationsFailed=%d)\n",
635  NumAdaptationsFailed);
636  }
637  free_adapted_templates(AdaptedTemplates);
638  AdaptedTemplates = BackupAdaptedTemplates;
639  BackupAdaptedTemplates = NULL;
640  NumAdaptationsFailed = 0;
641 }
642 
643 // Resets the backup adaptive classifier to empty.
644 void Classify::StartBackupAdaptiveClassifier() {
645  if (BackupAdaptedTemplates != NULL)
646  free_adapted_templates(BackupAdaptedTemplates);
647  BackupAdaptedTemplates = NewAdaptedTemplates(true);
648 }
649 
650 /*---------------------------------------------------------------------------*/
670 void Classify::SettupPass1() {
671  EnableLearning = classify_enable_learning;
672 
673  getDict().SettupStopperPass1();
674 
675 } /* SettupPass1 */
676 
677 
678 /*---------------------------------------------------------------------------*/
690 void Classify::SettupPass2() {
691  EnableLearning = FALSE;
692  getDict().SettupStopperPass2();
693 
694 } /* SettupPass2 */
695 
696 
697 /*---------------------------------------------------------------------------*/
717 void Classify::InitAdaptedClass(TBLOB *Blob,
718  CLASS_ID ClassId,
719  int FontinfoId,
720  ADAPT_CLASS Class,
721  ADAPT_TEMPLATES Templates) {
722  FEATURE_SET Features;
723  int Fid, Pid;
724  FEATURE Feature;
725  int NumFeatures;
726  TEMP_PROTO TempProto;
727  PROTO Proto;
728  INT_CLASS IClass;
730 
731  classify_norm_method.set_value(baseline);
732  Features = ExtractOutlineFeatures(Blob);
733  NumFeatures = Features->NumFeatures;
734  if (NumFeatures > UNLIKELY_NUM_FEAT || NumFeatures <= 0) {
735  FreeFeatureSet(Features);
736  return;
737  }
738 
739  Config = NewTempConfig(NumFeatures - 1, FontinfoId);
740  TempConfigFor(Class, 0) = Config;
741 
742  /* this is a kludge to construct cutoffs for adapted templates */
743  if (Templates == AdaptedTemplates)
744  BaselineCutoffs[ClassId] = CharNormCutoffs[ClassId];
745 
746  IClass = ClassForClassId (Templates->Templates, ClassId);
747 
748  for (Fid = 0; Fid < Features->NumFeatures; Fid++) {
749  Pid = AddIntProto (IClass);
750  assert (Pid != NO_PROTO);
751 
752  Feature = Features->Features[Fid];
753  TempProto = NewTempProto ();
754  Proto = &(TempProto->Proto);
755 
756  /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
757  ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
758  instead of the -0.25 to 0.75 used in baseline normalization */
759  Proto->Angle = Feature->Params[OutlineFeatDir];
760  Proto->X = Feature->Params[OutlineFeatX];
761  Proto->Y = Feature->Params[OutlineFeatY] - Y_DIM_OFFSET;
762  Proto->Length = Feature->Params[OutlineFeatLength];
763  FillABC(Proto);
764 
765  TempProto->ProtoId = Pid;
766  SET_BIT (Config->Protos, Pid);
767 
768  ConvertProto(Proto, Pid, IClass);
769  AddProtoToProtoPruner(Proto, Pid, IClass,
770  classify_learning_debug_level >= 2);
771 
772  Class->TempProtos = push (Class->TempProtos, TempProto);
773  }
774  FreeFeatureSet(Features);
775 
776  AddIntConfig(IClass);
777  ConvertConfig (AllProtosOn, 0, IClass);
778 
779  if (classify_learning_debug_level >= 1) {
780  tprintf("Added new class '%s' with class id %d and %d protos.\n",
781  unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
782  if (classify_learning_debug_level > 1)
783  DisplayAdaptedChar(Blob, IClass);
784  }
785 
786  if (IsEmptyAdaptedClass(Class))
787  (Templates->NumNonEmptyClasses)++;
788 } /* InitAdaptedClass */
789 
790 
791 /*---------------------------------------------------------------------------*/
812 int Classify::GetAdaptiveFeatures(TBLOB *Blob,
813  INT_FEATURE_ARRAY IntFeatures,
814  FEATURE_SET *FloatFeatures) {
815  FEATURE_SET Features;
816  int NumFeatures;
817 
818  classify_norm_method.set_value(baseline);
819  Features = ExtractPicoFeatures(Blob);
820 
821  NumFeatures = Features->NumFeatures;
822  if (NumFeatures > UNLIKELY_NUM_FEAT) {
823  FreeFeatureSet(Features);
824  return 0;
825  }
826 
827  ComputeIntFeatures(Features, IntFeatures);
828  *FloatFeatures = Features;
829 
830  return NumFeatures;
831 } /* GetAdaptiveFeatures */
832 
833 
834 /*-----------------------------------------------------------------------------
835  Private Code
836 -----------------------------------------------------------------------------*/
837 /*---------------------------------------------------------------------------*/
851 bool Classify::AdaptableWord(WERD_RES* word) {
852  if (word->best_choice == NULL) return false;
853  int BestChoiceLength = word->best_choice->length();
854  float adaptable_score =
855  getDict().segment_penalty_dict_case_ok + ADAPTABLE_WERD_ADJUSTMENT;
856  return // rules that apply in general - simplest to compute first
857  BestChoiceLength > 0 &&
858  BestChoiceLength == word->rebuild_word->NumBlobs() &&
859  BestChoiceLength <= MAX_ADAPTABLE_WERD_SIZE &&
860  // This basically ensures that the word is at least a dictionary match
861  // (freq word, user word, system dawg word, etc).
862  // Since all the other adjustments will make adjust factor higher
863  // than higher than adaptable_score=1.1+0.05=1.15
864  // Since these are other flags that ensure that the word is dict word,
865  // this check could be at times redundant.
866  word->best_choice->adjust_factor() <= adaptable_score &&
867  // Make sure that alternative choices are not dictionary words.
868  word->AlternativeChoiceAdjustmentsWorseThan(adaptable_score);
869 }
870 
871 /*---------------------------------------------------------------------------*/
887 void Classify::AdaptToChar(TBLOB* Blob, CLASS_ID ClassId, int FontinfoId,
888  FLOAT32 Threshold,
889  ADAPT_TEMPLATES adaptive_templates) {
890  int NumFeatures;
891  INT_FEATURE_ARRAY IntFeatures;
892  UnicharRating int_result;
893  INT_CLASS IClass;
894  ADAPT_CLASS Class;
895  TEMP_CONFIG TempConfig;
896  FEATURE_SET FloatFeatures;
897  int NewTempConfigId;
898 
899  if (!LegalClassId (ClassId))
900  return;
901 
902  int_result.unichar_id = ClassId;
903  Class = adaptive_templates->Class[ClassId];
904  assert(Class != NULL);
905  if (IsEmptyAdaptedClass(Class)) {
906  InitAdaptedClass(Blob, ClassId, FontinfoId, Class, adaptive_templates);
907  } else {
908  IClass = ClassForClassId(adaptive_templates->Templates, ClassId);
909 
910  NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
911  if (NumFeatures <= 0)
912  return;
913 
914  // Only match configs with the matching font.
915  BIT_VECTOR MatchingFontConfigs = NewBitVector(MAX_NUM_PROTOS);
916  for (int cfg = 0; cfg < IClass->NumConfigs; ++cfg) {
917  if (GetFontinfoId(Class, cfg) == FontinfoId) {
918  SET_BIT(MatchingFontConfigs, cfg);
919  } else {
920  reset_bit(MatchingFontConfigs, cfg);
921  }
922  }
923  im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
924  NumFeatures, IntFeatures,
925  &int_result, classify_adapt_feature_threshold,
926  NO_DEBUG, matcher_debug_separate_windows);
927  FreeBitVector(MatchingFontConfigs);
928 
929  SetAdaptiveThreshold(Threshold);
930 
931  if (1.0f - int_result.rating <= Threshold) {
932  if (ConfigIsPermanent(Class, int_result.config)) {
933  if (classify_learning_debug_level >= 1)
934  tprintf("Found good match to perm config %d = %4.1f%%.\n",
935  int_result.config, int_result.rating * 100.0);
936  FreeFeatureSet(FloatFeatures);
937  return;
938  }
939 
940  TempConfig = TempConfigFor(Class, int_result.config);
941  IncreaseConfidence(TempConfig);
942  if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) {
943  Class->MaxNumTimesSeen = TempConfig->NumTimesSeen;
944  }
945  if (classify_learning_debug_level >= 1)
946  tprintf("Increasing reliability of temp config %d to %d.\n",
947  int_result.config, TempConfig->NumTimesSeen);
948 
949  if (TempConfigReliable(ClassId, TempConfig)) {
950  MakePermanent(adaptive_templates, ClassId, int_result.config, Blob);
951  UpdateAmbigsGroup(ClassId, Blob);
952  }
953  } else {
954  if (classify_learning_debug_level >= 1) {
955  tprintf("Found poor match to temp config %d = %4.1f%%.\n",
956  int_result.config, int_result.rating * 100.0);
957  if (classify_learning_debug_level > 2)
958  DisplayAdaptedChar(Blob, IClass);
959  }
960  NewTempConfigId =
961  MakeNewTemporaryConfig(adaptive_templates, ClassId, FontinfoId,
962  NumFeatures, IntFeatures, FloatFeatures);
963  if (NewTempConfigId >= 0 &&
964  TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) {
965  MakePermanent(adaptive_templates, ClassId, NewTempConfigId, Blob);
966  UpdateAmbigsGroup(ClassId, Blob);
967  }
968 
969 #ifndef GRAPHICS_DISABLED
970  if (classify_learning_debug_level > 1) {
971  DisplayAdaptedChar(Blob, IClass);
972  }
973 #endif
974  }
975  FreeFeatureSet(FloatFeatures);
976  }
977 } /* AdaptToChar */
978 
979 void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
980 #ifndef GRAPHICS_DISABLED
981  INT_FX_RESULT_STRUCT fx_info;
984  BlobToTrainingSample(*blob, classify_nonlinear_norm, &fx_info,
985  &bl_features);
986  if (sample == NULL) return;
987 
988  UnicharRating int_result;
989  im_.Match(int_class, AllProtosOn, AllConfigsOn,
990  bl_features.size(), &bl_features[0],
991  &int_result, classify_adapt_feature_threshold,
992  NO_DEBUG, matcher_debug_separate_windows);
993  tprintf("Best match to temp config %d = %4.1f%%.\n",
994  int_result.config, int_result.rating * 100.0);
995  if (classify_learning_debug_level >= 2) {
996  uinT32 ConfigMask;
997  ConfigMask = 1 << int_result.config;
999  im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
1000  bl_features.size(), &bl_features[0],
1001  &int_result, classify_adapt_feature_threshold,
1002  6 | 0x19, matcher_debug_separate_windows);
1004  }
1005 #endif
1006 }
1007 
1008 
1009 
1010 /*---------------------------------------------------------------------------*/
1037 void Classify::AddNewResult(const UnicharRating& new_result,
1038  ADAPT_RESULTS *results) {
1039  int old_match = FindScoredUnichar(new_result.unichar_id, *results);
1040 
1041  if (new_result.rating + matcher_bad_match_pad < results->best_rating ||
1042  (old_match < results->match.size() &&
1043  new_result.rating <= results->match[old_match].rating))
1044  return; // New one not good enough.
1045 
1046  if (!unicharset.get_fragment(new_result.unichar_id))
1047  results->HasNonfragment = true;
1048 
1049  if (old_match < results->match.size()) {
1050  results->match[old_match].rating = new_result.rating;
1051  } else {
1052  results->match.push_back(new_result);
1053  }
1054 
1055  if (new_result.rating > results->best_rating &&
1056  // Ensure that fragments do not affect best rating, class and config.
1057  // This is needed so that at least one non-fragmented character is
1058  // always present in the results.
1059  // TODO(daria): verify that this helps accuracy and does not
1060  // hurt performance.
1061  !unicharset.get_fragment(new_result.unichar_id)) {
1062  results->best_match_index = old_match;
1063  results->best_rating = new_result.rating;
1064  results->best_unichar_id = new_result.unichar_id;
1065  }
1066 } /* AddNewResult */
1067 
1068 
1069 /*---------------------------------------------------------------------------*/
1089 void Classify::AmbigClassifier(
1090  const GenericVector<INT_FEATURE_STRUCT>& int_features,
1091  const INT_FX_RESULT_STRUCT& fx_info,
1092  const TBLOB *blob,
1093  INT_TEMPLATES templates,
1094  ADAPT_CLASS *classes,
1095  UNICHAR_ID *ambiguities,
1096  ADAPT_RESULTS *results) {
1097  if (int_features.empty()) return;
1098  uinT8* CharNormArray = new uinT8[unicharset.size()];
1099  UnicharRating int_result;
1100 
1101  results->BlobLength = GetCharNormFeature(fx_info, templates, NULL,
1102  CharNormArray);
1103  bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1104  if (debug)
1105  tprintf("AM Matches = ");
1106 
1107  int top = blob->bounding_box().top();
1108  int bottom = blob->bounding_box().bottom();
1109  while (*ambiguities >= 0) {
1110  CLASS_ID class_id = *ambiguities;
1111 
1112  int_result.unichar_id = class_id;
1113  im_.Match(ClassForClassId(templates, class_id),
1114  AllProtosOn, AllConfigsOn,
1115  int_features.size(), &int_features[0],
1116  &int_result,
1117  classify_adapt_feature_threshold, NO_DEBUG,
1118  matcher_debug_separate_windows);
1119 
1120  ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0,
1121  results->BlobLength,
1123  CharNormArray, &int_result, results);
1124  ambiguities++;
1125  }
1126  delete [] CharNormArray;
1127 } /* AmbigClassifier */
1128 
1129 /*---------------------------------------------------------------------------*/
1132 void Classify::MasterMatcher(INT_TEMPLATES templates,
1133  inT16 num_features,
1134  const INT_FEATURE_STRUCT* features,
1135  const uinT8* norm_factors,
1136  ADAPT_CLASS* classes,
1137  int debug,
1138  int matcher_multiplier,
1139  const TBOX& blob_box,
1140  const GenericVector<CP_RESULT_STRUCT>& results,
1141  ADAPT_RESULTS* final_results) {
1142  int top = blob_box.top();
1143  int bottom = blob_box.bottom();
1144  UnicharRating int_result;
1145  for (int c = 0; c < results.size(); c++) {
1146  CLASS_ID class_id = results[c].Class;
1147  BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
1148  : AllProtosOn;
1149  BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs
1150  : AllConfigsOn;
1151 
1152  int_result.unichar_id = class_id;
1153  im_.Match(ClassForClassId(templates, class_id),
1154  protos, configs,
1155  num_features, features,
1156  &int_result, classify_adapt_feature_threshold, debug,
1157  matcher_debug_separate_windows);
1158  bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1159  ExpandShapesAndApplyCorrections(classes, debug, class_id, bottom, top,
1160  results[c].Rating,
1161  final_results->BlobLength,
1162  matcher_multiplier, norm_factors,
1163  &int_result, final_results);
1164  }
1165 }
1166 
1167 // Converts configs to fonts, and if the result is not adapted, and a
1168 // shape_table_ is present, the shape is expanded to include all
1169 // unichar_ids represented, before applying a set of corrections to the
1170 // distance rating in int_result, (see ComputeCorrectedRating.)
1171 // The results are added to the final_results output.
1172 void Classify::ExpandShapesAndApplyCorrections(
1173  ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
1174  float cp_rating, int blob_length, int matcher_multiplier,
1175  const uinT8* cn_factors,
1176  UnicharRating* int_result, ADAPT_RESULTS* final_results) {
1177  if (classes != NULL) {
1178  // Adapted result. Convert configs to fontinfo_ids.
1179  int_result->adapted = true;
1180  for (int f = 0; f < int_result->fonts.size(); ++f) {
1181  int_result->fonts[f].fontinfo_id =
1182  GetFontinfoId(classes[class_id], int_result->fonts[f].fontinfo_id);
1183  }
1184  } else {
1185  // Pre-trained result. Map fonts using font_sets_.
1186  int_result->adapted = false;
1187  for (int f = 0; f < int_result->fonts.size(); ++f) {
1188  int_result->fonts[f].fontinfo_id =
1189  ClassAndConfigIDToFontOrShapeID(class_id,
1190  int_result->fonts[f].fontinfo_id);
1191  }
1192  if (shape_table_ != NULL) {
1193  // Two possible cases:
1194  // 1. Flat shapetable. All unichar-ids of the shapes referenced by
1195  // int_result->fonts are the same. In this case build a new vector of
1196  // mapped fonts and replace the fonts in int_result.
1197  // 2. Multi-unichar shapetable. Variable unichars in the shapes referenced
1198  // by int_result. In this case, build a vector of UnicharRating to
1199  // gather together different font-ids for each unichar. Also covers case1.
1200  GenericVector<UnicharRating> mapped_results;
1201  for (int f = 0; f < int_result->fonts.size(); ++f) {
1202  int shape_id = int_result->fonts[f].fontinfo_id;
1203  const Shape& shape = shape_table_->GetShape(shape_id);
1204  for (int c = 0; c < shape.size(); ++c) {
1205  int unichar_id = shape[c].unichar_id;
1206  if (!unicharset.get_enabled(unichar_id)) continue;
1207  // Find the mapped_result for unichar_id.
1208  int r = 0;
1209  for (r = 0; r < mapped_results.size() &&
1210  mapped_results[r].unichar_id != unichar_id; ++r) {}
1211  if (r == mapped_results.size()) {
1212  mapped_results.push_back(*int_result);
1213  mapped_results[r].unichar_id = unichar_id;
1214  mapped_results[r].fonts.truncate(0);
1215  }
1216  for (int i = 0; i < shape[c].font_ids.size(); ++i) {
1217  mapped_results[r].fonts.push_back(
1218  ScoredFont(shape[c].font_ids[i], int_result->fonts[f].score));
1219  }
1220  }
1221  }
1222  for (int m = 0; m < mapped_results.size(); ++m) {
1223  mapped_results[m].rating =
1224  ComputeCorrectedRating(debug, mapped_results[m].unichar_id,
1225  cp_rating, int_result->rating,
1226  int_result->feature_misses, bottom, top,
1227  blob_length, matcher_multiplier, cn_factors);
1228  AddNewResult(mapped_results[m], final_results);
1229  }
1230  return;
1231  }
1232  }
1233  if (unicharset.get_enabled(class_id)) {
1234  int_result->rating = ComputeCorrectedRating(debug, class_id, cp_rating,
1235  int_result->rating,
1236  int_result->feature_misses,
1237  bottom, top, blob_length,
1238  matcher_multiplier, cn_factors);
1239  AddNewResult(*int_result, final_results);
1240  }
1241 }
1242 
1243 // Applies a set of corrections to the confidence im_rating,
1244 // including the cn_correction, miss penalty and additional penalty
1245 // for non-alnums being vertical misfits. Returns the corrected confidence.
1246 double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
1247  double cp_rating, double im_rating,
1248  int feature_misses,
1249  int bottom, int top,
1250  int blob_length, int matcher_multiplier,
1251  const uinT8* cn_factors) {
1252  // Compute class feature corrections.
1253  double cn_corrected = im_.ApplyCNCorrection(1.0 - im_rating, blob_length,
1254  cn_factors[unichar_id],
1255  matcher_multiplier);
1256  double miss_penalty = tessedit_class_miss_scale * feature_misses;
1257  double vertical_penalty = 0.0;
1258  // Penalize non-alnums for being vertical misfits.
1259  if (!unicharset.get_isalpha(unichar_id) &&
1260  !unicharset.get_isdigit(unichar_id) &&
1261  cn_factors[unichar_id] != 0 && classify_misfit_junk_penalty > 0.0) {
1262  int min_bottom, max_bottom, min_top, max_top;
1263  unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom,
1264  &min_top, &max_top);
1265  if (debug) {
1266  tprintf("top=%d, vs [%d, %d], bottom=%d, vs [%d, %d]\n",
1267  top, min_top, max_top, bottom, min_bottom, max_bottom);
1268  }
1269  if (top < min_top || top > max_top ||
1270  bottom < min_bottom || bottom > max_bottom) {
1271  vertical_penalty = classify_misfit_junk_penalty;
1272  }
1273  }
1274  double result = 1.0 - (cn_corrected + miss_penalty + vertical_penalty);
1275  if (result < WORST_POSSIBLE_RATING)
1276  result = WORST_POSSIBLE_RATING;
1277  if (debug) {
1278  tprintf("%s: %2.1f%%(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
1279  unicharset.id_to_unichar(unichar_id),
1280  result * 100.0,
1281  cp_rating * 100.0,
1282  (1.0 - im_rating) * 100.0,
1283  (cn_corrected - (1.0 - im_rating)) * 100.0,
1284  cn_factors[unichar_id],
1285  miss_penalty * 100.0,
1286  vertical_penalty * 100.0);
1287  }
1288  return result;
1289 }
1290 
1291 /*---------------------------------------------------------------------------*/
1309 UNICHAR_ID *Classify::BaselineClassifier(
1310  TBLOB *Blob, const GenericVector<INT_FEATURE_STRUCT>& int_features,
1311  const INT_FX_RESULT_STRUCT& fx_info,
1312  ADAPT_TEMPLATES Templates, ADAPT_RESULTS *Results) {
1313  if (int_features.empty()) return NULL;
1314  uinT8* CharNormArray = new uinT8[unicharset.size()];
1315  ClearCharNormArray(CharNormArray);
1316 
1318  PruneClasses(Templates->Templates, int_features.size(), -1, &int_features[0],
1319  CharNormArray, BaselineCutoffs, &Results->CPResults);
1320 
1321  if (matcher_debug_level >= 2 || classify_debug_level > 1)
1322  tprintf("BL Matches = ");
1323 
1324  MasterMatcher(Templates->Templates, int_features.size(), &int_features[0],
1325  CharNormArray,
1326  Templates->Class, matcher_debug_flags, 0,
1327  Blob->bounding_box(), Results->CPResults, Results);
1328 
1329  delete [] CharNormArray;
1330  CLASS_ID ClassId = Results->best_unichar_id;
1331  if (ClassId == INVALID_UNICHAR_ID || Results->best_match_index < 0)
1332  return NULL;
1333 
1334  return Templates->Class[ClassId]->
1335  Config[Results->match[Results->best_match_index].config].Perm->Ambigs;
1336 } /* BaselineClassifier */
1337 
1338 
1339 /*---------------------------------------------------------------------------*/
1358 int Classify::CharNormClassifier(TBLOB *blob,
1359  const TrainingSample& sample,
1360  ADAPT_RESULTS *adapt_results) {
1361  // This is the length that is used for scaling ratings vs certainty.
1362  adapt_results->BlobLength =
1364  GenericVector<UnicharRating> unichar_results;
1365  static_classifier_->UnicharClassifySample(sample, blob->denorm().pix(), 0,
1366  -1, &unichar_results);
1367  // Convert results to the format used internally by AdaptiveClassifier.
1368  for (int r = 0; r < unichar_results.size(); ++r) {
1369  AddNewResult(unichar_results[r], adapt_results);
1370  }
1371  return sample.num_features();
1372 } /* CharNormClassifier */
1373 
1374 // As CharNormClassifier, but operates on a TrainingSample and outputs to
1375 // a GenericVector of ShapeRating without conversion to classes.
1376 int Classify::CharNormTrainingSample(bool pruner_only,
1377  int keep_this,
1378  const TrainingSample& sample,
1379  GenericVector<UnicharRating>* results) {
1380  results->clear();
1381  ADAPT_RESULTS* adapt_results = new ADAPT_RESULTS();
1382  adapt_results->Initialize();
1383  // Compute the bounding box of the features.
1384  int num_features = sample.num_features();
1385  // Only the top and bottom of the blob_box are used by MasterMatcher, so
1386  // fabricate right and left using top and bottom.
1387  TBOX blob_box(sample.geo_feature(GeoBottom), sample.geo_feature(GeoBottom),
1388  sample.geo_feature(GeoTop), sample.geo_feature(GeoTop));
1389  // Compute the char_norm_array from the saved cn_feature.
1390  FEATURE norm_feature = sample.GetCNFeature();
1391  uinT8* char_norm_array = new uinT8[unicharset.size()];
1392  int num_pruner_classes = MAX(unicharset.size(),
1393  PreTrainedTemplates->NumClasses);
1394  uinT8* pruner_norm_array = new uinT8[num_pruner_classes];
1395  adapt_results->BlobLength =
1396  static_cast<int>(ActualOutlineLength(norm_feature) * 20 + 0.5);
1397  ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
1398  pruner_norm_array);
1399 
1400  PruneClasses(PreTrainedTemplates, num_features, keep_this, sample.features(),
1401  pruner_norm_array,
1402  shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs,
1403  &adapt_results->CPResults);
1404  delete [] pruner_norm_array;
1405  if (keep_this >= 0) {
1406  adapt_results->CPResults[0].Class = keep_this;
1407  adapt_results->CPResults.truncate(1);
1408  }
1409  if (pruner_only) {
1410  // Convert pruner results to output format.
1411  for (int i = 0; i < adapt_results->CPResults.size(); ++i) {
1412  int class_id = adapt_results->CPResults[i].Class;
1413  results->push_back(
1414  UnicharRating(class_id, 1.0f - adapt_results->CPResults[i].Rating));
1415  }
1416  } else {
1417  MasterMatcher(PreTrainedTemplates, num_features, sample.features(),
1418  char_norm_array,
1419  NULL, matcher_debug_flags,
1421  blob_box, adapt_results->CPResults, adapt_results);
1422  // Convert master matcher results to output format.
1423  for (int i = 0; i < adapt_results->match.size(); i++) {
1424  results->push_back(adapt_results->match[i]);
1425  }
1426  results->sort(&UnicharRating::SortDescendingRating);
1427  }
1428  delete [] char_norm_array;
1429  delete adapt_results;
1430  return num_features;
1431 } /* CharNormTrainingSample */
1432 
1433 
1434 /*---------------------------------------------------------------------------*/
1449 void Classify::ClassifyAsNoise(ADAPT_RESULTS *results) {
1450  float rating = results->BlobLength / matcher_avg_noise_size;
1451  rating *= rating;
1452  rating /= 1.0 + rating;
1453 
1454  AddNewResult(UnicharRating(UNICHAR_SPACE, 1.0f - rating), results);
1455 } /* ClassifyAsNoise */
1456 
1463 void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
1464  ADAPT_RESULTS *Results,
1465  BLOB_CHOICE_LIST *Choices) {
1466  assert(Choices != NULL);
1467  FLOAT32 Rating;
1468  FLOAT32 Certainty;
1469  BLOB_CHOICE_IT temp_it;
1470  bool contains_nonfrag = false;
1471  temp_it.set_to_list(Choices);
1472  int choices_length = 0;
1473  // With no shape_table_ maintain the previous MAX_MATCHES as the maximum
1474  // number of returned results, but with a shape_table_ we want to have room
1475  // for at least the biggest shape (which might contain hundreds of Indic
1476  // grapheme fragments) and more, so use double the size of the biggest shape
1477  // if that is more than the default.
1478  int max_matches = MAX_MATCHES;
1479  if (shape_table_ != NULL) {
1480  max_matches = shape_table_->MaxNumUnichars() * 2;
1481  if (max_matches < MAX_MATCHES)
1482  max_matches = MAX_MATCHES;
1483  }
1484 
1485  float best_certainty = -MAX_FLOAT32;
1486  for (int i = 0; i < Results->match.size(); i++) {
1487  const UnicharRating& result = Results->match[i];
1488  bool adapted = result.adapted;
1489  bool current_is_frag = (unicharset.get_fragment(result.unichar_id) != NULL);
1490  if (temp_it.length()+1 == max_matches &&
1491  !contains_nonfrag && current_is_frag) {
1492  continue; // look for a non-fragmented character to fill the
1493  // last spot in Choices if only fragments are present
1494  }
1495  // BlobLength can never be legally 0, this means recognition failed.
1496  // But we must return a classification result because some invoking
1497  // functions (chopper/permuter) do not anticipate a null blob choice.
1498  // So we need to assign a poor, but not infinitely bad score.
1499  if (Results->BlobLength == 0) {
1500  Certainty = -20;
1501  Rating = 100; // should be -certainty * real_blob_length
1502  } else {
1503  Rating = Certainty = (1.0f - result.rating);
1504  Rating *= rating_scale * Results->BlobLength;
1505  Certainty *= -(getDict().certainty_scale);
1506  }
1507  // Adapted results, by their very nature, should have good certainty.
1508  // Those that don't are at best misleading, and often lead to errors,
1509  // so don't accept adapted results that are too far behind the best result,
1510  // whether adapted or static.
1511  // TODO(rays) find some way of automatically tuning these constants.
1512  if (Certainty > best_certainty) {
1513  best_certainty = MIN(Certainty, classify_adapted_pruning_threshold);
1514  } else if (adapted &&
1515  Certainty / classify_adapted_pruning_factor < best_certainty) {
1516  continue; // Don't accept bad adapted results.
1517  }
1518 
1519  float min_xheight, max_xheight, yshift;
1520  denorm.XHeightRange(result.unichar_id, unicharset, box,
1521  &min_xheight, &max_xheight, &yshift);
1522  BLOB_CHOICE* choice =
1523  new BLOB_CHOICE(result.unichar_id, Rating, Certainty,
1524  unicharset.get_script(result.unichar_id),
1525  min_xheight, max_xheight, yshift,
1526  adapted ? BCC_ADAPTED_CLASSIFIER
1528  choice->set_fonts(result.fonts);
1529  temp_it.add_to_end(choice);
1530  contains_nonfrag |= !current_is_frag; // update contains_nonfrag
1531  choices_length++;
1532  if (choices_length >= max_matches) break;
1533  }
1534  Results->match.truncate(choices_length);
1535 } // ConvertMatchesToChoices
1536 
1537 
1538 /*---------------------------------------------------------------------------*/
1539 #ifndef GRAPHICS_DISABLED
1540 
1550 void Classify::DebugAdaptiveClassifier(TBLOB *blob,
1551  ADAPT_RESULTS *Results) {
1552  if (static_classifier_ == NULL) return;
1553  INT_FX_RESULT_STRUCT fx_info;
1556  BlobToTrainingSample(*blob, false, &fx_info, &bl_features);
1557  if (sample == NULL) return;
1558  static_classifier_->DebugDisplay(*sample, blob->denorm().pix(),
1559  Results->best_unichar_id);
1560 } /* DebugAdaptiveClassifier */
1561 #endif
1562 
1563 /*---------------------------------------------------------------------------*/
1586 void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
1587  UNICHAR_ID *Ambiguities;
1588 
1589  INT_FX_RESULT_STRUCT fx_info;
1592  BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
1593  &bl_features);
1594  if (sample == NULL) return;
1595 
1596  if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min ||
1597  tess_cn_matching) {
1598  CharNormClassifier(Blob, *sample, Results);
1599  } else {
1600  Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
1601  AdaptedTemplates, Results);
1602  if ((!Results->match.empty() &&
1603  MarginalMatch(Results->best_rating,
1604  matcher_reliable_adaptive_result) &&
1605  !tess_bn_matching) ||
1606  Results->match.empty()) {
1607  CharNormClassifier(Blob, *sample, Results);
1608  } else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) {
1609  AmbigClassifier(bl_features, fx_info, Blob,
1610  PreTrainedTemplates,
1611  AdaptedTemplates->Class,
1612  Ambiguities,
1613  Results);
1614  }
1615  }
1616 
1617  // Force the blob to be classified as noise
1618  // if the results contain only fragments.
1619  // TODO(daria): verify that this is better than
1620  // just adding a NULL classification.
1621  if (!Results->HasNonfragment || Results->match.empty())
1622  ClassifyAsNoise(Results);
1623  delete sample;
1624 } /* DoAdaptiveMatch */
1625 
1626 /*---------------------------------------------------------------------------*/
1643 UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob,
1644  CLASS_ID CorrectClass) {
1645  ADAPT_RESULTS *Results = new ADAPT_RESULTS();
1646  UNICHAR_ID *Ambiguities;
1647  int i;
1648 
1649  Results->Initialize();
1650  INT_FX_RESULT_STRUCT fx_info;
1653  BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
1654  &bl_features);
1655  if (sample == NULL) {
1656  delete Results;
1657  return NULL;
1658  }
1659 
1660  CharNormClassifier(Blob, *sample, Results);
1661  delete sample;
1662  RemoveBadMatches(Results);
1663  Results->match.sort(&UnicharRating::SortDescendingRating);
1664 
1665  /* copy the class id's into an string of ambiguities - don't copy if
1666  the correct class is the only class id matched */
1667  Ambiguities = new UNICHAR_ID[Results->match.size() + 1];
1668  if (Results->match.size() > 1 ||
1669  (Results->match.size() == 1 &&
1670  Results->match[0].unichar_id != CorrectClass)) {
1671  for (i = 0; i < Results->match.size(); i++)
1672  Ambiguities[i] = Results->match[i].unichar_id;
1673  Ambiguities[i] = -1;
1674  } else {
1675  Ambiguities[0] = -1;
1676  }
1677 
1678  delete Results;
1679  return Ambiguities;
1680 } /* GetAmbiguities */
1681 
1682 // Returns true if the given blob looks too dissimilar to any character
1683 // present in the classifier templates.
1684 bool Classify::LooksLikeGarbage(TBLOB *blob) {
1685  BLOB_CHOICE_LIST *ratings = new BLOB_CHOICE_LIST();
1686  AdaptiveClassifier(blob, ratings);
1687  BLOB_CHOICE_IT ratings_it(ratings);
1688  const UNICHARSET &unicharset = getDict().getUnicharset();
1689  if (classify_debug_character_fragments) {
1690  print_ratings_list("======================\nLooksLikeGarbage() got ",
1691  ratings, unicharset);
1692  }
1693  for (ratings_it.mark_cycle_pt(); !ratings_it.cycled_list();
1694  ratings_it.forward()) {
1695  if (unicharset.get_fragment(ratings_it.data()->unichar_id()) != NULL) {
1696  continue;
1697  }
1698  float certainty = ratings_it.data()->certainty();
1699  delete ratings;
1700  return certainty <
1701  classify_character_fragments_garbage_certainty_threshold;
1702  }
1703  delete ratings;
1704  return true; // no whole characters in ratings
1705 }
1706 
1707 /*---------------------------------------------------------------------------*/
1733 int Classify::GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info,
1734  INT_TEMPLATES templates,
1735  uinT8* pruner_norm_array,
1736  uinT8* char_norm_array) {
1737  FEATURE norm_feature = NewFeature(&CharNormDesc);
1738  float baseline = kBlnBaselineOffset;
1739  float scale = MF_SCALE_FACTOR;
1740  norm_feature->Params[CharNormY] = (fx_info.Ymean - baseline) * scale;
1741  norm_feature->Params[CharNormLength] =
1742  fx_info.Length * scale / LENGTH_COMPRESSION;
1743  norm_feature->Params[CharNormRx] = fx_info.Rx * scale;
1744  norm_feature->Params[CharNormRy] = fx_info.Ry * scale;
1745  // Deletes norm_feature.
1746  ComputeCharNormArrays(norm_feature, templates, char_norm_array,
1747  pruner_norm_array);
1748  return IntCastRounded(fx_info.Length / kStandardFeatureLength);
1749 } /* GetCharNormFeature */
1750 
1751 // Computes the char_norm_array for the unicharset and, if not NULL, the
1752 // pruner_array as appropriate according to the existence of the shape_table.
1753 void Classify::ComputeCharNormArrays(FEATURE_STRUCT* norm_feature,
1754  INT_TEMPLATES_STRUCT* templates,
1755  uinT8* char_norm_array,
1756  uinT8* pruner_array) {
1757  ComputeIntCharNormArray(*norm_feature, char_norm_array);
1758  if (pruner_array != NULL) {
1759  if (shape_table_ == NULL) {
1760  ComputeIntCharNormArray(*norm_feature, pruner_array);
1761  } else {
1762  memset(pruner_array, MAX_UINT8,
1763  templates->NumClasses * sizeof(pruner_array[0]));
1764  // Each entry in the pruner norm array is the MIN of all the entries of
1765  // the corresponding unichars in the CharNormArray.
1766  for (int id = 0; id < templates->NumClasses; ++id) {
1767  int font_set_id = templates->Class[id]->font_set_id;
1768  const FontSet &fs = fontset_table_.get(font_set_id);
1769  for (int config = 0; config < fs.size; ++config) {
1770  const Shape& shape = shape_table_->GetShape(fs.configs[config]);
1771  for (int c = 0; c < shape.size(); ++c) {
1772  if (char_norm_array[shape[c].unichar_id] < pruner_array[id])
1773  pruner_array[id] = char_norm_array[shape[c].unichar_id];
1774  }
1775  }
1776  }
1777  }
1778  }
1779  FreeFeature(norm_feature);
1780 }
1781 
1782 /*---------------------------------------------------------------------------*/
1797 int Classify::MakeNewTemporaryConfig(ADAPT_TEMPLATES Templates,
1798  CLASS_ID ClassId,
1799  int FontinfoId,
1800  int NumFeatures,
1801  INT_FEATURE_ARRAY Features,
1802  FEATURE_SET FloatFeatures) {
1803  INT_CLASS IClass;
1804  ADAPT_CLASS Class;
1805  PROTO_ID OldProtos[MAX_NUM_PROTOS];
1806  FEATURE_ID BadFeatures[MAX_NUM_INT_FEATURES];
1807  int NumOldProtos;
1808  int NumBadFeatures;
1809  int MaxProtoId, OldMaxProtoId;
1810  int BlobLength = 0;
1811  int MaskSize;
1812  int ConfigId;
1814  int i;
1815  int debug_level = NO_DEBUG;
1816 
1817  if (classify_learning_debug_level >= 3)
1818  debug_level =
1820 
1821  IClass = ClassForClassId(Templates->Templates, ClassId);
1822  Class = Templates->Class[ClassId];
1823 
1824  if (IClass->NumConfigs >= MAX_NUM_CONFIGS) {
1825  ++NumAdaptationsFailed;
1826  if (classify_learning_debug_level >= 1)
1827  cprintf("Cannot make new temporary config: maximum number exceeded.\n");
1828  return -1;
1829  }
1830 
1831  OldMaxProtoId = IClass->NumProtos - 1;
1832 
1833  NumOldProtos = im_.FindGoodProtos(IClass, AllProtosOn, AllConfigsOff,
1834  BlobLength, NumFeatures, Features,
1835  OldProtos, classify_adapt_proto_threshold,
1836  debug_level);
1837 
1838  MaskSize = WordsInVectorOfSize(MAX_NUM_PROTOS);
1839  zero_all_bits(TempProtoMask, MaskSize);
1840  for (i = 0; i < NumOldProtos; i++)
1841  SET_BIT(TempProtoMask, OldProtos[i]);
1842 
1843  NumBadFeatures = im_.FindBadFeatures(IClass, TempProtoMask, AllConfigsOn,
1844  BlobLength, NumFeatures, Features,
1845  BadFeatures,
1846  classify_adapt_feature_threshold,
1847  debug_level);
1848 
1849  MaxProtoId = MakeNewTempProtos(FloatFeatures, NumBadFeatures, BadFeatures,
1850  IClass, Class, TempProtoMask);
1851  if (MaxProtoId == NO_PROTO) {
1852  ++NumAdaptationsFailed;
1853  if (classify_learning_debug_level >= 1)
1854  cprintf("Cannot make new temp protos: maximum number exceeded.\n");
1855  return -1;
1856  }
1857 
1858  ConfigId = AddIntConfig(IClass);
1859  ConvertConfig(TempProtoMask, ConfigId, IClass);
1860  Config = NewTempConfig(MaxProtoId, FontinfoId);
1861  TempConfigFor(Class, ConfigId) = Config;
1862  copy_all_bits(TempProtoMask, Config->Protos, Config->ProtoVectorSize);
1863 
1864  if (classify_learning_debug_level >= 1)
1865  cprintf("Making new temp config %d fontinfo id %d"
1866  " using %d old and %d new protos.\n",
1867  ConfigId, Config->FontinfoId,
1868  NumOldProtos, MaxProtoId - OldMaxProtoId);
1869 
1870  return ConfigId;
1871 } /* MakeNewTemporaryConfig */
1872 
1873 /*---------------------------------------------------------------------------*/
1894 PROTO_ID Classify::MakeNewTempProtos(FEATURE_SET Features,
1895  int NumBadFeat,
1896  FEATURE_ID BadFeat[],
1897  INT_CLASS IClass,
1898  ADAPT_CLASS Class,
1899  BIT_VECTOR TempProtoMask) {
1900  FEATURE_ID *ProtoStart;
1901  FEATURE_ID *ProtoEnd;
1902  FEATURE_ID *LastBad;
1903  TEMP_PROTO TempProto;
1904  PROTO Proto;
1905  FEATURE F1, F2;
1906  FLOAT32 X1, X2, Y1, Y2;
1907  FLOAT32 A1, A2, AngleDelta;
1908  FLOAT32 SegmentLength;
1909  PROTO_ID Pid;
1910 
1911  for (ProtoStart = BadFeat, LastBad = ProtoStart + NumBadFeat;
1912  ProtoStart < LastBad; ProtoStart = ProtoEnd) {
1913  F1 = Features->Features[*ProtoStart];
1914  X1 = F1->Params[PicoFeatX];
1915  Y1 = F1->Params[PicoFeatY];
1916  A1 = F1->Params[PicoFeatDir];
1917 
1918  for (ProtoEnd = ProtoStart + 1,
1919  SegmentLength = GetPicoFeatureLength();
1920  ProtoEnd < LastBad;
1921  ProtoEnd++, SegmentLength += GetPicoFeatureLength()) {
1922  F2 = Features->Features[*ProtoEnd];
1923  X2 = F2->Params[PicoFeatX];
1924  Y2 = F2->Params[PicoFeatY];
1925  A2 = F2->Params[PicoFeatDir];
1926 
1927  AngleDelta = fabs(A1 - A2);
1928  if (AngleDelta > 0.5)
1929  AngleDelta = 1.0 - AngleDelta;
1930 
1931  if (AngleDelta > matcher_clustering_max_angle_delta ||
1932  fabs(X1 - X2) > SegmentLength ||
1933  fabs(Y1 - Y2) > SegmentLength)
1934  break;
1935  }
1936 
1937  F2 = Features->Features[*(ProtoEnd - 1)];
1938  X2 = F2->Params[PicoFeatX];
1939  Y2 = F2->Params[PicoFeatY];
1940  A2 = F2->Params[PicoFeatDir];
1941 
1942  Pid = AddIntProto(IClass);
1943  if (Pid == NO_PROTO)
1944  return (NO_PROTO);
1945 
1946  TempProto = NewTempProto();
1947  Proto = &(TempProto->Proto);
1948 
1949  /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
1950  ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
1951  instead of the -0.25 to 0.75 used in baseline normalization */
1952  Proto->Length = SegmentLength;
1953  Proto->Angle = A1;
1954  Proto->X = (X1 + X2) / 2.0;
1955  Proto->Y = (Y1 + Y2) / 2.0 - Y_DIM_OFFSET;
1956  FillABC(Proto);
1957 
1958  TempProto->ProtoId = Pid;
1959  SET_BIT(TempProtoMask, Pid);
1960 
1961  ConvertProto(Proto, Pid, IClass);
1962  AddProtoToProtoPruner(Proto, Pid, IClass,
1963  classify_learning_debug_level >= 2);
1964 
1965  Class->TempProtos = push(Class->TempProtos, TempProto);
1966  }
1967  return IClass->NumProtos - 1;
1968 } /* MakeNewTempProtos */
1969 
1970 /*---------------------------------------------------------------------------*/
1983 void Classify::MakePermanent(ADAPT_TEMPLATES Templates,
1984  CLASS_ID ClassId,
1985  int ConfigId,
1986  TBLOB *Blob) {
1987  UNICHAR_ID *Ambigs;
1989  ADAPT_CLASS Class;
1990  PROTO_KEY ProtoKey;
1991 
1992  Class = Templates->Class[ClassId];
1993  Config = TempConfigFor(Class, ConfigId);
1994 
1995  MakeConfigPermanent(Class, ConfigId);
1996  if (Class->NumPermConfigs == 0)
1997  Templates->NumPermClasses++;
1998  Class->NumPermConfigs++;
1999 
2000  // Initialize permanent config.
2001  Ambigs = GetAmbiguities(Blob, ClassId);
2003  "PERM_CONFIG_STRUCT");
2004  Perm->Ambigs = Ambigs;
2005  Perm->FontinfoId = Config->FontinfoId;
2006 
2007  // Free memory associated with temporary config (since ADAPTED_CONFIG
2008  // is a union we need to clean up before we record permanent config).
2009  ProtoKey.Templates = Templates;
2010  ProtoKey.ClassId = ClassId;
2011  ProtoKey.ConfigId = ConfigId;
2012  Class->TempProtos = delete_d(Class->TempProtos, &ProtoKey, MakeTempProtoPerm);
2013  FreeTempConfig(Config);
2014 
2015  // Record permanent config.
2016  PermConfigFor(Class, ConfigId) = Perm;
2017 
2018  if (classify_learning_debug_level >= 1) {
2019  tprintf("Making config %d for %s (ClassId %d) permanent:"
2020  " fontinfo id %d, ambiguities '",
2021  ConfigId, getDict().getUnicharset().debug_str(ClassId).string(),
2022  ClassId, PermConfigFor(Class, ConfigId)->FontinfoId);
2023  for (UNICHAR_ID *AmbigsPointer = Ambigs;
2024  *AmbigsPointer >= 0; ++AmbigsPointer)
2025  tprintf("%s", unicharset.id_to_unichar(*AmbigsPointer));
2026  tprintf("'.\n");
2027  }
2028 } /* MakePermanent */
2029 } // namespace tesseract
2030 
2031 /*---------------------------------------------------------------------------*/
2046 int MakeTempProtoPerm(void *item1, void *item2) {
2047  ADAPT_CLASS Class;
2049  TEMP_PROTO TempProto;
2050  PROTO_KEY *ProtoKey;
2051 
2052  TempProto = (TEMP_PROTO) item1;
2053  ProtoKey = (PROTO_KEY *) item2;
2054 
2055  Class = ProtoKey->Templates->Class[ProtoKey->ClassId];
2056  Config = TempConfigFor(Class, ProtoKey->ConfigId);
2057 
2058  if (TempProto->ProtoId > Config->MaxProtoId ||
2059  !test_bit (Config->Protos, TempProto->ProtoId))
2060  return FALSE;
2061 
2062  MakeProtoPermanent(Class, TempProto->ProtoId);
2063  AddProtoToClassPruner(&(TempProto->Proto), ProtoKey->ClassId,
2064  ProtoKey->Templates->Templates);
2065  FreeTempProto(TempProto);
2066 
2067  return TRUE;
2068 } /* MakeTempProtoPerm */
2069 
2070 /*---------------------------------------------------------------------------*/
2071 namespace tesseract {
2083 void Classify::PrintAdaptiveMatchResults(const ADAPT_RESULTS& results) {
2084  for (int i = 0; i < results.match.size(); ++i) {
2085  tprintf("%s ", unicharset.debug_str(results.match[i].unichar_id).string());
2086  results.match[i].Print();
2087  }
2088 } /* PrintAdaptiveMatchResults */
2089 
2090 /*---------------------------------------------------------------------------*/
2106 void Classify::RemoveBadMatches(ADAPT_RESULTS *Results) {
2107  int Next, NextGood;
2108  FLOAT32 BadMatchThreshold;
2109  static const char* romans = "i v x I V X";
2110  BadMatchThreshold = Results->best_rating - matcher_bad_match_pad;
2111 
2112  if (classify_bln_numeric_mode) {
2113  UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ?
2114  unicharset.unichar_to_id("1") : -1;
2115  UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ?
2116  unicharset.unichar_to_id("0") : -1;
2117  float scored_one = ScoredUnichar(unichar_id_one, *Results);
2118  float scored_zero = ScoredUnichar(unichar_id_zero, *Results);
2119 
2120  for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2121  const UnicharRating& match = Results->match[Next];
2122  if (match.rating >= BadMatchThreshold) {
2123  if (!unicharset.get_isalpha(match.unichar_id) ||
2124  strstr(romans,
2125  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2126  } else if (unicharset.eq(match.unichar_id, "l") &&
2127  scored_one < BadMatchThreshold) {
2128  Results->match[Next].unichar_id = unichar_id_one;
2129  } else if (unicharset.eq(match.unichar_id, "O") &&
2130  scored_zero < BadMatchThreshold) {
2131  Results->match[Next].unichar_id = unichar_id_zero;
2132  } else {
2133  Results->match[Next].unichar_id = INVALID_UNICHAR_ID; // Don't copy.
2134  }
2135  if (Results->match[Next].unichar_id != INVALID_UNICHAR_ID) {
2136  if (NextGood == Next) {
2137  ++NextGood;
2138  } else {
2139  Results->match[NextGood++] = Results->match[Next];
2140  }
2141  }
2142  }
2143  }
2144  } else {
2145  for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2146  if (Results->match[Next].rating >= BadMatchThreshold) {
2147  if (NextGood == Next) {
2148  ++NextGood;
2149  } else {
2150  Results->match[NextGood++] = Results->match[Next];
2151  }
2152  }
2153  }
2154  }
2155  Results->match.truncate(NextGood);
2156 } /* RemoveBadMatches */
2157 
2158 /*----------------------------------------------------------------------------*/
2168 void Classify::RemoveExtraPuncs(ADAPT_RESULTS *Results) {
2169  int Next, NextGood;
2170  int punc_count; /*no of garbage characters */
2171  int digit_count;
2172  /*garbage characters */
2173  static char punc_chars[] = ". , ; : / ` ~ ' - = \\ | \" ! _ ^";
2174  static char digit_chars[] = "0 1 2 3 4 5 6 7 8 9";
2175 
2176  punc_count = 0;
2177  digit_count = 0;
2178  for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2179  const UnicharRating& match = Results->match[Next];
2180  bool keep = true;
2181  if (strstr(punc_chars,
2182  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2183  if (punc_count >= 2)
2184  keep = false;
2185  punc_count++;
2186  } else {
2187  if (strstr(digit_chars,
2188  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2189  if (digit_count >= 1)
2190  keep = false;
2191  digit_count++;
2192  }
2193  }
2194  if (keep) {
2195  if (NextGood == Next) {
2196  ++NextGood;
2197  } else {
2198  Results->match[NextGood++] = match;
2199  }
2200  }
2201  }
2202  Results->match.truncate(NextGood);
2203 } /* RemoveExtraPuncs */
2204 
2205 /*---------------------------------------------------------------------------*/
2220  Threshold = (Threshold == matcher_good_threshold) ? 0.9: (1.0 - Threshold);
2221  classify_adapt_proto_threshold.set_value(
2222  ClipToRange<int>(255 * Threshold, 0, 255));
2223  classify_adapt_feature_threshold.set_value(
2224  ClipToRange<int>(255 * Threshold, 0, 255));
2225 } /* SetAdaptiveThreshold */
2226 
2227 /*---------------------------------------------------------------------------*/
2240 void Classify::ShowBestMatchFor(int shape_id,
2241  const INT_FEATURE_STRUCT* features,
2242  int num_features) {
2243 #ifndef GRAPHICS_DISABLED
2244  uinT32 config_mask;
2245  if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) {
2246  tprintf("No built-in templates for class/shape %d\n", shape_id);
2247  return;
2248  }
2249  if (num_features <= 0) {
2250  tprintf("Illegal blob (char norm features)!\n");
2251  return;
2252  }
2253  UnicharRating cn_result;
2254  classify_norm_method.set_value(character);
2255  im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
2256  AllProtosOn, AllConfigsOn,
2257  num_features, features, &cn_result,
2258  classify_adapt_feature_threshold, NO_DEBUG,
2259  matcher_debug_separate_windows);
2260  tprintf("\n");
2261  config_mask = 1 << cn_result.config;
2262 
2263  tprintf("Static Shape ID: %d\n", shape_id);
2264  ShowMatchDisplay();
2265  im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
2266  AllProtosOn, reinterpret_cast<BIT_VECTOR>(&config_mask),
2267  num_features, features, &cn_result,
2268  classify_adapt_feature_threshold,
2269  matcher_debug_flags,
2270  matcher_debug_separate_windows);
2272 #endif // GRAPHICS_DISABLED
2273 } /* ShowBestMatchFor */
2274 
2275 // Returns a string for the classifier class_id: either the corresponding
2276 // unicharset debug_str or the shape_table_ debug str.
2277 STRING Classify::ClassIDToDebugStr(const INT_TEMPLATES_STRUCT* templates,
2278  int class_id, int config_id) const {
2279  STRING class_string;
2280  if (templates == PreTrainedTemplates && shape_table_ != NULL) {
2281  int shape_id = ClassAndConfigIDToFontOrShapeID(class_id, config_id);
2282  class_string = shape_table_->DebugStr(shape_id);
2283  } else {
2284  class_string = unicharset.debug_str(class_id);
2285  }
2286  return class_string;
2287 }
2288 
2289 // Converts a classifier class_id index to a shape_table_ index
2290 int Classify::ClassAndConfigIDToFontOrShapeID(int class_id,
2291  int int_result_config) const {
2292  int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id;
2293  // Older inttemps have no font_ids.
2294  if (font_set_id < 0)
2295  return kBlankFontinfoId;
2296  const FontSet &fs = fontset_table_.get(font_set_id);
2297  ASSERT_HOST(int_result_config >= 0 && int_result_config < fs.size);
2298  return fs.configs[int_result_config];
2299 }
2300 
2301 // Converts a shape_table_ index to a classifier class_id index (not a
2302 // unichar-id!). Uses a search, so not fast.
2303 int Classify::ShapeIDToClassID(int shape_id) const {
2304  for (int id = 0; id < PreTrainedTemplates->NumClasses; ++id) {
2305  int font_set_id = PreTrainedTemplates->Class[id]->font_set_id;
2306  ASSERT_HOST(font_set_id >= 0);
2307  const FontSet &fs = fontset_table_.get(font_set_id);
2308  for (int config = 0; config < fs.size; ++config) {
2309  if (fs.configs[config] == shape_id)
2310  return id;
2311  }
2312  }
2313  tprintf("Shape %d not found\n", shape_id);
2314  return -1;
2315 }
2316 
2317 // Returns true if the given TEMP_CONFIG is good enough to make it
2318 // a permanent config.
2319 bool Classify::TempConfigReliable(CLASS_ID class_id,
2320  const TEMP_CONFIG &config) {
2321  if (classify_learning_debug_level >= 1) {
2322  tprintf("NumTimesSeen for config of %s is %d\n",
2323  getDict().getUnicharset().debug_str(class_id).string(),
2324  config->NumTimesSeen);
2325  }
2326  if (config->NumTimesSeen >= matcher_sufficient_examples_for_prototyping) {
2327  return true;
2328  } else if (config->NumTimesSeen < matcher_min_examples_for_prototyping) {
2329  return false;
2330  } else if (use_ambigs_for_adaption) {
2331  // Go through the ambigs vector and see whether we have already seen
2332  // enough times all the characters represented by the ambigs vector.
2333  const UnicharIdVector *ambigs =
2334  getDict().getUnicharAmbigs().AmbigsForAdaption(class_id);
2335  int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
2336  for (int ambig = 0; ambig < ambigs_size; ++ambig) {
2337  ADAPT_CLASS ambig_class = AdaptedTemplates->Class[(*ambigs)[ambig]];
2338  assert(ambig_class != NULL);
2339  if (ambig_class->NumPermConfigs == 0 &&
2340  ambig_class->MaxNumTimesSeen <
2341  matcher_min_examples_for_prototyping) {
2342  if (classify_learning_debug_level >= 1) {
2343  tprintf("Ambig %s has not been seen enough times,"
2344  " not making config for %s permanent\n",
2345  getDict().getUnicharset().debug_str(
2346  (*ambigs)[ambig]).string(),
2347  getDict().getUnicharset().debug_str(class_id).string());
2348  }
2349  return false;
2350  }
2351  }
2352  }
2353  return true;
2354 }
2355 
2356 void Classify::UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob) {
2357  const UnicharIdVector *ambigs =
2358  getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id);
2359  int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
2360  if (classify_learning_debug_level >= 1) {
2361  tprintf("Running UpdateAmbigsGroup for %s class_id=%d\n",
2362  getDict().getUnicharset().debug_str(class_id).string(), class_id);
2363  }
2364  for (int ambig = 0; ambig < ambigs_size; ++ambig) {
2365  CLASS_ID ambig_class_id = (*ambigs)[ambig];
2366  const ADAPT_CLASS ambigs_class = AdaptedTemplates->Class[ambig_class_id];
2367  for (int cfg = 0; cfg < MAX_NUM_CONFIGS; ++cfg) {
2368  if (ConfigIsPermanent(ambigs_class, cfg)) continue;
2369  const TEMP_CONFIG config =
2370  TempConfigFor(AdaptedTemplates->Class[ambig_class_id], cfg);
2371  if (config != NULL && TempConfigReliable(ambig_class_id, config)) {
2372  if (classify_learning_debug_level >= 1) {
2373  tprintf("Making config %d of %s permanent\n", cfg,
2374  getDict().getUnicharset().debug_str(
2375  ambig_class_id).string());
2376  }
2377  MakePermanent(AdaptedTemplates, ambig_class_id, cfg, Blob);
2378  }
2379  }
2380  }
2381 }
2382 
2383 } // namespace tesseract
PERM_CONFIG_STRUCT * PERM_CONFIG
Definition: adaptive.h:55
GenericVector< CP_RESULT_STRUCT > CPResults
Definition: adaptmatch.cpp:89
#define MIN(x, y)
Definition: ndminx.h:28
int inT32
Definition: host.h:102
static void BreakPieces(const GenericVector< SEAM * > &seams, const GenericVector< TBLOB * > &blobs, int first, int last)
Definition: seam.cpp:194
GenericVector< STRING > correct_text
Definition: pageres.h:259
#define IsEmptyAdaptedClass(Class)
Definition: adaptive.h:90
#define SET_BIT(array, bit)
Definition: bitvec.h:57
bool MarginalMatch(float confidence, float matcher_great_threshold)
Definition: adaptmatch.cpp:122
#define set_all_bits(array, length)
Definition: bitvec.h:41
int IntCastRounded(double x)
Definition: helpers.h:172
int size() const
Definition: genericvector.h:72
int classify_integer_matcher_multiplier
int MakeTempProtoPerm(void *item1, void *item2)
#define PRINT_PROTO_MATCHES
Definition: intproto.h:194
void truncate(int size)
int push_back(T object)
TWERD * chopped_word
Definition: pageres.h:201
uinT8 ProtoVectorSize
Definition: adaptive.h:42
int AddIntConfig(INT_CLASS Class)
Definition: intproto.cpp:274
TBOX bounding_box() const
Definition: blobs.cpp:482
TEMP_PROTO_STRUCT * TEMP_PROTO
Definition: adaptive.h:37
UNICHAR_ID CLASS_ID
Definition: matchdefs.h:35
#define tprintf(...)
Definition: tprintf.h:31
int length() const
Definition: genericvector.h:79
BIT_VECTOR NewBitVector(int NumBits)
Definition: bitvec.cpp:90
BIT_VECTOR Protos
Definition: adaptive.h:45
static void JoinPieces(const GenericVector< SEAM * > &seams, const GenericVector< TBLOB * > &blobs, int first, int last)
Definition: seam.cpp:216
void free_int_templates(INT_TEMPLATES templates)
Definition: intproto.cpp:764
TEMP_PROTO NewTempProto()
Definition: adaptive.cpp:254
void FreeTempProto(void *arg)
Definition: adaptive.cpp:90
UNICHAR_ID best_unichar_id
Definition: adaptmatch.cpp:85
#define ADAPTABLE_WERD_ADJUSTMENT
Definition: adaptmatch.cpp:73
BIT_VECTOR PermConfigs
Definition: adaptive.h:69
const int kBlnBaselineOffset
Definition: normalis.h:29
ADAPT_CLASS Class[MAX_NUM_CLASSES]
Definition: adaptive.h:81
void FreeBitVector(BIT_VECTOR BitVector)
Definition: bitvec.cpp:55
int geo_feature(int index) const
int AddIntProto(INT_CLASS Class)
Definition: intproto.cpp:298
uinT8 NumPermConfigs
Definition: adaptive.h:65
#define LegalClassId(c)
Definition: intproto.h:179
void UpdateMatchDisplay()
Definition: intproto.cpp:466
#define MAX(x, y)
Definition: ndminx.h:24
FLOAT32 Y
Definition: protos.h:48
const double kStandardFeatureLength
Definition: intfx.h:46
const int kBlnXHeight
Definition: normalis.h:28
#define MAX_FLOAT32
Definition: host.h:124
void print_ratings_list(const char *msg, BLOB_CHOICE_LIST *ratings, const UNICHARSET &current_unicharset)
Definition: ratngs.cpp:819
LIST push(LIST list, void *element)
Definition: oldlist.cpp:323
CLUSTERCONFIG Config
void FillABC(PROTO Proto)
Definition: protos.cpp:198
void SetAdaptiveThreshold(FLOAT32 Threshold)
TBOX bounding_box() const
Definition: blobs.cpp:881
FEATURE Features[1]
Definition: ocrfeatures.h:72
#define NULL
Definition: host.h:144
inT16 left() const
Definition: rect.h:68
unsigned int uinT32
Definition: host.h:103
inT16 PROTO_ID
Definition: matchdefs.h:41
inT16 right() const
Definition: rect.h:75
TWERD * rebuild_word
Definition: pageres.h:244
NORM_PROTOS * ReadNormProtos(FILE *File)
const INT_FEATURE_STRUCT * features() const
void AddProtoToProtoPruner(PROTO Proto, int ProtoId, INT_CLASS Class, bool debug)
Definition: intproto.cpp:381
int NumBlobs() const
Definition: blobs.h:425
void FreeTempConfig(TEMP_CONFIG Config)
Definition: adaptive.cpp:80
void set_fonts(const GenericVector< tesseract::ScoredFont > &fonts)
Definition: ratngs.h:94
const FontInfo * fontinfo
Definition: pageres.h:288
#define FALSE
Definition: capi.h:29
INT_CLASS Class[MAX_NUM_CLASSES]
Definition: intproto.h:124
bool disable_character_fragments
#define MAX_ADAPTABLE_WERD_SIZE
Definition: adaptmatch.cpp:71
void InitIntegerFX()
Definition: intfx.cpp:55
GenericVector< ScoredFont > fonts
Definition: shapetable.h:88
void ComputeAdaptionThresholds(float certainty_scale, float min_rating, float max_rating, float rating_margin, float *thresholds)
Definition: pageres.cpp:553
bool PiecesAllNatural(int start, int count) const
Definition: pageres.cpp:1072
LIST delete_d(LIST list, void *key, int_compare is_equal)
Definition: oldlist.cpp:125
bool HasNonfragment
Definition: adaptmatch.cpp:84
#define MAX_NUM_CONFIGS
Definition: intproto.h:46
CLASS_ID ClassId
Definition: adaptmatch.cpp:115
#define UNLIKELY_NUM_FEAT
Definition: adaptmatch.cpp:69
void * alloc_struct(inT32 count, const char *)
Definition: memry.cpp:39
TBLOB * ClassifyNormalizeIfNeeded() const
Definition: blobs.cpp:363
FLOAT32 X
Definition: protos.h:47
int best_match_index
Definition: adaptmatch.cpp:86
PROTO_STRUCT Proto
Definition: adaptive.h:32
#define LENGTH_COMPRESSION
Definition: normfeat.h:26
Definition: rect.h:30
void ComputeBest()
Definition: adaptmatch.cpp:99
int size() const
Definition: shapetable.h:202
const FEATURE_DESC_STRUCT CharNormDesc
#define ClassForClassId(T, c)
Definition: intproto.h:181
uinT16 ProtoId
Definition: adaptive.h:30
#define PRINT_FEATURE_MATCHES
Definition: intproto.h:193
const char * string() const
Definition: strngs.cpp:193
int UNICHAR_ID
Definition: unichar.h:33
INT_FEATURE_STRUCT INT_FEATURE_ARRAY[MAX_NUM_INT_FEATURES]
Definition: intproto.h:155
#define ConfigIsPermanent(Class, ConfigId)
Definition: adaptive.h:93
FLOAT32 Length
Definition: protos.h:50
GenericVector< TBLOB * > blobs
Definition: blobs.h:436
#define NO_DEBUG
Definition: adaptmatch.cpp:70
const STRING debug_string() const
Definition: ratngs.h:502
FEATURE_STRUCT * GetCNFeature() const
#define MakeConfigPermanent(Class, ConfigId)
Definition: adaptive.h:96
#define MAX_NUM_INT_FEATURES
Definition: intproto.h:132
GenericVector< SEAM * > seam_array
Definition: pageres.h:203
void ShowMatchDisplay()
Definition: cluster.h:32
FLOAT32 Angle
Definition: protos.h:49
CharSegmentationType
Definition: classify.h:54
short inT16
Definition: host.h:100
#define IncreaseConfidence(TempConfig)
Definition: adaptive.h:108
#define ADAPT_TEMPLATE_SUFFIX
Definition: adaptmatch.cpp:66
#define MAX_INT32
Definition: host.h:120
#define MAX_MATCHES
Definition: adaptmatch.cpp:68
#define WordsInVectorOfSize(NumBits)
Definition: bitvec.h:63
void InitMatcherRatings(register FLOAT32 *Rating)
TEMP_CONFIG NewTempConfig(int MaxProtoId, int FontinfoId)
Definition: adaptive.cpp:223
Definition: strngs.h:44
const CHAR_FRAGMENT * get_fragment(UNICHAR_ID unichar_id) const
Definition: unicharset.h:682
#define GetPicoFeatureLength()
Definition: picofeat.h:59
bool empty() const
Definition: genericvector.h:84
uinT8 NumTimesSeen
Definition: adaptive.h:41
TrainingSample * BlobToTrainingSample(const TBLOB &blob, bool nonlinear_norm, INT_FX_RESULT_STRUCT *fx_info, GenericVector< INT_FEATURE_STRUCT > *bl_features)
Definition: intfx.cpp:81
void cprintf(const char *format,...)
Definition: callcpp.cpp:40
#define PermConfigFor(Class, ConfigId)
Definition: adaptive.h:105
#define Y_DIM_OFFSET
Definition: adaptmatch.cpp:75
Pix * pix() const
Definition: normalis.h:248
void free_adapted_templates(ADAPT_TEMPLATES templates)
Definition: adaptive.cpp:199
bool AlternativeChoiceAdjustmentsWorseThan(float threshold) const
Definition: pageres.cpp:430
uinT8 MaxNumTimesSeen
Definition: adaptive.h:66
#define MakeProtoPermanent(Class, ProtoId)
Definition: adaptive.h:99
uinT32 * BIT_VECTOR
Definition: bitvec.h:28
inT16 bottom() const
Definition: rect.h:61
uinT16 NumProtos
Definition: intproto.h:108
FLOAT32 best_rating
Definition: adaptmatch.cpp:87
#define TRUE
Definition: capi.h:28
#define copy_all_bits(source, dest, length)
Definition: bitvec.h:49
INT_TEMPLATES Templates
Definition: adaptive.h:77
void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS Class)
Definition: intproto.cpp:484
#define test_bit(array, bit)
Definition: bitvec.h:61
#define NO_PROTO
Definition: matchdefs.h:42
const DENORM & denorm() const
Definition: blobs.h:340
Definition: blobs.h:261
uinT8 FEATURE_ID
Definition: matchdefs.h:47
FLOAT32 ActualOutlineLength(FEATURE Feature)
Definition: normfeat.cpp:32
#define WORST_POSSIBLE_RATING
Definition: adaptmatch.cpp:77
STRING to_string() const
Definition: unicharset.h:73
float adjust_factor() const
Definition: ratngs.h:303
#define zero_all_bits(array, length)
Definition: bitvec.h:33
FLOAT32 Params[1]
Definition: ocrfeatures.h:65
void FreeFeature(FEATURE Feature)
Definition: ocrfeatures.cpp:59
#define MAX_UINT8
Definition: host.h:121
FEATURE NewFeature(const FEATURE_DESC_STRUCT *FeatureDesc)
unsigned char uinT8
Definition: host.h:99
char window_wait(ScrollView *win)
Definition: callcpp.cpp:111
ADAPT_TEMPLATES Templates
Definition: adaptmatch.cpp:114
uinT8 NumConfigs
Definition: intproto.h:110
void AddProtoToClassPruner(PROTO Proto, CLASS_ID ClassId, INT_TEMPLATES Templates)
Definition: intproto.cpp:337
#define UnusedClassIdIn(T, c)
Definition: intproto.h:180
UNICHAR_ID * Ambigs
Definition: adaptive.h:52
#define PRINT_MATCH_SUMMARY
Definition: intproto.h:190
void XHeightRange(int unichar_id, const UNICHARSET &unicharset, const TBOX &bbox, float *min_xht, float *max_xht, float *yshift) const
Definition: normalis.cpp:428
GenericVector< int > best_state
Definition: pageres.h:255
int length() const
Definition: ratngs.h:300
WERD_CHOICE * best_choice
Definition: pageres.h:219
inT32 BlobLength
Definition: adaptmatch.cpp:83
void plot(ScrollView *window)
Definition: blobs.cpp:918
void FreeFeatureSet(FEATURE_SET FeatureSet)
Definition: ocrfeatures.cpp:79
BIT_VECTOR PermProtos
Definition: adaptive.h:68
#define MAX_NUM_PROTOS
Definition: intproto.h:47
void Initialize()
Definition: adaptmatch.cpp:93
#define MAX_NUM_CLASSES
Definition: matchdefs.h:31
#define MF_SCALE_FACTOR
Definition: mfoutline.h:63
float FLOAT32
Definition: host.h:111
static void Update()
Definition: scrollview.cpp:715
GenericVector< UnicharRating > match
Definition: adaptmatch.cpp:88
#define TempConfigFor(Class, ConfigId)
Definition: adaptive.h:102
#define ASSERT_HOST(x)
Definition: errcode.h:84
void plot(ScrollView *window, ScrollView::Color color, ScrollView::Color child_color)
Definition: blobs.cpp:524
PROTO_ID MaxProtoId
Definition: adaptive.h:43
inT16 top() const
Definition: rect.h:54
#define reset_bit(array, bit)
Definition: bitvec.h:59