Tesseract  3.02
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
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 #include <ctype.h>
23 #include "ambigs.h"
24 #include "blobclass.h"
25 #include "blobs.h"
26 #include "helpers.h"
27 #include "normfeat.h"
28 #include "mfoutline.h"
29 #include "picofeat.h"
30 #include "float2int.h"
31 #include "outfeat.h"
32 #include "emalloc.h"
33 #include "intfx.h"
34 #include "speckle.h"
35 #include "efio.h"
36 #include "normmatch.h"
37 #include "permute.h"
38 #include "ndminx.h"
39 #include "intproto.h"
40 #include "const.h"
41 #include "globals.h"
42 #include "werd.h"
43 #include "callcpp.h"
44 #include "pageres.h"
45 #include "params.h"
46 #include "classify.h"
47 #include "shapetable.h"
48 #include "tessclassifier.h"
49 #include "trainingsample.h"
50 #include "unicharset.h"
51 #include "dict.h"
52 #include "featdefs.h"
53 #include "genericvector.h"
54 
55 #include <stdio.h>
56 #include <string.h>
57 #include <stdlib.h>
58 #include <math.h>
59 #ifdef __UNIX__
60 #include <assert.h>
61 #endif
62 
63 // Include automatically generated configuration file if running autoconf.
64 #ifdef HAVE_CONFIG_H
65 #include "config_auto.h"
66 #endif
67 
68 #define ADAPT_TEMPLATE_SUFFIX ".a"
69 
70 #define MAX_MATCHES 10
71 #define UNLIKELY_NUM_FEAT 200
72 #define NO_DEBUG 0
73 #define MAX_ADAPTABLE_WERD_SIZE 40
74 
75 #define ADAPTABLE_WERD_ADJUSTMENT (0.05)
76 
77 #define Y_DIM_OFFSET (Y_SHIFT - BASELINE_Y_SHIFT)
78 
79 #define WORST_POSSIBLE_RATING (1.0)
80 
81 struct ScoredClass {
83  int shape_id;
85  bool adapted;
89 };
90 
91 struct ADAPT_RESULTS {
98 
101  inline void Initialize() {
103  NumMatches = 0;
104  HasNonfragment = false;
106  best_match.shape_id = -1;
108  best_match.adapted = false;
109  best_match.config = 0;
110  best_match.fontinfo_id = kBlankFontinfoId;
111  best_match.fontinfo_id2 = kBlankFontinfoId;
112  }
113 };
114 
115 struct PROTO_KEY {
118  int ConfigId;
119 };
120 
121 /*-----------------------------------------------------------------------------
122  Private Macros
123 -----------------------------------------------------------------------------*/
124 #define MarginalMatch(Rating) \
125 ((Rating) > matcher_great_threshold)
126 
127 #define InitIntFX() (FeaturesHaveBeenExtracted = FALSE)
128 
129 /*-----------------------------------------------------------------------------
130  Private Function Prototypes
131 -----------------------------------------------------------------------------*/
132 int CompareByRating(const void *arg1, const void *arg2);
133 
135 
137 
138 void InitMatcherRatings(register FLOAT32 *Rating);
139 
140 int MakeTempProtoPerm(void *item1, void *item2);
141 
142 void SetAdaptiveThreshold(FLOAT32 Threshold);
143 
144 
145 /*-----------------------------------------------------------------------------
146  Public Code
147 -----------------------------------------------------------------------------*/
148 /*---------------------------------------------------------------------------*/
149 namespace tesseract {
179  const DENORM& denorm,
180  BLOB_CHOICE_LIST *Choices,
181  CLASS_PRUNER_RESULTS CPResults) {
182  assert(Choices != NULL);
183  ADAPT_RESULTS *Results = new ADAPT_RESULTS();
184  Results->Initialize();
185 
186  if (AdaptedTemplates == NULL)
188  DoAdaptiveMatch(Blob, denorm, Results);
189  if (CPResults != NULL)
190  memcpy(CPResults, Results->CPResults,
191  sizeof(CPResults[0]) * Results->NumMatches);
192 
193  RemoveBadMatches(Results);
194  qsort((void *)Results->match, Results->NumMatches,
195  sizeof(ScoredClass), CompareByRating);
196  RemoveExtraPuncs(Results);
197  ConvertMatchesToChoices(denorm, Blob->bounding_box(), Results, Choices);
198 
199  if (matcher_debug_level >= 1) {
200  cprintf ("AD Matches = ");
201  PrintAdaptiveMatchResults(stdout, Results);
202  }
203 
204  if (LargeSpeckle(Blob))
205  AddLargeSpeckleTo(Choices);
206 
207 #ifndef GRAPHICS_DISABLED
209  DebugAdaptiveClassifier(Blob, denorm, Results);
210 #endif
211 
212  NumClassesOutput += Choices->length();
213  if (Choices->length() == 0) {
215  tprintf ("Empty classification!\n"); // Should never normally happen.
216  Choices = new BLOB_CHOICE_LIST();
217  BLOB_CHOICE_IT temp_it;
218  temp_it.set_to_list(Choices);
219  temp_it.add_to_end(
220  new BLOB_CHOICE(0, 50.0f, -20.0f, -1, -1, NULL, 0, 0, false));
221  }
222 
223  delete Results;
224 } /* AdaptiveClassifier */
225 
226 // If *win is NULL, sets it to a new ScrollView() object with title msg.
227 // Clears the window and draws baselines.
228 void Classify::RefreshDebugWindow(ScrollView **win, const char *msg,
229  int y_offset, const TBOX &wbox) {
230  #ifndef GRAPHICS_DISABLED
231  const int kSampleSpaceWidth = 500;
232  if (*win == NULL) {
233  *win = new ScrollView(msg, 100, y_offset, kSampleSpaceWidth * 2, 200,
234  kSampleSpaceWidth * 2, 200, true);
235  }
236  (*win)->Clear();
237  (*win)->Pen(64, 64, 64);
238  (*win)->Line(-kSampleSpaceWidth, kBlnBaselineOffset,
239  kSampleSpaceWidth, kBlnBaselineOffset);
240  (*win)->Line(-kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset,
241  kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset);
242  (*win)->ZoomToRectangle(wbox.left(), wbox.top(),
243  wbox.right(), wbox.bottom());
244  #endif // GRAPHICS_DISABLED
245 }
246 
247 // Learns the given word using its chopped_word, seam_array, denorm,
248 // box_word, best_state, and correct_text to learn both correctly and
249 // incorrectly segmented blobs. If filename is not NULL, then LearnBlob
250 // is called and the data will be written to a file for static training.
251 // Otherwise AdaptToBlob is called for adaption within a document.
252 // If rejmap is not NULL, then only chars with a rejmap entry of '1' will
253 // be learned, otherwise all chars with good correct_text are learned.
254 void Classify::LearnWord(const char* filename, const char *rejmap,
255  WERD_RES *word) {
256  int word_len = word->correct_text.size();
257  if (word_len == 0) return;
258 
259  float* thresholds = NULL;
260  if (filename == NULL) {
261  // Adaption mode.
262  if (!EnableLearning || word->best_choice == NULL ||
263  // If word->best_choice is not recorded at the top of accumulator's
264  // best choices (which could happen for choices that are
265  // altered with ReplaceAmbig()) we skip the adaption.
266  !getDict().CurrentBestChoiceIs(*(word->best_choice)))
267  return; // Can't or won't adapt.
268 
269  NumWordsAdaptedTo++;
271  tprintf("\n\nAdapting to word = %s\n",
272  word->best_choice->debug_string().string());
273  thresholds = new float[word_len];
274  GetAdaptThresholds(word->rebuild_word, word->denorm, *word->best_choice,
275  *word->raw_choice, thresholds);
276  }
277  int start_blob = 0;
278  char prev_map_char = '0';
279 
280  #ifndef GRAPHICS_DISABLED
282  if (learn_fragmented_word_debug_win_ != NULL) {
283  window_wait(learn_fragmented_word_debug_win_);
284  }
285  RefreshDebugWindow(&learn_fragments_debug_win_, "LearnPieces", 400,
286  word->chopped_word->bounding_box());
287  RefreshDebugWindow(&learn_fragmented_word_debug_win_, "LearnWord", 200,
288  word->chopped_word->bounding_box());
289  word->chopped_word->plot(learn_fragmented_word_debug_win_);
291  }
292  #endif // GRAPHICS_DISABLED
293 
294  for (int ch = 0; ch < word_len; ++ch) {
296  tprintf("\nLearning %s\n", word->correct_text[ch].string());
297  }
298  char rej_map_char = rejmap != NULL ? *rejmap++ : '1';
299 
300  if (word->correct_text[ch].length() > 0 && rej_map_char == '1') {
301  float threshold = thresholds != NULL ? thresholds[ch] : 0.0f;
302 
303  LearnPieces(filename, start_blob, word->best_state[ch],
304  threshold, CST_WHOLE, word->correct_text[ch].string(), word);
305 
306  if (word->best_state[ch] > 1 && !disable_character_fragments) {
307  // Check that the character breaks into meaningful fragments
308  // that each match a whole character with at least
309  // classify_character_fragments_garbage_certainty_threshold
310  bool garbage = false;
311  TBLOB* frag_blob = word->chopped_word->blobs;
312  for (int i = 0; i < start_blob; ++i) frag_blob = frag_blob->next;
313  int frag;
314  for (frag = 0; frag < word->best_state[ch]; ++frag) {
316  garbage |= LooksLikeGarbage(word->denorm, frag_blob);
317  }
318  frag_blob = frag_blob->next;
319  }
320  // Learn the fragments.
321  if (!garbage) {
322  bool pieces_all_natural = word->PiecesAllNatural(start_blob,
323  word->best_state[ch]);
324  if (pieces_all_natural || !prioritize_division) {
325  for (frag = 0; frag < word->best_state[ch]; ++frag) {
326  GenericVector<STRING> tokens;
327  word->correct_text[ch].split(' ', &tokens);
328 
329  tokens[0] = CHAR_FRAGMENT::to_string(
330  tokens[0].string(), frag, word->best_state[ch],
331  pieces_all_natural);
332 
333  STRING full_string;
334  for (int i = 0; i < tokens.size(); i++) {
335  full_string += tokens[i];
336  if (i != tokens.size() - 1)
337  full_string += ' ';
338  }
339  LearnPieces(filename, start_blob + frag, 1,
340  threshold, CST_FRAGMENT, full_string.string(), word);
341  }
342  }
343  }
344  }
345 
346  // TODO(rays): re-enable this part of the code when we switch to the
347  // new classifier that needs to see examples of garbage.
348  /*
349  char next_map_char = ch + 1 < word_len
350  ? (rejmap != NULL ? *rejmap : '1')
351  : '0';
352  if (word->best_state[ch] > 1) {
353  // If the next blob is good, make junk with the rightmost fragment.
354  if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0 &&
355  next_map_char == '1') {
356  LearnPieces(filename, start_blob + word->best_state[ch] - 1,
357  word->best_state[ch + 1] + 1,
358  threshold, CST_IMPROPER, INVALID_UNICHAR, word);
359  }
360  // If the previous blob is good, make junk with the leftmost fragment.
361  if (ch > 0 && word->correct_text[ch - 1].length() > 0 &&
362  prev_map_char == '1') {
363  LearnPieces(filename, start_blob - word->best_state[ch - 1],
364  word->best_state[ch - 1] + 1,
365  threshold, CST_IMPROPER, INVALID_UNICHAR, word);
366  }
367  }
368  // If the next blob is good, make a join with it.
369  if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0 &&
370  next_map_char == '1') {
371  STRING joined_text = word->correct_text[ch];
372  joined_text += word->correct_text[ch + 1];
373  LearnPieces(filename, start_blob,
374  word->best_state[ch] + word->best_state[ch + 1],
375  threshold, CST_NGRAM, joined_text.string(), word);
376  }
377  */
378  }
379  start_blob += word->best_state[ch];
380  prev_map_char = rej_map_char;
381  }
382  delete [] thresholds;
383 } // LearnWord.
384 
385 // Builds a blob of length fragments, from the word, starting at start,
386 // and then learns it, as having the given correct_text.
387 // If filename is not NULL, then LearnBlob
388 // is called and the data will be written to a file for static training.
389 // Otherwise AdaptToBlob is called for adaption within a document.
390 // threshold is a magic number required by AdaptToChar and generated by
391 // GetAdaptThresholds.
392 // Although it can be partly inferred from the string, segmentation is
393 // provided to explicitly clarify the character segmentation.
394 void Classify::LearnPieces(const char* filename, int start, int length,
395  float threshold, CharSegmentationType segmentation,
396  const char* correct_text, WERD_RES *word) {
397  // TODO(daria) Remove/modify this if/when we want
398  // to train and/or adapt to n-grams.
399  if (segmentation != CST_WHOLE &&
400  (segmentation != CST_FRAGMENT || disable_character_fragments))
401  return;
402 
403  if (length > 1) {
405  start, start + length - 1);
406  }
407  TBLOB* blob = word->chopped_word->blobs;
408  for (int i = 0; i < start; ++i)
409  blob = blob->next;
410  // Rotate the blob if needed for classification.
411  const DENORM* denorm = &word->denorm;
412  TBLOB* rotated_blob = blob->ClassifyNormalizeIfNeeded(&denorm);
413  if (rotated_blob == NULL)
414  rotated_blob = blob;
415 
416  #ifndef GRAPHICS_DISABLED
417  // Draw debug windows showing the blob that is being learned if needed.
418  if (strcmp(classify_learn_debug_str.string(), correct_text) == 0) {
419  RefreshDebugWindow(&learn_debug_win_, "LearnPieces", 600,
420  word->chopped_word->bounding_box());
421  rotated_blob->plot(learn_debug_win_, ScrollView::GREEN, ScrollView::BROWN);
422  learn_debug_win_->Update();
423  window_wait(learn_debug_win_);
424  }
425  if (classify_debug_character_fragments && segmentation == CST_FRAGMENT) {
426  ASSERT_HOST(learn_fragments_debug_win_ != NULL); // set up in LearnWord
427  blob->plot(learn_fragments_debug_win_,
429  learn_fragments_debug_win_->Update();
430  }
431  #endif // GRAPHICS_DISABLED
432 
433  if (filename != NULL) {
434  classify_norm_method.set_value(character); // force char norm spc 30/11/93
435  tess_bn_matching.set_value(false); // turn it off
436  tess_cn_matching.set_value(false);
437  LearnBlob(feature_defs_, filename, rotated_blob, *denorm,
438  correct_text);
439  } else if (unicharset.contains_unichar(correct_text)) {
440  UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text);
441  int font_id = word->fontinfo != NULL
443  : 0;
445  tprintf("Adapting to char = %s, thr= %g font_id= %d\n",
446  unicharset.id_to_unichar(class_id), threshold, font_id);
447  // If filename is not NULL we are doing recognition
448  // (as opposed to training), so we must have already set word fonts.
449  AdaptToChar(rotated_blob, *denorm, class_id, font_id, threshold);
450  } else if (classify_debug_level >= 1) {
451  tprintf("Can't adapt to %s not in unicharset\n", correct_text);
452  }
453  if (rotated_blob != blob) {
454  delete rotated_blob;
455  delete denorm;
456  }
457 
458  break_pieces(blob, word->seam_array, start, start + length - 1);
459 } // LearnPieces.
460 
461 /*---------------------------------------------------------------------------*/
477  STRING Filename;
478  FILE *File;
479 
480  #ifndef SECURE_NAMES
481  if (AdaptedTemplates != NULL &&
483  Filename = imagefile + ADAPT_TEMPLATE_SUFFIX;
484  File = fopen (Filename.string(), "wb");
485  if (File == NULL)
486  cprintf ("Unable to save adapted templates to %s!\n", Filename.string());
487  else {
488  cprintf ("\nSaving adapted templates to %s ...", Filename.string());
489  fflush(stdout);
491  cprintf ("\n");
492  fclose(File);
493  }
494  }
495  #endif
496 
497  if (AdaptedTemplates != NULL) {
500  }
501 
502  if (PreTrainedTemplates != NULL) {
505  }
507  FreeNormProtos();
508  if (AllProtosOn != NULL) {
515  AllProtosOn = NULL;
516  PrunedProtos = NULL;
517  AllConfigsOn = NULL;
518  AllProtosOff = NULL;
521  }
522  delete shape_table_;
523  shape_table_ = NULL;
524 } /* EndAdaptiveClassifier */
525 
526 
527 /*---------------------------------------------------------------------------*/
545 void Classify::InitAdaptiveClassifier(bool load_pre_trained_templates) {
547  return;
548  if (AllProtosOn != NULL)
549  EndAdaptiveClassifier(); // Don't leak with multiple inits.
550 
551  // If there is no language_data_path_prefix, the classifier will be
552  // adaptive only.
553  if (language_data_path_prefix.length() > 0 &&
554  load_pre_trained_templates) {
558  if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded inttemp\n");
559 
564  tprintf("Error loading shape table!\n");
565  delete shape_table_;
566  shape_table_ = NULL;
567  } else if (tessdata_manager.DebugLevel() > 0) {
568  tprintf("Successfully loaded shape table!\n");
569  }
570  }
571 
576  CharNormCutoffs);
577  if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded pffmtable\n");
578 
580  NormProtos =
583  if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded normproto\n");
584  }
585 
587  InitIntegerFX();
588 
600 
601  for (int i = 0; i < MAX_NUM_CLASSES; i++) {
602  BaselineCutoffs[i] = 0;
603  }
604 
606  FILE *File;
607  STRING Filename;
608 
609  Filename = imagefile;
610  Filename += ADAPT_TEMPLATE_SUFFIX;
611  File = fopen(Filename.string(), "rb");
612  if (File == NULL) {
614  } else {
615  #ifndef SECURE_NAMES
616  cprintf("\nReading pre-adapted templates from %s ...\n",
617  Filename.string());
618  fflush(stdout);
619  #endif
621  cprintf("\n");
622  fclose(File);
624 
625  for (int i = 0; i < AdaptedTemplates->Templates->NumClasses; i++) {
626  BaselineCutoffs[i] = CharNormCutoffs[i];
627  }
628  }
629  } else {
630  if (AdaptedTemplates != NULL)
633  }
634 } /* InitAdaptiveClassifier */
635 
638  tprintf("Resetting adaptive classifier (NumAdaptationsFailed=%d)\n",
639  NumAdaptationsFailed);
640  }
643  NumAdaptationsFailed = 0;
644 }
645 
646 
647 /*---------------------------------------------------------------------------*/
660  #ifndef SECURE_NAMES
661 
662  fprintf (File, "\nADAPTIVE MATCHER STATISTICS:\n");
663  fprintf (File, "\tNum blobs classified = %d\n", AdaptiveMatcherCalls);
664  fprintf (File, "\tNum classes output = %d (Avg = %4.2f)\n",
665  NumClassesOutput,
666  ((AdaptiveMatcherCalls == 0) ? (0.0) :
667  ((float) NumClassesOutput / AdaptiveMatcherCalls)));
668  fprintf (File, "\t\tBaseline Classifier: %4d calls (%4.2f classes/call)\n",
669  BaselineClassifierCalls,
670  ((BaselineClassifierCalls == 0) ? (0.0) :
671  ((float) NumBaselineClassesTried / BaselineClassifierCalls)));
672  fprintf (File, "\t\tCharNorm Classifier: %4d calls (%4.2f classes/call)\n",
673  CharNormClassifierCalls,
674  ((CharNormClassifierCalls == 0) ? (0.0) :
675  ((float) NumCharNormClassesTried / CharNormClassifierCalls)));
676  fprintf (File, "\t\tAmbig Classifier: %4d calls (%4.2f classes/call)\n",
677  AmbigClassifierCalls,
678  ((AmbigClassifierCalls == 0) ? (0.0) :
679  ((float) NumAmbigClassesTried / AmbigClassifierCalls)));
680 
681  fprintf (File, "\nADAPTIVE LEARNER STATISTICS:\n");
682  fprintf (File, "\tNumber of words adapted to: %d\n", NumWordsAdaptedTo);
683  fprintf (File, "\tNumber of chars adapted to: %d\n", NumCharsAdaptedTo);
684 
686  #endif
687 } /* PrintAdaptiveStatistics */
688 
689 
690 /*---------------------------------------------------------------------------*/
712 
714 
715 } /* SettupPass1 */
716 
717 
718 /*---------------------------------------------------------------------------*/
733 
734 } /* SettupPass2 */
735 
736 
737 /*---------------------------------------------------------------------------*/
759  const DENORM& denorm,
760  CLASS_ID ClassId,
761  int FontinfoId,
762  ADAPT_CLASS Class,
763  ADAPT_TEMPLATES Templates) {
764  FEATURE_SET Features;
765  int Fid, Pid;
766  FEATURE Feature;
767  int NumFeatures;
768  TEMP_PROTO TempProto;
769  PROTO Proto;
770  INT_CLASS IClass;
772 
773  classify_norm_method.set_value(baseline);
774  Features = ExtractOutlineFeatures(Blob);
775  NumFeatures = Features->NumFeatures;
776  if (NumFeatures > UNLIKELY_NUM_FEAT || NumFeatures <= 0) {
777  FreeFeatureSet(Features);
778  return;
779  }
780 
781  Config = NewTempConfig(NumFeatures - 1, FontinfoId);
782  TempConfigFor(Class, 0) = Config;
783 
784  /* this is a kludge to construct cutoffs for adapted templates */
785  if (Templates == AdaptedTemplates)
786  BaselineCutoffs[ClassId] = CharNormCutoffs[ClassId];
787 
788  IClass = ClassForClassId (Templates->Templates, ClassId);
789 
790  for (Fid = 0; Fid < Features->NumFeatures; Fid++) {
791  Pid = AddIntProto (IClass);
792  assert (Pid != NO_PROTO);
793 
794  Feature = Features->Features[Fid];
795  TempProto = NewTempProto ();
796  Proto = &(TempProto->Proto);
797 
798  /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
799  ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
800  instead of the -0.25 to 0.75 used in baseline normalization */
801  Proto->Angle = Feature->Params[OutlineFeatDir];
802  Proto->X = Feature->Params[OutlineFeatX];
803  Proto->Y = Feature->Params[OutlineFeatY] - Y_DIM_OFFSET;
804  Proto->Length = Feature->Params[OutlineFeatLength];
805  FillABC(Proto);
806 
807  TempProto->ProtoId = Pid;
808  SET_BIT (Config->Protos, Pid);
809 
810  ConvertProto(Proto, Pid, IClass);
811  AddProtoToProtoPruner(Proto, Pid, IClass,
813 
814  Class->TempProtos = push (Class->TempProtos, TempProto);
815  }
816  FreeFeatureSet(Features);
817 
818  AddIntConfig(IClass);
819  ConvertConfig (AllProtosOn, 0, IClass);
820 
822  cprintf ("Added new class '%s' with class id %d and %d protos.\n",
823  unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
825  DisplayAdaptedChar(Blob, denorm, IClass);
826  }
827 
828  if (IsEmptyAdaptedClass(Class))
829  (Templates->NumNonEmptyClasses)++;
830 } /* InitAdaptedClass */
831 
832 
833 /*---------------------------------------------------------------------------*/
855  INT_FEATURE_ARRAY IntFeatures,
856  FEATURE_SET *FloatFeatures) {
857  FEATURE_SET Features;
858  int NumFeatures;
859 
860  classify_norm_method.set_value(baseline);
861  Features = ExtractPicoFeatures(Blob);
862 
863  NumFeatures = Features->NumFeatures;
864  if (NumFeatures > UNLIKELY_NUM_FEAT) {
865  FreeFeatureSet(Features);
866  return 0;
867  }
868 
869  ComputeIntFeatures(Features, IntFeatures);
870  *FloatFeatures = Features;
871 
872  return NumFeatures;
873 } /* GetAdaptiveFeatures */
874 
875 
876 /*-----------------------------------------------------------------------------
877  Private Code
878 -----------------------------------------------------------------------------*/
879 /*---------------------------------------------------------------------------*/
895  const WERD_CHOICE &BestChoiceWord,
896  const WERD_CHOICE &RawChoiceWord) {
897  int BestChoiceLength = BestChoiceWord.length();
898  float adaptable_score =
900  return // rules that apply in general - simplest to compute first
901  BestChoiceLength > 0 &&
902  BestChoiceLength == Word->NumBlobs() &&
903  BestChoiceLength <= MAX_ADAPTABLE_WERD_SIZE &&
904  // This basically ensures that the word is at least a dictionary match
905  // (freq word, user word, system dawg word, etc).
906  // Since all the other adjustments will make adjust factor higher
907  // than higher than adaptable_score=1.1+0.05=1.15
908  // Since these are other flags that ensure that the word is dict word,
909  // this check could be at times redundant.
910  getDict().CurrentBestChoiceAdjustFactor() <= adaptable_score &&
911  // Make sure that alternative choices are not dictionary words.
912  getDict().AlternativeChoicesWorseThan(adaptable_score) &&
913  getDict().CurrentBestChoiceIs(BestChoiceWord);
914 }
915 
916 /*---------------------------------------------------------------------------*/
934  const DENORM& denorm,
935  CLASS_ID ClassId,
936  int FontinfoId,
937  FLOAT32 Threshold) {
938  int NumFeatures;
939  INT_FEATURE_ARRAY IntFeatures;
940  INT_RESULT_STRUCT IntResult;
941  INT_CLASS IClass;
942  ADAPT_CLASS Class;
943  TEMP_CONFIG TempConfig;
944  FEATURE_SET FloatFeatures;
945  int NewTempConfigId;
946 
948  NumCharsAdaptedTo++;
949  if (!LegalClassId (ClassId))
950  return;
951 
952  Class = AdaptedTemplates->Class[ClassId];
953  assert(Class != NULL);
954  if (IsEmptyAdaptedClass(Class)) {
955  InitAdaptedClass(Blob, denorm, ClassId, FontinfoId, Class,
957  }
958  else {
959  IClass = ClassForClassId (AdaptedTemplates->Templates, ClassId);
960 
961  NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
962  if (NumFeatures <= 0)
963  return;
964 
966  // Only match configs with the matching font.
967  BIT_VECTOR MatchingFontConfigs = NewBitVector(MAX_NUM_PROTOS);
968  for (int cfg = 0; cfg < IClass->NumConfigs; ++cfg) {
969  if (GetFontinfoId(Class, cfg) == FontinfoId) {
970  SET_BIT(MatchingFontConfigs, cfg);
971  } else {
972  reset_bit(MatchingFontConfigs, cfg);
973  }
974  }
975  im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
976  NumFeatures, IntFeatures,
979  FreeBitVector(MatchingFontConfigs);
980 
981  SetAdaptiveThreshold(Threshold);
982 
983  if (IntResult.Rating <= Threshold) {
984  if (ConfigIsPermanent (Class, IntResult.Config)) {
986  cprintf ("Found good match to perm config %d = %4.1f%%.\n",
987  IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
988  FreeFeatureSet(FloatFeatures);
989  return;
990  }
991 
992  TempConfig = TempConfigFor (Class, IntResult.Config);
993  IncreaseConfidence(TempConfig);
994  if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) {
995  Class->MaxNumTimesSeen = TempConfig->NumTimesSeen;
996  }
998  cprintf ("Increasing reliability of temp config %d to %d.\n",
999  IntResult.Config, TempConfig->NumTimesSeen);
1000 
1001  if (TempConfigReliable(ClassId, TempConfig)) {
1002  MakePermanent(AdaptedTemplates, ClassId, IntResult.Config, denorm,
1003  Blob);
1004  UpdateAmbigsGroup(ClassId, denorm, Blob);
1005  }
1006  }
1007  else {
1008  if (classify_learning_debug_level >= 1) {
1009  cprintf ("Found poor match to temp config %d = %4.1f%%.\n",
1010  IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
1012  DisplayAdaptedChar(Blob, denorm, IClass);
1013  }
1014  NewTempConfigId = MakeNewTemporaryConfig(AdaptedTemplates,
1015  ClassId,
1016  FontinfoId,
1017  NumFeatures,
1018  IntFeatures,
1019  FloatFeatures);
1020  if (NewTempConfigId >= 0 &&
1021  TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) {
1022  MakePermanent(AdaptedTemplates, ClassId, NewTempConfigId, denorm, Blob);
1023  UpdateAmbigsGroup(ClassId, denorm, Blob);
1024  }
1025 
1026 #ifndef GRAPHICS_DISABLED
1028  DisplayAdaptedChar(Blob, denorm, IClass);
1029  }
1030 #endif
1031  }
1032  FreeFeatureSet(FloatFeatures);
1033  }
1034 } /* AdaptToChar */
1035 
1036 void Classify::DisplayAdaptedChar(TBLOB* blob, const DENORM& denorm,
1037  INT_CLASS_STRUCT* int_class) {
1038 #ifndef GRAPHICS_DISABLED
1039  int bloblength = 0;
1040  INT_FEATURE_ARRAY features;
1041  uinT8* norm_array = new uinT8[unicharset.size()];
1042  int num_features = GetBaselineFeatures(blob, denorm, PreTrainedTemplates,
1043  features,
1044  norm_array, &bloblength);
1045  delete [] norm_array;
1046  INT_RESULT_STRUCT IntResult;
1047 
1048  im_.Match(int_class, AllProtosOn, AllConfigsOn,
1049  num_features, features,
1052  cprintf ("Best match to temp config %d = %4.1f%%.\n",
1053  IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
1054  if (classify_learning_debug_level >= 2) {
1055  uinT32 ConfigMask;
1056  ConfigMask = 1 << IntResult.Config;
1057  ShowMatchDisplay();
1058  im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
1059  num_features, features,
1061  6 | 0x19, matcher_debug_separate_windows);
1063  }
1064 #endif
1065 }
1066 
1067 
1068 /*---------------------------------------------------------------------------*/
1083  const DENORM& denorm,
1084  CLASS_ID ClassId,
1085  int FontinfoId,
1086  FLOAT32 Threshold) {
1087  ADAPT_RESULTS *Results = new ADAPT_RESULTS();
1088  int i;
1089 
1090  Results->Initialize();
1091  CharNormClassifier(Blob, denorm, PreTrainedTemplates, Results);
1092  RemoveBadMatches(Results);
1093 
1094  if (Results->NumMatches != 1) {
1095  if (classify_learning_debug_level >= 1) {
1096  cprintf ("Rejecting punc = %s (Alternatives = ",
1097  unicharset.id_to_unichar(ClassId));
1098 
1099  for (i = 0; i < Results->NumMatches; i++)
1100  tprintf("%s", unicharset.id_to_unichar(Results->match[i].unichar_id));
1101  tprintf(")\n");
1102  }
1103  } else {
1104  #ifndef SECURE_NAMES
1106  cprintf ("Adapting to punc = %s, thr= %g\n",
1107  unicharset.id_to_unichar(ClassId), Threshold);
1108  #endif
1109  AdaptToChar(Blob, denorm, ClassId, FontinfoId, Threshold);
1110  }
1111  delete Results;
1112 } /* AdaptToPunc */
1113 
1114 
1115 /*---------------------------------------------------------------------------*/
1143  CLASS_ID class_id,
1144  int shape_id,
1145  FLOAT32 rating,
1146  bool adapted,
1147  int config,
1148  int fontinfo_id,
1149  int fontinfo_id2) {
1150  ScoredClass *old_match = FindScoredUnichar(results, class_id);
1151  ScoredClass match =
1152  { class_id,
1153  shape_id,
1154  rating,
1155  adapted,
1156  static_cast<inT16>(config),
1157  static_cast<inT16>(fontinfo_id),
1158  static_cast<inT16>(fontinfo_id2) };
1159 
1160  if (rating > results->best_match.rating + matcher_bad_match_pad ||
1161  (old_match && rating >= old_match->rating))
1162  return;
1163 
1164  if (!unicharset.get_fragment(class_id))
1165  results->HasNonfragment = true;
1166 
1167  if (old_match)
1168  old_match->rating = rating;
1169  else
1170  results->match[results->NumMatches++] = match;
1171 
1172  if (rating < results->best_match.rating &&
1173  // Ensure that fragments do not affect best rating, class and config.
1174  // This is needed so that at least one non-fragmented character is
1175  // always present in the results.
1176  // TODO(daria): verify that this helps accuracy and does not
1177  // hurt performance.
1178  !unicharset.get_fragment(class_id)) {
1179  results->best_match = match;
1180  }
1181 } /* AddNewResult */
1182 
1183 
1184 /*---------------------------------------------------------------------------*/
1206  const DENORM& denorm,
1207  INT_TEMPLATES Templates,
1208  ADAPT_CLASS *Classes,
1209  UNICHAR_ID *Ambiguities,
1210  ADAPT_RESULTS *Results) {
1211  int NumFeatures;
1212  INT_FEATURE_ARRAY IntFeatures;
1213  uinT8* CharNormArray = new uinT8[unicharset.size()];
1214  INT_RESULT_STRUCT IntResult;
1215  CLASS_ID ClassId;
1216 
1217  AmbigClassifierCalls++;
1218 
1219  NumFeatures = GetCharNormFeatures(Blob, denorm, Templates, IntFeatures,
1220  NULL, CharNormArray,
1221  &(Results->BlobLength), NULL);
1222  if (NumFeatures <= 0) {
1223  delete [] CharNormArray;
1224  return;
1225  }
1226 
1227  bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1228  if (debug)
1229  tprintf("AM Matches = ");
1230 
1231  int top = Blob->bounding_box().top();
1232  int bottom = Blob->bounding_box().bottom();
1233  while (*Ambiguities >= 0) {
1234  ClassId = *Ambiguities;
1235 
1237  im_.Match(ClassForClassId(Templates, ClassId),
1239  NumFeatures, IntFeatures,
1240  &IntResult,
1243 
1244  ExpandShapesAndApplyCorrections(NULL, debug, ClassId, bottom, top, 0,
1245  Results->BlobLength, CharNormArray,
1246  IntResult, Results);
1247  Ambiguities++;
1248 
1249  NumAmbigClassesTried++;
1250  }
1251  delete [] CharNormArray;
1252 } /* AmbigClassifier */
1253 
1254 /*---------------------------------------------------------------------------*/
1258  inT16 num_features,
1259  const INT_FEATURE_STRUCT* features,
1260  const uinT8* norm_factors,
1261  ADAPT_CLASS* classes,
1262  int debug,
1263  int num_classes,
1264  const TBOX& blob_box,
1265  CLASS_PRUNER_RESULTS results,
1266  ADAPT_RESULTS* final_results) {
1267  int top = blob_box.top();
1268  int bottom = blob_box.bottom();
1269  for (int c = 0; c < num_classes; c++) {
1270  CLASS_ID class_id = results[c].Class;
1271  INT_RESULT_STRUCT& int_result = results[c].IMResult;
1272  BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
1273  : AllProtosOn;
1274  BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs
1275  : AllConfigsOn;
1276 
1277  im_.Match(ClassForClassId(templates, class_id),
1278  protos, configs,
1279  num_features, features,
1280  &int_result, classify_adapt_feature_threshold, debug,
1282  bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1283  ExpandShapesAndApplyCorrections(classes, debug, class_id, bottom, top,
1284  results[c].Rating,
1285  final_results->BlobLength, norm_factors,
1286  int_result, final_results);
1287  }
1288 }
1289 
1290 // Converts configs to fonts, and if the result is not adapted, and a
1291 // shape_table_ is present, the shape is expanded to include all
1292 // unichar_ids represented, before applying a set of corrections to the
1293 // distance rating in int_result, (see ComputeCorrectedRating.)
1294 // The results are added to the final_results output.
1296  ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
1297  float cp_rating, int blob_length, const uinT8* cn_factors,
1298  INT_RESULT_STRUCT& int_result, ADAPT_RESULTS* final_results) {
1299  // Compute the fontinfo_ids.
1300  int fontinfo_id = kBlankFontinfoId;
1301  int fontinfo_id2 = kBlankFontinfoId;
1302  if (classes != NULL) {
1303  // Adapted result.
1304  fontinfo_id = GetFontinfoId(classes[class_id], int_result.Config);
1305  if (int_result.Config2 >= 0)
1306  fontinfo_id2 = GetFontinfoId(classes[class_id], int_result.Config2);
1307  } else {
1308  // Pre-trained result.
1309  fontinfo_id = ClassAndConfigIDToFontOrShapeID(class_id, int_result.Config);
1310  if (int_result.Config2 >= 0) {
1311  fontinfo_id2 = ClassAndConfigIDToFontOrShapeID(class_id,
1312  int_result.Config2);
1313  }
1314  if (shape_table_ != NULL) {
1315  // Actually fontinfo_id is an index into the shape_table_ and it
1316  // contains a list of unchar_id/font_id pairs.
1317  int shape_id = fontinfo_id;
1318  const Shape& shape = shape_table_->GetShape(fontinfo_id);
1319  double min_rating = 0.0;
1320  for (int c = 0; c < shape.size(); ++c) {
1321  int unichar_id = shape[c].unichar_id;
1322  fontinfo_id = shape[c].font_ids[0];
1323  if (shape[c].font_ids.size() > 1)
1324  fontinfo_id2 = shape[c].font_ids[1];
1325  else if (fontinfo_id2 != kBlankFontinfoId)
1326  fontinfo_id2 = shape_table_->GetShape(fontinfo_id2)[0].font_ids[0];
1327  double rating = ComputeCorrectedRating(debug, unichar_id, cp_rating,
1328  int_result.Rating,
1329  int_result.FeatureMisses,
1330  bottom, top, blob_length,
1331  cn_factors);
1332  if (c == 0 || rating < min_rating)
1333  min_rating = rating;
1334  if (unicharset.get_enabled(unichar_id)) {
1335  AddNewResult(final_results, unichar_id, shape_id, rating,
1336  classes != NULL, int_result.Config,
1337  fontinfo_id, fontinfo_id2);
1338  }
1339  }
1340  int_result.Rating = min_rating;
1341  return;
1342  }
1343  }
1344  double rating = ComputeCorrectedRating(debug, class_id, cp_rating,
1345  int_result.Rating,
1346  int_result.FeatureMisses,
1347  bottom, top, blob_length,
1348  cn_factors);
1349  if (unicharset.get_enabled(class_id)) {
1350  AddNewResult(final_results, class_id, -1, rating,
1351  classes != NULL, int_result.Config,
1352  fontinfo_id, fontinfo_id2);
1353  }
1354  int_result.Rating = rating;
1355 }
1356 
1357 // Applies a set of corrections to the distance im_rating,
1358 // including the cn_correction, miss penalty and additional penalty
1359 // for non-alnums being vertical misfits. Returns the corrected distance.
1360 double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
1361  double cp_rating, double im_rating,
1362  int feature_misses,
1363  int bottom, int top,
1364  int blob_length,
1365  const uinT8* cn_factors) {
1366  // Compute class feature corrections.
1367  double cn_corrected = im_.ApplyCNCorrection(im_rating, blob_length,
1368  cn_factors[unichar_id]);
1369  double miss_penalty = tessedit_class_miss_scale * feature_misses;
1370  double vertical_penalty = 0.0;
1371  // Penalize non-alnums for being vertical misfits.
1372  if (!unicharset.get_isalpha(unichar_id) &&
1373  !unicharset.get_isdigit(unichar_id) &&
1374  cn_factors[unichar_id] != 0 && classify_misfit_junk_penalty > 0.0) {
1375  int min_bottom, max_bottom, min_top, max_top;
1376  unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom,
1377  &min_top, &max_top);
1378  if (debug) {
1379  tprintf("top=%d, vs [%d, %d], bottom=%d, vs [%d, %d]\n",
1380  top, min_top, max_top, bottom, min_bottom, max_bottom);
1381  }
1382  if (top < min_top || top > max_top ||
1383  bottom < min_bottom || bottom > max_bottom) {
1384  vertical_penalty = classify_misfit_junk_penalty;
1385  }
1386  }
1387  double result =cn_corrected + miss_penalty + vertical_penalty;
1388  if (result > WORST_POSSIBLE_RATING)
1389  result = WORST_POSSIBLE_RATING;
1390  if (debug) {
1391  tprintf("%s: %2.1f(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
1392  unicharset.id_to_unichar(unichar_id),
1393  result * 100.0,
1394  cp_rating * 100.0,
1395  im_rating * 100.0,
1396  (cn_corrected - im_rating) * 100.0,
1397  cn_factors[unichar_id],
1398  miss_penalty * 100.0,
1399  vertical_penalty * 100.0);
1400  }
1401  return result;
1402 }
1403 
1404 /*---------------------------------------------------------------------------*/
1424  const DENORM& denorm,
1425  ADAPT_TEMPLATES Templates,
1426  ADAPT_RESULTS *Results) {
1427  int NumFeatures;
1428  int NumClasses;
1429  INT_FEATURE_ARRAY IntFeatures;
1430  uinT8* CharNormArray = new uinT8[unicharset.size()];
1431  CLASS_ID ClassId;
1432 
1433  BaselineClassifierCalls++;
1434 
1435  NumFeatures = GetBaselineFeatures(
1436  Blob, denorm, Templates->Templates, IntFeatures, CharNormArray,
1437  &(Results->BlobLength));
1438  if (NumFeatures <= 0) {
1439  delete [] CharNormArray;
1440  return NULL;
1441  }
1442 
1443  NumClasses = PruneClasses(Templates->Templates, NumFeatures, IntFeatures,
1444  CharNormArray, BaselineCutoffs, Results->CPResults);
1445 
1446  NumBaselineClassesTried += NumClasses;
1447 
1448  if (matcher_debug_level >= 2 || classify_debug_level > 1)
1449  cprintf ("BL Matches = ");
1450 
1452  MasterMatcher(Templates->Templates, NumFeatures, IntFeatures, CharNormArray,
1453  Templates->Class, matcher_debug_flags, NumClasses,
1454  Blob->bounding_box(), Results->CPResults, Results);
1455 
1456  delete [] CharNormArray;
1457  ClassId = Results->best_match.unichar_id;
1458  if (ClassId == NO_CLASS)
1459  return (NULL);
1460  /* this is a bug - maybe should return "" */
1461 
1462  return Templates->Class[ClassId]->
1463  Config[Results->best_match.config].Perm->Ambigs;
1464 } /* BaselineClassifier */
1465 
1466 
1467 /*---------------------------------------------------------------------------*/
1488  const DENORM& denorm,
1489  INT_TEMPLATES Templates,
1490  ADAPT_RESULTS *Results) {
1491  int NumFeatures;
1492  int NumClasses;
1493  INT_FEATURE_ARRAY IntFeatures;
1494 
1495  CharNormClassifierCalls++;
1496 
1497  uinT8* CharNormArray = new uinT8[unicharset.size()];
1498  int num_pruner_classes = MAX(unicharset.size(),
1500  uinT8* PrunerNormArray = new uinT8[num_pruner_classes];
1501  NumFeatures = GetCharNormFeatures(Blob, denorm, Templates, IntFeatures,
1502  PrunerNormArray, CharNormArray,
1503  &(Results->BlobLength), NULL);
1504  if (NumFeatures <= 0) {
1505  delete [] CharNormArray;
1506  delete [] PrunerNormArray;
1507  return 0;
1508  }
1509 
1510  NumClasses = PruneClasses(Templates, NumFeatures, IntFeatures,
1511  PrunerNormArray,
1512  shape_table_ != NULL ? &shapetable_cutoffs_[0]
1513  : CharNormCutoffs,
1514  Results->CPResults);
1515 
1516  if (tessedit_single_match && NumClasses > 1)
1517  NumClasses = 1;
1518  NumCharNormClassesTried += NumClasses;
1519 
1521  MasterMatcher(Templates, NumFeatures, IntFeatures, CharNormArray,
1522  NULL, matcher_debug_flags, NumClasses,
1523  Blob->bounding_box(), Results->CPResults, Results);
1524  delete [] CharNormArray;
1525  delete [] PrunerNormArray;
1526  return NumFeatures;
1527 } /* CharNormClassifier */
1528 
1529 // As CharNormClassifier, but operates on a TrainingSample and outputs to
1530 // a GenericVector of ShapeRating without conversion to classes.
1532  const TrainingSample& sample,
1533  GenericVector<ShapeRating>* results) {
1534  results->clear();
1535  ADAPT_RESULTS* adapt_results = new ADAPT_RESULTS();
1536  adapt_results->Initialize();
1537  // Compute the bounding box of the features.
1538  int num_features = sample.num_features();
1539  TBOX blob_box;
1540  for (int f = 0; f < num_features; ++f) {
1541  const INT_FEATURE_STRUCT feature = sample.features()[f];
1542  TBOX fbox(feature.X, feature.Y, feature.X, feature.Y);
1543  blob_box += fbox;
1544  }
1545  // Compute the char_norm_array from the saved cn_feature.
1546  FEATURE norm_feature = NewFeature(&CharNormDesc);
1547  norm_feature->Params[CharNormY] = sample.cn_feature(CharNormY);
1548  norm_feature->Params[CharNormLength] = sample.cn_feature(CharNormLength);
1549  norm_feature->Params[CharNormRx] = sample.cn_feature(CharNormRx);
1550  norm_feature->Params[CharNormRy] = sample.cn_feature(CharNormRy);
1551  uinT8* char_norm_array = new uinT8[unicharset.size()];
1552  int num_pruner_classes = MAX(unicharset.size(),
1554  uinT8* pruner_norm_array = new uinT8[num_pruner_classes];
1555  adapt_results->BlobLength =
1556  static_cast<int>(ActualOutlineLength(norm_feature) * 20 + 0.5);
1557  ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
1558  pruner_norm_array);
1559 
1560  int num_classes = PruneClasses(PreTrainedTemplates, num_features,
1561  sample.features(),
1562  pruner_norm_array,
1563  shape_table_ != NULL ? &shapetable_cutoffs_[0]
1564  : CharNormCutoffs,
1565  adapt_results->CPResults);
1566  delete [] pruner_norm_array;
1567  if (pruner_only) {
1568  // Convert pruner results to output format.
1569  for (int i = 0; i < num_classes; ++i) {
1570  int class_id = adapt_results->CPResults[i].Class;
1571  int shape_id = class_id;
1572  if (shape_table_ != NULL) {
1573  // All shapes in a class have the same combination of unichars, so
1574  // it doesn't really matter which config we give it, as we aren't
1575  // trying to get the font here.
1576  shape_id = ClassAndConfigIDToFontOrShapeID(class_id, 0);
1577  }
1578  results->push_back(
1579  ShapeRating(shape_id, 1.0f - adapt_results->CPResults[i].Rating));
1580  }
1581  } else {
1583  MasterMatcher(PreTrainedTemplates, num_features, sample.features(),
1584  char_norm_array,
1585  NULL, matcher_debug_flags, num_classes,
1586  blob_box, adapt_results->CPResults, adapt_results);
1587  // Convert master matcher results to output format.
1588  for (int i = 0; i < adapt_results->NumMatches; i++) {
1589  ScoredClass next = adapt_results->match[i];
1590  results->push_back(ShapeRating(next.shape_id, 1.0f - next.rating));
1591  }
1593  }
1594  delete [] char_norm_array;
1595  delete adapt_results;
1596  return num_features;
1597 } /* CharNormTrainingSample */
1598 
1599 
1600 /*---------------------------------------------------------------------------*/
1616  register FLOAT32 Rating;
1617 
1618  Rating = Results->BlobLength / matcher_avg_noise_size;
1619  Rating *= Rating;
1620  Rating /= 1.0 + Rating;
1621 
1622  AddNewResult(Results, NO_CLASS, -1, Rating, false, -1,
1623  kBlankFontinfoId, kBlankFontinfoId);
1624 } /* ClassifyAsNoise */
1625 } // namespace tesseract
1626 
1627 
1628 /*---------------------------------------------------------------------------*/
1629 // Return a pointer to the scored unichar in results, or NULL if not present.
1631  for (int i = 0; i < results->NumMatches; i++) {
1632  if (results->match[i].unichar_id == id)
1633  return &results->match[i];
1634  }
1635  return NULL;
1636 }
1637 
1638 // Retrieve the current rating for a unichar id if we have rated it, defaulting
1639 // to WORST_POSSIBLE_RATING.
1641  ScoredClass poor_result =
1642  {id, -1, WORST_POSSIBLE_RATING, false, -1,
1643  kBlankFontinfoId, kBlankFontinfoId};
1644  ScoredClass *entry = FindScoredUnichar(results, id);
1645  return (entry == NULL) ? poor_result : *entry;
1646 }
1647 
1648 // Compare character classes by rating as for qsort(3).
1649 // For repeatability, use character class id as a tie-breaker.
1650 int CompareByRating(const void *arg1, // ScoredClass *class1
1651  const void *arg2) { // ScoredClass *class2
1652  const ScoredClass *class1 = (const ScoredClass *)arg1;
1653  const ScoredClass *class2 = (const ScoredClass *)arg2;
1654 
1655  if (class1->rating < class2->rating)
1656  return -1;
1657  else if (class1->rating > class2->rating)
1658  return 1;
1659 
1660  if (class1->unichar_id < class2->unichar_id)
1661  return -1;
1662  else if (class1->unichar_id > class2->unichar_id)
1663  return 1;
1664  return 0;
1665 }
1666 
1667 /*---------------------------------------------------------------------------*/
1668 namespace tesseract {
1675 void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
1676  ADAPT_RESULTS *Results,
1677  BLOB_CHOICE_LIST *Choices) {
1678  assert(Choices != NULL);
1679  FLOAT32 Rating;
1680  FLOAT32 Certainty;
1681  BLOB_CHOICE_IT temp_it;
1682  bool contains_nonfrag = false;
1683  temp_it.set_to_list(Choices);
1684  int choices_length = 0;
1685  // With no shape_table_ maintain the previous MAX_MATCHES as the maximum
1686  // number of returned results, but with a shape_table_ we want to have room
1687  // for at least the biggest shape (which might contain hundreds of Indic
1688  // grapheme fragments) and more, so use double the size of the biggest shape
1689  // if that is more than the default.
1690  int max_matches = MAX_MATCHES;
1691  if (shape_table_ != NULL) {
1692  max_matches = shape_table_->MaxNumUnichars() * 2;
1693  if (max_matches < MAX_MATCHES)
1694  max_matches = MAX_MATCHES;
1695  }
1696 
1697  for (int i = 0; i < Results->NumMatches; i++) {
1698  ScoredClass next = Results->match[i];
1699  int fontinfo_id = next.fontinfo_id;
1700  int fontinfo_id2 = next.fontinfo_id2;
1701  bool adapted = next.adapted;
1702  bool current_is_frag = (unicharset.get_fragment(next.unichar_id) != NULL);
1703  if (temp_it.length()+1 == max_matches &&
1704  !contains_nonfrag && current_is_frag) {
1705  continue; // look for a non-fragmented character to fill the
1706  // last spot in Choices if only fragments are present
1707  }
1708  // BlobLength can never be legally 0, this means recognition failed.
1709  // But we must return a classification result because some invoking
1710  // functions (chopper/permuter) do not anticipate a null blob choice.
1711  // So we need to assign a poor, but not infinitely bad score.
1712  if (Results->BlobLength == 0) {
1713  Certainty = -20;
1714  Rating = 100; // should be -certainty * real_blob_length
1715  } else {
1716  Rating = Certainty = next.rating;
1717  Rating *= rating_scale * Results->BlobLength;
1718  Certainty *= -(getDict().certainty_scale);
1719  }
1720  inT16 min_xheight, max_xheight;
1721  denorm.XHeightRange(next.unichar_id, unicharset, box,
1722  &min_xheight, &max_xheight);
1723  temp_it.add_to_end(new BLOB_CHOICE(next.unichar_id, Rating, Certainty,
1724  fontinfo_id, fontinfo_id2,
1725  unicharset.get_script(next.unichar_id),
1726  min_xheight, max_xheight, adapted));
1727  contains_nonfrag |= !current_is_frag; // update contains_nonfrag
1728  choices_length++;
1729  if (choices_length >= max_matches) break;
1730  }
1731  Results->NumMatches = choices_length;
1732 } // ConvertMatchesToChoices
1733 
1734 
1735 /*---------------------------------------------------------------------------*/
1736 #ifndef GRAPHICS_DISABLED
1737 
1749  const DENORM& denorm,
1750  ADAPT_RESULTS *Results) {
1751  for (int i = 0; i < Results->NumMatches; i++) {
1752  if (Results->match[i].rating < Results->best_match.rating)
1753  Results->best_match = Results->match[i];
1754  }
1755  const char *Prompt =
1756  "Left-click in IntegerMatch Window to continue or right click to debug...";
1757  CLASS_ID unichar_id = Results->best_match.unichar_id;
1758  int shape_id = Results->best_match.shape_id;
1759  bool adaptive_on = true;
1760  bool pretrained_on = true;
1761 
1762  const char* debug_mode;
1763  do {
1764  if (!pretrained_on)
1765  debug_mode = "Adaptive Templates Only";
1766  else if (!adaptive_on)
1767  debug_mode = "PreTrained Templates Only";
1768  else
1769  debug_mode = "All Templates";
1770  ShowMatchDisplay();
1771  tprintf("Debugging class %d = %s in mode %s ...",
1772  unichar_id, unicharset.id_to_unichar(unichar_id), debug_mode);
1773  if (shape_id >= 0 && shape_table_ != NULL) {
1774  tprintf(" from shape %s\n", shape_table_->DebugStr(shape_id).string());
1775  }
1776  ShowBestMatchFor(Blob, denorm, unichar_id, shape_id, adaptive_on,
1777  pretrained_on, Results);
1779  } while ((unichar_id = GetClassToDebug(Prompt, &adaptive_on,
1780  &pretrained_on, &shape_id)) != 0);
1781 } /* DebugAdaptiveClassifier */
1782 #endif
1783 
1784 /*---------------------------------------------------------------------------*/
1809  const DENORM& denorm,
1810  ADAPT_RESULTS *Results) {
1811  UNICHAR_ID *Ambiguities;
1812 
1813  AdaptiveMatcherCalls++;
1814  InitIntFX();
1815 
1816  if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min ||
1817  tess_cn_matching) {
1818  CharNormClassifier(Blob, denorm, PreTrainedTemplates, Results);
1819  } else {
1820  Ambiguities = BaselineClassifier(Blob, denorm, AdaptedTemplates, Results);
1821  if ((Results->NumMatches > 0 &&
1822  MarginalMatch (Results->best_match.rating) &&
1823  !tess_bn_matching) ||
1824  Results->NumMatches == 0) {
1825  CharNormClassifier(Blob, denorm, PreTrainedTemplates, Results);
1826  } else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) {
1827  AmbigClassifier(Blob, denorm,
1828  PreTrainedTemplates,
1829  AdaptedTemplates->Class,
1830  Ambiguities,
1831  Results);
1832  }
1833  }
1834 
1835  // Force the blob to be classified as noise
1836  // if the results contain only fragments.
1837  // TODO(daria): verify that this is better than
1838  // just adding a NULL classification.
1839  if (!Results->HasNonfragment || Results->NumMatches == 0)
1840  ClassifyAsNoise(Results);
1841 } /* DoAdaptiveMatch */
1842 
1843 /*---------------------------------------------------------------------------*/
1870  const DENORM& denorm,
1871  const WERD_CHOICE& BestChoice,
1872  const WERD_CHOICE& BestRawChoice,
1873  FLOAT32 Thresholds[]) {
1874  getDict().FindClassifierErrors(matcher_perfect_threshold,
1875  matcher_good_threshold,
1876  matcher_rating_margin,
1877  Thresholds);
1878 } /* GetAdaptThresholds */
1879 
1880 /*---------------------------------------------------------------------------*/
1899  const DENORM& denorm,
1900  CLASS_ID CorrectClass) {
1901  ADAPT_RESULTS *Results = new ADAPT_RESULTS();
1902  UNICHAR_ID *Ambiguities;
1903  int i;
1904 
1905  Results->Initialize();
1906 
1907  CharNormClassifier(Blob, denorm, PreTrainedTemplates, Results);
1908  RemoveBadMatches(Results);
1909  qsort((void *)Results->match, Results->NumMatches,
1910  sizeof(ScoredClass), CompareByRating);
1911 
1912  /* copy the class id's into an string of ambiguities - don't copy if
1913  the correct class is the only class id matched */
1914  Ambiguities = (UNICHAR_ID *) Emalloc (sizeof (UNICHAR_ID) *
1915  (Results->NumMatches + 1));
1916  if (Results->NumMatches > 1 ||
1917  (Results->NumMatches == 1 &&
1918  Results->match[0].unichar_id != CorrectClass)) {
1919  for (i = 0; i < Results->NumMatches; i++)
1920  Ambiguities[i] = Results->match[i].unichar_id;
1921  Ambiguities[i] = -1;
1922  } else {
1923  Ambiguities[0] = -1;
1924  }
1925 
1926  delete Results;
1927  return Ambiguities;
1928 } /* GetAmbiguities */
1929 
1930 /*---------------------------------------------------------------------------*/
1958  const DENORM& denorm,
1959  INT_TEMPLATES Templates,
1960  INT_FEATURE_ARRAY IntFeatures,
1961  uinT8* CharNormArray,
1962  inT32 *BlobLength) {
1963  register INT_FEATURE Src, Dest, End;
1964 
1965  if (!FeaturesHaveBeenExtracted) {
1966  FeaturesOK = ExtractIntFeat(Blob, denorm, BaselineFeatures,
1967  CharNormFeatures, &FXInfo, NULL);
1968  FeaturesHaveBeenExtracted = TRUE;
1969  }
1970 
1971  if (!FeaturesOK) {
1972  *BlobLength = FXInfo.NumBL;
1973  return 0;
1974  }
1975 
1976  for (Src = BaselineFeatures, End = Src + FXInfo.NumBL, Dest = IntFeatures;
1977  Src < End;
1978  *Dest++ = *Src++);
1979 
1980  ClearCharNormArray(CharNormArray);
1981  *BlobLength = FXInfo.NumBL;
1982  return FXInfo.NumBL;
1983 } /* GetBaselineFeatures */
1984 
1986  FeaturesHaveBeenExtracted = FALSE;
1987 }
1988 
1989 // Returns true if the given blob looks too dissimilar to any character
1990 // present in the classifier templates.
1991 bool Classify::LooksLikeGarbage(const DENORM& denorm, TBLOB *blob) {
1992  BLOB_CHOICE_LIST *ratings = new BLOB_CHOICE_LIST();
1993  AdaptiveClassifier(blob, denorm, ratings, NULL);
1994  BLOB_CHOICE_IT ratings_it(ratings);
1995  const UNICHARSET &unicharset = getDict().getUnicharset();
1996  if (classify_debug_character_fragments) {
1997  print_ratings_list("======================\nLooksLikeGarbage() got ",
1998  ratings, unicharset);
1999  }
2000  for (ratings_it.mark_cycle_pt(); !ratings_it.cycled_list();
2001  ratings_it.forward()) {
2002  if (unicharset.get_fragment(ratings_it.data()->unichar_id()) != NULL) {
2003  continue;
2004  }
2005  delete ratings;
2006  return (ratings_it.data()->certainty() <
2007  classify_character_fragments_garbage_certainty_threshold);
2008  }
2009  delete ratings;
2010  return true; // no whole characters in ratings
2011 }
2012 
2013 /*---------------------------------------------------------------------------*/
2046  const DENORM& denorm,
2047  INT_TEMPLATES Templates,
2048  INT_FEATURE_ARRAY IntFeatures,
2049  uinT8* PrunerNormArray,
2050  uinT8* CharNormArray,
2051  inT32 *BlobLength,
2052  inT32 *FeatureOutlineArray) {
2053  register INT_FEATURE Src, Dest, End;
2054  FEATURE NormFeature;
2055  FLOAT32 Baseline, Scale;
2056  inT32 FeatureOutlineIndex[MAX_NUM_INT_FEATURES];
2057 
2058  if (!FeaturesHaveBeenExtracted) {
2059  FeaturesOK = ExtractIntFeat(Blob, denorm, BaselineFeatures,
2060  CharNormFeatures, &FXInfo,
2061  FeatureOutlineIndex);
2062  FeaturesHaveBeenExtracted = TRUE;
2063  }
2064 
2065  if (!FeaturesOK) {
2066  *BlobLength = FXInfo.NumBL;
2067  return (0);
2068  }
2069 
2070  for (Src = CharNormFeatures, End = Src + FXInfo.NumCN, Dest = IntFeatures;
2071  Src < End;
2072  *Dest++ = *Src++);
2073  for (int i = 0; FeatureOutlineArray && i < FXInfo.NumCN; ++i) {
2074  FeatureOutlineArray[i] = FeatureOutlineIndex[i];
2075  }
2076 
2077  NormFeature = NewFeature(&CharNormDesc);
2078  Baseline = BASELINE_OFFSET;
2079  Scale = MF_SCALE_FACTOR;
2080  NormFeature->Params[CharNormY] = (FXInfo.Ymean - Baseline) * Scale;
2081  NormFeature->Params[CharNormLength] =
2082  FXInfo.Length * Scale / LENGTH_COMPRESSION;
2083  NormFeature->Params[CharNormRx] = FXInfo.Rx * Scale;
2084  NormFeature->Params[CharNormRy] = FXInfo.Ry * Scale;
2085  ComputeCharNormArrays(NormFeature, Templates, CharNormArray, PrunerNormArray);
2086  *BlobLength = FXInfo.NumBL;
2087  return (FXInfo.NumCN);
2088 } /* GetCharNormFeatures */
2089 
2090 // Computes the char_norm_array for the unicharset and, if not NULL, the
2091 // pruner_array as appropriate according to the existence of the shape_table.
2093  INT_TEMPLATES_STRUCT* templates,
2094  uinT8* char_norm_array,
2095  uinT8* pruner_array) {
2096  ComputeIntCharNormArray(*norm_feature, char_norm_array);
2097  if (pruner_array != NULL) {
2098  if (shape_table_ == NULL) {
2099  ComputeIntCharNormArray(*norm_feature, pruner_array);
2100  } else {
2101  memset(pruner_array, MAX_UINT8,
2102  templates->NumClasses * sizeof(pruner_array[0]));
2103  // Each entry in the pruner norm array is the MIN of all the entries of
2104  // the corresponding unichars in the CharNormArray.
2105  for (int id = 0; id < templates->NumClasses; ++id) {
2106  int font_set_id = templates->Class[id]->font_set_id;
2107  const FontSet &fs = fontset_table_.get(font_set_id);
2108  for (int config = 0; config < fs.size; ++config) {
2109  const Shape& shape = shape_table_->GetShape(fs.configs[config]);
2110  for (int c = 0; c < shape.size(); ++c) {
2111  if (char_norm_array[shape[c].unichar_id] < pruner_array[id])
2112  pruner_array[id] = char_norm_array[shape[c].unichar_id];
2113  }
2114  }
2115  }
2116  }
2117  }
2118  FreeFeature(norm_feature);
2119 }
2120 
2121 /*---------------------------------------------------------------------------*/
2137  CLASS_ID ClassId,
2138  int FontinfoId,
2139  int NumFeatures,
2140  INT_FEATURE_ARRAY Features,
2141  FEATURE_SET FloatFeatures) {
2142  INT_CLASS IClass;
2143  ADAPT_CLASS Class;
2144  PROTO_ID OldProtos[MAX_NUM_PROTOS];
2145  FEATURE_ID BadFeatures[MAX_NUM_INT_FEATURES];
2146  int NumOldProtos;
2147  int NumBadFeatures;
2148  int MaxProtoId, OldMaxProtoId;
2149  int BlobLength = 0;
2150  int MaskSize;
2151  int ConfigId;
2153  int i;
2154  int debug_level = NO_DEBUG;
2155 
2156  if (classify_learning_debug_level >= 3)
2157  debug_level =
2159 
2160  IClass = ClassForClassId(Templates->Templates, ClassId);
2161  Class = Templates->Class[ClassId];
2162 
2163  if (IClass->NumConfigs >= MAX_NUM_CONFIGS) {
2164  ++NumAdaptationsFailed;
2165  if (classify_learning_debug_level >= 1)
2166  cprintf("Cannot make new temporary config: maximum number exceeded.\n");
2167  return -1;
2168  }
2169 
2170  OldMaxProtoId = IClass->NumProtos - 1;
2171 
2172  NumOldProtos = im_.FindGoodProtos(IClass, AllProtosOn, AllConfigsOff,
2173  BlobLength, NumFeatures, Features,
2174  OldProtos, classify_adapt_proto_threshold,
2175  debug_level);
2176 
2177  MaskSize = WordsInVectorOfSize(MAX_NUM_PROTOS);
2178  zero_all_bits(TempProtoMask, MaskSize);
2179  for (i = 0; i < NumOldProtos; i++)
2180  SET_BIT(TempProtoMask, OldProtos[i]);
2181 
2182  NumBadFeatures = im_.FindBadFeatures(IClass, TempProtoMask, AllConfigsOn,
2183  BlobLength, NumFeatures, Features,
2184  BadFeatures,
2185  classify_adapt_feature_threshold,
2186  debug_level);
2187 
2188  MaxProtoId = MakeNewTempProtos(FloatFeatures, NumBadFeatures, BadFeatures,
2189  IClass, Class, TempProtoMask);
2190  if (MaxProtoId == NO_PROTO) {
2191  ++NumAdaptationsFailed;
2192  if (classify_learning_debug_level >= 1)
2193  cprintf("Cannot make new temp protos: maximum number exceeded.\n");
2194  return -1;
2195  }
2196 
2197  ConfigId = AddIntConfig(IClass);
2198  ConvertConfig(TempProtoMask, ConfigId, IClass);
2199  Config = NewTempConfig(MaxProtoId, FontinfoId);
2200  TempConfigFor(Class, ConfigId) = Config;
2201  copy_all_bits(TempProtoMask, Config->Protos, Config->ProtoVectorSize);
2202 
2203  if (classify_learning_debug_level >= 1)
2204  cprintf("Making new temp config %d fontinfo id %d"
2205  " using %d old and %d new protos.\n",
2206  ConfigId, Config->FontinfoId,
2207  NumOldProtos, MaxProtoId - OldMaxProtoId);
2208 
2209  return ConfigId;
2210 } /* MakeNewTemporaryConfig */
2211 
2212 /*---------------------------------------------------------------------------*/
2234  int NumBadFeat,
2235  FEATURE_ID BadFeat[],
2236  INT_CLASS IClass,
2237  ADAPT_CLASS Class,
2238  BIT_VECTOR TempProtoMask) {
2239  FEATURE_ID *ProtoStart;
2240  FEATURE_ID *ProtoEnd;
2241  FEATURE_ID *LastBad;
2242  TEMP_PROTO TempProto;
2243  PROTO Proto;
2244  FEATURE F1, F2;
2245  FLOAT32 X1, X2, Y1, Y2;
2246  FLOAT32 A1, A2, AngleDelta;
2247  FLOAT32 SegmentLength;
2248  PROTO_ID Pid;
2249 
2250  for (ProtoStart = BadFeat, LastBad = ProtoStart + NumBadFeat;
2251  ProtoStart < LastBad; ProtoStart = ProtoEnd) {
2252  F1 = Features->Features[*ProtoStart];
2253  X1 = F1->Params[PicoFeatX];
2254  Y1 = F1->Params[PicoFeatY];
2255  A1 = F1->Params[PicoFeatDir];
2256 
2257  for (ProtoEnd = ProtoStart + 1,
2258  SegmentLength = GetPicoFeatureLength();
2259  ProtoEnd < LastBad;
2260  ProtoEnd++, SegmentLength += GetPicoFeatureLength()) {
2261  F2 = Features->Features[*ProtoEnd];
2262  X2 = F2->Params[PicoFeatX];
2263  Y2 = F2->Params[PicoFeatY];
2264  A2 = F2->Params[PicoFeatDir];
2265 
2266  AngleDelta = fabs(A1 - A2);
2267  if (AngleDelta > 0.5)
2268  AngleDelta = 1.0 - AngleDelta;
2269 
2270  if (AngleDelta > matcher_clustering_max_angle_delta ||
2271  fabs(X1 - X2) > SegmentLength ||
2272  fabs(Y1 - Y2) > SegmentLength)
2273  break;
2274  }
2275 
2276  F2 = Features->Features[*(ProtoEnd - 1)];
2277  X2 = F2->Params[PicoFeatX];
2278  Y2 = F2->Params[PicoFeatY];
2279  A2 = F2->Params[PicoFeatDir];
2280 
2281  Pid = AddIntProto(IClass);
2282  if (Pid == NO_PROTO)
2283  return (NO_PROTO);
2284 
2285  TempProto = NewTempProto();
2286  Proto = &(TempProto->Proto);
2287 
2288  /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
2289  ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
2290  instead of the -0.25 to 0.75 used in baseline normalization */
2291  Proto->Length = SegmentLength;
2292  Proto->Angle = A1;
2293  Proto->X = (X1 + X2) / 2.0;
2294  Proto->Y = (Y1 + Y2) / 2.0 - Y_DIM_OFFSET;
2295  FillABC(Proto);
2296 
2297  TempProto->ProtoId = Pid;
2298  SET_BIT(TempProtoMask, Pid);
2299 
2300  ConvertProto(Proto, Pid, IClass);
2301  AddProtoToProtoPruner(Proto, Pid, IClass,
2302  classify_learning_debug_level >= 2);
2303 
2304  Class->TempProtos = push(Class->TempProtos, TempProto);
2305  }
2306  return IClass->NumProtos - 1;
2307 } /* MakeNewTempProtos */
2308 
2309 /*---------------------------------------------------------------------------*/
2324  CLASS_ID ClassId,
2325  int ConfigId,
2326  const DENORM& denorm,
2327  TBLOB *Blob) {
2328  UNICHAR_ID *Ambigs;
2330  ADAPT_CLASS Class;
2331  PROTO_KEY ProtoKey;
2332 
2333  Class = Templates->Class[ClassId];
2334  Config = TempConfigFor(Class, ConfigId);
2335 
2336  MakeConfigPermanent(Class, ConfigId);
2337  if (Class->NumPermConfigs == 0)
2338  Templates->NumPermClasses++;
2339  Class->NumPermConfigs++;
2340 
2341  // Initialize permanent config.
2342  Ambigs = GetAmbiguities(Blob, denorm, ClassId);
2344  "PERM_CONFIG_STRUCT");
2345  Perm->Ambigs = Ambigs;
2346  Perm->FontinfoId = Config->FontinfoId;
2347 
2348  // Free memory associated with temporary config (since ADAPTED_CONFIG
2349  // is a union we need to clean up before we record permanent config).
2350  ProtoKey.Templates = Templates;
2351  ProtoKey.ClassId = ClassId;
2352  ProtoKey.ConfigId = ConfigId;
2353  Class->TempProtos = delete_d(Class->TempProtos, &ProtoKey, MakeTempProtoPerm);
2354  FreeTempConfig(Config);
2355 
2356  // Record permanent config.
2357  PermConfigFor(Class, ConfigId) = Perm;
2358 
2359  if (classify_learning_debug_level >= 1) {
2360  tprintf("Making config %d for %s (ClassId %d) permanent:"
2361  " fontinfo id %d, ambiguities '",
2362  ConfigId, getDict().getUnicharset().debug_str(ClassId).string(),
2363  ClassId, PermConfigFor(Class, ConfigId)->FontinfoId);
2364  for (UNICHAR_ID *AmbigsPointer = Ambigs;
2365  *AmbigsPointer >= 0; ++AmbigsPointer)
2366  tprintf("%s", unicharset.id_to_unichar(*AmbigsPointer));
2367  tprintf("'.\n");
2368  }
2369 } /* MakePermanent */
2370 } // namespace tesseract
2371 
2372 /*---------------------------------------------------------------------------*/
2387 int MakeTempProtoPerm(void *item1, void *item2) {
2388  ADAPT_CLASS Class;
2390  TEMP_PROTO TempProto;
2391  PROTO_KEY *ProtoKey;
2392 
2393  TempProto = (TEMP_PROTO) item1;
2394  ProtoKey = (PROTO_KEY *) item2;
2395 
2396  Class = ProtoKey->Templates->Class[ProtoKey->ClassId];
2397  Config = TempConfigFor(Class, ProtoKey->ConfigId);
2398 
2399  if (TempProto->ProtoId > Config->MaxProtoId ||
2400  !test_bit (Config->Protos, TempProto->ProtoId))
2401  return FALSE;
2402 
2403  MakeProtoPermanent(Class, TempProto->ProtoId);
2404  AddProtoToClassPruner(&(TempProto->Proto), ProtoKey->ClassId,
2405  ProtoKey->Templates->Templates);
2406  FreeTempProto(TempProto);
2407 
2408  return TRUE;
2409 } /* MakeTempProtoPerm */
2410 
2411 /*---------------------------------------------------------------------------*/
2412 namespace tesseract {
2425  for (int i = 0; i < Results->NumMatches; ++i) {
2426  tprintf("%s(%d), shape %d, %.2f ",
2427  unicharset.debug_str(Results->match[i].unichar_id).string(),
2428  Results->match[i].unichar_id, Results->match[i].shape_id,
2429  Results->match[i].rating * 100.0);
2430  }
2431  tprintf("\n");
2432 } /* PrintAdaptiveMatchResults */
2433 
2434 /*---------------------------------------------------------------------------*/
2451  int Next, NextGood;
2452  FLOAT32 BadMatchThreshold;
2453  static const char* romans = "i v x I V X";
2454  BadMatchThreshold = Results->best_match.rating + matcher_bad_match_pad;
2455 
2456  if (classify_bln_numeric_mode) {
2457  UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ?
2458  unicharset.unichar_to_id("1") : -1;
2459  UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ?
2460  unicharset.unichar_to_id("0") : -1;
2461  ScoredClass scored_one = ScoredUnichar(Results, unichar_id_one);
2462  ScoredClass scored_zero = ScoredUnichar(Results, unichar_id_zero);
2463 
2464  for (Next = NextGood = 0; Next < Results->NumMatches; Next++) {
2465  if (Results->match[Next].rating <= BadMatchThreshold) {
2466  ScoredClass match = Results->match[Next];
2467  if (!unicharset.get_isalpha(match.unichar_id) ||
2468  strstr(romans,
2469  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2470  Results->match[NextGood++] = Results->match[Next];
2471  } else if (unicharset.eq(match.unichar_id, "l") &&
2472  scored_one.rating >= BadMatchThreshold) {
2473  Results->match[NextGood] = scored_one;
2474  Results->match[NextGood].rating = match.rating;
2475  NextGood++;
2476  } else if (unicharset.eq(match.unichar_id, "O") &&
2477  scored_zero.rating >= BadMatchThreshold) {
2478  Results->match[NextGood] = scored_zero;
2479  Results->match[NextGood].rating = match.rating;
2480  NextGood++;
2481  }
2482  }
2483  }
2484  } else {
2485  for (Next = NextGood = 0; Next < Results->NumMatches; Next++) {
2486  if (Results->match[Next].rating <= BadMatchThreshold)
2487  Results->match[NextGood++] = Results->match[Next];
2488  }
2489  }
2490  Results->NumMatches = NextGood;
2491 } /* RemoveBadMatches */
2492 
2493 /*----------------------------------------------------------------------------*/
2504  int Next, NextGood;
2505  int punc_count; /*no of garbage characters */
2506  int digit_count;
2507  /*garbage characters */
2508  static char punc_chars[] = ". , ; : / ` ~ ' - = \\ | \" ! _ ^";
2509  static char digit_chars[] = "0 1 2 3 4 5 6 7 8 9";
2510 
2511  punc_count = 0;
2512  digit_count = 0;
2513  for (Next = NextGood = 0; Next < Results->NumMatches; Next++) {
2514  ScoredClass match = Results->match[Next];
2515  if (strstr(punc_chars,
2516  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2517  if (punc_count < 2)
2518  Results->match[NextGood++] = match;
2519  punc_count++;
2520  } else {
2521  if (strstr(digit_chars,
2522  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2523  if (digit_count < 1)
2524  Results->match[NextGood++] = match;
2525  digit_count++;
2526  } else {
2527  Results->match[NextGood++] = match;
2528  }
2529  }
2530  }
2531  Results->NumMatches = NextGood;
2532 } /* RemoveExtraPuncs */
2533 
2534 /*---------------------------------------------------------------------------*/
2549  Threshold = (Threshold == matcher_good_threshold) ? 0.9: (1.0 - Threshold);
2550  classify_adapt_proto_threshold.set_value(
2551  ClipToRange<int>(255 * Threshold, 0, 255));
2552  classify_adapt_feature_threshold.set_value(
2553  ClipToRange<int>(255 * Threshold, 0, 255));
2554 } /* SetAdaptiveThreshold */
2555 
2556 /*---------------------------------------------------------------------------*/
2580  const DENORM& denorm,
2581  CLASS_ID ClassId,
2582  int shape_id,
2583  BOOL8 AdaptiveOn,
2584  BOOL8 PreTrainedOn,
2585  ADAPT_RESULTS *Results) {
2586  int NumCNFeatures = 0, NumBLFeatures = 0;
2587  INT_FEATURE_ARRAY CNFeatures, BLFeatures;
2588  INT_RESULT_STRUCT CNResult, BLResult;
2589  inT32 BlobLength;
2590  uinT32 ConfigMask;
2591  static int next_config = -1;
2592 
2593  if (PreTrainedOn) next_config = -1;
2594 
2595  CNResult.Rating = BLResult.Rating = 2.0;
2596 
2597  if (!LegalClassId (ClassId)) {
2598  cprintf ("%d is not a legal class id!!\n", ClassId);
2599  return;
2600  }
2601 
2602  uinT8 *CNAdjust = new uinT8[MAX_NUM_CLASSES];
2603  uinT8 *BLAdjust = new uinT8[MAX_NUM_CLASSES];
2604 
2605  if (shape_table_ == NULL)
2606  shape_id = ClassId;
2607  else
2608  shape_id = ShapeIDToClassID(shape_id);
2609  if (PreTrainedOn && shape_id >= 0) {
2610  if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) {
2611  tprintf("No built-in templates for class/shape %d\n", shape_id);
2612  } else {
2613  NumCNFeatures = GetCharNormFeatures(Blob, denorm, PreTrainedTemplates,
2614  CNFeatures, NULL, CNAdjust,
2615  &BlobLength, NULL);
2616  if (NumCNFeatures <= 0) {
2617  tprintf("Illegal blob (char norm features)!\n");
2618  } else {
2619  im_.SetCharNormMatch(classify_integer_matcher_multiplier);
2620  im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
2621  AllProtosOn, AllConfigsOn,
2622  NumCNFeatures, CNFeatures,
2623  &CNResult,
2624  classify_adapt_feature_threshold, NO_DEBUG,
2625  matcher_debug_separate_windows);
2626  ExpandShapesAndApplyCorrections(NULL, false, shape_id,
2627  Blob->bounding_box().bottom(),
2628  Blob->bounding_box().top(),
2629  0, BlobLength, CNAdjust,
2630  CNResult, Results);
2631  }
2632  }
2633  }
2634 
2635  if (AdaptiveOn) {
2636  if (ClassId < 0 || ClassId >= AdaptedTemplates->Templates->NumClasses) {
2637  tprintf("Invalid adapted class id: %d\n", ClassId);
2638  } else if (UnusedClassIdIn(AdaptedTemplates->Templates, ClassId) ||
2639  AdaptedTemplates->Class[ClassId] == NULL ||
2640  IsEmptyAdaptedClass(AdaptedTemplates->Class[ClassId])) {
2641  tprintf("No AD templates for class %d = %s\n",
2642  ClassId, unicharset.id_to_unichar(ClassId));
2643  } else {
2644  NumBLFeatures = GetBaselineFeatures(Blob,
2645  denorm,
2646  AdaptedTemplates->Templates,
2647  BLFeatures, BLAdjust,
2648  &BlobLength);
2649  if (NumBLFeatures <= 0)
2650  tprintf("Illegal blob (baseline features)!\n");
2651  else {
2652  im_.SetBaseLineMatch();
2653  im_.Match(ClassForClassId(AdaptedTemplates->Templates, ClassId),
2654  AllProtosOn, AllConfigsOn,
2655  NumBLFeatures, BLFeatures,
2656  &BLResult,
2657  classify_adapt_feature_threshold, NO_DEBUG,
2658  matcher_debug_separate_windows);
2659  ExpandShapesAndApplyCorrections(
2660  AdaptedTemplates->Class, false,
2661  ClassId, Blob->bounding_box().bottom(),
2662  Blob->bounding_box().top(), 0, BlobLength, CNAdjust,
2663  BLResult, Results);
2664  }
2665  }
2666  }
2667 
2668  tprintf("\n");
2669  if (BLResult.Rating < CNResult.Rating) {
2670  if (next_config < 0) {
2671  ConfigMask = 1 << BLResult.Config;
2672  next_config = 0;
2673  } else {
2674  ConfigMask = 1 << next_config;
2675  ++next_config;
2676  }
2677  classify_norm_method.set_value(baseline);
2678 
2679  im_.SetBaseLineMatch();
2680  tprintf("Adaptive Class ID: %d\n", ClassId);
2681  im_.Match(ClassForClassId(AdaptedTemplates->Templates, ClassId),
2682  AllProtosOn, (BIT_VECTOR) &ConfigMask,
2683  NumBLFeatures, BLFeatures,
2684  &BLResult,
2685  classify_adapt_feature_threshold,
2686  matcher_debug_flags,
2687  matcher_debug_separate_windows);
2688  ExpandShapesAndApplyCorrections(
2689  AdaptedTemplates->Class, true,
2690  ClassId, Blob->bounding_box().bottom(),
2691  Blob->bounding_box().top(), 0, BlobLength, CNAdjust,
2692  BLResult, Results);
2693  } else if (shape_id >= 0) {
2694  ConfigMask = 1 << CNResult.Config;
2695  classify_norm_method.set_value(character);
2696 
2697  tprintf("Static Shape ID: %d\n", shape_id);
2698  im_.SetCharNormMatch(classify_integer_matcher_multiplier);
2699  im_.Match(ClassForClassId (PreTrainedTemplates, shape_id),
2700  AllProtosOn, (BIT_VECTOR) & ConfigMask,
2701  NumCNFeatures, CNFeatures,
2702  &CNResult,
2703  classify_adapt_feature_threshold,
2704  matcher_debug_flags,
2705  matcher_debug_separate_windows);
2706  ExpandShapesAndApplyCorrections(NULL, true, shape_id,
2707  Blob->bounding_box().bottom(),
2708  Blob->bounding_box().top(),
2709  0, BlobLength, CNAdjust,
2710  CNResult, Results);
2711  }
2712 
2713  // Clean up.
2714  delete[] CNAdjust;
2715  delete[] BLAdjust;
2716 } /* ShowBestMatchFor */
2717 
2718 // Returns a string for the classifier class_id: either the corresponding
2719 // unicharset debug_str or the shape_table_ debug str.
2721  int class_id, int config_id) const {
2722  STRING class_string;
2723  if (templates == PreTrainedTemplates && shape_table_ != NULL) {
2724  int shape_id = ClassAndConfigIDToFontOrShapeID(class_id, config_id);
2725  class_string = shape_table_->DebugStr(shape_id);
2726  } else {
2727  class_string = unicharset.debug_str(class_id);
2728  }
2729  return class_string;
2730 }
2731 
2732 // Converts a classifier class_id index to a shape_table_ index
2734  int int_result_config) const {
2735  int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id;
2736  // Older inttemps have no font_ids.
2737  if (font_set_id < 0)
2738  return kBlankFontinfoId;
2739  const FontSet &fs = fontset_table_.get(font_set_id);
2740  ASSERT_HOST(int_result_config >= 0 && int_result_config < fs.size);
2741  return fs.configs[int_result_config];
2742 }
2743 
2744 // Converts a shape_table_ index to a classifier class_id index (not a
2745 // unichar-id!). Uses a search, so not fast.
2746 int Classify::ShapeIDToClassID(int shape_id) const {
2747  for (int id = 0; id < PreTrainedTemplates->NumClasses; ++id) {
2748  int font_set_id = PreTrainedTemplates->Class[id]->font_set_id;
2749  ASSERT_HOST(font_set_id >= 0);
2750  const FontSet &fs = fontset_table_.get(font_set_id);
2751  for (int config = 0; config < fs.size; ++config) {
2752  if (fs.configs[config] == shape_id)
2753  return id;
2754  }
2755  }
2756  tprintf("Shape %d not found\n", shape_id);
2757  return -1;
2758 }
2759 
2760 // Returns true if the given TEMP_CONFIG is good enough to make it
2761 // a permanent config.
2763  const TEMP_CONFIG &config) {
2764  if (classify_learning_debug_level >= 1) {
2765  tprintf("NumTimesSeen for config of %s is %d\n",
2766  getDict().getUnicharset().debug_str(class_id).string(),
2767  config->NumTimesSeen);
2768  }
2769  if (config->NumTimesSeen >= matcher_sufficient_examples_for_prototyping) {
2770  return true;
2771  } else if (config->NumTimesSeen < matcher_min_examples_for_prototyping) {
2772  return false;
2773  } else if (use_ambigs_for_adaption) {
2774  // Go through the ambigs vector and see whether we have already seen
2775  // enough times all the characters represented by the ambigs vector.
2776  const UnicharIdVector *ambigs =
2777  getDict().getUnicharAmbigs().AmbigsForAdaption(class_id);
2778  int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
2779  for (int ambig = 0; ambig < ambigs_size; ++ambig) {
2780  ADAPT_CLASS ambig_class = AdaptedTemplates->Class[(*ambigs)[ambig]];
2781  assert(ambig_class != NULL);
2782  if (ambig_class->NumPermConfigs == 0 &&
2783  ambig_class->MaxNumTimesSeen <
2784  matcher_min_examples_for_prototyping) {
2785  if (classify_learning_debug_level >= 1) {
2786  tprintf("Ambig %s has not been seen enough times,"
2787  " not making config for %s permanent\n",
2788  getDict().getUnicharset().debug_str(
2789  (*ambigs)[ambig]).string(),
2790  getDict().getUnicharset().debug_str(class_id).string());
2791  }
2792  return false;
2793  }
2794  }
2795  }
2796  return true;
2797 }
2798 
2799 void Classify::UpdateAmbigsGroup(CLASS_ID class_id, const DENORM& denorm,
2800  TBLOB *Blob) {
2801  const UnicharIdVector *ambigs =
2802  getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id);
2803  int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
2804  if (classify_learning_debug_level >= 1) {
2805  tprintf("Running UpdateAmbigsGroup for %s class_id=%d\n",
2806  getDict().getUnicharset().debug_str(class_id).string(), class_id);
2807  }
2808  for (int ambig = 0; ambig < ambigs_size; ++ambig) {
2809  CLASS_ID ambig_class_id = (*ambigs)[ambig];
2810  const ADAPT_CLASS ambigs_class = AdaptedTemplates->Class[ambig_class_id];
2811  for (int cfg = 0; cfg < MAX_NUM_CONFIGS; ++cfg) {
2812  if (ConfigIsPermanent(ambigs_class, cfg)) continue;
2813  const TEMP_CONFIG config =
2814  TempConfigFor(AdaptedTemplates->Class[ambig_class_id], cfg);
2815  if (config != NULL && TempConfigReliable(ambig_class_id, config)) {
2816  if (classify_learning_debug_level >= 1) {
2817  tprintf("Making config %d of %s permanent\n", cfg,
2818  getDict().getUnicharset().debug_str(
2819  ambig_class_id).string());
2820  }
2821  MakePermanent(AdaptedTemplates, ambig_class_id, cfg, denorm, Blob);
2822  }
2823  }
2824  }
2825 }
2826 
2827 } // namespace tesseract