tesseract 3.04.01

classify/adaptmatch.cpp

Go to the documentation of this file.
00001 /******************************************************************************
00002  ** Filename:    adaptmatch.c
00003  ** Purpose:     High level adaptive matcher.
00004  ** Author:      Dan Johnson
00005  ** History:     Mon Mar 11 10:00:10 1991, DSJ, Created.
00006  **
00007  ** (c) Copyright Hewlett-Packard Company, 1988.
00008  ** Licensed under the Apache License, Version 2.0 (the "License");
00009  ** you may not use this file except in compliance with the License.
00010  ** You may obtain a copy of the License at
00011  ** http://www.apache.org/licenses/LICENSE-2.0
00012  ** Unless required by applicable law or agreed to in writing, software
00013  ** distributed under the License is distributed on an "AS IS" BASIS,
00014  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00015  ** See the License for the specific language governing permissions and
00016  ** limitations under the License.
00017  ******************************************************************************/
00018 
00019 /*-----------------------------------------------------------------------------
00020           Include Files and Type Defines
00021 -----------------------------------------------------------------------------*/
00022 #ifdef HAVE_CONFIG_H
00023 #include "config_auto.h"
00024 #endif
00025 
00026 #include <ctype.h>
00027 #include "shapeclassifier.h"
00028 #include "ambigs.h"
00029 #include "blobclass.h"
00030 #include "blobs.h"
00031 #include "callcpp.h"
00032 #include "classify.h"
00033 #include "const.h"
00034 #include "dict.h"
00035 #include "efio.h"
00036 #include "emalloc.h"
00037 #include "featdefs.h"
00038 #include "float2int.h"
00039 #include "genericvector.h"
00040 #include "globals.h"
00041 #include "helpers.h"
00042 #include "intfx.h"
00043 #include "intproto.h"
00044 #include "mfoutline.h"
00045 #include "ndminx.h"
00046 #include "normfeat.h"
00047 #include "normmatch.h"
00048 #include "outfeat.h"
00049 #include "pageres.h"
00050 #include "params.h"
00051 #include "picofeat.h"
00052 #include "shapetable.h"
00053 #include "tessclassifier.h"
00054 #include "trainingsample.h"
00055 #include "unicharset.h"
00056 #include "werd.h"
00057 
00058 #include <stdio.h>
00059 #include <string.h>
00060 #include <stdlib.h>
00061 #include <math.h>
00062 #ifdef __UNIX__
00063 #include <assert.h>
00064 #endif
00065 
00066 #define ADAPT_TEMPLATE_SUFFIX ".a"
00067 
00068 #define MAX_MATCHES         10
00069 #define UNLIKELY_NUM_FEAT 200
00070 #define NO_DEBUG      0
00071 #define MAX_ADAPTABLE_WERD_SIZE 40
00072 
00073 #define ADAPTABLE_WERD_ADJUSTMENT    (0.05)
00074 
00075 #define Y_DIM_OFFSET    (Y_SHIFT - BASELINE_Y_SHIFT)
00076 
00077 #define WORST_POSSIBLE_RATING (0.0f)
00078 
00079 using tesseract::UnicharRating;
00080 using tesseract::ScoredFont;
00081 
00082 struct ADAPT_RESULTS {
00083   inT32 BlobLength;
00084   bool HasNonfragment;
00085   UNICHAR_ID best_unichar_id;
00086   int best_match_index;
00087   FLOAT32 best_rating;
00088   GenericVector<UnicharRating> match;
00089   GenericVector<CP_RESULT_STRUCT> CPResults;
00090 
00093   inline void Initialize() {
00094     BlobLength = MAX_INT32;
00095     HasNonfragment = false;
00096     ComputeBest();
00097   }
00098   // Computes best_unichar_id, best_match_index and best_rating.
00099   void ComputeBest() {
00100     best_unichar_id = INVALID_UNICHAR_ID;
00101     best_match_index = -1;
00102     best_rating = WORST_POSSIBLE_RATING;
00103     for (int i = 0; i < match.size(); ++i) {
00104       if (match[i].rating > best_rating) {
00105         best_rating = match[i].rating;
00106         best_unichar_id = match[i].unichar_id;
00107         best_match_index = i;
00108       }
00109     }
00110   }
00111 };
00112 
00113 struct PROTO_KEY {
00114   ADAPT_TEMPLATES Templates;
00115   CLASS_ID ClassId;
00116   int ConfigId;
00117 };
00118 
00119 /*-----------------------------------------------------------------------------
00120           Private Macros
00121 -----------------------------------------------------------------------------*/
00122 inline bool MarginalMatch(float confidence, float matcher_great_threshold) {
00123   return (1.0f - confidence) > matcher_great_threshold;
00124 }
00125 
00126 /*-----------------------------------------------------------------------------
00127           Private Function Prototypes
00128 -----------------------------------------------------------------------------*/
00129 // Returns the index of the given id in results, if present, or the size of the
00130 // vector (index it will go at) if not present.
00131 static int FindScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) {
00132   for (int i = 0; i < results.match.size(); i++) {
00133     if (results.match[i].unichar_id == id)
00134       return i;
00135   }
00136   return results.match.size();
00137 }
00138 
00139 // Returns the current rating for a unichar id if we have rated it, defaulting
00140 // to WORST_POSSIBLE_RATING.
00141 static float ScoredUnichar(UNICHAR_ID id, const ADAPT_RESULTS& results) {
00142   int index = FindScoredUnichar(id, results);
00143   if (index >= results.match.size()) return WORST_POSSIBLE_RATING;
00144   return results.match[index].rating;
00145 }
00146 
00147 void InitMatcherRatings(register FLOAT32 *Rating);
00148 
00149 int MakeTempProtoPerm(void *item1, void *item2);
00150 
00151 void SetAdaptiveThreshold(FLOAT32 Threshold);
00152 
00153 
00154 /*-----------------------------------------------------------------------------
00155               Public Code
00156 -----------------------------------------------------------------------------*/
00157 /*---------------------------------------------------------------------------*/
00158 namespace tesseract {
00185 void Classify::AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices) {
00186   assert(Choices != NULL);
00187   ADAPT_RESULTS *Results = new ADAPT_RESULTS;
00188   Results->Initialize();
00189 
00190   ASSERT_HOST(AdaptedTemplates != NULL);
00191 
00192   DoAdaptiveMatch(Blob, Results);
00193 
00194   RemoveBadMatches(Results);
00195   Results->match.sort(&UnicharRating::SortDescendingRating);
00196   RemoveExtraPuncs(Results);
00197   Results->ComputeBest();
00198   ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results,
00199                           Choices);
00200 
00201   // TODO(rays) Move to before ConvertMatchesToChoices!
00202   if (LargeSpeckle(*Blob) || Choices->length() == 0)
00203     AddLargeSpeckleTo(Results->BlobLength, Choices);
00204 
00205   if (matcher_debug_level >= 1) {
00206     tprintf("AD Matches =  ");
00207     PrintAdaptiveMatchResults(*Results);
00208   }
00209 
00210 #ifndef GRAPHICS_DISABLED
00211   if (classify_enable_adaptive_debugger)
00212     DebugAdaptiveClassifier(Blob, Results);
00213 #endif
00214 
00215   delete Results;
00216 }                                /* AdaptiveClassifier */
00217 
00218 // If *win is NULL, sets it to a new ScrollView() object with title msg.
00219 // Clears the window and draws baselines.
00220 void Classify::RefreshDebugWindow(ScrollView **win, const char *msg,
00221                                   int y_offset, const TBOX &wbox) {
00222   #ifndef GRAPHICS_DISABLED
00223   const int kSampleSpaceWidth = 500;
00224   if (*win == NULL) {
00225     *win = new ScrollView(msg, 100, y_offset, kSampleSpaceWidth * 2, 200,
00226                           kSampleSpaceWidth * 2, 200, true);
00227   }
00228   (*win)->Clear();
00229   (*win)->Pen(64, 64, 64);
00230   (*win)->Line(-kSampleSpaceWidth, kBlnBaselineOffset,
00231                kSampleSpaceWidth, kBlnBaselineOffset);
00232   (*win)->Line(-kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset,
00233                kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset);
00234   (*win)->ZoomToRectangle(wbox.left(), wbox.top(),
00235                           wbox.right(), wbox.bottom());
00236   #endif  // GRAPHICS_DISABLED
00237 }
00238 
00239 // Learns the given word using its chopped_word, seam_array, denorm,
00240 // box_word, best_state, and correct_text to learn both correctly and
00241 // incorrectly segmented blobs. If fontname is not NULL, then LearnBlob
00242 // is called and the data will be saved in an internal buffer.
00243 // Otherwise AdaptToBlob is called for adaption within a document.
00244 void Classify::LearnWord(const char* fontname, WERD_RES* word) {
00245   int word_len = word->correct_text.size();
00246   if (word_len == 0) return;
00247 
00248   float* thresholds = NULL;
00249   if (fontname == NULL) {
00250     // Adaption mode.
00251     if (!EnableLearning || word->best_choice == NULL)
00252       return;  // Can't or won't adapt.
00253 
00254     if (classify_learning_debug_level >= 1)
00255       tprintf("\n\nAdapting to word = %s\n",
00256               word->best_choice->debug_string().string());
00257     thresholds = new float[word_len];
00258     word->ComputeAdaptionThresholds(certainty_scale,
00259                                     matcher_perfect_threshold,
00260                                     matcher_good_threshold,
00261                                     matcher_rating_margin, thresholds);
00262   }
00263   int start_blob = 0;
00264 
00265   #ifndef GRAPHICS_DISABLED
00266   if (classify_debug_character_fragments) {
00267     if (learn_fragmented_word_debug_win_ != NULL) {
00268       window_wait(learn_fragmented_word_debug_win_);
00269     }
00270     RefreshDebugWindow(&learn_fragments_debug_win_, "LearnPieces", 400,
00271                        word->chopped_word->bounding_box());
00272     RefreshDebugWindow(&learn_fragmented_word_debug_win_, "LearnWord", 200,
00273                        word->chopped_word->bounding_box());
00274     word->chopped_word->plot(learn_fragmented_word_debug_win_);
00275     ScrollView::Update();
00276   }
00277   #endif  // GRAPHICS_DISABLED
00278 
00279   for (int ch = 0; ch < word_len; ++ch) {
00280     if (classify_debug_character_fragments) {
00281       tprintf("\nLearning %s\n",  word->correct_text[ch].string());
00282     }
00283     if (word->correct_text[ch].length() > 0) {
00284       float threshold = thresholds != NULL ? thresholds[ch] : 0.0f;
00285 
00286       LearnPieces(fontname, start_blob, word->best_state[ch], threshold,
00287                   CST_WHOLE, word->correct_text[ch].string(), word);
00288 
00289       if (word->best_state[ch] > 1 && !disable_character_fragments) {
00290         // Check that the character breaks into meaningful fragments
00291         // that each match a whole character with at least
00292         // classify_character_fragments_garbage_certainty_threshold
00293         bool garbage = false;
00294         int frag;
00295         for (frag = 0; frag < word->best_state[ch]; ++frag) {
00296           TBLOB* frag_blob = word->chopped_word->blobs[start_blob + frag];
00297           if (classify_character_fragments_garbage_certainty_threshold < 0) {
00298             garbage |= LooksLikeGarbage(frag_blob);
00299           }
00300         }
00301         // Learn the fragments.
00302         if (!garbage) {
00303           bool pieces_all_natural = word->PiecesAllNatural(start_blob,
00304               word->best_state[ch]);
00305           if (pieces_all_natural || !prioritize_division) {
00306             for (frag = 0; frag < word->best_state[ch]; ++frag) {
00307               GenericVector<STRING> tokens;
00308               word->correct_text[ch].split(' ', &tokens);
00309 
00310               tokens[0] = CHAR_FRAGMENT::to_string(
00311                   tokens[0].string(), frag, word->best_state[ch],
00312                   pieces_all_natural);
00313 
00314               STRING full_string;
00315               for (int i = 0; i < tokens.size(); i++) {
00316                 full_string += tokens[i];
00317                 if (i != tokens.size() - 1)
00318                   full_string += ' ';
00319               }
00320               LearnPieces(fontname, start_blob + frag, 1, threshold,
00321                           CST_FRAGMENT, full_string.string(), word);
00322             }
00323           }
00324         }
00325       }
00326 
00327       // TODO(rays): re-enable this part of the code when we switch to the
00328       // new classifier that needs to see examples of garbage.
00329       /*
00330       if (word->best_state[ch] > 1) {
00331         // If the next blob is good, make junk with the rightmost fragment.
00332         if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
00333           LearnPieces(fontname, start_blob + word->best_state[ch] - 1,
00334                       word->best_state[ch + 1] + 1,
00335                       threshold, CST_IMPROPER, INVALID_UNICHAR, word);
00336         }
00337         // If the previous blob is good, make junk with the leftmost fragment.
00338         if (ch > 0 && word->correct_text[ch - 1].length() > 0) {
00339           LearnPieces(fontname, start_blob - word->best_state[ch - 1],
00340                       word->best_state[ch - 1] + 1,
00341                       threshold, CST_IMPROPER, INVALID_UNICHAR, word);
00342         }
00343       }
00344       // If the next blob is good, make a join with it.
00345       if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
00346         STRING joined_text = word->correct_text[ch];
00347         joined_text += word->correct_text[ch + 1];
00348         LearnPieces(fontname, start_blob,
00349                     word->best_state[ch] + word->best_state[ch + 1],
00350                     threshold, CST_NGRAM, joined_text.string(), word);
00351       }
00352       */
00353     }
00354     start_blob += word->best_state[ch];
00355   }
00356   delete [] thresholds;
00357 }  // LearnWord.
00358 
00359 // Builds a blob of length fragments, from the word, starting at start,
00360 // and then learns it, as having the given correct_text.
00361 // If fontname is not NULL, then LearnBlob is called and the data will be
00362 // saved in an internal buffer for static training.
00363 // Otherwise AdaptToBlob is called for adaption within a document.
00364 // threshold is a magic number required by AdaptToChar and generated by
00365 // ComputeAdaptionThresholds.
00366 // Although it can be partly inferred from the string, segmentation is
00367 // provided to explicitly clarify the character segmentation.
00368 void Classify::LearnPieces(const char* fontname, int start, int length,
00369                            float threshold, CharSegmentationType segmentation,
00370                            const char* correct_text, WERD_RES* word) {
00371   // TODO(daria) Remove/modify this if/when we want
00372   // to train and/or adapt to n-grams.
00373   if (segmentation != CST_WHOLE &&
00374       (segmentation != CST_FRAGMENT || disable_character_fragments))
00375     return;
00376 
00377   if (length > 1) {
00378     SEAM::JoinPieces(word->seam_array, word->chopped_word->blobs, start,
00379                      start + length - 1);
00380   }
00381   TBLOB* blob = word->chopped_word->blobs[start];
00382   // Rotate the blob if needed for classification.
00383   TBLOB* rotated_blob = blob->ClassifyNormalizeIfNeeded();
00384   if (rotated_blob == NULL)
00385     rotated_blob = blob;
00386 
00387   #ifndef GRAPHICS_DISABLED
00388   // Draw debug windows showing the blob that is being learned if needed.
00389   if (strcmp(classify_learn_debug_str.string(), correct_text) == 0) {
00390     RefreshDebugWindow(&learn_debug_win_, "LearnPieces", 600,
00391                        word->chopped_word->bounding_box());
00392     rotated_blob->plot(learn_debug_win_, ScrollView::GREEN, ScrollView::BROWN);
00393     learn_debug_win_->Update();
00394     window_wait(learn_debug_win_);
00395   }
00396   if (classify_debug_character_fragments && segmentation == CST_FRAGMENT) {
00397     ASSERT_HOST(learn_fragments_debug_win_ != NULL);  // set up in LearnWord
00398     blob->plot(learn_fragments_debug_win_,
00399                ScrollView::BLUE, ScrollView::BROWN);
00400     learn_fragments_debug_win_->Update();
00401   }
00402   #endif  // GRAPHICS_DISABLED
00403 
00404   if (fontname != NULL) {
00405     classify_norm_method.set_value(character);  // force char norm spc 30/11/93
00406     tess_bn_matching.set_value(false);    // turn it off
00407     tess_cn_matching.set_value(false);
00408     DENORM bl_denorm, cn_denorm;
00409     INT_FX_RESULT_STRUCT fx_info;
00410     SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm,
00411                      &bl_denorm, &cn_denorm, &fx_info);
00412     LearnBlob(fontname, rotated_blob, cn_denorm, fx_info, correct_text);
00413   } else if (unicharset.contains_unichar(correct_text)) {
00414     UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text);
00415     int font_id = word->fontinfo != NULL
00416                 ? fontinfo_table_.get_id(*word->fontinfo)
00417                 : 0;
00418     if (classify_learning_debug_level >= 1)
00419       tprintf("Adapting to char = %s, thr= %g font_id= %d\n",
00420               unicharset.id_to_unichar(class_id), threshold, font_id);
00421     // If filename is not NULL we are doing recognition
00422     // (as opposed to training), so we must have already set word fonts.
00423     AdaptToChar(rotated_blob, class_id, font_id, threshold, AdaptedTemplates);
00424     if (BackupAdaptedTemplates != NULL) {
00425       // Adapt the backup templates too. They will be used if the primary gets
00426       // too full.
00427       AdaptToChar(rotated_blob, class_id, font_id, threshold,
00428                   BackupAdaptedTemplates);
00429     }
00430   } else if (classify_debug_level >= 1) {
00431     tprintf("Can't adapt to %s not in unicharset\n", correct_text);
00432   }
00433   if (rotated_blob != blob) {
00434     delete rotated_blob;
00435   }
00436 
00437   SEAM::BreakPieces(word->seam_array, word->chopped_word->blobs, start,
00438                     start + length - 1);
00439 }  // LearnPieces.
00440 
00441 /*---------------------------------------------------------------------------*/
00456 void Classify::EndAdaptiveClassifier() {
00457   STRING Filename;
00458   FILE *File;
00459 
00460   if (AdaptedTemplates != NULL &&
00461       classify_enable_adaptive_matcher && classify_save_adapted_templates) {
00462     Filename = imagefile + ADAPT_TEMPLATE_SUFFIX;
00463     File = fopen (Filename.string(), "wb");
00464     if (File == NULL)
00465       cprintf ("Unable to save adapted templates to %s!\n", Filename.string());
00466     else {
00467       cprintf ("\nSaving adapted templates to %s ...", Filename.string());
00468       fflush(stdout);
00469       WriteAdaptedTemplates(File, AdaptedTemplates);
00470       cprintf ("\n");
00471       fclose(File);
00472     }
00473   }
00474 
00475   if (AdaptedTemplates != NULL) {
00476     free_adapted_templates(AdaptedTemplates);
00477     AdaptedTemplates = NULL;
00478   }
00479   if (BackupAdaptedTemplates != NULL) {
00480     free_adapted_templates(BackupAdaptedTemplates);
00481     BackupAdaptedTemplates = NULL;
00482   }
00483 
00484   if (PreTrainedTemplates != NULL) {
00485     free_int_templates(PreTrainedTemplates);
00486     PreTrainedTemplates = NULL;
00487   }
00488   getDict().EndDangerousAmbigs();
00489   FreeNormProtos();
00490   if (AllProtosOn != NULL) {
00491     FreeBitVector(AllProtosOn);
00492     FreeBitVector(AllConfigsOn);
00493     FreeBitVector(AllConfigsOff);
00494     FreeBitVector(TempProtoMask);
00495     AllProtosOn = NULL;
00496     AllConfigsOn = NULL;
00497     AllConfigsOff = NULL;
00498     TempProtoMask = NULL;
00499   }
00500   delete shape_table_;
00501   shape_table_ = NULL;
00502   if (static_classifier_ != NULL) {
00503     delete static_classifier_;
00504     static_classifier_ = NULL;
00505   }
00506 }                                /* EndAdaptiveClassifier */
00507 
00508 
00509 /*---------------------------------------------------------------------------*/
00527 void Classify::InitAdaptiveClassifier(bool load_pre_trained_templates) {
00528   if (!classify_enable_adaptive_matcher)
00529     return;
00530   if (AllProtosOn != NULL)
00531     EndAdaptiveClassifier();  // Don't leak with multiple inits.
00532 
00533   // If there is no language_data_path_prefix, the classifier will be
00534   // adaptive only.
00535   if (language_data_path_prefix.length() > 0 &&
00536       load_pre_trained_templates) {
00537     ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_INTTEMP));
00538     PreTrainedTemplates =
00539       ReadIntTemplates(tessdata_manager.GetDataFilePtr());
00540     if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded inttemp\n");
00541 
00542     if (tessdata_manager.SeekToStart(TESSDATA_SHAPE_TABLE)) {
00543       shape_table_ = new ShapeTable(unicharset);
00544       if (!shape_table_->DeSerialize(tessdata_manager.swap(),
00545                                      tessdata_manager.GetDataFilePtr())) {
00546         tprintf("Error loading shape table!\n");
00547         delete shape_table_;
00548         shape_table_ = NULL;
00549       } else if (tessdata_manager.DebugLevel() > 0) {
00550         tprintf("Successfully loaded shape table!\n");
00551       }
00552     }
00553 
00554     ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_PFFMTABLE));
00555     ReadNewCutoffs(tessdata_manager.GetDataFilePtr(),
00556                    tessdata_manager.swap(),
00557                    tessdata_manager.GetEndOffset(TESSDATA_PFFMTABLE),
00558                    CharNormCutoffs);
00559     if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded pffmtable\n");
00560 
00561     ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_NORMPROTO));
00562     NormProtos =
00563       ReadNormProtos(tessdata_manager.GetDataFilePtr(),
00564                      tessdata_manager.GetEndOffset(TESSDATA_NORMPROTO));
00565     if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded normproto\n");
00566     static_classifier_ = new TessClassifier(false, this);
00567   }
00568 
00569   im_.Init(&classify_debug_level);
00570   InitIntegerFX();
00571 
00572   AllProtosOn = NewBitVector(MAX_NUM_PROTOS);
00573   AllConfigsOn = NewBitVector(MAX_NUM_CONFIGS);
00574   AllConfigsOff = NewBitVector(MAX_NUM_CONFIGS);
00575   TempProtoMask = NewBitVector(MAX_NUM_PROTOS);
00576   set_all_bits(AllProtosOn, WordsInVectorOfSize(MAX_NUM_PROTOS));
00577   set_all_bits(AllConfigsOn, WordsInVectorOfSize(MAX_NUM_CONFIGS));
00578   zero_all_bits(AllConfigsOff, WordsInVectorOfSize(MAX_NUM_CONFIGS));
00579 
00580   for (int i = 0; i < MAX_NUM_CLASSES; i++) {
00581      BaselineCutoffs[i] = 0;
00582   }
00583 
00584   if (classify_use_pre_adapted_templates) {
00585     FILE *File;
00586     STRING Filename;
00587 
00588     Filename = imagefile;
00589     Filename += ADAPT_TEMPLATE_SUFFIX;
00590     File = fopen(Filename.string(), "rb");
00591     if (File == NULL) {
00592       AdaptedTemplates = NewAdaptedTemplates(true);
00593     } else {
00594       cprintf("\nReading pre-adapted templates from %s ...\n",
00595               Filename.string());
00596       fflush(stdout);
00597       AdaptedTemplates = ReadAdaptedTemplates(File);
00598       cprintf("\n");
00599       fclose(File);
00600       PrintAdaptedTemplates(stdout, AdaptedTemplates);
00601 
00602       for (int i = 0; i < AdaptedTemplates->Templates->NumClasses; i++) {
00603         BaselineCutoffs[i] = CharNormCutoffs[i];
00604       }
00605     }
00606   } else {
00607     if (AdaptedTemplates != NULL)
00608       free_adapted_templates(AdaptedTemplates);
00609     AdaptedTemplates = NewAdaptedTemplates(true);
00610   }
00611 }                                /* InitAdaptiveClassifier */
00612 
00613 void Classify::ResetAdaptiveClassifierInternal() {
00614   if (classify_learning_debug_level > 0) {
00615     tprintf("Resetting adaptive classifier (NumAdaptationsFailed=%d)\n",
00616             NumAdaptationsFailed);
00617   }
00618   free_adapted_templates(AdaptedTemplates);
00619   AdaptedTemplates = NewAdaptedTemplates(true);
00620   if (BackupAdaptedTemplates != NULL)
00621     free_adapted_templates(BackupAdaptedTemplates);
00622   BackupAdaptedTemplates = NULL;
00623   NumAdaptationsFailed = 0;
00624 }
00625 
00626 // If there are backup adapted templates, switches to those, otherwise resets
00627 // the main adaptive classifier (because it is full.)
00628 void Classify::SwitchAdaptiveClassifier() {
00629   if (BackupAdaptedTemplates == NULL) {
00630     ResetAdaptiveClassifierInternal();
00631     return;
00632   }
00633   if (classify_learning_debug_level > 0) {
00634     tprintf("Switch to backup adaptive classifier (NumAdaptationsFailed=%d)\n",
00635             NumAdaptationsFailed);
00636   }
00637   free_adapted_templates(AdaptedTemplates);
00638   AdaptedTemplates = BackupAdaptedTemplates;
00639   BackupAdaptedTemplates = NULL;
00640   NumAdaptationsFailed = 0;
00641 }
00642 
00643 // Resets the backup adaptive classifier to empty.
00644 void Classify::StartBackupAdaptiveClassifier() {
00645   if (BackupAdaptedTemplates != NULL)
00646     free_adapted_templates(BackupAdaptedTemplates);
00647   BackupAdaptedTemplates = NewAdaptedTemplates(true);
00648 }
00649 
00650 /*---------------------------------------------------------------------------*/
00670 void Classify::SettupPass1() {
00671   EnableLearning = classify_enable_learning;
00672 
00673   getDict().SettupStopperPass1();
00674 
00675 }                                /* SettupPass1 */
00676 
00677 
00678 /*---------------------------------------------------------------------------*/
00690 void Classify::SettupPass2() {
00691   EnableLearning = FALSE;
00692   getDict().SettupStopperPass2();
00693 
00694 }                                /* SettupPass2 */
00695 
00696 
00697 /*---------------------------------------------------------------------------*/
00717 void Classify::InitAdaptedClass(TBLOB *Blob,
00718                                 CLASS_ID ClassId,
00719                                 int FontinfoId,
00720                                 ADAPT_CLASS Class,
00721                                 ADAPT_TEMPLATES Templates) {
00722   FEATURE_SET Features;
00723   int Fid, Pid;
00724   FEATURE Feature;
00725   int NumFeatures;
00726   TEMP_PROTO TempProto;
00727   PROTO Proto;
00728   INT_CLASS IClass;
00729   TEMP_CONFIG Config;
00730 
00731   classify_norm_method.set_value(baseline);
00732   Features = ExtractOutlineFeatures(Blob);
00733   NumFeatures = Features->NumFeatures;
00734   if (NumFeatures > UNLIKELY_NUM_FEAT || NumFeatures <= 0) {
00735     FreeFeatureSet(Features);
00736     return;
00737   }
00738 
00739   Config = NewTempConfig(NumFeatures - 1, FontinfoId);
00740   TempConfigFor(Class, 0) = Config;
00741 
00742   /* this is a kludge to construct cutoffs for adapted templates */
00743   if (Templates == AdaptedTemplates)
00744     BaselineCutoffs[ClassId] = CharNormCutoffs[ClassId];
00745 
00746   IClass = ClassForClassId (Templates->Templates, ClassId);
00747 
00748   for (Fid = 0; Fid < Features->NumFeatures; Fid++) {
00749     Pid = AddIntProto (IClass);
00750     assert (Pid != NO_PROTO);
00751 
00752     Feature = Features->Features[Fid];
00753     TempProto = NewTempProto ();
00754     Proto = &(TempProto->Proto);
00755 
00756     /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
00757        ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
00758        instead of the -0.25 to 0.75 used in baseline normalization */
00759     Proto->Angle = Feature->Params[OutlineFeatDir];
00760     Proto->X = Feature->Params[OutlineFeatX];
00761     Proto->Y = Feature->Params[OutlineFeatY] - Y_DIM_OFFSET;
00762     Proto->Length = Feature->Params[OutlineFeatLength];
00763     FillABC(Proto);
00764 
00765     TempProto->ProtoId = Pid;
00766     SET_BIT (Config->Protos, Pid);
00767 
00768     ConvertProto(Proto, Pid, IClass);
00769     AddProtoToProtoPruner(Proto, Pid, IClass,
00770                           classify_learning_debug_level >= 2);
00771 
00772     Class->TempProtos = push (Class->TempProtos, TempProto);
00773   }
00774   FreeFeatureSet(Features);
00775 
00776   AddIntConfig(IClass);
00777   ConvertConfig (AllProtosOn, 0, IClass);
00778 
00779   if (classify_learning_debug_level >= 1) {
00780     tprintf("Added new class '%s' with class id %d and %d protos.\n",
00781             unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
00782     if (classify_learning_debug_level > 1)
00783       DisplayAdaptedChar(Blob, IClass);
00784   }
00785 
00786   if (IsEmptyAdaptedClass(Class))
00787     (Templates->NumNonEmptyClasses)++;
00788 }                                /* InitAdaptedClass */
00789 
00790 
00791 /*---------------------------------------------------------------------------*/
00812 int Classify::GetAdaptiveFeatures(TBLOB *Blob,
00813                                   INT_FEATURE_ARRAY IntFeatures,
00814                                   FEATURE_SET *FloatFeatures) {
00815   FEATURE_SET Features;
00816   int NumFeatures;
00817 
00818   classify_norm_method.set_value(baseline);
00819   Features = ExtractPicoFeatures(Blob);
00820 
00821   NumFeatures = Features->NumFeatures;
00822   if (NumFeatures > UNLIKELY_NUM_FEAT) {
00823     FreeFeatureSet(Features);
00824     return 0;
00825   }
00826 
00827   ComputeIntFeatures(Features, IntFeatures);
00828   *FloatFeatures = Features;
00829 
00830   return NumFeatures;
00831 }                                /* GetAdaptiveFeatures */
00832 
00833 
00834 /*-----------------------------------------------------------------------------
00835               Private Code
00836 -----------------------------------------------------------------------------*/
00837 /*---------------------------------------------------------------------------*/
00850 bool Classify::AdaptableWord(WERD_RES* word) {
00851   if (word->best_choice == NULL) return false;
00852   int BestChoiceLength = word->best_choice->length();
00853   float adaptable_score =
00854     getDict().segment_penalty_dict_case_ok + ADAPTABLE_WERD_ADJUSTMENT;
00855   return   // rules that apply in general - simplest to compute first
00856       BestChoiceLength > 0 &&
00857       BestChoiceLength == word->rebuild_word->NumBlobs() &&
00858       BestChoiceLength <= MAX_ADAPTABLE_WERD_SIZE &&
00859       // This basically ensures that the word is at least a dictionary match
00860       // (freq word, user word, system dawg word, etc).
00861       // Since all the other adjustments will make adjust factor higher
00862       // than higher than adaptable_score=1.1+0.05=1.15
00863       // Since these are other flags that ensure that the word is dict word,
00864       // this check could be at times redundant.
00865       word->best_choice->adjust_factor() <= adaptable_score &&
00866       // Make sure that alternative choices are not dictionary words.
00867       word->AlternativeChoiceAdjustmentsWorseThan(adaptable_score);
00868 }
00869 
00870 /*---------------------------------------------------------------------------*/
00886 void Classify::AdaptToChar(TBLOB* Blob, CLASS_ID ClassId, int FontinfoId,
00887                            FLOAT32 Threshold,
00888                            ADAPT_TEMPLATES adaptive_templates) {
00889   int NumFeatures;
00890   INT_FEATURE_ARRAY IntFeatures;
00891   UnicharRating int_result;
00892   INT_CLASS IClass;
00893   ADAPT_CLASS Class;
00894   TEMP_CONFIG TempConfig;
00895   FEATURE_SET FloatFeatures;
00896   int NewTempConfigId;
00897 
00898   if (!LegalClassId (ClassId))
00899     return;
00900 
00901   int_result.unichar_id = ClassId;
00902   Class = adaptive_templates->Class[ClassId];
00903   assert(Class != NULL);
00904   if (IsEmptyAdaptedClass(Class)) {
00905     InitAdaptedClass(Blob, ClassId, FontinfoId, Class, adaptive_templates);
00906   } else {
00907     IClass = ClassForClassId(adaptive_templates->Templates, ClassId);
00908 
00909     NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
00910     if (NumFeatures <= 0)
00911       return;
00912 
00913     // Only match configs with the matching font.
00914     BIT_VECTOR MatchingFontConfigs = NewBitVector(MAX_NUM_PROTOS);
00915     for (int cfg = 0; cfg < IClass->NumConfigs; ++cfg) {
00916       if (GetFontinfoId(Class, cfg) == FontinfoId) {
00917         SET_BIT(MatchingFontConfigs, cfg);
00918       } else {
00919         reset_bit(MatchingFontConfigs, cfg);
00920       }
00921     }
00922     im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
00923               NumFeatures, IntFeatures,
00924               &int_result, classify_adapt_feature_threshold,
00925               NO_DEBUG, matcher_debug_separate_windows);
00926     FreeBitVector(MatchingFontConfigs);
00927 
00928     SetAdaptiveThreshold(Threshold);
00929 
00930     if (1.0f - int_result.rating <= Threshold) {
00931       if (ConfigIsPermanent(Class, int_result.config)) {
00932         if (classify_learning_debug_level >= 1)
00933           tprintf("Found good match to perm config %d = %4.1f%%.\n",
00934                   int_result.config, int_result.rating * 100.0);
00935         FreeFeatureSet(FloatFeatures);
00936         return;
00937       }
00938 
00939       TempConfig = TempConfigFor(Class, int_result.config);
00940       IncreaseConfidence(TempConfig);
00941       if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) {
00942         Class->MaxNumTimesSeen = TempConfig->NumTimesSeen;
00943       }
00944       if (classify_learning_debug_level >= 1)
00945         tprintf("Increasing reliability of temp config %d to %d.\n",
00946                 int_result.config, TempConfig->NumTimesSeen);
00947 
00948       if (TempConfigReliable(ClassId, TempConfig)) {
00949         MakePermanent(adaptive_templates, ClassId, int_result.config, Blob);
00950         UpdateAmbigsGroup(ClassId, Blob);
00951       }
00952     } else {
00953       if (classify_learning_debug_level >= 1) {
00954         tprintf("Found poor match to temp config %d = %4.1f%%.\n",
00955                 int_result.config, int_result.rating * 100.0);
00956         if (classify_learning_debug_level > 2)
00957           DisplayAdaptedChar(Blob, IClass);
00958       }
00959       NewTempConfigId =
00960           MakeNewTemporaryConfig(adaptive_templates, ClassId, FontinfoId,
00961                                  NumFeatures, IntFeatures, FloatFeatures);
00962       if (NewTempConfigId >= 0 &&
00963           TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) {
00964         MakePermanent(adaptive_templates, ClassId, NewTempConfigId, Blob);
00965         UpdateAmbigsGroup(ClassId, Blob);
00966       }
00967 
00968 #ifndef GRAPHICS_DISABLED
00969       if (classify_learning_debug_level > 1) {
00970         DisplayAdaptedChar(Blob, IClass);
00971       }
00972 #endif
00973     }
00974     FreeFeatureSet(FloatFeatures);
00975   }
00976 }                                /* AdaptToChar */
00977 
00978 void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
00979 #ifndef GRAPHICS_DISABLED
00980   INT_FX_RESULT_STRUCT fx_info;
00981   GenericVector<INT_FEATURE_STRUCT> bl_features;
00982   TrainingSample* sample =
00983       BlobToTrainingSample(*blob, classify_nonlinear_norm, &fx_info,
00984                            &bl_features);
00985   if (sample == NULL) return;
00986 
00987   UnicharRating int_result;
00988   im_.Match(int_class, AllProtosOn, AllConfigsOn,
00989             bl_features.size(), &bl_features[0],
00990             &int_result, classify_adapt_feature_threshold,
00991             NO_DEBUG, matcher_debug_separate_windows);
00992   tprintf("Best match to temp config %d = %4.1f%%.\n",
00993           int_result.config, int_result.rating * 100.0);
00994   if (classify_learning_debug_level >= 2) {
00995     uinT32 ConfigMask;
00996     ConfigMask = 1 << int_result.config;
00997     ShowMatchDisplay();
00998     im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
00999               bl_features.size(), &bl_features[0],
01000               &int_result, classify_adapt_feature_threshold,
01001               6 | 0x19, matcher_debug_separate_windows);
01002     UpdateMatchDisplay();
01003   }
01004 #endif
01005 }
01006 
01007 
01008 
01029 void Classify::AddNewResult(const UnicharRating& new_result,
01030                             ADAPT_RESULTS *results) {
01031   int old_match = FindScoredUnichar(new_result.unichar_id, *results);
01032 
01033   if (new_result.rating + matcher_bad_match_pad < results->best_rating ||
01034       (old_match < results->match.size() &&
01035        new_result.rating <= results->match[old_match].rating))
01036     return;  // New one not good enough.
01037 
01038   if (!unicharset.get_fragment(new_result.unichar_id))
01039     results->HasNonfragment = true;
01040 
01041   if (old_match < results->match.size()) {
01042     results->match[old_match].rating = new_result.rating;
01043   } else {
01044     results->match.push_back(new_result);
01045   }
01046 
01047   if (new_result.rating > results->best_rating &&
01048       // Ensure that fragments do not affect best rating, class and config.
01049       // This is needed so that at least one non-fragmented character is
01050       // always present in the results.
01051       // TODO(daria): verify that this helps accuracy and does not
01052       // hurt performance.
01053       !unicharset.get_fragment(new_result.unichar_id)) {
01054     results->best_match_index = old_match;
01055     results->best_rating = new_result.rating;
01056     results->best_unichar_id = new_result.unichar_id;
01057   }
01058 }                                /* AddNewResult */
01059 
01060 
01061 /*---------------------------------------------------------------------------*/
01083 void Classify::AmbigClassifier(
01084     const GenericVector<INT_FEATURE_STRUCT>& int_features,
01085     const INT_FX_RESULT_STRUCT& fx_info,
01086     const TBLOB *blob,
01087     INT_TEMPLATES templates,
01088     ADAPT_CLASS *classes,
01089     UNICHAR_ID *ambiguities,
01090     ADAPT_RESULTS *results) {
01091   if (int_features.empty()) return;
01092   uinT8* CharNormArray = new uinT8[unicharset.size()];
01093   UnicharRating int_result;
01094 
01095   results->BlobLength = GetCharNormFeature(fx_info, templates, NULL,
01096                                            CharNormArray);
01097   bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
01098   if (debug)
01099     tprintf("AM Matches =  ");
01100 
01101   int top = blob->bounding_box().top();
01102   int bottom = blob->bounding_box().bottom();
01103   while (*ambiguities >= 0) {
01104     CLASS_ID class_id = *ambiguities;
01105 
01106     int_result.unichar_id = class_id;
01107     im_.Match(ClassForClassId(templates, class_id),
01108               AllProtosOn, AllConfigsOn,
01109               int_features.size(), &int_features[0],
01110               &int_result,
01111               classify_adapt_feature_threshold, NO_DEBUG,
01112               matcher_debug_separate_windows);
01113 
01114     ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0,
01115                                     results->BlobLength,
01116                                     classify_integer_matcher_multiplier,
01117                                     CharNormArray, &int_result, results);
01118     ambiguities++;
01119   }
01120   delete [] CharNormArray;
01121 }                                /* AmbigClassifier */
01122 
01123 /*---------------------------------------------------------------------------*/
01126 void Classify::MasterMatcher(INT_TEMPLATES templates,
01127                              inT16 num_features,
01128                              const INT_FEATURE_STRUCT* features,
01129                              const uinT8* norm_factors,
01130                              ADAPT_CLASS* classes,
01131                              int debug,
01132                              int matcher_multiplier,
01133                              const TBOX& blob_box,
01134                              const GenericVector<CP_RESULT_STRUCT>& results,
01135                              ADAPT_RESULTS* final_results) {
01136   int top = blob_box.top();
01137   int bottom = blob_box.bottom();
01138   UnicharRating int_result;
01139   for (int c = 0; c < results.size(); c++) {
01140     CLASS_ID class_id = results[c].Class;
01141     BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
01142                                         : AllProtosOn;
01143     BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs
01144                                          : AllConfigsOn;
01145 
01146     int_result.unichar_id = class_id;
01147     im_.Match(ClassForClassId(templates, class_id),
01148               protos, configs,
01149               num_features, features,
01150               &int_result, classify_adapt_feature_threshold, debug,
01151               matcher_debug_separate_windows);
01152     bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
01153     ExpandShapesAndApplyCorrections(classes, debug, class_id, bottom, top,
01154                                     results[c].Rating,
01155                                     final_results->BlobLength,
01156                                     matcher_multiplier, norm_factors,
01157                                     &int_result, final_results);
01158   }
01159 }
01160 
01161 // Converts configs to fonts, and if the result is not adapted, and a
01162 // shape_table_ is present, the shape is expanded to include all
01163 // unichar_ids represented, before applying a set of corrections to the
01164 // distance rating in int_result, (see ComputeCorrectedRating.)
01165 // The results are added to the final_results output.
01166 void Classify::ExpandShapesAndApplyCorrections(
01167     ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
01168     float cp_rating, int blob_length, int matcher_multiplier,
01169     const uinT8* cn_factors,
01170     UnicharRating* int_result, ADAPT_RESULTS* final_results) {
01171   if (classes != NULL) {
01172     // Adapted result. Convert configs to fontinfo_ids.
01173     int_result->adapted = true;
01174     for (int f = 0; f < int_result->fonts.size(); ++f) {
01175       int_result->fonts[f].fontinfo_id =
01176           GetFontinfoId(classes[class_id], int_result->fonts[f].fontinfo_id);
01177     }
01178   } else {
01179     // Pre-trained result. Map fonts using font_sets_.
01180     int_result->adapted = false;
01181     for (int f = 0; f < int_result->fonts.size(); ++f) {
01182       int_result->fonts[f].fontinfo_id =
01183           ClassAndConfigIDToFontOrShapeID(class_id,
01184                                           int_result->fonts[f].fontinfo_id);
01185     }
01186     if (shape_table_ != NULL) {
01187       // Two possible cases:
01188       // 1. Flat shapetable. All unichar-ids of the shapes referenced by
01189       // int_result->fonts are the same. In this case build a new vector of
01190       // mapped fonts and replace the fonts in int_result.
01191       // 2. Multi-unichar shapetable. Variable unichars in the shapes referenced
01192       // by int_result. In this case, build a vector of UnicharRating to
01193       // gather together different font-ids for each unichar. Also covers case1.
01194       GenericVector<UnicharRating> mapped_results;
01195       for (int f = 0; f < int_result->fonts.size(); ++f) {
01196         int shape_id = int_result->fonts[f].fontinfo_id;
01197         const Shape& shape = shape_table_->GetShape(shape_id);
01198         for (int c = 0; c < shape.size(); ++c) {
01199           int unichar_id = shape[c].unichar_id;
01200           if (!unicharset.get_enabled(unichar_id)) continue;
01201           // Find the mapped_result for unichar_id.
01202           int r = 0;
01203           for (r = 0; r < mapped_results.size() &&
01204                mapped_results[r].unichar_id != unichar_id; ++r) {}
01205           if (r == mapped_results.size()) {
01206             mapped_results.push_back(*int_result);
01207             mapped_results[r].unichar_id = unichar_id;
01208             mapped_results[r].fonts.truncate(0);
01209           }
01210           for (int i = 0; i < shape[c].font_ids.size(); ++i) {
01211             mapped_results[r].fonts.push_back(
01212                 ScoredFont(shape[c].font_ids[i], int_result->fonts[f].score));
01213           }
01214         }
01215       }
01216       for (int m = 0; m < mapped_results.size(); ++m) {
01217         mapped_results[m].rating =
01218             ComputeCorrectedRating(debug, mapped_results[m].unichar_id,
01219                                    cp_rating, int_result->rating,
01220                                    int_result->feature_misses, bottom, top,
01221                                    blob_length, matcher_multiplier, cn_factors);
01222         AddNewResult(mapped_results[m], final_results);
01223       }
01224       return;
01225     }
01226   }
01227   if (unicharset.get_enabled(class_id)) {
01228     int_result->rating = ComputeCorrectedRating(debug, class_id, cp_rating,
01229                                                 int_result->rating,
01230                                                 int_result->feature_misses,
01231                                                 bottom, top, blob_length,
01232                                                 matcher_multiplier, cn_factors);
01233     AddNewResult(*int_result, final_results);
01234   }
01235 }
01236 
01237 // Applies a set of corrections to the confidence im_rating,
01238 // including the cn_correction, miss penalty and additional penalty
01239 // for non-alnums being vertical misfits. Returns the corrected confidence.
01240 double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
01241                                         double cp_rating, double im_rating,
01242                                         int feature_misses,
01243                                         int bottom, int top,
01244                                         int blob_length, int matcher_multiplier,
01245                                         const uinT8* cn_factors) {
01246   // Compute class feature corrections.
01247   double cn_corrected = im_.ApplyCNCorrection(1.0 - im_rating, blob_length,
01248                                               cn_factors[unichar_id],
01249                                               matcher_multiplier);
01250   double miss_penalty = tessedit_class_miss_scale * feature_misses;
01251   double vertical_penalty = 0.0;
01252   // Penalize non-alnums for being vertical misfits.
01253   if (!unicharset.get_isalpha(unichar_id) &&
01254       !unicharset.get_isdigit(unichar_id) &&
01255       cn_factors[unichar_id] != 0 && classify_misfit_junk_penalty > 0.0) {
01256     int min_bottom, max_bottom, min_top, max_top;
01257     unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom,
01258                               &min_top, &max_top);
01259     if (debug) {
01260       tprintf("top=%d, vs [%d, %d], bottom=%d, vs [%d, %d]\n",
01261               top, min_top, max_top, bottom, min_bottom, max_bottom);
01262     }
01263     if (top < min_top || top > max_top ||
01264         bottom < min_bottom || bottom > max_bottom) {
01265       vertical_penalty = classify_misfit_junk_penalty;
01266     }
01267   }
01268   double result = 1.0 - (cn_corrected + miss_penalty + vertical_penalty);
01269   if (result < WORST_POSSIBLE_RATING)
01270     result = WORST_POSSIBLE_RATING;
01271   if (debug) {
01272     tprintf("%s: %2.1f%%(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
01273             unicharset.id_to_unichar(unichar_id),
01274             result * 100.0,
01275             cp_rating * 100.0,
01276             (1.0 - im_rating) * 100.0,
01277             (cn_corrected - (1.0 - im_rating)) * 100.0,
01278             cn_factors[unichar_id],
01279             miss_penalty * 100.0,
01280             vertical_penalty * 100.0);
01281   }
01282   return result;
01283 }
01284 
01285 /*---------------------------------------------------------------------------*/
01305 UNICHAR_ID *Classify::BaselineClassifier(
01306     TBLOB *Blob, const GenericVector<INT_FEATURE_STRUCT>& int_features,
01307     const INT_FX_RESULT_STRUCT& fx_info,
01308     ADAPT_TEMPLATES Templates, ADAPT_RESULTS *Results) {
01309   if (int_features.empty()) return NULL;
01310   uinT8* CharNormArray = new uinT8[unicharset.size()];
01311   ClearCharNormArray(CharNormArray);
01312 
01313   Results->BlobLength = IntCastRounded(fx_info.Length / kStandardFeatureLength);
01314   PruneClasses(Templates->Templates, int_features.size(), -1, &int_features[0],
01315                CharNormArray, BaselineCutoffs, &Results->CPResults);
01316 
01317   if (matcher_debug_level >= 2 || classify_debug_level > 1)
01318     tprintf("BL Matches =  ");
01319 
01320   MasterMatcher(Templates->Templates, int_features.size(), &int_features[0],
01321                 CharNormArray,
01322                 Templates->Class, matcher_debug_flags, 0,
01323                 Blob->bounding_box(), Results->CPResults, Results);
01324 
01325   delete [] CharNormArray;
01326   CLASS_ID ClassId = Results->best_unichar_id;
01327   if (ClassId == INVALID_UNICHAR_ID || Results->best_match_index < 0)
01328     return NULL;
01329 
01330   return Templates->Class[ClassId]->
01331       Config[Results->match[Results->best_match_index].config].Perm->Ambigs;
01332 }                                /* BaselineClassifier */
01333 
01334 
01335 /*---------------------------------------------------------------------------*/
01354 int Classify::CharNormClassifier(TBLOB *blob,
01355                                  const TrainingSample& sample,
01356                                  ADAPT_RESULTS *adapt_results) {
01357   // This is the length that is used for scaling ratings vs certainty.
01358   adapt_results->BlobLength =
01359       IntCastRounded(sample.outline_length() / kStandardFeatureLength);
01360   GenericVector<UnicharRating> unichar_results;
01361   static_classifier_->UnicharClassifySample(sample, blob->denorm().pix(), 0,
01362                                             -1, &unichar_results);
01363   // Convert results to the format used internally by AdaptiveClassifier.
01364   for (int r = 0; r < unichar_results.size(); ++r) {
01365     AddNewResult(unichar_results[r], adapt_results);
01366   }
01367   return sample.num_features();
01368 }                                /* CharNormClassifier */
01369 
01370 // As CharNormClassifier, but operates on a TrainingSample and outputs to
01371 // a GenericVector of ShapeRating without conversion to classes.
01372 int Classify::CharNormTrainingSample(bool pruner_only,
01373                                      int keep_this,
01374                                      const TrainingSample& sample,
01375                                      GenericVector<UnicharRating>* results) {
01376   results->clear();
01377   ADAPT_RESULTS* adapt_results = new ADAPT_RESULTS();
01378   adapt_results->Initialize();
01379   // Compute the bounding box of the features.
01380   int num_features = sample.num_features();
01381   // Only the top and bottom of the blob_box are used by MasterMatcher, so
01382   // fabricate right and left using top and bottom.
01383   TBOX blob_box(sample.geo_feature(GeoBottom), sample.geo_feature(GeoBottom),
01384                 sample.geo_feature(GeoTop), sample.geo_feature(GeoTop));
01385   // Compute the char_norm_array from the saved cn_feature.
01386   FEATURE norm_feature = sample.GetCNFeature();
01387   uinT8* char_norm_array = new uinT8[unicharset.size()];
01388   int num_pruner_classes = MAX(unicharset.size(),
01389                                PreTrainedTemplates->NumClasses);
01390   uinT8* pruner_norm_array = new uinT8[num_pruner_classes];
01391   adapt_results->BlobLength =
01392       static_cast<int>(ActualOutlineLength(norm_feature) * 20 + 0.5);
01393   ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
01394                         pruner_norm_array);
01395 
01396   PruneClasses(PreTrainedTemplates, num_features, keep_this, sample.features(),
01397                pruner_norm_array,
01398                shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs,
01399                &adapt_results->CPResults);
01400   delete [] pruner_norm_array;
01401   if (keep_this >= 0) {
01402     adapt_results->CPResults[0].Class = keep_this;
01403     adapt_results->CPResults.truncate(1);
01404   }
01405   if (pruner_only) {
01406     // Convert pruner results to output format.
01407     for (int i = 0; i < adapt_results->CPResults.size(); ++i) {
01408       int class_id = adapt_results->CPResults[i].Class;
01409       results->push_back(
01410           UnicharRating(class_id, 1.0f - adapt_results->CPResults[i].Rating));
01411     }
01412   } else {
01413     MasterMatcher(PreTrainedTemplates, num_features, sample.features(),
01414                   char_norm_array,
01415                   NULL, matcher_debug_flags,
01416                   classify_integer_matcher_multiplier,
01417                   blob_box, adapt_results->CPResults, adapt_results);
01418     // Convert master matcher results to output format.
01419     for (int i = 0; i < adapt_results->match.size(); i++) {
01420       results->push_back(adapt_results->match[i]);
01421     }
01422     results->sort(&UnicharRating::SortDescendingRating);
01423   }
01424   delete [] char_norm_array;
01425   delete adapt_results;
01426   return num_features;
01427 }                                /* CharNormTrainingSample */
01428 
01429 
01430 /*---------------------------------------------------------------------------*/
01445 void Classify::ClassifyAsNoise(ADAPT_RESULTS *results) {
01446   float rating = results->BlobLength / matcher_avg_noise_size;
01447   rating *= rating;
01448   rating /= 1.0 + rating;
01449 
01450   AddNewResult(UnicharRating(UNICHAR_SPACE, 1.0f - rating), results);
01451 }                                /* ClassifyAsNoise */
01452 
01459 void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
01460                                        ADAPT_RESULTS *Results,
01461                                        BLOB_CHOICE_LIST *Choices) {
01462   assert(Choices != NULL);
01463   FLOAT32 Rating;
01464   FLOAT32 Certainty;
01465   BLOB_CHOICE_IT temp_it;
01466   bool contains_nonfrag = false;
01467   temp_it.set_to_list(Choices);
01468   int choices_length = 0;
01469   // With no shape_table_ maintain the previous MAX_MATCHES as the maximum
01470   // number of returned results, but with a shape_table_ we want to have room
01471   // for at least the biggest shape (which might contain hundreds of Indic
01472   // grapheme fragments) and more, so use double the size of the biggest shape
01473   // if that is more than the default.
01474   int max_matches = MAX_MATCHES;
01475   if (shape_table_ != NULL) {
01476     max_matches = shape_table_->MaxNumUnichars() * 2;
01477     if (max_matches < MAX_MATCHES)
01478       max_matches = MAX_MATCHES;
01479   }
01480 
01481   float best_certainty = -MAX_FLOAT32;
01482   for (int i = 0; i < Results->match.size(); i++) {
01483     const UnicharRating& result = Results->match[i];
01484     bool adapted = result.adapted;
01485     bool current_is_frag = (unicharset.get_fragment(result.unichar_id) != NULL);
01486     if (temp_it.length()+1 == max_matches &&
01487         !contains_nonfrag && current_is_frag) {
01488       continue;  // look for a non-fragmented character to fill the
01489                  // last spot in Choices if only fragments are present
01490     }
01491     // BlobLength can never be legally 0, this means recognition failed.
01492     // But we must return a classification result because some invoking
01493     // functions (chopper/permuter) do not anticipate a null blob choice.
01494     // So we need to assign a poor, but not infinitely bad score.
01495     if (Results->BlobLength == 0) {
01496       Certainty = -20;
01497       Rating = 100;    // should be -certainty * real_blob_length
01498     } else {
01499       Rating = Certainty = (1.0f - result.rating);
01500       Rating *= rating_scale * Results->BlobLength;
01501       Certainty *= -(getDict().certainty_scale);
01502     }
01503     // Adapted results, by their very nature, should have good certainty.
01504     // Those that don't are at best misleading, and often lead to errors,
01505     // so don't accept adapted results that are too far behind the best result,
01506     // whether adapted or static.
01507     // TODO(rays) find some way of automatically tuning these constants.
01508     if (Certainty > best_certainty) {
01509       best_certainty = MIN(Certainty, classify_adapted_pruning_threshold);
01510     } else if (adapted &&
01511                Certainty / classify_adapted_pruning_factor < best_certainty) {
01512       continue;  // Don't accept bad adapted results.
01513     }
01514 
01515     float min_xheight, max_xheight, yshift;
01516     denorm.XHeightRange(result.unichar_id, unicharset, box,
01517                         &min_xheight, &max_xheight, &yshift);
01518     BLOB_CHOICE* choice =
01519         new BLOB_CHOICE(result.unichar_id, Rating, Certainty,
01520                         unicharset.get_script(result.unichar_id),
01521                         min_xheight, max_xheight, yshift,
01522                         adapted ? BCC_ADAPTED_CLASSIFIER
01523                                 : BCC_STATIC_CLASSIFIER);
01524     choice->set_fonts(result.fonts);
01525     temp_it.add_to_end(choice);
01526     contains_nonfrag |= !current_is_frag;  // update contains_nonfrag
01527     choices_length++;
01528     if (choices_length >= max_matches) break;
01529   }
01530   Results->match.truncate(choices_length);
01531 }  // ConvertMatchesToChoices
01532 
01533 
01534 /*---------------------------------------------------------------------------*/
01535 #ifndef GRAPHICS_DISABLED
01536 
01546 void Classify::DebugAdaptiveClassifier(TBLOB *blob,
01547                                        ADAPT_RESULTS *Results) {
01548   if (static_classifier_ == NULL) return;
01549   INT_FX_RESULT_STRUCT fx_info;
01550   GenericVector<INT_FEATURE_STRUCT> bl_features;
01551   TrainingSample* sample =
01552       BlobToTrainingSample(*blob, false, &fx_info, &bl_features);
01553   if (sample == NULL) return;
01554   static_classifier_->DebugDisplay(*sample, blob->denorm().pix(),
01555                                    Results->best_unichar_id);
01556 }                                /* DebugAdaptiveClassifier */
01557 #endif
01558 
01559 /*---------------------------------------------------------------------------*/
01582 void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
01583   UNICHAR_ID *Ambiguities;
01584 
01585   INT_FX_RESULT_STRUCT fx_info;
01586   GenericVector<INT_FEATURE_STRUCT> bl_features;
01587   TrainingSample* sample =
01588       BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
01589                            &bl_features);
01590   if (sample == NULL) return;
01591 
01592   if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min ||
01593       tess_cn_matching) {
01594     CharNormClassifier(Blob, *sample, Results);
01595   } else {
01596     Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
01597                                      AdaptedTemplates, Results);
01598     if ((!Results->match.empty() &&
01599          MarginalMatch(Results->best_rating,
01600                        matcher_reliable_adaptive_result) &&
01601          !tess_bn_matching) ||
01602         Results->match.empty()) {
01603       CharNormClassifier(Blob, *sample, Results);
01604     } else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) {
01605       AmbigClassifier(bl_features, fx_info, Blob,
01606                       PreTrainedTemplates,
01607                       AdaptedTemplates->Class,
01608                       Ambiguities,
01609                       Results);
01610     }
01611   }
01612 
01613   // Force the blob to be classified as noise
01614   // if the results contain only fragments.
01615   // TODO(daria): verify that this is better than
01616   // just adding a NULL classification.
01617   if (!Results->HasNonfragment || Results->match.empty())
01618     ClassifyAsNoise(Results);
01619   delete sample;
01620 }   /* DoAdaptiveMatch */
01621 
01622 /*---------------------------------------------------------------------------*/
01639 UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob,
01640                                      CLASS_ID CorrectClass) {
01641   ADAPT_RESULTS *Results = new ADAPT_RESULTS();
01642   UNICHAR_ID *Ambiguities;
01643   int i;
01644 
01645   Results->Initialize();
01646   INT_FX_RESULT_STRUCT fx_info;
01647   GenericVector<INT_FEATURE_STRUCT> bl_features;
01648   TrainingSample* sample =
01649       BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
01650                            &bl_features);
01651   if (sample == NULL) {
01652     delete Results;
01653     return NULL;
01654   }
01655 
01656   CharNormClassifier(Blob, *sample, Results);
01657   delete sample;
01658   RemoveBadMatches(Results);
01659   Results->match.sort(&UnicharRating::SortDescendingRating);
01660 
01661   /* copy the class id's into an string of ambiguities - don't copy if
01662      the correct class is the only class id matched */
01663   Ambiguities = new UNICHAR_ID[Results->match.size() + 1];
01664   if (Results->match.size() > 1 ||
01665       (Results->match.size() == 1 &&
01666           Results->match[0].unichar_id != CorrectClass)) {
01667     for (i = 0; i < Results->match.size(); i++)
01668       Ambiguities[i] = Results->match[i].unichar_id;
01669     Ambiguities[i] = -1;
01670   } else {
01671     Ambiguities[0] = -1;
01672   }
01673 
01674   delete Results;
01675   return Ambiguities;
01676 }                              /* GetAmbiguities */
01677 
01678 // Returns true if the given blob looks too dissimilar to any character
01679 // present in the classifier templates.
01680 bool Classify::LooksLikeGarbage(TBLOB *blob) {
01681   BLOB_CHOICE_LIST *ratings = new BLOB_CHOICE_LIST();
01682   AdaptiveClassifier(blob, ratings);
01683   BLOB_CHOICE_IT ratings_it(ratings);
01684   const UNICHARSET &unicharset = getDict().getUnicharset();
01685   if (classify_debug_character_fragments) {
01686     print_ratings_list("======================\nLooksLikeGarbage() got ",
01687                        ratings, unicharset);
01688   }
01689   for (ratings_it.mark_cycle_pt(); !ratings_it.cycled_list();
01690        ratings_it.forward()) {
01691     if (unicharset.get_fragment(ratings_it.data()->unichar_id()) != NULL) {
01692       continue;
01693     }
01694     float certainty = ratings_it.data()->certainty();
01695     delete ratings;
01696     return certainty <
01697             classify_character_fragments_garbage_certainty_threshold;
01698   }
01699   delete ratings;
01700   return true;  // no whole characters in ratings
01701 }
01702 
01703 /*---------------------------------------------------------------------------*/
01727 int Classify::GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info,
01728                                  INT_TEMPLATES templates,
01729                                  uinT8* pruner_norm_array,
01730                                  uinT8* char_norm_array) {
01731   FEATURE norm_feature = NewFeature(&CharNormDesc);
01732   float baseline = kBlnBaselineOffset;
01733   float scale = MF_SCALE_FACTOR;
01734   norm_feature->Params[CharNormY] = (fx_info.Ymean - baseline) * scale;
01735   norm_feature->Params[CharNormLength] =
01736       fx_info.Length * scale / LENGTH_COMPRESSION;
01737   norm_feature->Params[CharNormRx] = fx_info.Rx * scale;
01738   norm_feature->Params[CharNormRy] = fx_info.Ry * scale;
01739   // Deletes norm_feature.
01740   ComputeCharNormArrays(norm_feature, templates, char_norm_array,
01741                         pruner_norm_array);
01742   return IntCastRounded(fx_info.Length / kStandardFeatureLength);
01743 }                              /* GetCharNormFeature */
01744 
01745 // Computes the char_norm_array for the unicharset and, if not NULL, the
01746 // pruner_array as appropriate according to the existence of the shape_table.
01747 void Classify::ComputeCharNormArrays(FEATURE_STRUCT* norm_feature,
01748                                      INT_TEMPLATES_STRUCT* templates,
01749                                      uinT8* char_norm_array,
01750                                      uinT8* pruner_array) {
01751   ComputeIntCharNormArray(*norm_feature, char_norm_array);
01752   if (pruner_array != NULL) {
01753     if (shape_table_ == NULL) {
01754       ComputeIntCharNormArray(*norm_feature, pruner_array);
01755     } else {
01756       memset(pruner_array, MAX_UINT8,
01757              templates->NumClasses * sizeof(pruner_array[0]));
01758       // Each entry in the pruner norm array is the MIN of all the entries of
01759       // the corresponding unichars in the CharNormArray.
01760       for (int id = 0; id < templates->NumClasses; ++id) {
01761         int font_set_id = templates->Class[id]->font_set_id;
01762         const FontSet &fs = fontset_table_.get(font_set_id);
01763         for (int config = 0; config < fs.size; ++config) {
01764           const Shape& shape = shape_table_->GetShape(fs.configs[config]);
01765           for (int c = 0; c < shape.size(); ++c) {
01766             if (char_norm_array[shape[c].unichar_id] < pruner_array[id])
01767               pruner_array[id] = char_norm_array[shape[c].unichar_id];
01768           }
01769         }
01770       }
01771     }
01772   }
01773   FreeFeature(norm_feature);
01774 }
01775 
01776 /*---------------------------------------------------------------------------*/
01791 int Classify::MakeNewTemporaryConfig(ADAPT_TEMPLATES Templates,
01792                            CLASS_ID ClassId,
01793                            int FontinfoId,
01794                            int NumFeatures,
01795                            INT_FEATURE_ARRAY Features,
01796                            FEATURE_SET FloatFeatures) {
01797   INT_CLASS IClass;
01798   ADAPT_CLASS Class;
01799   PROTO_ID OldProtos[MAX_NUM_PROTOS];
01800   FEATURE_ID BadFeatures[MAX_NUM_INT_FEATURES];
01801   int NumOldProtos;
01802   int NumBadFeatures;
01803   int MaxProtoId, OldMaxProtoId;
01804   int BlobLength = 0;
01805   int MaskSize;
01806   int ConfigId;
01807   TEMP_CONFIG Config;
01808   int i;
01809   int debug_level = NO_DEBUG;
01810 
01811   if (classify_learning_debug_level >= 3)
01812     debug_level =
01813         PRINT_MATCH_SUMMARY | PRINT_FEATURE_MATCHES | PRINT_PROTO_MATCHES;
01814 
01815   IClass = ClassForClassId(Templates->Templates, ClassId);
01816   Class = Templates->Class[ClassId];
01817 
01818   if (IClass->NumConfigs >= MAX_NUM_CONFIGS) {
01819     ++NumAdaptationsFailed;
01820     if (classify_learning_debug_level >= 1)
01821       cprintf("Cannot make new temporary config: maximum number exceeded.\n");
01822     return -1;
01823   }
01824 
01825   OldMaxProtoId = IClass->NumProtos - 1;
01826 
01827   NumOldProtos = im_.FindGoodProtos(IClass, AllProtosOn, AllConfigsOff,
01828                                     BlobLength, NumFeatures, Features,
01829                                     OldProtos, classify_adapt_proto_threshold,
01830                                     debug_level);
01831 
01832   MaskSize = WordsInVectorOfSize(MAX_NUM_PROTOS);
01833   zero_all_bits(TempProtoMask, MaskSize);
01834   for (i = 0; i < NumOldProtos; i++)
01835     SET_BIT(TempProtoMask, OldProtos[i]);
01836 
01837   NumBadFeatures = im_.FindBadFeatures(IClass, TempProtoMask, AllConfigsOn,
01838                                        BlobLength, NumFeatures, Features,
01839                                        BadFeatures,
01840                                        classify_adapt_feature_threshold,
01841                                        debug_level);
01842 
01843   MaxProtoId = MakeNewTempProtos(FloatFeatures, NumBadFeatures, BadFeatures,
01844                                  IClass, Class, TempProtoMask);
01845   if (MaxProtoId == NO_PROTO) {
01846     ++NumAdaptationsFailed;
01847     if (classify_learning_debug_level >= 1)
01848       cprintf("Cannot make new temp protos: maximum number exceeded.\n");
01849     return -1;
01850   }
01851 
01852   ConfigId = AddIntConfig(IClass);
01853   ConvertConfig(TempProtoMask, ConfigId, IClass);
01854   Config = NewTempConfig(MaxProtoId, FontinfoId);
01855   TempConfigFor(Class, ConfigId) = Config;
01856   copy_all_bits(TempProtoMask, Config->Protos, Config->ProtoVectorSize);
01857 
01858   if (classify_learning_debug_level >= 1)
01859     cprintf("Making new temp config %d fontinfo id %d"
01860             " using %d old and %d new protos.\n",
01861             ConfigId, Config->FontinfoId,
01862             NumOldProtos, MaxProtoId - OldMaxProtoId);
01863 
01864   return ConfigId;
01865 }                              /* MakeNewTemporaryConfig */
01866 
01867 /*---------------------------------------------------------------------------*/
01888 PROTO_ID Classify::MakeNewTempProtos(FEATURE_SET Features,
01889                                      int NumBadFeat,
01890                                      FEATURE_ID BadFeat[],
01891                                      INT_CLASS IClass,
01892                                      ADAPT_CLASS Class,
01893                                      BIT_VECTOR TempProtoMask) {
01894   FEATURE_ID *ProtoStart;
01895   FEATURE_ID *ProtoEnd;
01896   FEATURE_ID *LastBad;
01897   TEMP_PROTO TempProto;
01898   PROTO Proto;
01899   FEATURE F1, F2;
01900   FLOAT32 X1, X2, Y1, Y2;
01901   FLOAT32 A1, A2, AngleDelta;
01902   FLOAT32 SegmentLength;
01903   PROTO_ID Pid;
01904 
01905   for (ProtoStart = BadFeat, LastBad = ProtoStart + NumBadFeat;
01906        ProtoStart < LastBad; ProtoStart = ProtoEnd) {
01907     F1 = Features->Features[*ProtoStart];
01908     X1 = F1->Params[PicoFeatX];
01909     Y1 = F1->Params[PicoFeatY];
01910     A1 = F1->Params[PicoFeatDir];
01911 
01912     for (ProtoEnd = ProtoStart + 1,
01913          SegmentLength = GetPicoFeatureLength();
01914          ProtoEnd < LastBad;
01915          ProtoEnd++, SegmentLength += GetPicoFeatureLength()) {
01916       F2 = Features->Features[*ProtoEnd];
01917       X2 = F2->Params[PicoFeatX];
01918       Y2 = F2->Params[PicoFeatY];
01919       A2 = F2->Params[PicoFeatDir];
01920 
01921       AngleDelta = fabs(A1 - A2);
01922       if (AngleDelta > 0.5)
01923         AngleDelta = 1.0 - AngleDelta;
01924 
01925       if (AngleDelta > matcher_clustering_max_angle_delta ||
01926           fabs(X1 - X2) > SegmentLength ||
01927           fabs(Y1 - Y2) > SegmentLength)
01928         break;
01929     }
01930 
01931     F2 = Features->Features[*(ProtoEnd - 1)];
01932     X2 = F2->Params[PicoFeatX];
01933     Y2 = F2->Params[PicoFeatY];
01934     A2 = F2->Params[PicoFeatDir];
01935 
01936     Pid = AddIntProto(IClass);
01937     if (Pid == NO_PROTO)
01938       return (NO_PROTO);
01939 
01940     TempProto = NewTempProto();
01941     Proto = &(TempProto->Proto);
01942 
01943     /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
01944        ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
01945        instead of the -0.25 to 0.75 used in baseline normalization */
01946     Proto->Length = SegmentLength;
01947     Proto->Angle = A1;
01948     Proto->X = (X1 + X2) / 2.0;
01949     Proto->Y = (Y1 + Y2) / 2.0 - Y_DIM_OFFSET;
01950     FillABC(Proto);
01951 
01952     TempProto->ProtoId = Pid;
01953     SET_BIT(TempProtoMask, Pid);
01954 
01955     ConvertProto(Proto, Pid, IClass);
01956     AddProtoToProtoPruner(Proto, Pid, IClass,
01957                           classify_learning_debug_level >= 2);
01958 
01959     Class->TempProtos = push(Class->TempProtos, TempProto);
01960   }
01961   return IClass->NumProtos - 1;
01962 }                              /* MakeNewTempProtos */
01963 
01964 /*---------------------------------------------------------------------------*/
01977 void Classify::MakePermanent(ADAPT_TEMPLATES Templates,
01978                              CLASS_ID ClassId,
01979                              int ConfigId,
01980                              TBLOB *Blob) {
01981   UNICHAR_ID *Ambigs;
01982   TEMP_CONFIG Config;
01983   ADAPT_CLASS Class;
01984   PROTO_KEY ProtoKey;
01985 
01986   Class = Templates->Class[ClassId];
01987   Config = TempConfigFor(Class, ConfigId);
01988 
01989   MakeConfigPermanent(Class, ConfigId);
01990   if (Class->NumPermConfigs == 0)
01991     Templates->NumPermClasses++;
01992   Class->NumPermConfigs++;
01993 
01994   // Initialize permanent config.
01995   Ambigs = GetAmbiguities(Blob, ClassId);
01996   PERM_CONFIG Perm = (PERM_CONFIG) alloc_struct(sizeof(PERM_CONFIG_STRUCT),
01997                                                 "PERM_CONFIG_STRUCT");
01998   Perm->Ambigs = Ambigs;
01999   Perm->FontinfoId = Config->FontinfoId;
02000 
02001   // Free memory associated with temporary config (since ADAPTED_CONFIG
02002   // is a union we need to clean up before we record permanent config).
02003   ProtoKey.Templates = Templates;
02004   ProtoKey.ClassId = ClassId;
02005   ProtoKey.ConfigId = ConfigId;
02006   Class->TempProtos = delete_d(Class->TempProtos, &ProtoKey, MakeTempProtoPerm);
02007   FreeTempConfig(Config);
02008 
02009   // Record permanent config.
02010   PermConfigFor(Class, ConfigId) = Perm;
02011 
02012   if (classify_learning_debug_level >= 1) {
02013     tprintf("Making config %d for %s (ClassId %d) permanent:"
02014             " fontinfo id %d, ambiguities '",
02015             ConfigId, getDict().getUnicharset().debug_str(ClassId).string(),
02016             ClassId, PermConfigFor(Class, ConfigId)->FontinfoId);
02017     for (UNICHAR_ID *AmbigsPointer = Ambigs;
02018         *AmbigsPointer >= 0; ++AmbigsPointer)
02019       tprintf("%s", unicharset.id_to_unichar(*AmbigsPointer));
02020     tprintf("'.\n");
02021   }
02022 }                              /* MakePermanent */
02023 }  // namespace tesseract
02024 
02025 /*---------------------------------------------------------------------------*/
02040 int MakeTempProtoPerm(void *item1, void *item2) {
02041   ADAPT_CLASS Class;
02042   TEMP_CONFIG Config;
02043   TEMP_PROTO TempProto;
02044   PROTO_KEY *ProtoKey;
02045 
02046   TempProto = (TEMP_PROTO) item1;
02047   ProtoKey = (PROTO_KEY *) item2;
02048 
02049   Class = ProtoKey->Templates->Class[ProtoKey->ClassId];
02050   Config = TempConfigFor(Class, ProtoKey->ConfigId);
02051 
02052   if (TempProto->ProtoId > Config->MaxProtoId ||
02053       !test_bit (Config->Protos, TempProto->ProtoId))
02054     return FALSE;
02055 
02056   MakeProtoPermanent(Class, TempProto->ProtoId);
02057   AddProtoToClassPruner(&(TempProto->Proto), ProtoKey->ClassId,
02058                          ProtoKey->Templates->Templates);
02059   FreeTempProto(TempProto);
02060 
02061   return TRUE;
02062 }                              /* MakeTempProtoPerm */
02063 
02064 /*---------------------------------------------------------------------------*/
02065 namespace tesseract {
02076 void Classify::PrintAdaptiveMatchResults(const ADAPT_RESULTS& results) {
02077   for (int i = 0; i < results.match.size(); ++i) {
02078     tprintf("%s  ", unicharset.debug_str(results.match[i].unichar_id).string());
02079     results.match[i].Print();
02080   }
02081 }                              /* PrintAdaptiveMatchResults */
02082 
02083 /*---------------------------------------------------------------------------*/
02099 void Classify::RemoveBadMatches(ADAPT_RESULTS *Results) {
02100   int Next, NextGood;
02101   FLOAT32 BadMatchThreshold;
02102   static const char* romans = "i v x I V X";
02103   BadMatchThreshold = Results->best_rating - matcher_bad_match_pad;
02104 
02105   if (classify_bln_numeric_mode) {
02106     UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ?
02107         unicharset.unichar_to_id("1") : -1;
02108     UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ?
02109         unicharset.unichar_to_id("0") : -1;
02110     float scored_one = ScoredUnichar(unichar_id_one, *Results);
02111     float scored_zero = ScoredUnichar(unichar_id_zero, *Results);
02112 
02113     for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
02114       const UnicharRating& match = Results->match[Next];
02115       if (match.rating >= BadMatchThreshold) {
02116         if (!unicharset.get_isalpha(match.unichar_id) ||
02117             strstr(romans,
02118                    unicharset.id_to_unichar(match.unichar_id)) != NULL) {
02119         } else if (unicharset.eq(match.unichar_id, "l") &&
02120                    scored_one < BadMatchThreshold) {
02121           Results->match[Next].unichar_id = unichar_id_one;
02122         } else if (unicharset.eq(match.unichar_id, "O") &&
02123                    scored_zero < BadMatchThreshold) {
02124           Results->match[Next].unichar_id = unichar_id_zero;
02125         } else {
02126           Results->match[Next].unichar_id = INVALID_UNICHAR_ID;  // Don't copy.
02127         }
02128         if (Results->match[Next].unichar_id != INVALID_UNICHAR_ID) {
02129           if (NextGood == Next) {
02130             ++NextGood;
02131           } else {
02132             Results->match[NextGood++] = Results->match[Next];
02133           }
02134         }
02135       }
02136     }
02137   } else {
02138     for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
02139       if (Results->match[Next].rating >= BadMatchThreshold) {
02140         if (NextGood == Next) {
02141           ++NextGood;
02142         } else {
02143           Results->match[NextGood++] = Results->match[Next];
02144         }
02145       }
02146     }
02147   }
02148   Results->match.truncate(NextGood);
02149 }                              /* RemoveBadMatches */
02150 
02151 /*----------------------------------------------------------------------------*/
02161 void Classify::RemoveExtraPuncs(ADAPT_RESULTS *Results) {
02162   int Next, NextGood;
02163   int punc_count;              /*no of garbage characters */
02164   int digit_count;
02165   /*garbage characters */
02166   static char punc_chars[] = ". , ; : / ` ~ ' - = \\ | \" ! _ ^";
02167   static char digit_chars[] = "0 1 2 3 4 5 6 7 8 9";
02168 
02169   punc_count = 0;
02170   digit_count = 0;
02171   for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
02172     const UnicharRating& match = Results->match[Next];
02173     bool keep = true;
02174     if (strstr(punc_chars,
02175                unicharset.id_to_unichar(match.unichar_id)) != NULL) {
02176       if (punc_count >= 2)
02177         keep = false;
02178       punc_count++;
02179     } else {
02180       if (strstr(digit_chars,
02181                  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
02182         if (digit_count >= 1)
02183           keep = false;
02184         digit_count++;
02185       }
02186     }
02187     if (keep) {
02188       if (NextGood == Next) {
02189         ++NextGood;
02190       } else {
02191         Results->match[NextGood++] = match;
02192       }
02193     }
02194   }
02195   Results->match.truncate(NextGood);
02196 }                              /* RemoveExtraPuncs */
02197 
02198 /*---------------------------------------------------------------------------*/
02212 void Classify::SetAdaptiveThreshold(FLOAT32 Threshold) {
02213   Threshold = (Threshold == matcher_good_threshold) ? 0.9: (1.0 - Threshold);
02214   classify_adapt_proto_threshold.set_value(
02215       ClipToRange<int>(255 * Threshold, 0, 255));
02216   classify_adapt_feature_threshold.set_value(
02217       ClipToRange<int>(255 * Threshold, 0, 255));
02218 }                              /* SetAdaptiveThreshold */
02219 
02220 /*---------------------------------------------------------------------------*/
02233 void Classify::ShowBestMatchFor(int shape_id,
02234                                 const INT_FEATURE_STRUCT* features,
02235                                 int num_features) {
02236 #ifndef GRAPHICS_DISABLED
02237   uinT32 config_mask;
02238   if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) {
02239     tprintf("No built-in templates for class/shape %d\n", shape_id);
02240     return;
02241   }
02242   if (num_features <= 0) {
02243     tprintf("Illegal blob (char norm features)!\n");
02244     return;
02245   }
02246   UnicharRating cn_result;
02247   classify_norm_method.set_value(character);
02248   im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
02249             AllProtosOn, AllConfigsOn,
02250             num_features, features, &cn_result,
02251             classify_adapt_feature_threshold, NO_DEBUG,
02252             matcher_debug_separate_windows);
02253   tprintf("\n");
02254   config_mask = 1 << cn_result.config;
02255 
02256   tprintf("Static Shape ID: %d\n", shape_id);
02257   ShowMatchDisplay();
02258   im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
02259             AllProtosOn, reinterpret_cast<BIT_VECTOR>(&config_mask),
02260             num_features, features, &cn_result,
02261             classify_adapt_feature_threshold,
02262             matcher_debug_flags,
02263             matcher_debug_separate_windows);
02264   UpdateMatchDisplay();
02265 #endif  // GRAPHICS_DISABLED
02266 }                              /* ShowBestMatchFor */
02267 
02268 // Returns a string for the classifier class_id: either the corresponding
02269 // unicharset debug_str or the shape_table_ debug str.
02270 STRING Classify::ClassIDToDebugStr(const INT_TEMPLATES_STRUCT* templates,
02271                                    int class_id, int config_id) const {
02272   STRING class_string;
02273   if (templates == PreTrainedTemplates && shape_table_ != NULL) {
02274     int shape_id = ClassAndConfigIDToFontOrShapeID(class_id, config_id);
02275     class_string = shape_table_->DebugStr(shape_id);
02276   } else {
02277     class_string = unicharset.debug_str(class_id);
02278   }
02279   return class_string;
02280 }
02281 
02282 // Converts a classifier class_id index to a shape_table_ index
02283 int Classify::ClassAndConfigIDToFontOrShapeID(int class_id,
02284                                               int int_result_config) const {
02285   int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id;
02286   // Older inttemps have no font_ids.
02287   if (font_set_id < 0)
02288     return kBlankFontinfoId;
02289   const FontSet &fs = fontset_table_.get(font_set_id);
02290   ASSERT_HOST(int_result_config >= 0 && int_result_config < fs.size);
02291   return fs.configs[int_result_config];
02292 }
02293 
02294 // Converts a shape_table_ index to a classifier class_id index (not a
02295 // unichar-id!). Uses a search, so not fast.
02296 int Classify::ShapeIDToClassID(int shape_id) const {
02297   for (int id = 0; id < PreTrainedTemplates->NumClasses; ++id) {
02298     int font_set_id = PreTrainedTemplates->Class[id]->font_set_id;
02299     ASSERT_HOST(font_set_id >= 0);
02300     const FontSet &fs = fontset_table_.get(font_set_id);
02301     for (int config = 0; config < fs.size; ++config) {
02302       if (fs.configs[config] == shape_id)
02303         return id;
02304     }
02305   }
02306   tprintf("Shape %d not found\n", shape_id);
02307   return -1;
02308 }
02309 
02310 // Returns true if the given TEMP_CONFIG is good enough to make it
02311 // a permanent config.
02312 bool Classify::TempConfigReliable(CLASS_ID class_id,
02313                                   const TEMP_CONFIG &config) {
02314   if (classify_learning_debug_level >= 1) {
02315     tprintf("NumTimesSeen for config of %s is %d\n",
02316             getDict().getUnicharset().debug_str(class_id).string(),
02317             config->NumTimesSeen);
02318   }
02319   if (config->NumTimesSeen >= matcher_sufficient_examples_for_prototyping) {
02320     return true;
02321   } else if (config->NumTimesSeen < matcher_min_examples_for_prototyping) {
02322     return false;
02323   } else if (use_ambigs_for_adaption) {
02324     // Go through the ambigs vector and see whether we have already seen
02325     // enough times all the characters represented by the ambigs vector.
02326     const UnicharIdVector *ambigs =
02327       getDict().getUnicharAmbigs().AmbigsForAdaption(class_id);
02328     int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
02329     for (int ambig = 0; ambig < ambigs_size; ++ambig) {
02330       ADAPT_CLASS ambig_class = AdaptedTemplates->Class[(*ambigs)[ambig]];
02331       assert(ambig_class != NULL);
02332       if (ambig_class->NumPermConfigs == 0 &&
02333           ambig_class->MaxNumTimesSeen <
02334           matcher_min_examples_for_prototyping) {
02335         if (classify_learning_debug_level >= 1) {
02336           tprintf("Ambig %s has not been seen enough times,"
02337                   " not making config for %s permanent\n",
02338                   getDict().getUnicharset().debug_str(
02339                       (*ambigs)[ambig]).string(),
02340                   getDict().getUnicharset().debug_str(class_id).string());
02341         }
02342         return false;
02343       }
02344     }
02345   }
02346   return true;
02347 }
02348 
02349 void Classify::UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob) {
02350   const UnicharIdVector *ambigs =
02351     getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id);
02352   int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
02353   if (classify_learning_debug_level >= 1) {
02354     tprintf("Running UpdateAmbigsGroup for %s class_id=%d\n",
02355             getDict().getUnicharset().debug_str(class_id).string(), class_id);
02356   }
02357   for (int ambig = 0; ambig < ambigs_size; ++ambig) {
02358     CLASS_ID ambig_class_id = (*ambigs)[ambig];
02359     const ADAPT_CLASS ambigs_class = AdaptedTemplates->Class[ambig_class_id];
02360     for (int cfg = 0; cfg < MAX_NUM_CONFIGS; ++cfg) {
02361       if (ConfigIsPermanent(ambigs_class, cfg)) continue;
02362       const TEMP_CONFIG config =
02363         TempConfigFor(AdaptedTemplates->Class[ambig_class_id], cfg);
02364       if (config != NULL && TempConfigReliable(ambig_class_id, config)) {
02365         if (classify_learning_debug_level >= 1) {
02366           tprintf("Making config %d of %s permanent\n", cfg,
02367                   getDict().getUnicharset().debug_str(
02368                       ambig_class_id).string());
02369         }
02370         MakePermanent(AdaptedTemplates, ambig_class_id, cfg, Blob);
02371       }
02372     }
02373   }
02374 }
02375 
02376 }  // namespace tesseract
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines