tesseract  4.00.00dev
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(TessdataManager* mgr) {
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 && mgr != nullptr) {
536  TFile fp;
538  PreTrainedTemplates = ReadIntTemplates(&fp);
539 
540  if (mgr->GetComponent(TESSDATA_SHAPE_TABLE, &fp)) {
541  shape_table_ = new ShapeTable(unicharset);
542  if (!shape_table_->DeSerialize(&fp)) {
543  tprintf("Error loading shape table!\n");
544  delete shape_table_;
545  shape_table_ = NULL;
546  }
547  }
548 
550  ReadNewCutoffs(&fp, CharNormCutoffs);
551 
553  NormProtos = ReadNormProtos(&fp);
554  static_classifier_ = new TessClassifier(false, this);
555  }
556 
557  im_.Init(&classify_debug_level);
558  InitIntegerFX();
559 
560  AllProtosOn = NewBitVector(MAX_NUM_PROTOS);
561  AllConfigsOn = NewBitVector(MAX_NUM_CONFIGS);
562  AllConfigsOff = NewBitVector(MAX_NUM_CONFIGS);
563  TempProtoMask = NewBitVector(MAX_NUM_PROTOS);
567 
568  for (int i = 0; i < MAX_NUM_CLASSES; i++) {
569  BaselineCutoffs[i] = 0;
570  }
571 
572  if (classify_use_pre_adapted_templates) {
573  TFile fp;
574  STRING Filename;
575 
576  Filename = imagefile;
577  Filename += ADAPT_TEMPLATE_SUFFIX;
578  if (!fp.Open(Filename.string(), nullptr)) {
579  AdaptedTemplates = NewAdaptedTemplates(true);
580  } else {
581  cprintf("\nReading pre-adapted templates from %s ...\n",
582  Filename.string());
583  fflush(stdout);
584  AdaptedTemplates = ReadAdaptedTemplates(&fp);
585  cprintf("\n");
586  PrintAdaptedTemplates(stdout, AdaptedTemplates);
587 
588  for (int i = 0; i < AdaptedTemplates->Templates->NumClasses; i++) {
589  BaselineCutoffs[i] = CharNormCutoffs[i];
590  }
591  }
592  } else {
593  if (AdaptedTemplates != NULL)
594  free_adapted_templates(AdaptedTemplates);
595  AdaptedTemplates = NewAdaptedTemplates(true);
596  }
597 } /* InitAdaptiveClassifier */
598 
599 void Classify::ResetAdaptiveClassifierInternal() {
600  if (classify_learning_debug_level > 0) {
601  tprintf("Resetting adaptive classifier (NumAdaptationsFailed=%d)\n",
602  NumAdaptationsFailed);
603  }
604  free_adapted_templates(AdaptedTemplates);
605  AdaptedTemplates = NewAdaptedTemplates(true);
606  if (BackupAdaptedTemplates != NULL)
607  free_adapted_templates(BackupAdaptedTemplates);
608  BackupAdaptedTemplates = NULL;
609  NumAdaptationsFailed = 0;
610 }
611 
612 // If there are backup adapted templates, switches to those, otherwise resets
613 // the main adaptive classifier (because it is full.)
614 void Classify::SwitchAdaptiveClassifier() {
615  if (BackupAdaptedTemplates == NULL) {
616  ResetAdaptiveClassifierInternal();
617  return;
618  }
619  if (classify_learning_debug_level > 0) {
620  tprintf("Switch to backup adaptive classifier (NumAdaptationsFailed=%d)\n",
621  NumAdaptationsFailed);
622  }
623  free_adapted_templates(AdaptedTemplates);
624  AdaptedTemplates = BackupAdaptedTemplates;
625  BackupAdaptedTemplates = NULL;
626  NumAdaptationsFailed = 0;
627 }
628 
629 // Resets the backup adaptive classifier to empty.
630 void Classify::StartBackupAdaptiveClassifier() {
631  if (BackupAdaptedTemplates != NULL)
632  free_adapted_templates(BackupAdaptedTemplates);
633  BackupAdaptedTemplates = NewAdaptedTemplates(true);
634 }
635 
636 /*---------------------------------------------------------------------------*/
656 void Classify::SettupPass1() {
657  EnableLearning = classify_enable_learning;
658 
659  getDict().SettupStopperPass1();
660 
661 } /* SettupPass1 */
662 
663 
664 /*---------------------------------------------------------------------------*/
676 void Classify::SettupPass2() {
677  EnableLearning = FALSE;
678  getDict().SettupStopperPass2();
679 
680 } /* SettupPass2 */
681 
682 
683 /*---------------------------------------------------------------------------*/
703 void Classify::InitAdaptedClass(TBLOB *Blob,
704  CLASS_ID ClassId,
705  int FontinfoId,
706  ADAPT_CLASS Class,
707  ADAPT_TEMPLATES Templates) {
708  FEATURE_SET Features;
709  int Fid, Pid;
710  FEATURE Feature;
711  int NumFeatures;
712  TEMP_PROTO TempProto;
713  PROTO Proto;
714  INT_CLASS IClass;
716 
717  classify_norm_method.set_value(baseline);
718  Features = ExtractOutlineFeatures(Blob);
719  NumFeatures = Features->NumFeatures;
720  if (NumFeatures > UNLIKELY_NUM_FEAT || NumFeatures <= 0) {
721  FreeFeatureSet(Features);
722  return;
723  }
724 
725  Config = NewTempConfig(NumFeatures - 1, FontinfoId);
726  TempConfigFor(Class, 0) = Config;
727 
728  /* this is a kludge to construct cutoffs for adapted templates */
729  if (Templates == AdaptedTemplates)
730  BaselineCutoffs[ClassId] = CharNormCutoffs[ClassId];
731 
732  IClass = ClassForClassId (Templates->Templates, ClassId);
733 
734  for (Fid = 0; Fid < Features->NumFeatures; Fid++) {
735  Pid = AddIntProto (IClass);
736  assert (Pid != NO_PROTO);
737 
738  Feature = Features->Features[Fid];
739  TempProto = NewTempProto ();
740  Proto = &(TempProto->Proto);
741 
742  /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
743  ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
744  instead of the -0.25 to 0.75 used in baseline normalization */
745  Proto->Angle = Feature->Params[OutlineFeatDir];
746  Proto->X = Feature->Params[OutlineFeatX];
747  Proto->Y = Feature->Params[OutlineFeatY] - Y_DIM_OFFSET;
748  Proto->Length = Feature->Params[OutlineFeatLength];
749  FillABC(Proto);
750 
751  TempProto->ProtoId = Pid;
752  SET_BIT (Config->Protos, Pid);
753 
754  ConvertProto(Proto, Pid, IClass);
755  AddProtoToProtoPruner(Proto, Pid, IClass,
756  classify_learning_debug_level >= 2);
757 
758  Class->TempProtos = push (Class->TempProtos, TempProto);
759  }
760  FreeFeatureSet(Features);
761 
762  AddIntConfig(IClass);
763  ConvertConfig (AllProtosOn, 0, IClass);
764 
765  if (classify_learning_debug_level >= 1) {
766  tprintf("Added new class '%s' with class id %d and %d protos.\n",
767  unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
768  if (classify_learning_debug_level > 1)
769  DisplayAdaptedChar(Blob, IClass);
770  }
771 
772  if (IsEmptyAdaptedClass(Class))
773  (Templates->NumNonEmptyClasses)++;
774 } /* InitAdaptedClass */
775 
776 
777 /*---------------------------------------------------------------------------*/
798 int Classify::GetAdaptiveFeatures(TBLOB *Blob,
799  INT_FEATURE_ARRAY IntFeatures,
800  FEATURE_SET *FloatFeatures) {
801  FEATURE_SET Features;
802  int NumFeatures;
803 
804  classify_norm_method.set_value(baseline);
805  Features = ExtractPicoFeatures(Blob);
806 
807  NumFeatures = Features->NumFeatures;
808  if (NumFeatures == 0 || NumFeatures > UNLIKELY_NUM_FEAT) {
809  FreeFeatureSet(Features);
810  return 0;
811  }
812 
813  ComputeIntFeatures(Features, IntFeatures);
814  *FloatFeatures = Features;
815 
816  return NumFeatures;
817 } /* GetAdaptiveFeatures */
818 
819 
820 /*-----------------------------------------------------------------------------
821  Private Code
822 -----------------------------------------------------------------------------*/
823 /*---------------------------------------------------------------------------*/
836 bool Classify::AdaptableWord(WERD_RES* word) {
837  if (word->best_choice == NULL) return false;
838  int BestChoiceLength = word->best_choice->length();
839  float adaptable_score =
840  getDict().segment_penalty_dict_case_ok + ADAPTABLE_WERD_ADJUSTMENT;
841  return // rules that apply in general - simplest to compute first
842  BestChoiceLength > 0 &&
843  BestChoiceLength == word->rebuild_word->NumBlobs() &&
844  BestChoiceLength <= MAX_ADAPTABLE_WERD_SIZE &&
845  // This basically ensures that the word is at least a dictionary match
846  // (freq word, user word, system dawg word, etc).
847  // Since all the other adjustments will make adjust factor higher
848  // than higher than adaptable_score=1.1+0.05=1.15
849  // Since these are other flags that ensure that the word is dict word,
850  // this check could be at times redundant.
851  word->best_choice->adjust_factor() <= adaptable_score &&
852  // Make sure that alternative choices are not dictionary words.
853  word->AlternativeChoiceAdjustmentsWorseThan(adaptable_score);
854 }
855 
856 /*---------------------------------------------------------------------------*/
872 void Classify::AdaptToChar(TBLOB* Blob, CLASS_ID ClassId, int FontinfoId,
873  FLOAT32 Threshold,
874  ADAPT_TEMPLATES adaptive_templates) {
875  int NumFeatures;
876  INT_FEATURE_ARRAY IntFeatures;
877  UnicharRating int_result;
878  INT_CLASS IClass;
879  ADAPT_CLASS Class;
880  TEMP_CONFIG TempConfig;
881  FEATURE_SET FloatFeatures;
882  int NewTempConfigId;
883 
884  if (!LegalClassId (ClassId))
885  return;
886 
887  int_result.unichar_id = ClassId;
888  Class = adaptive_templates->Class[ClassId];
889  assert(Class != NULL);
890  if (IsEmptyAdaptedClass(Class)) {
891  InitAdaptedClass(Blob, ClassId, FontinfoId, Class, adaptive_templates);
892  } else {
893  IClass = ClassForClassId(adaptive_templates->Templates, ClassId);
894 
895  NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
896  if (NumFeatures <= 0) {
897  return; // Features already freed by GetAdaptiveFeatures.
898  }
899 
900  // Only match configs with the matching font.
901  BIT_VECTOR MatchingFontConfigs = NewBitVector(MAX_NUM_PROTOS);
902  for (int cfg = 0; cfg < IClass->NumConfigs; ++cfg) {
903  if (GetFontinfoId(Class, cfg) == FontinfoId) {
904  SET_BIT(MatchingFontConfigs, cfg);
905  } else {
906  reset_bit(MatchingFontConfigs, cfg);
907  }
908  }
909  im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
910  NumFeatures, IntFeatures,
911  &int_result, classify_adapt_feature_threshold,
912  NO_DEBUG, matcher_debug_separate_windows);
913  FreeBitVector(MatchingFontConfigs);
914 
915  SetAdaptiveThreshold(Threshold);
916 
917  if (1.0f - int_result.rating <= Threshold) {
918  if (ConfigIsPermanent(Class, int_result.config)) {
919  if (classify_learning_debug_level >= 1)
920  tprintf("Found good match to perm config %d = %4.1f%%.\n",
921  int_result.config, int_result.rating * 100.0);
922  FreeFeatureSet(FloatFeatures);
923  return;
924  }
925 
926  TempConfig = TempConfigFor(Class, int_result.config);
927  IncreaseConfidence(TempConfig);
928  if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) {
929  Class->MaxNumTimesSeen = TempConfig->NumTimesSeen;
930  }
931  if (classify_learning_debug_level >= 1)
932  tprintf("Increasing reliability of temp config %d to %d.\n",
933  int_result.config, TempConfig->NumTimesSeen);
934 
935  if (TempConfigReliable(ClassId, TempConfig)) {
936  MakePermanent(adaptive_templates, ClassId, int_result.config, Blob);
937  UpdateAmbigsGroup(ClassId, Blob);
938  }
939  } else {
940  if (classify_learning_debug_level >= 1) {
941  tprintf("Found poor match to temp config %d = %4.1f%%.\n",
942  int_result.config, int_result.rating * 100.0);
943  if (classify_learning_debug_level > 2)
944  DisplayAdaptedChar(Blob, IClass);
945  }
946  NewTempConfigId =
947  MakeNewTemporaryConfig(adaptive_templates, ClassId, FontinfoId,
948  NumFeatures, IntFeatures, FloatFeatures);
949  if (NewTempConfigId >= 0 &&
950  TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) {
951  MakePermanent(adaptive_templates, ClassId, NewTempConfigId, Blob);
952  UpdateAmbigsGroup(ClassId, Blob);
953  }
954 
955 #ifndef GRAPHICS_DISABLED
956  if (classify_learning_debug_level > 1) {
957  DisplayAdaptedChar(Blob, IClass);
958  }
959 #endif
960  }
961  FreeFeatureSet(FloatFeatures);
962  }
963 } /* AdaptToChar */
964 
965 void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
966 #ifndef GRAPHICS_DISABLED
967  INT_FX_RESULT_STRUCT fx_info;
970  BlobToTrainingSample(*blob, classify_nonlinear_norm, &fx_info,
971  &bl_features);
972  if (sample == NULL) return;
973 
974  UnicharRating int_result;
975  im_.Match(int_class, AllProtosOn, AllConfigsOn,
976  bl_features.size(), &bl_features[0],
977  &int_result, classify_adapt_feature_threshold,
978  NO_DEBUG, matcher_debug_separate_windows);
979  tprintf("Best match to temp config %d = %4.1f%%.\n",
980  int_result.config, int_result.rating * 100.0);
981  if (classify_learning_debug_level >= 2) {
982  uinT32 ConfigMask;
983  ConfigMask = 1 << int_result.config;
985  im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
986  bl_features.size(), &bl_features[0],
987  &int_result, classify_adapt_feature_threshold,
988  6 | 0x19, matcher_debug_separate_windows);
990  }
991 
992  delete sample;
993 #endif
994 }
995 
1016 void Classify::AddNewResult(const UnicharRating& new_result,
1017  ADAPT_RESULTS *results) {
1018  int old_match = FindScoredUnichar(new_result.unichar_id, *results);
1019 
1020  if (new_result.rating + matcher_bad_match_pad < results->best_rating ||
1021  (old_match < results->match.size() &&
1022  new_result.rating <= results->match[old_match].rating))
1023  return; // New one not good enough.
1024 
1025  if (!unicharset.get_fragment(new_result.unichar_id))
1026  results->HasNonfragment = true;
1027 
1028  if (old_match < results->match.size()) {
1029  results->match[old_match].rating = new_result.rating;
1030  } else {
1031  results->match.push_back(new_result);
1032  }
1033 
1034  if (new_result.rating > results->best_rating &&
1035  // Ensure that fragments do not affect best rating, class and config.
1036  // This is needed so that at least one non-fragmented character is
1037  // always present in the results.
1038  // TODO(daria): verify that this helps accuracy and does not
1039  // hurt performance.
1040  !unicharset.get_fragment(new_result.unichar_id)) {
1041  results->best_match_index = old_match;
1042  results->best_rating = new_result.rating;
1043  results->best_unichar_id = new_result.unichar_id;
1044  }
1045 } /* AddNewResult */
1046 
1047 
1048 /*---------------------------------------------------------------------------*/
1070 void Classify::AmbigClassifier(
1071  const GenericVector<INT_FEATURE_STRUCT>& int_features,
1072  const INT_FX_RESULT_STRUCT& fx_info,
1073  const TBLOB *blob,
1074  INT_TEMPLATES templates,
1075  ADAPT_CLASS *classes,
1076  UNICHAR_ID *ambiguities,
1077  ADAPT_RESULTS *results) {
1078  if (int_features.empty()) return;
1079  uinT8* CharNormArray = new uinT8[unicharset.size()];
1080  UnicharRating int_result;
1081 
1082  results->BlobLength = GetCharNormFeature(fx_info, templates, NULL,
1083  CharNormArray);
1084  bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1085  if (debug)
1086  tprintf("AM Matches = ");
1087 
1088  int top = blob->bounding_box().top();
1089  int bottom = blob->bounding_box().bottom();
1090  while (*ambiguities >= 0) {
1091  CLASS_ID class_id = *ambiguities;
1092 
1093  int_result.unichar_id = class_id;
1094  im_.Match(ClassForClassId(templates, class_id),
1095  AllProtosOn, AllConfigsOn,
1096  int_features.size(), &int_features[0],
1097  &int_result,
1098  classify_adapt_feature_threshold, NO_DEBUG,
1099  matcher_debug_separate_windows);
1100 
1101  ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0,
1102  results->BlobLength,
1104  CharNormArray, &int_result, results);
1105  ambiguities++;
1106  }
1107  delete [] CharNormArray;
1108 } /* AmbigClassifier */
1109 
1110 /*---------------------------------------------------------------------------*/
1113 void Classify::MasterMatcher(INT_TEMPLATES templates,
1114  inT16 num_features,
1116  const uinT8* norm_factors,
1117  ADAPT_CLASS* classes,
1118  int debug,
1119  int matcher_multiplier,
1120  const TBOX& blob_box,
1121  const GenericVector<CP_RESULT_STRUCT>& results,
1122  ADAPT_RESULTS* final_results) {
1123  int top = blob_box.top();
1124  int bottom = blob_box.bottom();
1125  UnicharRating int_result;
1126  for (int c = 0; c < results.size(); c++) {
1127  CLASS_ID class_id = results[c].Class;
1128  BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
1129  : AllProtosOn;
1130  BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs
1131  : AllConfigsOn;
1132 
1133  int_result.unichar_id = class_id;
1134  im_.Match(ClassForClassId(templates, class_id),
1135  protos, configs,
1136  num_features, features,
1137  &int_result, classify_adapt_feature_threshold, debug,
1138  matcher_debug_separate_windows);
1139  bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1140  ExpandShapesAndApplyCorrections(classes, debug, class_id, bottom, top,
1141  results[c].Rating,
1142  final_results->BlobLength,
1143  matcher_multiplier, norm_factors,
1144  &int_result, final_results);
1145  }
1146 }
1147 
1148 // Converts configs to fonts, and if the result is not adapted, and a
1149 // shape_table_ is present, the shape is expanded to include all
1150 // unichar_ids represented, before applying a set of corrections to the
1151 // distance rating in int_result, (see ComputeCorrectedRating.)
1152 // The results are added to the final_results output.
1153 void Classify::ExpandShapesAndApplyCorrections(
1154  ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
1155  float cp_rating, int blob_length, int matcher_multiplier,
1156  const uinT8* cn_factors,
1157  UnicharRating* int_result, ADAPT_RESULTS* final_results) {
1158  if (classes != NULL) {
1159  // Adapted result. Convert configs to fontinfo_ids.
1160  int_result->adapted = true;
1161  for (int f = 0; f < int_result->fonts.size(); ++f) {
1162  int_result->fonts[f].fontinfo_id =
1163  GetFontinfoId(classes[class_id], int_result->fonts[f].fontinfo_id);
1164  }
1165  } else {
1166  // Pre-trained result. Map fonts using font_sets_.
1167  int_result->adapted = false;
1168  for (int f = 0; f < int_result->fonts.size(); ++f) {
1169  int_result->fonts[f].fontinfo_id =
1170  ClassAndConfigIDToFontOrShapeID(class_id,
1171  int_result->fonts[f].fontinfo_id);
1172  }
1173  if (shape_table_ != NULL) {
1174  // Two possible cases:
1175  // 1. Flat shapetable. All unichar-ids of the shapes referenced by
1176  // int_result->fonts are the same. In this case build a new vector of
1177  // mapped fonts and replace the fonts in int_result.
1178  // 2. Multi-unichar shapetable. Variable unichars in the shapes referenced
1179  // by int_result. In this case, build a vector of UnicharRating to
1180  // gather together different font-ids for each unichar. Also covers case1.
1181  GenericVector<UnicharRating> mapped_results;
1182  for (int f = 0; f < int_result->fonts.size(); ++f) {
1183  int shape_id = int_result->fonts[f].fontinfo_id;
1184  const Shape& shape = shape_table_->GetShape(shape_id);
1185  for (int c = 0; c < shape.size(); ++c) {
1186  int unichar_id = shape[c].unichar_id;
1187  if (!unicharset.get_enabled(unichar_id)) continue;
1188  // Find the mapped_result for unichar_id.
1189  int r = 0;
1190  for (r = 0; r < mapped_results.size() &&
1191  mapped_results[r].unichar_id != unichar_id; ++r) {}
1192  if (r == mapped_results.size()) {
1193  mapped_results.push_back(*int_result);
1194  mapped_results[r].unichar_id = unichar_id;
1195  mapped_results[r].fonts.truncate(0);
1196  }
1197  for (int i = 0; i < shape[c].font_ids.size(); ++i) {
1198  mapped_results[r].fonts.push_back(
1199  ScoredFont(shape[c].font_ids[i], int_result->fonts[f].score));
1200  }
1201  }
1202  }
1203  for (int m = 0; m < mapped_results.size(); ++m) {
1204  mapped_results[m].rating =
1205  ComputeCorrectedRating(debug, mapped_results[m].unichar_id,
1206  cp_rating, int_result->rating,
1207  int_result->feature_misses, bottom, top,
1208  blob_length, matcher_multiplier, cn_factors);
1209  AddNewResult(mapped_results[m], final_results);
1210  }
1211  return;
1212  }
1213  }
1214  if (unicharset.get_enabled(class_id)) {
1215  int_result->rating = ComputeCorrectedRating(debug, class_id, cp_rating,
1216  int_result->rating,
1217  int_result->feature_misses,
1218  bottom, top, blob_length,
1219  matcher_multiplier, cn_factors);
1220  AddNewResult(*int_result, final_results);
1221  }
1222 }
1223 
1224 // Applies a set of corrections to the confidence im_rating,
1225 // including the cn_correction, miss penalty and additional penalty
1226 // for non-alnums being vertical misfits. Returns the corrected confidence.
1227 double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
1228  double cp_rating, double im_rating,
1229  int feature_misses,
1230  int bottom, int top,
1231  int blob_length, int matcher_multiplier,
1232  const uinT8* cn_factors) {
1233  // Compute class feature corrections.
1234  double cn_corrected = im_.ApplyCNCorrection(1.0 - im_rating, blob_length,
1235  cn_factors[unichar_id],
1236  matcher_multiplier);
1237  double miss_penalty = tessedit_class_miss_scale * feature_misses;
1238  double vertical_penalty = 0.0;
1239  // Penalize non-alnums for being vertical misfits.
1240  if (!unicharset.get_isalpha(unichar_id) &&
1241  !unicharset.get_isdigit(unichar_id) &&
1242  cn_factors[unichar_id] != 0 && classify_misfit_junk_penalty > 0.0) {
1243  int min_bottom, max_bottom, min_top, max_top;
1244  unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom,
1245  &min_top, &max_top);
1246  if (debug) {
1247  tprintf("top=%d, vs [%d, %d], bottom=%d, vs [%d, %d]\n",
1248  top, min_top, max_top, bottom, min_bottom, max_bottom);
1249  }
1250  if (top < min_top || top > max_top ||
1251  bottom < min_bottom || bottom > max_bottom) {
1252  vertical_penalty = classify_misfit_junk_penalty;
1253  }
1254  }
1255  double result = 1.0 - (cn_corrected + miss_penalty + vertical_penalty);
1256  if (result < WORST_POSSIBLE_RATING)
1257  result = WORST_POSSIBLE_RATING;
1258  if (debug) {
1259  tprintf("%s: %2.1f%%(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
1260  unicharset.id_to_unichar(unichar_id),
1261  result * 100.0,
1262  cp_rating * 100.0,
1263  (1.0 - im_rating) * 100.0,
1264  (cn_corrected - (1.0 - im_rating)) * 100.0,
1265  cn_factors[unichar_id],
1266  miss_penalty * 100.0,
1267  vertical_penalty * 100.0);
1268  }
1269  return result;
1270 }
1271 
1272 /*---------------------------------------------------------------------------*/
1292 UNICHAR_ID *Classify::BaselineClassifier(
1293  TBLOB *Blob, const GenericVector<INT_FEATURE_STRUCT>& int_features,
1294  const INT_FX_RESULT_STRUCT& fx_info,
1295  ADAPT_TEMPLATES Templates, ADAPT_RESULTS *Results) {
1296  if (int_features.empty()) return NULL;
1297  uinT8* CharNormArray = new uinT8[unicharset.size()];
1298  ClearCharNormArray(CharNormArray);
1299 
1301  PruneClasses(Templates->Templates, int_features.size(), -1, &int_features[0],
1302  CharNormArray, BaselineCutoffs, &Results->CPResults);
1303 
1304  if (matcher_debug_level >= 2 || classify_debug_level > 1)
1305  tprintf("BL Matches = ");
1306 
1307  MasterMatcher(Templates->Templates, int_features.size(), &int_features[0],
1308  CharNormArray,
1309  Templates->Class, matcher_debug_flags, 0,
1310  Blob->bounding_box(), Results->CPResults, Results);
1311 
1312  delete [] CharNormArray;
1313  CLASS_ID ClassId = Results->best_unichar_id;
1314  if (ClassId == INVALID_UNICHAR_ID || Results->best_match_index < 0)
1315  return NULL;
1316 
1317  return Templates->Class[ClassId]->
1318  Config[Results->match[Results->best_match_index].config].Perm->Ambigs;
1319 } /* BaselineClassifier */
1320 
1321 
1322 /*---------------------------------------------------------------------------*/
1341 int Classify::CharNormClassifier(TBLOB *blob,
1342  const TrainingSample& sample,
1343  ADAPT_RESULTS *adapt_results) {
1344  // This is the length that is used for scaling ratings vs certainty.
1345  adapt_results->BlobLength =
1347  GenericVector<UnicharRating> unichar_results;
1348  static_classifier_->UnicharClassifySample(sample, blob->denorm().pix(), 0,
1349  -1, &unichar_results);
1350  // Convert results to the format used internally by AdaptiveClassifier.
1351  for (int r = 0; r < unichar_results.size(); ++r) {
1352  AddNewResult(unichar_results[r], adapt_results);
1353  }
1354  return sample.num_features();
1355 } /* CharNormClassifier */
1356 
1357 // As CharNormClassifier, but operates on a TrainingSample and outputs to
1358 // a GenericVector of ShapeRating without conversion to classes.
1359 int Classify::CharNormTrainingSample(bool pruner_only,
1360  int keep_this,
1361  const TrainingSample& sample,
1362  GenericVector<UnicharRating>* results) {
1363  results->clear();
1364  ADAPT_RESULTS* adapt_results = new ADAPT_RESULTS();
1365  adapt_results->Initialize();
1366  // Compute the bounding box of the features.
1367  int num_features = sample.num_features();
1368  // Only the top and bottom of the blob_box are used by MasterMatcher, so
1369  // fabricate right and left using top and bottom.
1370  TBOX blob_box(sample.geo_feature(GeoBottom), sample.geo_feature(GeoBottom),
1371  sample.geo_feature(GeoTop), sample.geo_feature(GeoTop));
1372  // Compute the char_norm_array from the saved cn_feature.
1373  FEATURE norm_feature = sample.GetCNFeature();
1374  uinT8* char_norm_array = new uinT8[unicharset.size()];
1375  int num_pruner_classes = MAX(unicharset.size(),
1376  PreTrainedTemplates->NumClasses);
1377  uinT8* pruner_norm_array = new uinT8[num_pruner_classes];
1378  adapt_results->BlobLength =
1379  static_cast<int>(ActualOutlineLength(norm_feature) * 20 + 0.5);
1380  ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
1381  pruner_norm_array);
1382 
1383  PruneClasses(PreTrainedTemplates, num_features, keep_this, sample.features(),
1384  pruner_norm_array,
1385  shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs,
1386  &adapt_results->CPResults);
1387  delete [] pruner_norm_array;
1388  if (keep_this >= 0) {
1389  adapt_results->CPResults[0].Class = keep_this;
1390  adapt_results->CPResults.truncate(1);
1391  }
1392  if (pruner_only) {
1393  // Convert pruner results to output format.
1394  for (int i = 0; i < adapt_results->CPResults.size(); ++i) {
1395  int class_id = adapt_results->CPResults[i].Class;
1396  results->push_back(
1397  UnicharRating(class_id, 1.0f - adapt_results->CPResults[i].Rating));
1398  }
1399  } else {
1400  MasterMatcher(PreTrainedTemplates, num_features, sample.features(),
1401  char_norm_array,
1402  NULL, matcher_debug_flags,
1404  blob_box, adapt_results->CPResults, adapt_results);
1405  // Convert master matcher results to output format.
1406  for (int i = 0; i < adapt_results->match.size(); i++) {
1407  results->push_back(adapt_results->match[i]);
1408  }
1409  results->sort(&UnicharRating::SortDescendingRating);
1410  }
1411  delete [] char_norm_array;
1412  delete adapt_results;
1413  return num_features;
1414 } /* CharNormTrainingSample */
1415 
1416 
1417 /*---------------------------------------------------------------------------*/
1432 void Classify::ClassifyAsNoise(ADAPT_RESULTS *results) {
1433  float rating = results->BlobLength / matcher_avg_noise_size;
1434  rating *= rating;
1435  rating /= 1.0 + rating;
1436 
1437  AddNewResult(UnicharRating(UNICHAR_SPACE, 1.0f - rating), results);
1438 } /* ClassifyAsNoise */
1439 
1446 void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
1447  ADAPT_RESULTS *Results,
1448  BLOB_CHOICE_LIST *Choices) {
1449  assert(Choices != NULL);
1450  FLOAT32 Rating;
1451  FLOAT32 Certainty;
1452  BLOB_CHOICE_IT temp_it;
1453  bool contains_nonfrag = false;
1454  temp_it.set_to_list(Choices);
1455  int choices_length = 0;
1456  // With no shape_table_ maintain the previous MAX_MATCHES as the maximum
1457  // number of returned results, but with a shape_table_ we want to have room
1458  // for at least the biggest shape (which might contain hundreds of Indic
1459  // grapheme fragments) and more, so use double the size of the biggest shape
1460  // if that is more than the default.
1461  int max_matches = MAX_MATCHES;
1462  if (shape_table_ != NULL) {
1463  max_matches = shape_table_->MaxNumUnichars() * 2;
1464  if (max_matches < MAX_MATCHES)
1465  max_matches = MAX_MATCHES;
1466  }
1467 
1468  float best_certainty = -MAX_FLOAT32;
1469  for (int i = 0; i < Results->match.size(); i++) {
1470  const UnicharRating& result = Results->match[i];
1471  bool adapted = result.adapted;
1472  bool current_is_frag = (unicharset.get_fragment(result.unichar_id) != NULL);
1473  if (temp_it.length()+1 == max_matches &&
1474  !contains_nonfrag && current_is_frag) {
1475  continue; // look for a non-fragmented character to fill the
1476  // last spot in Choices if only fragments are present
1477  }
1478  // BlobLength can never be legally 0, this means recognition failed.
1479  // But we must return a classification result because some invoking
1480  // functions (chopper/permuter) do not anticipate a null blob choice.
1481  // So we need to assign a poor, but not infinitely bad score.
1482  if (Results->BlobLength == 0) {
1483  Certainty = -20;
1484  Rating = 100; // should be -certainty * real_blob_length
1485  } else {
1486  Rating = Certainty = (1.0f - result.rating);
1487  Rating *= rating_scale * Results->BlobLength;
1488  Certainty *= -(getDict().certainty_scale);
1489  }
1490  // Adapted results, by their very nature, should have good certainty.
1491  // Those that don't are at best misleading, and often lead to errors,
1492  // so don't accept adapted results that are too far behind the best result,
1493  // whether adapted or static.
1494  // TODO(rays) find some way of automatically tuning these constants.
1495  if (Certainty > best_certainty) {
1496  best_certainty = MIN(Certainty, classify_adapted_pruning_threshold);
1497  } else if (adapted &&
1498  Certainty / classify_adapted_pruning_factor < best_certainty) {
1499  continue; // Don't accept bad adapted results.
1500  }
1501 
1502  float min_xheight, max_xheight, yshift;
1503  denorm.XHeightRange(result.unichar_id, unicharset, box,
1504  &min_xheight, &max_xheight, &yshift);
1505  BLOB_CHOICE* choice =
1506  new BLOB_CHOICE(result.unichar_id, Rating, Certainty,
1507  unicharset.get_script(result.unichar_id),
1508  min_xheight, max_xheight, yshift,
1509  adapted ? BCC_ADAPTED_CLASSIFIER
1511  choice->set_fonts(result.fonts);
1512  temp_it.add_to_end(choice);
1513  contains_nonfrag |= !current_is_frag; // update contains_nonfrag
1514  choices_length++;
1515  if (choices_length >= max_matches) break;
1516  }
1517  Results->match.truncate(choices_length);
1518 } // ConvertMatchesToChoices
1519 
1520 
1521 /*---------------------------------------------------------------------------*/
1522 #ifndef GRAPHICS_DISABLED
1523 
1533 void Classify::DebugAdaptiveClassifier(TBLOB *blob,
1534  ADAPT_RESULTS *Results) {
1535  if (static_classifier_ == NULL) return;
1536  INT_FX_RESULT_STRUCT fx_info;
1539  BlobToTrainingSample(*blob, false, &fx_info, &bl_features);
1540  if (sample == NULL) return;
1541  static_classifier_->DebugDisplay(*sample, blob->denorm().pix(),
1542  Results->best_unichar_id);
1543 } /* DebugAdaptiveClassifier */
1544 #endif
1545 
1546 /*---------------------------------------------------------------------------*/
1569 void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
1570  UNICHAR_ID *Ambiguities;
1571 
1572  INT_FX_RESULT_STRUCT fx_info;
1575  BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
1576  &bl_features);
1577  if (sample == NULL) return;
1578 
1579  if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min ||
1580  tess_cn_matching) {
1581  CharNormClassifier(Blob, *sample, Results);
1582  } else {
1583  Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
1584  AdaptedTemplates, Results);
1585  if ((!Results->match.empty() &&
1586  MarginalMatch(Results->best_rating,
1587  matcher_reliable_adaptive_result) &&
1588  !tess_bn_matching) ||
1589  Results->match.empty()) {
1590  CharNormClassifier(Blob, *sample, Results);
1591  } else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) {
1592  AmbigClassifier(bl_features, fx_info, Blob,
1593  PreTrainedTemplates,
1594  AdaptedTemplates->Class,
1595  Ambiguities,
1596  Results);
1597  }
1598  }
1599 
1600  // Force the blob to be classified as noise
1601  // if the results contain only fragments.
1602  // TODO(daria): verify that this is better than
1603  // just adding a NULL classification.
1604  if (!Results->HasNonfragment || Results->match.empty())
1605  ClassifyAsNoise(Results);
1606  delete sample;
1607 } /* DoAdaptiveMatch */
1608 
1609 /*---------------------------------------------------------------------------*/
1626 UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob,
1627  CLASS_ID CorrectClass) {
1628  ADAPT_RESULTS *Results = new ADAPT_RESULTS();
1629  UNICHAR_ID *Ambiguities;
1630  int i;
1631 
1632  Results->Initialize();
1633  INT_FX_RESULT_STRUCT fx_info;
1636  BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
1637  &bl_features);
1638  if (sample == NULL) {
1639  delete Results;
1640  return NULL;
1641  }
1642 
1643  CharNormClassifier(Blob, *sample, Results);
1644  delete sample;
1645  RemoveBadMatches(Results);
1646  Results->match.sort(&UnicharRating::SortDescendingRating);
1647 
1648  /* copy the class id's into an string of ambiguities - don't copy if
1649  the correct class is the only class id matched */
1650  Ambiguities = new UNICHAR_ID[Results->match.size() + 1];
1651  if (Results->match.size() > 1 ||
1652  (Results->match.size() == 1 &&
1653  Results->match[0].unichar_id != CorrectClass)) {
1654  for (i = 0; i < Results->match.size(); i++)
1655  Ambiguities[i] = Results->match[i].unichar_id;
1656  Ambiguities[i] = -1;
1657  } else {
1658  Ambiguities[0] = -1;
1659  }
1660 
1661  delete Results;
1662  return Ambiguities;
1663 } /* GetAmbiguities */
1664 
1665 // Returns true if the given blob looks too dissimilar to any character
1666 // present in the classifier templates.
1667 bool Classify::LooksLikeGarbage(TBLOB *blob) {
1668  BLOB_CHOICE_LIST *ratings = new BLOB_CHOICE_LIST();
1669  AdaptiveClassifier(blob, ratings);
1670  BLOB_CHOICE_IT ratings_it(ratings);
1671  const UNICHARSET &unicharset = getDict().getUnicharset();
1672  if (classify_debug_character_fragments) {
1673  print_ratings_list("======================\nLooksLikeGarbage() got ",
1674  ratings, unicharset);
1675  }
1676  for (ratings_it.mark_cycle_pt(); !ratings_it.cycled_list();
1677  ratings_it.forward()) {
1678  if (unicharset.get_fragment(ratings_it.data()->unichar_id()) != NULL) {
1679  continue;
1680  }
1681  float certainty = ratings_it.data()->certainty();
1682  delete ratings;
1683  return certainty <
1684  classify_character_fragments_garbage_certainty_threshold;
1685  }
1686  delete ratings;
1687  return true; // no whole characters in ratings
1688 }
1689 
1690 /*---------------------------------------------------------------------------*/
1714 int Classify::GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info,
1715  INT_TEMPLATES templates,
1716  uinT8* pruner_norm_array,
1717  uinT8* char_norm_array) {
1718  FEATURE norm_feature = NewFeature(&CharNormDesc);
1719  float baseline = kBlnBaselineOffset;
1720  float scale = MF_SCALE_FACTOR;
1721  norm_feature->Params[CharNormY] = (fx_info.Ymean - baseline) * scale;
1722  norm_feature->Params[CharNormLength] =
1723  fx_info.Length * scale / LENGTH_COMPRESSION;
1724  norm_feature->Params[CharNormRx] = fx_info.Rx * scale;
1725  norm_feature->Params[CharNormRy] = fx_info.Ry * scale;
1726  // Deletes norm_feature.
1727  ComputeCharNormArrays(norm_feature, templates, char_norm_array,
1728  pruner_norm_array);
1729  return IntCastRounded(fx_info.Length / kStandardFeatureLength);
1730 } /* GetCharNormFeature */
1731 
1732 // Computes the char_norm_array for the unicharset and, if not NULL, the
1733 // pruner_array as appropriate according to the existence of the shape_table.
1734 void Classify::ComputeCharNormArrays(FEATURE_STRUCT* norm_feature,
1735  INT_TEMPLATES_STRUCT* templates,
1736  uinT8* char_norm_array,
1737  uinT8* pruner_array) {
1738  ComputeIntCharNormArray(*norm_feature, char_norm_array);
1739  if (pruner_array != NULL) {
1740  if (shape_table_ == NULL) {
1741  ComputeIntCharNormArray(*norm_feature, pruner_array);
1742  } else {
1743  memset(pruner_array, MAX_UINT8,
1744  templates->NumClasses * sizeof(pruner_array[0]));
1745  // Each entry in the pruner norm array is the MIN of all the entries of
1746  // the corresponding unichars in the CharNormArray.
1747  for (int id = 0; id < templates->NumClasses; ++id) {
1748  int font_set_id = templates->Class[id]->font_set_id;
1749  const FontSet &fs = fontset_table_.get(font_set_id);
1750  for (int config = 0; config < fs.size; ++config) {
1751  const Shape& shape = shape_table_->GetShape(fs.configs[config]);
1752  for (int c = 0; c < shape.size(); ++c) {
1753  if (char_norm_array[shape[c].unichar_id] < pruner_array[id])
1754  pruner_array[id] = char_norm_array[shape[c].unichar_id];
1755  }
1756  }
1757  }
1758  }
1759  }
1760  FreeFeature(norm_feature);
1761 }
1762 
1763 /*---------------------------------------------------------------------------*/
1778 int Classify::MakeNewTemporaryConfig(ADAPT_TEMPLATES Templates,
1779  CLASS_ID ClassId,
1780  int FontinfoId,
1781  int NumFeatures,
1782  INT_FEATURE_ARRAY Features,
1783  FEATURE_SET FloatFeatures) {
1784  INT_CLASS IClass;
1785  ADAPT_CLASS Class;
1786  PROTO_ID OldProtos[MAX_NUM_PROTOS];
1787  FEATURE_ID BadFeatures[MAX_NUM_INT_FEATURES];
1788  int NumOldProtos;
1789  int NumBadFeatures;
1790  int MaxProtoId, OldMaxProtoId;
1791  int BlobLength = 0;
1792  int MaskSize;
1793  int ConfigId;
1795  int i;
1796  int debug_level = NO_DEBUG;
1797 
1798  if (classify_learning_debug_level >= 3)
1799  debug_level =
1801 
1802  IClass = ClassForClassId(Templates->Templates, ClassId);
1803  Class = Templates->Class[ClassId];
1804 
1805  if (IClass->NumConfigs >= MAX_NUM_CONFIGS) {
1806  ++NumAdaptationsFailed;
1807  if (classify_learning_debug_level >= 1)
1808  cprintf("Cannot make new temporary config: maximum number exceeded.\n");
1809  return -1;
1810  }
1811 
1812  OldMaxProtoId = IClass->NumProtos - 1;
1813 
1814  NumOldProtos = im_.FindGoodProtos(IClass, AllProtosOn, AllConfigsOff,
1815  BlobLength, NumFeatures, Features,
1816  OldProtos, classify_adapt_proto_threshold,
1817  debug_level);
1818 
1819  MaskSize = WordsInVectorOfSize(MAX_NUM_PROTOS);
1820  zero_all_bits(TempProtoMask, MaskSize);
1821  for (i = 0; i < NumOldProtos; i++)
1822  SET_BIT(TempProtoMask, OldProtos[i]);
1823 
1824  NumBadFeatures = im_.FindBadFeatures(IClass, TempProtoMask, AllConfigsOn,
1825  BlobLength, NumFeatures, Features,
1826  BadFeatures,
1827  classify_adapt_feature_threshold,
1828  debug_level);
1829 
1830  MaxProtoId = MakeNewTempProtos(FloatFeatures, NumBadFeatures, BadFeatures,
1831  IClass, Class, TempProtoMask);
1832  if (MaxProtoId == NO_PROTO) {
1833  ++NumAdaptationsFailed;
1834  if (classify_learning_debug_level >= 1)
1835  cprintf("Cannot make new temp protos: maximum number exceeded.\n");
1836  return -1;
1837  }
1838 
1839  ConfigId = AddIntConfig(IClass);
1840  ConvertConfig(TempProtoMask, ConfigId, IClass);
1841  Config = NewTempConfig(MaxProtoId, FontinfoId);
1842  TempConfigFor(Class, ConfigId) = Config;
1843  copy_all_bits(TempProtoMask, Config->Protos, Config->ProtoVectorSize);
1844 
1845  if (classify_learning_debug_level >= 1)
1846  cprintf("Making new temp config %d fontinfo id %d"
1847  " using %d old and %d new protos.\n",
1848  ConfigId, Config->FontinfoId,
1849  NumOldProtos, MaxProtoId - OldMaxProtoId);
1850 
1851  return ConfigId;
1852 } /* MakeNewTemporaryConfig */
1853 
1854 /*---------------------------------------------------------------------------*/
1875 PROTO_ID Classify::MakeNewTempProtos(FEATURE_SET Features,
1876  int NumBadFeat,
1877  FEATURE_ID BadFeat[],
1878  INT_CLASS IClass,
1879  ADAPT_CLASS Class,
1880  BIT_VECTOR TempProtoMask) {
1881  FEATURE_ID *ProtoStart;
1882  FEATURE_ID *ProtoEnd;
1883  FEATURE_ID *LastBad;
1884  TEMP_PROTO TempProto;
1885  PROTO Proto;
1886  FEATURE F1, F2;
1887  FLOAT32 X1, X2, Y1, Y2;
1888  FLOAT32 A1, A2, AngleDelta;
1889  FLOAT32 SegmentLength;
1890  PROTO_ID Pid;
1891 
1892  for (ProtoStart = BadFeat, LastBad = ProtoStart + NumBadFeat;
1893  ProtoStart < LastBad; ProtoStart = ProtoEnd) {
1894  F1 = Features->Features[*ProtoStart];
1895  X1 = F1->Params[PicoFeatX];
1896  Y1 = F1->Params[PicoFeatY];
1897  A1 = F1->Params[PicoFeatDir];
1898 
1899  for (ProtoEnd = ProtoStart + 1,
1900  SegmentLength = GetPicoFeatureLength();
1901  ProtoEnd < LastBad;
1902  ProtoEnd++, SegmentLength += GetPicoFeatureLength()) {
1903  F2 = Features->Features[*ProtoEnd];
1904  X2 = F2->Params[PicoFeatX];
1905  Y2 = F2->Params[PicoFeatY];
1906  A2 = F2->Params[PicoFeatDir];
1907 
1908  AngleDelta = fabs(A1 - A2);
1909  if (AngleDelta > 0.5)
1910  AngleDelta = 1.0 - AngleDelta;
1911 
1912  if (AngleDelta > matcher_clustering_max_angle_delta ||
1913  fabs(X1 - X2) > SegmentLength ||
1914  fabs(Y1 - Y2) > SegmentLength)
1915  break;
1916  }
1917 
1918  F2 = Features->Features[*(ProtoEnd - 1)];
1919  X2 = F2->Params[PicoFeatX];
1920  Y2 = F2->Params[PicoFeatY];
1921  A2 = F2->Params[PicoFeatDir];
1922 
1923  Pid = AddIntProto(IClass);
1924  if (Pid == NO_PROTO)
1925  return (NO_PROTO);
1926 
1927  TempProto = NewTempProto();
1928  Proto = &(TempProto->Proto);
1929 
1930  /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
1931  ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
1932  instead of the -0.25 to 0.75 used in baseline normalization */
1933  Proto->Length = SegmentLength;
1934  Proto->Angle = A1;
1935  Proto->X = (X1 + X2) / 2.0;
1936  Proto->Y = (Y1 + Y2) / 2.0 - Y_DIM_OFFSET;
1937  FillABC(Proto);
1938 
1939  TempProto->ProtoId = Pid;
1940  SET_BIT(TempProtoMask, Pid);
1941 
1942  ConvertProto(Proto, Pid, IClass);
1943  AddProtoToProtoPruner(Proto, Pid, IClass,
1944  classify_learning_debug_level >= 2);
1945 
1946  Class->TempProtos = push(Class->TempProtos, TempProto);
1947  }
1948  return IClass->NumProtos - 1;
1949 } /* MakeNewTempProtos */
1950 
1951 /*---------------------------------------------------------------------------*/
1964 void Classify::MakePermanent(ADAPT_TEMPLATES Templates,
1965  CLASS_ID ClassId,
1966  int ConfigId,
1967  TBLOB *Blob) {
1968  UNICHAR_ID *Ambigs;
1970  ADAPT_CLASS Class;
1971  PROTO_KEY ProtoKey;
1972 
1973  Class = Templates->Class[ClassId];
1974  Config = TempConfigFor(Class, ConfigId);
1975 
1976  MakeConfigPermanent(Class, ConfigId);
1977  if (Class->NumPermConfigs == 0)
1978  Templates->NumPermClasses++;
1979  Class->NumPermConfigs++;
1980 
1981  // Initialize permanent config.
1982  Ambigs = GetAmbiguities(Blob, ClassId);
1983  PERM_CONFIG Perm = (PERM_CONFIG) malloc(sizeof(PERM_CONFIG_STRUCT));
1984  Perm->Ambigs = Ambigs;
1985  Perm->FontinfoId = Config->FontinfoId;
1986 
1987  // Free memory associated with temporary config (since ADAPTED_CONFIG
1988  // is a union we need to clean up before we record permanent config).
1989  ProtoKey.Templates = Templates;
1990  ProtoKey.ClassId = ClassId;
1991  ProtoKey.ConfigId = ConfigId;
1992  Class->TempProtos = delete_d(Class->TempProtos, &ProtoKey, MakeTempProtoPerm);
1993  FreeTempConfig(Config);
1994 
1995  // Record permanent config.
1996  PermConfigFor(Class, ConfigId) = Perm;
1997 
1998  if (classify_learning_debug_level >= 1) {
1999  tprintf("Making config %d for %s (ClassId %d) permanent:"
2000  " fontinfo id %d, ambiguities '",
2001  ConfigId, getDict().getUnicharset().debug_str(ClassId).string(),
2002  ClassId, PermConfigFor(Class, ConfigId)->FontinfoId);
2003  for (UNICHAR_ID *AmbigsPointer = Ambigs;
2004  *AmbigsPointer >= 0; ++AmbigsPointer)
2005  tprintf("%s", unicharset.id_to_unichar(*AmbigsPointer));
2006  tprintf("'.\n");
2007  }
2008 } /* MakePermanent */
2009 } // namespace tesseract
2010 
2011 /*---------------------------------------------------------------------------*/
2026 int MakeTempProtoPerm(void *item1, void *item2) {
2027  ADAPT_CLASS Class;
2029  TEMP_PROTO TempProto;
2030  PROTO_KEY *ProtoKey;
2031 
2032  TempProto = (TEMP_PROTO) item1;
2033  ProtoKey = (PROTO_KEY *) item2;
2034 
2035  Class = ProtoKey->Templates->Class[ProtoKey->ClassId];
2036  Config = TempConfigFor(Class, ProtoKey->ConfigId);
2037 
2038  if (TempProto->ProtoId > Config->MaxProtoId ||
2039  !test_bit (Config->Protos, TempProto->ProtoId))
2040  return FALSE;
2041 
2042  MakeProtoPermanent(Class, TempProto->ProtoId);
2043  AddProtoToClassPruner(&(TempProto->Proto), ProtoKey->ClassId,
2044  ProtoKey->Templates->Templates);
2045  FreeTempProto(TempProto);
2046 
2047  return TRUE;
2048 } /* MakeTempProtoPerm */
2049 
2050 /*---------------------------------------------------------------------------*/
2051 namespace tesseract {
2062 void Classify::PrintAdaptiveMatchResults(const ADAPT_RESULTS& results) {
2063  for (int i = 0; i < results.match.size(); ++i) {
2064  tprintf("%s ", unicharset.debug_str(results.match[i].unichar_id).string());
2065  results.match[i].Print();
2066  }
2067 } /* PrintAdaptiveMatchResults */
2068 
2069 /*---------------------------------------------------------------------------*/
2085 void Classify::RemoveBadMatches(ADAPT_RESULTS *Results) {
2086  int Next, NextGood;
2087  FLOAT32 BadMatchThreshold;
2088  static const char* romans = "i v x I V X";
2089  BadMatchThreshold = Results->best_rating - matcher_bad_match_pad;
2090 
2091  if (classify_bln_numeric_mode) {
2092  UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ?
2093  unicharset.unichar_to_id("1") : -1;
2094  UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ?
2095  unicharset.unichar_to_id("0") : -1;
2096  float scored_one = ScoredUnichar(unichar_id_one, *Results);
2097  float scored_zero = ScoredUnichar(unichar_id_zero, *Results);
2098 
2099  for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2100  const UnicharRating& match = Results->match[Next];
2101  if (match.rating >= BadMatchThreshold) {
2102  if (!unicharset.get_isalpha(match.unichar_id) ||
2103  strstr(romans,
2104  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2105  } else if (unicharset.eq(match.unichar_id, "l") &&
2106  scored_one < BadMatchThreshold) {
2107  Results->match[Next].unichar_id = unichar_id_one;
2108  } else if (unicharset.eq(match.unichar_id, "O") &&
2109  scored_zero < BadMatchThreshold) {
2110  Results->match[Next].unichar_id = unichar_id_zero;
2111  } else {
2112  Results->match[Next].unichar_id = INVALID_UNICHAR_ID; // Don't copy.
2113  }
2114  if (Results->match[Next].unichar_id != INVALID_UNICHAR_ID) {
2115  if (NextGood == Next) {
2116  ++NextGood;
2117  } else {
2118  Results->match[NextGood++] = Results->match[Next];
2119  }
2120  }
2121  }
2122  }
2123  } else {
2124  for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2125  if (Results->match[Next].rating >= BadMatchThreshold) {
2126  if (NextGood == Next) {
2127  ++NextGood;
2128  } else {
2129  Results->match[NextGood++] = Results->match[Next];
2130  }
2131  }
2132  }
2133  }
2134  Results->match.truncate(NextGood);
2135 } /* RemoveBadMatches */
2136 
2137 /*----------------------------------------------------------------------------*/
2147 void Classify::RemoveExtraPuncs(ADAPT_RESULTS *Results) {
2148  int Next, NextGood;
2149  int punc_count; /*no of garbage characters */
2150  int digit_count;
2151  /*garbage characters */
2152  static char punc_chars[] = ". , ; : / ` ~ ' - = \\ | \" ! _ ^";
2153  static char digit_chars[] = "0 1 2 3 4 5 6 7 8 9";
2154 
2155  punc_count = 0;
2156  digit_count = 0;
2157  for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2158  const UnicharRating& match = Results->match[Next];
2159  bool keep = true;
2160  if (strstr(punc_chars,
2161  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2162  if (punc_count >= 2)
2163  keep = false;
2164  punc_count++;
2165  } else {
2166  if (strstr(digit_chars,
2167  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2168  if (digit_count >= 1)
2169  keep = false;
2170  digit_count++;
2171  }
2172  }
2173  if (keep) {
2174  if (NextGood == Next) {
2175  ++NextGood;
2176  } else {
2177  Results->match[NextGood++] = match;
2178  }
2179  }
2180  }
2181  Results->match.truncate(NextGood);
2182 } /* RemoveExtraPuncs */
2183 
2184 /*---------------------------------------------------------------------------*/
2199  Threshold = (Threshold == matcher_good_threshold) ? 0.9: (1.0 - Threshold);
2200  classify_adapt_proto_threshold.set_value(
2201  ClipToRange<int>(255 * Threshold, 0, 255));
2202  classify_adapt_feature_threshold.set_value(
2203  ClipToRange<int>(255 * Threshold, 0, 255));
2204 } /* SetAdaptiveThreshold */
2205 
2206 /*---------------------------------------------------------------------------*/
2219 void Classify::ShowBestMatchFor(int shape_id,
2221  int num_features) {
2222 #ifndef GRAPHICS_DISABLED
2223  uinT32 config_mask;
2224  if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) {
2225  tprintf("No built-in templates for class/shape %d\n", shape_id);
2226  return;
2227  }
2228  if (num_features <= 0) {
2229  tprintf("Illegal blob (char norm features)!\n");
2230  return;
2231  }
2232  UnicharRating cn_result;
2233  classify_norm_method.set_value(character);
2234  im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
2235  AllProtosOn, AllConfigsOn,
2236  num_features, features, &cn_result,
2237  classify_adapt_feature_threshold, NO_DEBUG,
2238  matcher_debug_separate_windows);
2239  tprintf("\n");
2240  config_mask = 1 << cn_result.config;
2241 
2242  tprintf("Static Shape ID: %d\n", shape_id);
2243  ShowMatchDisplay();
2244  im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
2245  AllProtosOn, &config_mask, // TODO: or reinterpret_cast<BIT_VECTOR>(&config_mask) anyway?
2246  num_features, features, &cn_result,
2247  classify_adapt_feature_threshold,
2248  matcher_debug_flags,
2249  matcher_debug_separate_windows);
2251 #endif // GRAPHICS_DISABLED
2252 } /* ShowBestMatchFor */
2253 
2254 // Returns a string for the classifier class_id: either the corresponding
2255 // unicharset debug_str or the shape_table_ debug str.
2256 STRING Classify::ClassIDToDebugStr(const INT_TEMPLATES_STRUCT* templates,
2257  int class_id, int config_id) const {
2258  STRING class_string;
2259  if (templates == PreTrainedTemplates && shape_table_ != NULL) {
2260  int shape_id = ClassAndConfigIDToFontOrShapeID(class_id, config_id);
2261  class_string = shape_table_->DebugStr(shape_id);
2262  } else {
2263  class_string = unicharset.debug_str(class_id);
2264  }
2265  return class_string;
2266 }
2267 
2268 // Converts a classifier class_id index to a shape_table_ index
2269 int Classify::ClassAndConfigIDToFontOrShapeID(int class_id,
2270  int int_result_config) const {
2271  int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id;
2272  // Older inttemps have no font_ids.
2273  if (font_set_id < 0)
2274  return kBlankFontinfoId;
2275  const FontSet &fs = fontset_table_.get(font_set_id);
2276  ASSERT_HOST(int_result_config >= 0 && int_result_config < fs.size);
2277  return fs.configs[int_result_config];
2278 }
2279 
2280 // Converts a shape_table_ index to a classifier class_id index (not a
2281 // unichar-id!). Uses a search, so not fast.
2282 int Classify::ShapeIDToClassID(int shape_id) const {
2283  for (int id = 0; id < PreTrainedTemplates->NumClasses; ++id) {
2284  int font_set_id = PreTrainedTemplates->Class[id]->font_set_id;
2285  ASSERT_HOST(font_set_id >= 0);
2286  const FontSet &fs = fontset_table_.get(font_set_id);
2287  for (int config = 0; config < fs.size; ++config) {
2288  if (fs.configs[config] == shape_id)
2289  return id;
2290  }
2291  }
2292  tprintf("Shape %d not found\n", shape_id);
2293  return -1;
2294 }
2295 
2296 // Returns true if the given TEMP_CONFIG is good enough to make it
2297 // a permanent config.
2298 bool Classify::TempConfigReliable(CLASS_ID class_id,
2299  const TEMP_CONFIG &config) {
2300  if (classify_learning_debug_level >= 1) {
2301  tprintf("NumTimesSeen for config of %s is %d\n",
2302  getDict().getUnicharset().debug_str(class_id).string(),
2303  config->NumTimesSeen);
2304  }
2305  if (config->NumTimesSeen >= matcher_sufficient_examples_for_prototyping) {
2306  return true;
2307  } else if (config->NumTimesSeen < matcher_min_examples_for_prototyping) {
2308  return false;
2309  } else if (use_ambigs_for_adaption) {
2310  // Go through the ambigs vector and see whether we have already seen
2311  // enough times all the characters represented by the ambigs vector.
2312  const UnicharIdVector *ambigs =
2313  getDict().getUnicharAmbigs().AmbigsForAdaption(class_id);
2314  int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
2315  for (int ambig = 0; ambig < ambigs_size; ++ambig) {
2316  ADAPT_CLASS ambig_class = AdaptedTemplates->Class[(*ambigs)[ambig]];
2317  assert(ambig_class != NULL);
2318  if (ambig_class->NumPermConfigs == 0 &&
2319  ambig_class->MaxNumTimesSeen <
2320  matcher_min_examples_for_prototyping) {
2321  if (classify_learning_debug_level >= 1) {
2322  tprintf("Ambig %s has not been seen enough times,"
2323  " not making config for %s permanent\n",
2324  getDict().getUnicharset().debug_str(
2325  (*ambigs)[ambig]).string(),
2326  getDict().getUnicharset().debug_str(class_id).string());
2327  }
2328  return false;
2329  }
2330  }
2331  }
2332  return true;
2333 }
2334 
2335 void Classify::UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob) {
2336  const UnicharIdVector *ambigs =
2337  getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id);
2338  int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
2339  if (classify_learning_debug_level >= 1) {
2340  tprintf("Running UpdateAmbigsGroup for %s class_id=%d\n",
2341  getDict().getUnicharset().debug_str(class_id).string(), class_id);
2342  }
2343  for (int ambig = 0; ambig < ambigs_size; ++ambig) {
2344  CLASS_ID ambig_class_id = (*ambigs)[ambig];
2345  const ADAPT_CLASS ambigs_class = AdaptedTemplates->Class[ambig_class_id];
2346  for (int cfg = 0; cfg < MAX_NUM_CONFIGS; ++cfg) {
2347  if (ConfigIsPermanent(ambigs_class, cfg)) continue;
2348  const TEMP_CONFIG config =
2349  TempConfigFor(AdaptedTemplates->Class[ambig_class_id], cfg);
2350  if (config != NULL && TempConfigReliable(ambig_class_id, config)) {
2351  if (classify_learning_debug_level >= 1) {
2352  tprintf("Making config %d of %s permanent\n", cfg,
2353  getDict().getUnicharset().debug_str(
2354  ambig_class_id).string());
2355  }
2356  MakePermanent(AdaptedTemplates, ambig_class_id, cfg, Blob);
2357  }
2358  }
2359  }
2360 }
2361 
2362 } // namespace tesseract
Pix * pix() const
Definition: normalis.h:248
void AddProtoToClassPruner(PROTO Proto, CLASS_ID ClassId, INT_TEMPLATES Templates)
Definition: intproto.cpp:342
STRING to_string() const
Definition: unicharset.h:73
#define ADAPT_TEMPLATE_SUFFIX
Definition: adaptmatch.cpp:66
void FreeBitVector(BIT_VECTOR BitVector)
Definition: bitvec.cpp:54
GenericVector< CP_RESULT_STRUCT > CPResults
Definition: adaptmatch.cpp:89
#define TRUE
Definition: capi.h:45
FEATURE Features[1]
Definition: ocrfeatures.h:72
FEATURE_STRUCT * GetCNFeature() const
const FEATURE_DESC_STRUCT CharNormDesc
int32_t inT32
Definition: host.h:38
BIT_VECTOR PermConfigs
Definition: adaptive.h:68
void plot(ScrollView *window)
Definition: blobs.cpp:916
static void BreakPieces(const GenericVector< SEAM *> &seams, const GenericVector< TBLOB *> &blobs, int first, int last)
Definition: seam.cpp:194
int UNICHAR_ID
Definition: unichar.h:33
#define ADAPTABLE_WERD_ADJUSTMENT
Definition: adaptmatch.cpp:73
int MakeTempProtoPerm(void *item1, void *item2)
GenericVector< int > best_state
Definition: pageres.h:255
const CHAR_FRAGMENT * get_fragment(UNICHAR_ID unichar_id) const
Definition: unicharset.h:694
WERD_CHOICE * best_choice
Definition: pageres.h:219
int best_match_index
Definition: adaptmatch.cpp:86
int length() const
Definition: ratngs.h:301
inT32 BlobLength
Definition: adaptmatch.cpp:83
void FreeFeatureSet(FEATURE_SET FeatureSet)
Definition: ocrfeatures.cpp:71
#define MAX_NUM_PROTOS
Definition: intproto.h:47
uinT8 NumConfigs
Definition: intproto.h:110
#define MAX_INT32
Definition: host.h:62
TEMP_CONFIG NewTempConfig(int MaxProtoId, int FontinfoId)
Definition: adaptive.cpp:221
UNICHAR_ID CLASS_ID
Definition: matchdefs.h:35
#define UnusedClassIdIn(T, c)
Definition: intproto.h:180
#define MAX_UINT8
Definition: host.h:63
NORM_PROTOS * ReadNormProtos(FILE *File)
bool HasNonfragment
Definition: adaptmatch.cpp:84
#define WordsInVectorOfSize(NumBits)
Definition: bitvec.h:63
#define LENGTH_COMPRESSION
Definition: normfeat.h:26
uinT8 ProtoVectorSize
Definition: adaptive.h:42
#define UNLIKELY_NUM_FEAT
Definition: adaptmatch.cpp:69
#define Y_DIM_OFFSET
Definition: adaptmatch.cpp:75
#define copy_all_bits(source, dest, length)
Definition: bitvec.h:49
#define PRINT_MATCH_SUMMARY
Definition: intproto.h:190
const double kStandardFeatureLength
Definition: intfx.h:46
const int kBlnXHeight
Definition: normalis.h:28
GenericVector< STRING > correct_text
Definition: pageres.h:259
#define WORST_POSSIBLE_RATING
Definition: adaptmatch.cpp:77
void XHeightRange(int unichar_id, const UNICHARSET &unicharset, const TBOX &bbox, float *min_xht, float *max_xht, float *yshift) const
Definition: normalis.cpp:428
void FreeTempConfig(TEMP_CONFIG Config)
Definition: adaptive.cpp:81
int push_back(T object)
PERM_CONFIG_STRUCT * PERM_CONFIG
Definition: adaptive.h:54
const STRING debug_string() const
Definition: ratngs.h:503
GenericVector< ScoredFont > fonts
Definition: shapetable.h:88
TWERD * rebuild_word
Definition: pageres.h:244
#define tprintf(...)
Definition: tprintf.h:31
void FillABC(PROTO Proto)
Definition: protos.cpp:197
const char * string() const
Definition: strngs.cpp:198
BIT_VECTOR Protos
Definition: adaptive.h:44
const int kBlnBaselineOffset
Definition: normalis.h:29
FLOAT32 ActualOutlineLength(FEATURE Feature)
Definition: normfeat.cpp:32
#define PermConfigFor(Class, ConfigId)
Definition: adaptive.h:104
#define MakeConfigPermanent(Class, ConfigId)
Definition: adaptive.h:95
void ComputeBest()
Definition: adaptmatch.cpp:99
uinT32 * BIT_VECTOR
Definition: bitvec.h:28
#define MF_SCALE_FACTOR
Definition: mfoutline.h:63
bool empty() const
Definition: genericvector.h:90
void truncate(int size)
uinT16 NumProtos
Definition: intproto.h:108
#define LegalClassId(c)
Definition: intproto.h:179
#define PRINT_FEATURE_MATCHES
Definition: intproto.h:193
void set_fonts(const GenericVector< tesseract::ScoredFont > &fonts)
Definition: ratngs.h:94
int IntCastRounded(double x)
Definition: helpers.h:179
void Initialize()
Definition: adaptmatch.cpp:93
CLUSTERCONFIG Config
#define IncreaseConfidence(TempConfig)
Definition: adaptive.h:107
int size() const
Definition: genericvector.h:72
bool disable_character_fragments
TEMP_PROTO_STRUCT * TEMP_PROTO
Definition: adaptive.h:37
BIT_VECTOR NewBitVector(int NumBits)
Definition: bitvec.cpp:89
int16_t inT16
Definition: host.h:36
uinT8 MaxNumTimesSeen
Definition: adaptive.h:65
#define ASSERT_HOST(x)
Definition: errcode.h:84
inT16 left() const
Definition: rect.h:68
int classify_integer_matcher_multiplier
INT_TEMPLATES Templates
Definition: adaptive.h:76
FLOAT32 Length
Definition: protos.h:50
PROTO_ID MaxProtoId
Definition: adaptive.h:43
#define ClassForClassId(T, c)
Definition: intproto.h:181
bool MarginalMatch(float confidence, float matcher_great_threshold)
Definition: adaptmatch.cpp:122
uint32_t uinT32
Definition: host.h:39
const DENORM & denorm() const
Definition: blobs.h:340
LIST delete_d(LIST list, void *key, int_compare is_equal)
Definition: oldlist.cpp:120
static void JoinPieces(const GenericVector< SEAM *> &seams, const GenericVector< TBLOB *> &blobs, int first, int last)
Definition: seam.cpp:216
TrainingSample * BlobToTrainingSample(const TBLOB &blob, bool nonlinear_norm, INT_FX_RESULT_STRUCT *fx_info, GenericVector< INT_FEATURE_STRUCT > *bl_features)
Definition: intfx.cpp:81
int AddIntConfig(INT_CLASS Class)
Definition: intproto.cpp:270
void free_adapted_templates(ADAPT_TEMPLATES templates)
Definition: adaptive.cpp:197
Definition: strngs.h:45
#define zero_all_bits(array, length)
Definition: bitvec.h:33
#define FALSE
Definition: capi.h:46
bool GetComponent(TessdataType type, TFile *fp)
TBLOB * ClassifyNormalizeIfNeeded() const
Definition: blobs.cpp:363
const FontInfo * fontinfo
Definition: pageres.h:288
static void Update()
Definition: scrollview.cpp:715
char window_wait(ScrollView *win)
Definition: callcpp.cpp:111
FEATURE NewFeature(const FEATURE_DESC_STRUCT *FeatureDesc)
Definition: ocrfeatures.cpp:88
#define set_all_bits(array, length)
Definition: bitvec.h:41
void InitMatcherRatings(register FLOAT32 *Rating)
#define TempConfigFor(Class, ConfigId)
Definition: adaptive.h:101
#define MAX_FLOAT32
Definition: host.h:66
uinT16 ProtoId
Definition: adaptive.h:30
INT_CLASS Class[MAX_NUM_CLASSES]
Definition: intproto.h:124
int AddIntProto(INT_CLASS Class)
Definition: intproto.cpp:293
FLOAT32 Y
Definition: protos.h:48
uinT8 NumTimesSeen
Definition: adaptive.h:41
FLOAT32 X
Definition: protos.h:47
TEMP_PROTO NewTempProto()
Definition: adaptive.cpp:248
void FreeTempProto(void *arg)
Definition: adaptive.cpp:88
int length() const
Definition: genericvector.h:85
bool AlternativeChoiceAdjustmentsWorseThan(float threshold) const
Definition: pageres.cpp:431
void AddProtoToProtoPruner(PROTO Proto, int ProtoId, INT_CLASS Class, bool debug)
Definition: intproto.cpp:384
int NumBlobs() const
Definition: blobs.h:425
inT16 top() const
Definition: rect.h:54
void FreeFeature(FEATURE Feature)
Definition: ocrfeatures.cpp:59
#define MAX(x, y)
Definition: ndminx.h:24
void SetAdaptiveThreshold(FLOAT32 Threshold)
int geo_feature(int index) const
void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS Class)
Definition: intproto.cpp:487
float FLOAT32
Definition: host.h:42
Definition: rect.h:30
GenericVector< TBLOB * > blobs
Definition: blobs.h:436
FLOAT32 Angle
Definition: protos.h:49
#define NO_PROTO
Definition: matchdefs.h:42
void plot(ScrollView *window, ScrollView::Color color, ScrollView::Color child_color)
Definition: blobs.cpp:524
#define test_bit(array, bit)
Definition: bitvec.h:61
#define MIN(x, y)
Definition: ndminx.h:28
CLASS_ID ClassId
Definition: adaptmatch.cpp:115
Definition: blobs.h:261
BIT_VECTOR PermProtos
Definition: adaptive.h:67
#define MAX_NUM_INT_FEATURES
Definition: intproto.h:132
ADAPT_CLASS Class[MAX_NUM_CLASSES]
Definition: adaptive.h:80
#define NO_DEBUG
Definition: adaptmatch.cpp:70
float adjust_factor() const
Definition: ratngs.h:304
uint8_t uinT8
Definition: host.h:35
inT16 right() const
Definition: rect.h:75
bool Open(const STRING &filename, FileReader reader)
Definition: serialis.cpp:38
const INT_FEATURE_STRUCT * features() const
GenericVector< UnicharRating > match
Definition: adaptmatch.cpp:88
PROTO_STRUCT Proto
Definition: adaptive.h:32
uinT8 NumPermConfigs
Definition: adaptive.h:64
#define MAX_MATCHES
Definition: adaptmatch.cpp:68
void cprintf(const char *format,...)
Definition: callcpp.cpp:40
void ComputeAdaptionThresholds(float certainty_scale, float min_rating, float max_rating, float rating_margin, float *thresholds)
Definition: pageres.cpp:553
#define IsEmptyAdaptedClass(Class)
Definition: adaptive.h:89
#define MAX_NUM_CONFIGS
Definition: intproto.h:46
int size() const
Definition: shapetable.h:200
#define SET_BIT(array, bit)
Definition: bitvec.h:57
inT16 bottom() const
Definition: rect.h:61
void print_ratings_list(const char *msg, BLOB_CHOICE_LIST *ratings, const UNICHARSET &current_unicharset)
Definition: ratngs.cpp:819
FLOAT32 Params[1]
Definition: ocrfeatures.h:65
#define GetPicoFeatureLength()
Definition: picofeat.h:59
CharSegmentationType
Definition: classify.h:54
void free_int_templates(INT_TEMPLATES templates)
Definition: intproto.cpp:739
Definition: cluster.h:32
#define MAX_NUM_CLASSES
Definition: matchdefs.h:31
#define ConfigIsPermanent(Class, ConfigId)
Definition: adaptive.h:92
uinT8 FEATURE_ID
Definition: matchdefs.h:47
UNICHAR_ID best_unichar_id
Definition: adaptmatch.cpp:85
INT_FEATURE_STRUCT INT_FEATURE_ARRAY[MAX_NUM_INT_FEATURES]
Definition: intproto.h:155
void ShowMatchDisplay()
ADAPT_TEMPLATES Templates
Definition: adaptmatch.cpp:114
TBOX bounding_box() const
Definition: blobs.cpp:482
TWERD * chopped_word
Definition: pageres.h:201
#define MAX_ADAPTABLE_WERD_SIZE
Definition: adaptmatch.cpp:71
LIST push(LIST list, void *element)
Definition: oldlist.cpp:317
void InitIntegerFX()
Definition: intfx.cpp:55
#define reset_bit(array, bit)
Definition: bitvec.h:59
#define PRINT_PROTO_MATCHES
Definition: intproto.h:194
void UpdateMatchDisplay()
Definition: intproto.cpp:467
UNICHAR_ID * Ambigs
Definition: adaptive.h:51
const char features[]
Definition: feature_tests.c:2
inT16 PROTO_ID
Definition: matchdefs.h:41
#define MakeProtoPermanent(Class, ProtoId)
Definition: adaptive.h:98
TBOX bounding_box() const
Definition: blobs.cpp:879
GenericVector< SEAM * > seam_array
Definition: pageres.h:203
FLOAT32 best_rating
Definition: adaptmatch.cpp:87
bool PiecesAllNatural(int start, int count) const
Definition: pageres.cpp:1073