tesseract  3.03
/usr/local/google/home/jbreiden/tesseract-ocr-read-only/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 "ambigs.h"
00028 #include "blobclass.h"
00029 #include "blobs.h"
00030 #include "helpers.h"
00031 #include "normfeat.h"
00032 #include "mfoutline.h"
00033 #include "picofeat.h"
00034 #include "float2int.h"
00035 #include "outfeat.h"
00036 #include "emalloc.h"
00037 #include "intfx.h"
00038 #include "efio.h"
00039 #include "normmatch.h"
00040 #include "ndminx.h"
00041 #include "intproto.h"
00042 #include "const.h"
00043 #include "globals.h"
00044 #include "werd.h"
00045 #include "callcpp.h"
00046 #include "pageres.h"
00047 #include "params.h"
00048 #include "classify.h"
00049 #include "shapetable.h"
00050 #include "tessclassifier.h"
00051 #include "trainingsample.h"
00052 #include "unicharset.h"
00053 #include "dict.h"
00054 #include "featdefs.h"
00055 #include "genericvector.h"
00056 
00057 #include <stdio.h>
00058 #include <string.h>
00059 #include <stdlib.h>
00060 #include <math.h>
00061 #ifdef __UNIX__
00062 #include <assert.h>
00063 #endif
00064 
00065 #define ADAPT_TEMPLATE_SUFFIX ".a"
00066 
00067 #define MAX_MATCHES         10
00068 #define UNLIKELY_NUM_FEAT 200
00069 #define NO_DEBUG      0
00070 #define MAX_ADAPTABLE_WERD_SIZE 40
00071 
00072 #define ADAPTABLE_WERD_ADJUSTMENT    (0.05)
00073 
00074 #define Y_DIM_OFFSET    (Y_SHIFT - BASELINE_Y_SHIFT)
00075 
00076 #define WORST_POSSIBLE_RATING (1.0)
00077 
00078 struct ScoredClass {
00079   CLASS_ID unichar_id;
00080   int shape_id;
00081   FLOAT32 rating;
00082   bool adapted;
00083   inT16 config;
00084   inT16 fontinfo_id;
00085   inT16 fontinfo_id2;
00086 };
00087 
00088 struct ADAPT_RESULTS {
00089   inT32 BlobLength;
00090   bool HasNonfragment;
00091   GenericVector<ScoredClass> match;
00092   ScoredClass best_match;
00093   GenericVector<CP_RESULT_STRUCT> CPResults;
00094 
00097   inline void Initialize() {
00098      BlobLength = MAX_INT32;
00099      HasNonfragment = false;
00100      best_match.unichar_id = NO_CLASS;
00101      best_match.shape_id = -1;
00102      best_match.rating = WORST_POSSIBLE_RATING;
00103      best_match.adapted = false;
00104      best_match.config = 0;
00105      best_match.fontinfo_id = kBlankFontinfoId;
00106      best_match.fontinfo_id2 = kBlankFontinfoId;
00107   }
00108 };
00109 
00110 struct PROTO_KEY {
00111   ADAPT_TEMPLATES Templates;
00112   CLASS_ID ClassId;
00113   int ConfigId;
00114 };
00115 
00116 /*-----------------------------------------------------------------------------
00117           Private Macros
00118 -----------------------------------------------------------------------------*/
00119 #define MarginalMatch(Rating)       \
00120 ((Rating) > matcher_great_threshold)
00121 
00122 /*-----------------------------------------------------------------------------
00123           Private Function Prototypes
00124 -----------------------------------------------------------------------------*/
00125 int CompareByRating(const void *arg1, const void *arg2);
00126 
00127 ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id);
00128 
00129 ScoredClass ScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id);
00130 
00131 void InitMatcherRatings(register FLOAT32 *Rating);
00132 
00133 int MakeTempProtoPerm(void *item1, void *item2);
00134 
00135 void SetAdaptiveThreshold(FLOAT32 Threshold);
00136 
00137 
00138 /*-----------------------------------------------------------------------------
00139               Public Code
00140 -----------------------------------------------------------------------------*/
00141 /*---------------------------------------------------------------------------*/
00142 namespace tesseract {
00169 void Classify::AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices) {
00170   assert(Choices != NULL);
00171   ADAPT_RESULTS *Results = new ADAPT_RESULTS;
00172   Results->Initialize();
00173 
00174   ASSERT_HOST(AdaptedTemplates != NULL);
00175 
00176   DoAdaptiveMatch(Blob, Results);
00177 
00178   RemoveBadMatches(Results);
00179   Results->match.sort(CompareByRating);
00180   RemoveExtraPuncs(Results);
00181   ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results,
00182                           Choices);
00183 
00184   if (matcher_debug_level >= 1) {
00185     cprintf ("AD Matches =  ");
00186     PrintAdaptiveMatchResults(stdout, Results);
00187   }
00188 
00189   if (LargeSpeckle(*Blob) || Choices->length() == 0)
00190     AddLargeSpeckleTo(Results->BlobLength, Choices);
00191 
00192 #ifndef GRAPHICS_DISABLED
00193   if (classify_enable_adaptive_debugger)
00194     DebugAdaptiveClassifier(Blob, Results);
00195 #endif
00196 
00197   delete Results;
00198 }                                /* AdaptiveClassifier */
00199 
00200 // If *win is NULL, sets it to a new ScrollView() object with title msg.
00201 // Clears the window and draws baselines.
00202 void Classify::RefreshDebugWindow(ScrollView **win, const char *msg,
00203                                   int y_offset, const TBOX &wbox) {
00204   #ifndef GRAPHICS_DISABLED
00205   const int kSampleSpaceWidth = 500;
00206   if (*win == NULL) {
00207     *win = new ScrollView(msg, 100, y_offset, kSampleSpaceWidth * 2, 200,
00208                           kSampleSpaceWidth * 2, 200, true);
00209   }
00210   (*win)->Clear();
00211   (*win)->Pen(64, 64, 64);
00212   (*win)->Line(-kSampleSpaceWidth, kBlnBaselineOffset,
00213                kSampleSpaceWidth, kBlnBaselineOffset);
00214   (*win)->Line(-kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset,
00215                kSampleSpaceWidth, kBlnXHeight + kBlnBaselineOffset);
00216   (*win)->ZoomToRectangle(wbox.left(), wbox.top(),
00217                           wbox.right(), wbox.bottom());
00218   #endif  // GRAPHICS_DISABLED
00219 }
00220 
00221 // Learns the given word using its chopped_word, seam_array, denorm,
00222 // box_word, best_state, and correct_text to learn both correctly and
00223 // incorrectly segmented blobs. If filename is not NULL, then LearnBlob
00224 // is called and the data will be written to a file for static training.
00225 // Otherwise AdaptToBlob is called for adaption within a document.
00226 // If rejmap is not NULL, then only chars with a rejmap entry of '1' will
00227 // be learned, otherwise all chars with good correct_text are learned.
00228 void Classify::LearnWord(const char* filename, WERD_RES *word) {
00229   int word_len = word->correct_text.size();
00230   if (word_len == 0) return;
00231 
00232   float* thresholds = NULL;
00233   if (filename == NULL) {
00234     // Adaption mode.
00235     if (!EnableLearning || word->best_choice == NULL)
00236       return;  // Can't or won't adapt.
00237 
00238     if (classify_learning_debug_level >= 1)
00239       tprintf("\n\nAdapting to word = %s\n",
00240               word->best_choice->debug_string().string());
00241     thresholds = new float[word_len];
00242     word->ComputeAdaptionThresholds(certainty_scale,
00243                                     matcher_perfect_threshold,
00244                                     matcher_good_threshold,
00245                                     matcher_rating_margin, thresholds);
00246   }
00247   int start_blob = 0;
00248 
00249   #ifndef GRAPHICS_DISABLED
00250   if (classify_debug_character_fragments) {
00251     if (learn_fragmented_word_debug_win_ != NULL) {
00252       window_wait(learn_fragmented_word_debug_win_);
00253     }
00254     RefreshDebugWindow(&learn_fragments_debug_win_, "LearnPieces", 400,
00255                        word->chopped_word->bounding_box());
00256     RefreshDebugWindow(&learn_fragmented_word_debug_win_, "LearnWord", 200,
00257                        word->chopped_word->bounding_box());
00258     word->chopped_word->plot(learn_fragmented_word_debug_win_);
00259     ScrollView::Update();
00260   }
00261   #endif  // GRAPHICS_DISABLED
00262 
00263   for (int ch = 0; ch < word_len; ++ch) {
00264     if (classify_debug_character_fragments) {
00265       tprintf("\nLearning %s\n",  word->correct_text[ch].string());
00266     }
00267     if (word->correct_text[ch].length() > 0) {
00268       float threshold = thresholds != NULL ? thresholds[ch] : 0.0f;
00269 
00270       LearnPieces(filename, start_blob, word->best_state[ch],
00271                   threshold, CST_WHOLE, word->correct_text[ch].string(), word);
00272 
00273       if (word->best_state[ch] > 1 && !disable_character_fragments) {
00274         // Check that the character breaks into meaningful fragments
00275         // that each match a whole character with at least
00276         // classify_character_fragments_garbage_certainty_threshold
00277         bool garbage = false;
00278         int frag;
00279         for (frag = 0; frag < word->best_state[ch]; ++frag) {
00280           TBLOB* frag_blob = word->chopped_word->blobs[start_blob + frag];
00281           if (classify_character_fragments_garbage_certainty_threshold < 0) {
00282             garbage |= LooksLikeGarbage(frag_blob);
00283           }
00284         }
00285         // Learn the fragments.
00286         if (!garbage) {
00287           bool pieces_all_natural = word->PiecesAllNatural(start_blob,
00288               word->best_state[ch]);
00289           if (pieces_all_natural || !prioritize_division) {
00290             for (frag = 0; frag < word->best_state[ch]; ++frag) {
00291               GenericVector<STRING> tokens;
00292               word->correct_text[ch].split(' ', &tokens);
00293 
00294               tokens[0] = CHAR_FRAGMENT::to_string(
00295                   tokens[0].string(), frag, word->best_state[ch],
00296                   pieces_all_natural);
00297 
00298               STRING full_string;
00299               for (int i = 0; i < tokens.size(); i++) {
00300                 full_string += tokens[i];
00301                 if (i != tokens.size() - 1)
00302                   full_string += ' ';
00303               }
00304               LearnPieces(filename, start_blob + frag, 1,
00305                           threshold, CST_FRAGMENT, full_string.string(), word);
00306             }
00307           }
00308         }
00309       }
00310 
00311       // TODO(rays): re-enable this part of the code when we switch to the
00312       // new classifier that needs to see examples of garbage.
00313       /*
00314       if (word->best_state[ch] > 1) {
00315         // If the next blob is good, make junk with the rightmost fragment.
00316         if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
00317           LearnPieces(filename, start_blob + word->best_state[ch] - 1,
00318                       word->best_state[ch + 1] + 1,
00319                       threshold, CST_IMPROPER, INVALID_UNICHAR, word);
00320         }
00321         // If the previous blob is good, make junk with the leftmost fragment.
00322         if (ch > 0 && word->correct_text[ch - 1].length() > 0) {
00323           LearnPieces(filename, start_blob - word->best_state[ch - 1],
00324                       word->best_state[ch - 1] + 1,
00325                       threshold, CST_IMPROPER, INVALID_UNICHAR, word);
00326         }
00327       }
00328       // If the next blob is good, make a join with it.
00329       if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
00330         STRING joined_text = word->correct_text[ch];
00331         joined_text += word->correct_text[ch + 1];
00332         LearnPieces(filename, start_blob,
00333                     word->best_state[ch] + word->best_state[ch + 1],
00334                     threshold, CST_NGRAM, joined_text.string(), word);
00335       }
00336       */
00337     }
00338     start_blob += word->best_state[ch];
00339   }
00340   delete [] thresholds;
00341 }  // LearnWord.
00342 
00343 // Builds a blob of length fragments, from the word, starting at start,
00344 // and then learns it, as having the given correct_text.
00345 // If filename is not NULL, then LearnBlob
00346 // is called and the data will be written to a file for static training.
00347 // Otherwise AdaptToBlob is called for adaption within a document.
00348 // threshold is a magic number required by AdaptToChar and generated by
00349 // ComputeAdaptionThresholds.
00350 // Although it can be partly inferred from the string, segmentation is
00351 // provided to explicitly clarify the character segmentation.
00352 void Classify::LearnPieces(const char* filename, int start, int length,
00353                            float threshold, CharSegmentationType segmentation,
00354                            const char* correct_text, WERD_RES *word) {
00355   // TODO(daria) Remove/modify this if/when we want
00356   // to train and/or adapt to n-grams.
00357   if (segmentation != CST_WHOLE &&
00358       (segmentation != CST_FRAGMENT || disable_character_fragments))
00359     return;
00360 
00361   if (length > 1) {
00362     join_pieces(word->seam_array, start, start + length - 1,
00363                 word->chopped_word);
00364   }
00365   TBLOB* blob = word->chopped_word->blobs[start];
00366   // Rotate the blob if needed for classification.
00367   TBLOB* rotated_blob = blob->ClassifyNormalizeIfNeeded();
00368   if (rotated_blob == NULL)
00369     rotated_blob = blob;
00370 
00371   #ifndef GRAPHICS_DISABLED
00372   // Draw debug windows showing the blob that is being learned if needed.
00373   if (strcmp(classify_learn_debug_str.string(), correct_text) == 0) {
00374     RefreshDebugWindow(&learn_debug_win_, "LearnPieces", 600,
00375                        word->chopped_word->bounding_box());
00376     rotated_blob->plot(learn_debug_win_, ScrollView::GREEN, ScrollView::BROWN);
00377     learn_debug_win_->Update();
00378     window_wait(learn_debug_win_);
00379   }
00380   if (classify_debug_character_fragments && segmentation == CST_FRAGMENT) {
00381     ASSERT_HOST(learn_fragments_debug_win_ != NULL);  // set up in LearnWord
00382     blob->plot(learn_fragments_debug_win_,
00383                ScrollView::BLUE, ScrollView::BROWN);
00384     learn_fragments_debug_win_->Update();
00385   }
00386   #endif  // GRAPHICS_DISABLED
00387 
00388   if (filename != NULL) {
00389     classify_norm_method.set_value(character);  // force char norm spc 30/11/93
00390     tess_bn_matching.set_value(false);    // turn it off
00391     tess_cn_matching.set_value(false);
00392     DENORM bl_denorm, cn_denorm;
00393     INT_FX_RESULT_STRUCT fx_info;
00394     SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm,
00395                      &bl_denorm, &cn_denorm, &fx_info);
00396     LearnBlob(feature_defs_, filename, rotated_blob, bl_denorm, cn_denorm,
00397               fx_info, correct_text);
00398   } else if (unicharset.contains_unichar(correct_text)) {
00399     UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text);
00400     int font_id = word->fontinfo != NULL
00401                 ? fontinfo_table_.get_id(*word->fontinfo)
00402                 : 0;
00403     if (classify_learning_debug_level >= 1)
00404       tprintf("Adapting to char = %s, thr= %g font_id= %d\n",
00405               unicharset.id_to_unichar(class_id), threshold, font_id);
00406     // If filename is not NULL we are doing recognition
00407     // (as opposed to training), so we must have already set word fonts.
00408     AdaptToChar(rotated_blob, class_id, font_id, threshold);
00409   } else if (classify_debug_level >= 1) {
00410     tprintf("Can't adapt to %s not in unicharset\n", correct_text);
00411   }
00412   if (rotated_blob != blob) {
00413     delete rotated_blob;
00414   }
00415 
00416   break_pieces(word->seam_array, start, start + length - 1, word->chopped_word);
00417 }  // LearnPieces.
00418 
00419 /*---------------------------------------------------------------------------*/
00434 void Classify::EndAdaptiveClassifier() {
00435   STRING Filename;
00436   FILE *File;
00437 
00438   #ifndef SECURE_NAMES
00439   if (AdaptedTemplates != NULL &&
00440       classify_enable_adaptive_matcher && classify_save_adapted_templates) {
00441     Filename = imagefile + ADAPT_TEMPLATE_SUFFIX;
00442     File = fopen (Filename.string(), "wb");
00443     if (File == NULL)
00444       cprintf ("Unable to save adapted templates to %s!\n", Filename.string());
00445     else {
00446       cprintf ("\nSaving adapted templates to %s ...", Filename.string());
00447       fflush(stdout);
00448       WriteAdaptedTemplates(File, AdaptedTemplates);
00449       cprintf ("\n");
00450       fclose(File);
00451     }
00452   }
00453   #endif
00454 
00455   if (AdaptedTemplates != NULL) {
00456     free_adapted_templates(AdaptedTemplates);
00457     AdaptedTemplates = NULL;
00458   }
00459 
00460   if (PreTrainedTemplates != NULL) {
00461     free_int_templates(PreTrainedTemplates);
00462     PreTrainedTemplates = NULL;
00463   }
00464   getDict().EndDangerousAmbigs();
00465   FreeNormProtos();
00466   if (AllProtosOn != NULL) {
00467     FreeBitVector(AllProtosOn);
00468     FreeBitVector(AllConfigsOn);
00469     FreeBitVector(AllConfigsOff);
00470     FreeBitVector(TempProtoMask);
00471     AllProtosOn = NULL;
00472     AllConfigsOn = NULL;
00473     AllConfigsOff = NULL;
00474     TempProtoMask = NULL;
00475   }
00476   delete shape_table_;
00477   shape_table_ = NULL;
00478   if (static_classifier_ != NULL) {
00479     delete static_classifier_;
00480     static_classifier_ = NULL;
00481   }
00482 }                                /* EndAdaptiveClassifier */
00483 
00484 
00485 /*---------------------------------------------------------------------------*/
00503 void Classify::InitAdaptiveClassifier(bool load_pre_trained_templates) {
00504   if (!classify_enable_adaptive_matcher)
00505     return;
00506   if (AllProtosOn != NULL)
00507     EndAdaptiveClassifier();  // Don't leak with multiple inits.
00508 
00509   // If there is no language_data_path_prefix, the classifier will be
00510   // adaptive only.
00511   if (language_data_path_prefix.length() > 0 &&
00512       load_pre_trained_templates) {
00513     ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_INTTEMP));
00514     PreTrainedTemplates =
00515       ReadIntTemplates(tessdata_manager.GetDataFilePtr());
00516     if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded inttemp\n");
00517 
00518     if (tessdata_manager.SeekToStart(TESSDATA_SHAPE_TABLE)) {
00519       shape_table_ = new ShapeTable(unicharset);
00520       if (!shape_table_->DeSerialize(tessdata_manager.swap(),
00521                                      tessdata_manager.GetDataFilePtr())) {
00522         tprintf("Error loading shape table!\n");
00523         delete shape_table_;
00524         shape_table_ = NULL;
00525       } else if (tessdata_manager.DebugLevel() > 0) {
00526         tprintf("Successfully loaded shape table!\n");
00527       }
00528     }
00529 
00530     ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_PFFMTABLE));
00531     ReadNewCutoffs(tessdata_manager.GetDataFilePtr(),
00532                    tessdata_manager.swap(),
00533                    tessdata_manager.GetEndOffset(TESSDATA_PFFMTABLE),
00534                    CharNormCutoffs);
00535     if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded pffmtable\n");
00536 
00537     ASSERT_HOST(tessdata_manager.SeekToStart(TESSDATA_NORMPROTO));
00538     NormProtos =
00539       ReadNormProtos(tessdata_manager.GetDataFilePtr(),
00540                      tessdata_manager.GetEndOffset(TESSDATA_NORMPROTO));
00541     if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded normproto\n");
00542     static_classifier_ = new TessClassifier(false, this);
00543   }
00544 
00545   im_.Init(&classify_debug_level);
00546   InitIntegerFX();
00547 
00548   AllProtosOn = NewBitVector(MAX_NUM_PROTOS);
00549   AllConfigsOn = NewBitVector(MAX_NUM_CONFIGS);
00550   AllConfigsOff = NewBitVector(MAX_NUM_CONFIGS);
00551   TempProtoMask = NewBitVector(MAX_NUM_PROTOS);
00552   set_all_bits(AllProtosOn, WordsInVectorOfSize(MAX_NUM_PROTOS));
00553   set_all_bits(AllConfigsOn, WordsInVectorOfSize(MAX_NUM_CONFIGS));
00554   zero_all_bits(AllConfigsOff, WordsInVectorOfSize(MAX_NUM_CONFIGS));
00555 
00556   for (int i = 0; i < MAX_NUM_CLASSES; i++) {
00557      BaselineCutoffs[i] = 0;
00558   }
00559 
00560   if (classify_use_pre_adapted_templates) {
00561     FILE *File;
00562     STRING Filename;
00563 
00564     Filename = imagefile;
00565     Filename += ADAPT_TEMPLATE_SUFFIX;
00566     File = fopen(Filename.string(), "rb");
00567     if (File == NULL) {
00568       AdaptedTemplates = NewAdaptedTemplates(true);
00569     } else {
00570       #ifndef SECURE_NAMES
00571       cprintf("\nReading pre-adapted templates from %s ...\n",
00572               Filename.string());
00573       fflush(stdout);
00574       #endif
00575       AdaptedTemplates = ReadAdaptedTemplates(File);
00576       cprintf("\n");
00577       fclose(File);
00578       PrintAdaptedTemplates(stdout, AdaptedTemplates);
00579 
00580       for (int i = 0; i < AdaptedTemplates->Templates->NumClasses; i++) {
00581         BaselineCutoffs[i] = CharNormCutoffs[i];
00582       }
00583     }
00584   } else {
00585     if (AdaptedTemplates != NULL)
00586       free_adapted_templates(AdaptedTemplates);
00587     AdaptedTemplates = NewAdaptedTemplates(true);
00588   }
00589 }                                /* InitAdaptiveClassifier */
00590 
00591 void Classify::ResetAdaptiveClassifierInternal() {
00592   if (classify_learning_debug_level > 0) {
00593     tprintf("Resetting adaptive classifier (NumAdaptationsFailed=%d)\n",
00594             NumAdaptationsFailed);
00595   }
00596   free_adapted_templates(AdaptedTemplates);
00597   AdaptedTemplates = NewAdaptedTemplates(true);
00598   NumAdaptationsFailed = 0;
00599 }
00600 
00601 
00602 
00603 /*---------------------------------------------------------------------------*/
00623 void Classify::SettupPass1() {
00624   EnableLearning = classify_enable_learning;
00625 
00626   getDict().SettupStopperPass1();
00627 
00628 }                                /* SettupPass1 */
00629 
00630 
00631 /*---------------------------------------------------------------------------*/
00643 void Classify::SettupPass2() {
00644   EnableLearning = FALSE;
00645   getDict().SettupStopperPass2();
00646 
00647 }                                /* SettupPass2 */
00648 
00649 
00650 /*---------------------------------------------------------------------------*/
00670 void Classify::InitAdaptedClass(TBLOB *Blob,
00671                                 CLASS_ID ClassId,
00672                                 int FontinfoId,
00673                                 ADAPT_CLASS Class,
00674                                 ADAPT_TEMPLATES Templates) {
00675   FEATURE_SET Features;
00676   int Fid, Pid;
00677   FEATURE Feature;
00678   int NumFeatures;
00679   TEMP_PROTO TempProto;
00680   PROTO Proto;
00681   INT_CLASS IClass;
00682   TEMP_CONFIG Config;
00683 
00684   classify_norm_method.set_value(baseline);
00685   Features = ExtractOutlineFeatures(Blob);
00686   NumFeatures = Features->NumFeatures;
00687   if (NumFeatures > UNLIKELY_NUM_FEAT || NumFeatures <= 0) {
00688     FreeFeatureSet(Features);
00689     return;
00690   }
00691 
00692   Config = NewTempConfig(NumFeatures - 1, FontinfoId);
00693   TempConfigFor(Class, 0) = Config;
00694 
00695   /* this is a kludge to construct cutoffs for adapted templates */
00696   if (Templates == AdaptedTemplates)
00697     BaselineCutoffs[ClassId] = CharNormCutoffs[ClassId];
00698 
00699   IClass = ClassForClassId (Templates->Templates, ClassId);
00700 
00701   for (Fid = 0; Fid < Features->NumFeatures; Fid++) {
00702     Pid = AddIntProto (IClass);
00703     assert (Pid != NO_PROTO);
00704 
00705     Feature = Features->Features[Fid];
00706     TempProto = NewTempProto ();
00707     Proto = &(TempProto->Proto);
00708 
00709     /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
00710        ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
00711        instead of the -0.25 to 0.75 used in baseline normalization */
00712     Proto->Angle = Feature->Params[OutlineFeatDir];
00713     Proto->X = Feature->Params[OutlineFeatX];
00714     Proto->Y = Feature->Params[OutlineFeatY] - Y_DIM_OFFSET;
00715     Proto->Length = Feature->Params[OutlineFeatLength];
00716     FillABC(Proto);
00717 
00718     TempProto->ProtoId = Pid;
00719     SET_BIT (Config->Protos, Pid);
00720 
00721     ConvertProto(Proto, Pid, IClass);
00722     AddProtoToProtoPruner(Proto, Pid, IClass,
00723                           classify_learning_debug_level >= 2);
00724 
00725     Class->TempProtos = push (Class->TempProtos, TempProto);
00726   }
00727   FreeFeatureSet(Features);
00728 
00729   AddIntConfig(IClass);
00730   ConvertConfig (AllProtosOn, 0, IClass);
00731 
00732   if (classify_learning_debug_level >= 1) {
00733     cprintf ("Added new class '%s' with class id %d and %d protos.\n",
00734              unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
00735     if (classify_learning_debug_level > 1)
00736       DisplayAdaptedChar(Blob, IClass);
00737   }
00738 
00739   if (IsEmptyAdaptedClass(Class))
00740     (Templates->NumNonEmptyClasses)++;
00741 }                                /* InitAdaptedClass */
00742 
00743 
00744 /*---------------------------------------------------------------------------*/
00765 int Classify::GetAdaptiveFeatures(TBLOB *Blob,
00766                                   INT_FEATURE_ARRAY IntFeatures,
00767                                   FEATURE_SET *FloatFeatures) {
00768   FEATURE_SET Features;
00769   int NumFeatures;
00770 
00771   classify_norm_method.set_value(baseline);
00772   Features = ExtractPicoFeatures(Blob);
00773 
00774   NumFeatures = Features->NumFeatures;
00775   if (NumFeatures > UNLIKELY_NUM_FEAT) {
00776     FreeFeatureSet(Features);
00777     return 0;
00778   }
00779 
00780   ComputeIntFeatures(Features, IntFeatures);
00781   *FloatFeatures = Features;
00782 
00783   return NumFeatures;
00784 }                                /* GetAdaptiveFeatures */
00785 
00786 
00787 /*-----------------------------------------------------------------------------
00788               Private Code
00789 -----------------------------------------------------------------------------*/
00790 /*---------------------------------------------------------------------------*/
00804 bool Classify::AdaptableWord(WERD_RES* word) {
00805   if (word->best_choice == NULL) return false;
00806   int BestChoiceLength = word->best_choice->length();
00807   float adaptable_score =
00808     getDict().segment_penalty_dict_case_ok + ADAPTABLE_WERD_ADJUSTMENT;
00809   return   // rules that apply in general - simplest to compute first
00810       BestChoiceLength > 0 &&
00811       BestChoiceLength == word->rebuild_word->NumBlobs() &&
00812       BestChoiceLength <= MAX_ADAPTABLE_WERD_SIZE &&
00813       // This basically ensures that the word is at least a dictionary match
00814       // (freq word, user word, system dawg word, etc).
00815       // Since all the other adjustments will make adjust factor higher
00816       // than higher than adaptable_score=1.1+0.05=1.15
00817       // Since these are other flags that ensure that the word is dict word,
00818       // this check could be at times redundant.
00819       word->best_choice->adjust_factor() <= adaptable_score &&
00820       // Make sure that alternative choices are not dictionary words.
00821       word->AlternativeChoiceAdjustmentsWorseThan(adaptable_score);
00822 }
00823 
00824 /*---------------------------------------------------------------------------*/
00840 void Classify::AdaptToChar(TBLOB *Blob,
00841                            CLASS_ID ClassId,
00842                            int FontinfoId,
00843                            FLOAT32 Threshold) {
00844   int NumFeatures;
00845   INT_FEATURE_ARRAY IntFeatures;
00846   INT_RESULT_STRUCT IntResult;
00847   INT_CLASS IClass;
00848   ADAPT_CLASS Class;
00849   TEMP_CONFIG TempConfig;
00850   FEATURE_SET FloatFeatures;
00851   int NewTempConfigId;
00852 
00853   if (!LegalClassId (ClassId))
00854     return;
00855 
00856   Class = AdaptedTemplates->Class[ClassId];
00857   assert(Class != NULL);
00858   if (IsEmptyAdaptedClass(Class)) {
00859     InitAdaptedClass(Blob, ClassId, FontinfoId, Class, AdaptedTemplates);
00860   }
00861   else {
00862     IClass = ClassForClassId (AdaptedTemplates->Templates, ClassId);
00863 
00864     NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
00865     if (NumFeatures <= 0)
00866       return;
00867 
00868     // Only match configs with the matching font.
00869     BIT_VECTOR MatchingFontConfigs = NewBitVector(MAX_NUM_PROTOS);
00870     for (int cfg = 0; cfg < IClass->NumConfigs; ++cfg) {
00871       if (GetFontinfoId(Class, cfg) == FontinfoId) {
00872         SET_BIT(MatchingFontConfigs, cfg);
00873       } else {
00874         reset_bit(MatchingFontConfigs, cfg);
00875       }
00876     }
00877     im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
00878               NumFeatures, IntFeatures,
00879               &IntResult, classify_adapt_feature_threshold,
00880               NO_DEBUG, matcher_debug_separate_windows);
00881     FreeBitVector(MatchingFontConfigs);
00882 
00883     SetAdaptiveThreshold(Threshold);
00884 
00885     if (IntResult.Rating <= Threshold) {
00886       if (ConfigIsPermanent (Class, IntResult.Config)) {
00887         if (classify_learning_debug_level >= 1)
00888           cprintf ("Found good match to perm config %d = %4.1f%%.\n",
00889             IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
00890         FreeFeatureSet(FloatFeatures);
00891         return;
00892       }
00893 
00894       TempConfig = TempConfigFor (Class, IntResult.Config);
00895       IncreaseConfidence(TempConfig);
00896       if (TempConfig->NumTimesSeen > Class->MaxNumTimesSeen) {
00897         Class->MaxNumTimesSeen = TempConfig->NumTimesSeen;
00898       }
00899       if (classify_learning_debug_level >= 1)
00900         cprintf ("Increasing reliability of temp config %d to %d.\n",
00901           IntResult.Config, TempConfig->NumTimesSeen);
00902 
00903       if (TempConfigReliable(ClassId, TempConfig)) {
00904         MakePermanent(AdaptedTemplates, ClassId, IntResult.Config, Blob);
00905         UpdateAmbigsGroup(ClassId, Blob);
00906       }
00907     }
00908     else {
00909       if (classify_learning_debug_level >= 1) {
00910         cprintf ("Found poor match to temp config %d = %4.1f%%.\n",
00911           IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
00912         if (classify_learning_debug_level > 2)
00913           DisplayAdaptedChar(Blob, IClass);
00914       }
00915       NewTempConfigId = MakeNewTemporaryConfig(AdaptedTemplates,
00916                                                ClassId,
00917                                                FontinfoId,
00918                                                NumFeatures,
00919                                                IntFeatures,
00920                                                FloatFeatures);
00921       if (NewTempConfigId >= 0 &&
00922           TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) {
00923         MakePermanent(AdaptedTemplates, ClassId, NewTempConfigId, Blob);
00924         UpdateAmbigsGroup(ClassId, Blob);
00925       }
00926 
00927 #ifndef GRAPHICS_DISABLED
00928       if (classify_learning_debug_level > 1) {
00929         DisplayAdaptedChar(Blob, IClass);
00930       }
00931 #endif
00932     }
00933     FreeFeatureSet(FloatFeatures);
00934   }
00935 }                                /* AdaptToChar */
00936 
00937 void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
00938 #ifndef GRAPHICS_DISABLED
00939   INT_FX_RESULT_STRUCT fx_info;
00940   GenericVector<INT_FEATURE_STRUCT> bl_features;
00941   TrainingSample* sample =
00942       BlobToTrainingSample(*blob, classify_nonlinear_norm, &fx_info,
00943                            &bl_features);
00944   if (sample == NULL) return;
00945 
00946   INT_RESULT_STRUCT IntResult;
00947   im_.Match(int_class, AllProtosOn, AllConfigsOn,
00948             bl_features.size(), &bl_features[0],
00949             &IntResult, classify_adapt_feature_threshold,
00950             NO_DEBUG, matcher_debug_separate_windows);
00951   cprintf ("Best match to temp config %d = %4.1f%%.\n",
00952     IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
00953   if (classify_learning_debug_level >= 2) {
00954     uinT32 ConfigMask;
00955     ConfigMask = 1 << IntResult.Config;
00956     ShowMatchDisplay();
00957     im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
00958               bl_features.size(), &bl_features[0],
00959               &IntResult, classify_adapt_feature_threshold,
00960               6 | 0x19, matcher_debug_separate_windows);
00961     UpdateMatchDisplay();
00962   }
00963 #endif
00964 }
00965 
00966 
00967 
00968 /*---------------------------------------------------------------------------*/
00995 void Classify::AddNewResult(ADAPT_RESULTS *results,
00996                             CLASS_ID class_id,
00997                             int shape_id,
00998                             FLOAT32 rating,
00999                             bool adapted,
01000                             int config,
01001                             int fontinfo_id,
01002                             int fontinfo_id2) {
01003   ScoredClass *old_match = FindScoredUnichar(results, class_id);
01004   ScoredClass match =
01005       { class_id,
01006         shape_id,
01007         rating,
01008         adapted,
01009         static_cast<inT16>(config),
01010         static_cast<inT16>(fontinfo_id),
01011         static_cast<inT16>(fontinfo_id2) };
01012 
01013   if (rating > results->best_match.rating + matcher_bad_match_pad ||
01014       (old_match && rating >= old_match->rating))
01015     return;
01016 
01017   if (!unicharset.get_fragment(class_id))
01018     results->HasNonfragment = true;
01019 
01020   if (old_match)
01021     old_match->rating = rating;
01022   else
01023     results->match.push_back(match);
01024 
01025   if (rating < results->best_match.rating &&
01026       // Ensure that fragments do not affect best rating, class and config.
01027       // This is needed so that at least one non-fragmented character is
01028       // always present in the results.
01029       // TODO(daria): verify that this helps accuracy and does not
01030       // hurt performance.
01031       !unicharset.get_fragment(class_id)) {
01032     results->best_match = match;
01033   }
01034 }                                /* AddNewResult */
01035 
01036 
01037 /*---------------------------------------------------------------------------*/
01057 void Classify::AmbigClassifier(
01058     const GenericVector<INT_FEATURE_STRUCT>& int_features,
01059     const INT_FX_RESULT_STRUCT& fx_info,
01060     const TBLOB *blob,
01061     INT_TEMPLATES templates,
01062     ADAPT_CLASS *classes,
01063     UNICHAR_ID *ambiguities,
01064     ADAPT_RESULTS *results) {
01065   if (int_features.empty()) return;
01066   uinT8* CharNormArray = new uinT8[unicharset.size()];
01067   INT_RESULT_STRUCT IntResult;
01068 
01069   results->BlobLength = GetCharNormFeature(fx_info, templates, NULL,
01070                                            CharNormArray);
01071   bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
01072   if (debug)
01073     tprintf("AM Matches =  ");
01074 
01075   int top = blob->bounding_box().top();
01076   int bottom = blob->bounding_box().bottom();
01077   while (*ambiguities >= 0) {
01078     CLASS_ID class_id = *ambiguities;
01079 
01080     im_.Match(ClassForClassId(templates, class_id),
01081               AllProtosOn, AllConfigsOn,
01082               int_features.size(), &int_features[0],
01083               &IntResult,
01084               classify_adapt_feature_threshold, NO_DEBUG,
01085               matcher_debug_separate_windows);
01086 
01087     ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0,
01088                                     results->BlobLength,
01089                                     classify_integer_matcher_multiplier,
01090                                     CharNormArray, IntResult, results);
01091     ambiguities++;
01092   }
01093   delete [] CharNormArray;
01094 }                                /* AmbigClassifier */
01095 
01096 /*---------------------------------------------------------------------------*/
01099 void Classify::MasterMatcher(INT_TEMPLATES templates,
01100                              inT16 num_features,
01101                              const INT_FEATURE_STRUCT* features,
01102                              const uinT8* norm_factors,
01103                              ADAPT_CLASS* classes,
01104                              int debug,
01105                              int matcher_multiplier,
01106                              const TBOX& blob_box,
01107                              const GenericVector<CP_RESULT_STRUCT>& results,
01108                              ADAPT_RESULTS* final_results) {
01109   int top = blob_box.top();
01110   int bottom = blob_box.bottom();
01111   for (int c = 0; c < results.size(); c++) {
01112     CLASS_ID class_id = results[c].Class;
01113     INT_RESULT_STRUCT& int_result = results[c].IMResult;
01114     BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
01115                                         : AllProtosOn;
01116     BIT_VECTOR configs = classes != NULL ? classes[class_id]->PermConfigs
01117                                          : AllConfigsOn;
01118 
01119     im_.Match(ClassForClassId(templates, class_id),
01120               protos, configs,
01121               num_features, features,
01122               &int_result, classify_adapt_feature_threshold, debug,
01123               matcher_debug_separate_windows);
01124     bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
01125     ExpandShapesAndApplyCorrections(classes, debug, class_id, bottom, top,
01126                                     results[c].Rating,
01127                                     final_results->BlobLength,
01128                                     matcher_multiplier, norm_factors,
01129                                     int_result, final_results);
01130   }
01131 }
01132 
01133 // Converts configs to fonts, and if the result is not adapted, and a
01134 // shape_table_ is present, the shape is expanded to include all
01135 // unichar_ids represented, before applying a set of corrections to the
01136 // distance rating in int_result, (see ComputeCorrectedRating.)
01137 // The results are added to the final_results output.
01138 void Classify::ExpandShapesAndApplyCorrections(
01139     ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
01140     float cp_rating, int blob_length, int matcher_multiplier,
01141     const uinT8* cn_factors,
01142     INT_RESULT_STRUCT& int_result, ADAPT_RESULTS* final_results) {
01143   // Compute the fontinfo_ids.
01144   int fontinfo_id = kBlankFontinfoId;
01145   int fontinfo_id2 = kBlankFontinfoId;
01146   if (classes != NULL) {
01147     // Adapted result.
01148     fontinfo_id = GetFontinfoId(classes[class_id], int_result.Config);
01149     if (int_result.Config2 >= 0)
01150       fontinfo_id2 = GetFontinfoId(classes[class_id], int_result.Config2);
01151   } else {
01152     // Pre-trained result.
01153     fontinfo_id = ClassAndConfigIDToFontOrShapeID(class_id, int_result.Config);
01154     if (int_result.Config2 >= 0) {
01155       fontinfo_id2 = ClassAndConfigIDToFontOrShapeID(class_id,
01156                                                      int_result.Config2);
01157     }
01158     if (shape_table_ != NULL) {
01159       // Actually fontinfo_id is an index into the shape_table_ and it
01160       // contains a list of unchar_id/font_id pairs.
01161       int shape_id = fontinfo_id;
01162       const Shape& shape = shape_table_->GetShape(fontinfo_id);
01163       double min_rating = 0.0;
01164       for (int c = 0; c < shape.size(); ++c) {
01165         int unichar_id = shape[c].unichar_id;
01166         fontinfo_id = shape[c].font_ids[0];
01167         if (shape[c].font_ids.size() > 1)
01168           fontinfo_id2 = shape[c].font_ids[1];
01169         else if (fontinfo_id2 != kBlankFontinfoId)
01170           fontinfo_id2 = shape_table_->GetShape(fontinfo_id2)[0].font_ids[0];
01171         double rating = ComputeCorrectedRating(debug, unichar_id, cp_rating,
01172                                                int_result.Rating,
01173                                                int_result.FeatureMisses,
01174                                                bottom, top, blob_length,
01175                                                matcher_multiplier, cn_factors);
01176         if (c == 0 || rating < min_rating)
01177           min_rating = rating;
01178         if (unicharset.get_enabled(unichar_id)) {
01179           AddNewResult(final_results, unichar_id, shape_id, rating,
01180                        classes != NULL, int_result.Config,
01181                        fontinfo_id, fontinfo_id2);
01182         }
01183       }
01184       int_result.Rating = min_rating;
01185       return;
01186     }
01187   }
01188   double rating = ComputeCorrectedRating(debug, class_id, cp_rating,
01189                                          int_result.Rating,
01190                                          int_result.FeatureMisses,
01191                                          bottom, top, blob_length,
01192                                          matcher_multiplier, cn_factors);
01193   if (unicharset.get_enabled(class_id)) {
01194     AddNewResult(final_results, class_id, -1, rating,
01195                  classes != NULL, int_result.Config,
01196                  fontinfo_id, fontinfo_id2);
01197   }
01198   int_result.Rating = rating;
01199 }
01200 
01201 // Applies a set of corrections to the distance im_rating,
01202 // including the cn_correction, miss penalty and additional penalty
01203 // for non-alnums being vertical misfits. Returns the corrected distance.
01204 double Classify::ComputeCorrectedRating(bool debug, int unichar_id,
01205                                         double cp_rating, double im_rating,
01206                                         int feature_misses,
01207                                         int bottom, int top,
01208                                         int blob_length, int matcher_multiplier,
01209                                         const uinT8* cn_factors) {
01210   // Compute class feature corrections.
01211   double cn_corrected = im_.ApplyCNCorrection(im_rating, blob_length,
01212                                               cn_factors[unichar_id],
01213                                               matcher_multiplier);
01214   double miss_penalty = tessedit_class_miss_scale * feature_misses;
01215   double vertical_penalty = 0.0;
01216   // Penalize non-alnums for being vertical misfits.
01217   if (!unicharset.get_isalpha(unichar_id) &&
01218       !unicharset.get_isdigit(unichar_id) &&
01219       cn_factors[unichar_id] != 0 && classify_misfit_junk_penalty > 0.0) {
01220     int min_bottom, max_bottom, min_top, max_top;
01221     unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom,
01222                               &min_top, &max_top);
01223     if (debug) {
01224       tprintf("top=%d, vs [%d, %d], bottom=%d, vs [%d, %d]\n",
01225               top, min_top, max_top, bottom, min_bottom, max_bottom);
01226     }
01227     if (top < min_top || top > max_top ||
01228         bottom < min_bottom || bottom > max_bottom) {
01229       vertical_penalty = classify_misfit_junk_penalty;
01230     }
01231   }
01232   double result =cn_corrected + miss_penalty + vertical_penalty;
01233   if (result > WORST_POSSIBLE_RATING)
01234     result = WORST_POSSIBLE_RATING;
01235   if (debug) {
01236     tprintf("%s: %2.1f(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
01237             unicharset.id_to_unichar(unichar_id),
01238             result * 100.0,
01239             cp_rating * 100.0,
01240             im_rating * 100.0,
01241             (cn_corrected - im_rating) * 100.0,
01242             cn_factors[unichar_id],
01243             miss_penalty * 100.0,
01244             vertical_penalty * 100.0);
01245   }
01246   return result;
01247 }
01248 
01249 /*---------------------------------------------------------------------------*/
01267 UNICHAR_ID *Classify::BaselineClassifier(
01268     TBLOB *Blob, const GenericVector<INT_FEATURE_STRUCT>& int_features,
01269     const INT_FX_RESULT_STRUCT& fx_info,
01270     ADAPT_TEMPLATES Templates, ADAPT_RESULTS *Results) {
01271   if (int_features.empty()) return NULL;
01272   uinT8* CharNormArray = new uinT8[unicharset.size()];
01273   ClearCharNormArray(CharNormArray);
01274 
01275   Results->BlobLength = IntCastRounded(fx_info.Length / kStandardFeatureLength);
01276   PruneClasses(Templates->Templates, int_features.size(), &int_features[0],
01277                CharNormArray, BaselineCutoffs, &Results->CPResults);
01278 
01279   if (matcher_debug_level >= 2 || classify_debug_level > 1)
01280     cprintf ("BL Matches =  ");
01281 
01282   MasterMatcher(Templates->Templates, int_features.size(), &int_features[0],
01283                 CharNormArray,
01284                 Templates->Class, matcher_debug_flags, 0,
01285                 Blob->bounding_box(), Results->CPResults, Results);
01286 
01287   delete [] CharNormArray;
01288   CLASS_ID ClassId = Results->best_match.unichar_id;
01289   if (ClassId == NO_CLASS)
01290     return (NULL);
01291   /* this is a bug - maybe should return "" */
01292 
01293   return Templates->Class[ClassId]->
01294       Config[Results->best_match.config].Perm->Ambigs;
01295 }                                /* BaselineClassifier */
01296 
01297 
01298 /*---------------------------------------------------------------------------*/
01317 int Classify::CharNormClassifier(TBLOB *blob,
01318                                  const TrainingSample& sample,
01319                                  ADAPT_RESULTS *adapt_results) {
01320   // This is the length that is used for scaling ratings vs certainty.
01321   adapt_results->BlobLength =
01322       IntCastRounded(sample.outline_length() / kStandardFeatureLength);
01323   GenericVector<UnicharRating> unichar_results;
01324   static_classifier_->UnicharClassifySample(sample, blob->denorm().pix(), 0,
01325                                             -1, &unichar_results);
01326   // Convert results to the format used internally by AdaptiveClassifier.
01327   for (int r = 0; r < unichar_results.size(); ++r) {
01328     int unichar_id = unichar_results[r].unichar_id;
01329     // Fonts are listed in order of preference.
01330     int font1 = unichar_results[r].fonts.size() >= 1
01331               ? unichar_results[r].fonts[0] : kBlankFontinfoId;
01332     int font2 = unichar_results[r].fonts.size() >= 2
01333               ? unichar_results[r].fonts[1] : kBlankFontinfoId;
01334     float rating = 1.0f - unichar_results[r].rating;
01335     AddNewResult(adapt_results, unichar_id, -1, rating, false, 0, font1, font2);
01336   }
01337   return sample.num_features();
01338 }                                /* CharNormClassifier */
01339 
01340 // As CharNormClassifier, but operates on a TrainingSample and outputs to
01341 // a GenericVector of ShapeRating without conversion to classes.
01342 int Classify::CharNormTrainingSample(bool pruner_only,
01343                                      int keep_this,
01344                                      const TrainingSample& sample,
01345                                      GenericVector<UnicharRating>* results) {
01346   results->clear();
01347   ADAPT_RESULTS* adapt_results = new ADAPT_RESULTS();
01348   adapt_results->Initialize();
01349   // Compute the bounding box of the features.
01350   int num_features = sample.num_features();
01351   // Only the top and bottom of the blob_box are used by MasterMatcher, so
01352   // fabricate right and left using top and bottom.
01353   TBOX blob_box(sample.geo_feature(GeoBottom), sample.geo_feature(GeoBottom),
01354                 sample.geo_feature(GeoTop), sample.geo_feature(GeoTop));
01355   // Compute the char_norm_array from the saved cn_feature.
01356   FEATURE norm_feature = sample.GetCNFeature();
01357   uinT8* char_norm_array = new uinT8[unicharset.size()];
01358   int num_pruner_classes = MAX(unicharset.size(),
01359                                PreTrainedTemplates->NumClasses);
01360   uinT8* pruner_norm_array = new uinT8[num_pruner_classes];
01361   adapt_results->BlobLength =
01362       static_cast<int>(ActualOutlineLength(norm_feature) * 20 + 0.5);
01363   ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
01364                         pruner_norm_array);
01365 
01366   PruneClasses(PreTrainedTemplates, num_features, sample.features(),
01367                pruner_norm_array,
01368                shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs,
01369                &adapt_results->CPResults);
01370   delete [] pruner_norm_array;
01371   if (keep_this >= 0) {
01372     adapt_results->CPResults[0].Class = keep_this;
01373     adapt_results->CPResults.truncate(1);
01374   }
01375   if (pruner_only) {
01376     // Convert pruner results to output format.
01377     for (int i = 0; i < adapt_results->CPResults.size(); ++i) {
01378       int class_id = adapt_results->CPResults[i].Class;
01379       results->push_back(
01380           UnicharRating(class_id, 1.0f - adapt_results->CPResults[i].Rating));
01381     }
01382   } else {
01383     MasterMatcher(PreTrainedTemplates, num_features, sample.features(),
01384                   char_norm_array,
01385                   NULL, matcher_debug_flags,
01386                   classify_integer_matcher_multiplier,
01387                   blob_box, adapt_results->CPResults, adapt_results);
01388     // Convert master matcher results to output format.
01389     for (int i = 0; i < adapt_results->match.size(); i++) {
01390       ScoredClass next = adapt_results->match[i];
01391       UnicharRating rating(next.unichar_id, 1.0f - next.rating);
01392       if (next.fontinfo_id >= 0) {
01393         rating.fonts.push_back(next.fontinfo_id);
01394         if (next.fontinfo_id2 >= 0)
01395           rating.fonts.push_back(next.fontinfo_id2);
01396       }
01397       results->push_back(rating);
01398     }
01399     results->sort(&UnicharRating::SortDescendingRating);
01400   }
01401   delete [] char_norm_array;
01402   delete adapt_results;
01403   return num_features;
01404 }                                /* CharNormTrainingSample */
01405 
01406 
01407 /*---------------------------------------------------------------------------*/
01422 void Classify::ClassifyAsNoise(ADAPT_RESULTS *Results) {
01423   register FLOAT32 Rating;
01424 
01425   Rating = Results->BlobLength / matcher_avg_noise_size;
01426   Rating *= Rating;
01427   Rating /= 1.0 + Rating;
01428 
01429   AddNewResult(Results, NO_CLASS, -1, Rating, false, -1,
01430                kBlankFontinfoId, kBlankFontinfoId);
01431 }                                /* ClassifyAsNoise */
01432 }  // namespace tesseract
01433 
01434 
01435 /*---------------------------------------------------------------------------*/
01436 // Return a pointer to the scored unichar in results, or NULL if not present.
01437 ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) {
01438   for (int i = 0; i < results->match.size(); i++) {
01439     if (results->match[i].unichar_id == id)
01440       return &results->match[i];
01441   }
01442   return NULL;
01443 }
01444 
01445 // Retrieve the current rating for a unichar id if we have rated it, defaulting
01446 // to WORST_POSSIBLE_RATING.
01447 ScoredClass ScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) {
01448   ScoredClass poor_result =
01449       {id, -1, WORST_POSSIBLE_RATING, false, -1,
01450           kBlankFontinfoId, kBlankFontinfoId};
01451   ScoredClass *entry = FindScoredUnichar(results, id);
01452   return (entry == NULL) ? poor_result : *entry;
01453 }
01454 
01455 // Compare character classes by rating as for qsort(3).
01456 // For repeatability, use character class id as a tie-breaker.
01457 int CompareByRating(const void *arg1,    // ScoredClass *class1
01458                     const void *arg2) {  // ScoredClass *class2
01459   const ScoredClass *class1 = (const ScoredClass *)arg1;
01460   const ScoredClass *class2 = (const ScoredClass *)arg2;
01461 
01462   if (class1->rating < class2->rating)
01463     return -1;
01464   else if (class1->rating > class2->rating)
01465     return 1;
01466 
01467   if (class1->unichar_id < class2->unichar_id)
01468     return -1;
01469   else if (class1->unichar_id > class2->unichar_id)
01470     return 1;
01471   return 0;
01472 }
01473 
01474 /*---------------------------------------------------------------------------*/
01475 namespace tesseract {
01482 void Classify::ConvertMatchesToChoices(const DENORM& denorm, const TBOX& box,
01483                                        ADAPT_RESULTS *Results,
01484                                        BLOB_CHOICE_LIST *Choices) {
01485   assert(Choices != NULL);
01486   FLOAT32 Rating;
01487   FLOAT32 Certainty;
01488   BLOB_CHOICE_IT temp_it;
01489   bool contains_nonfrag = false;
01490   temp_it.set_to_list(Choices);
01491   int choices_length = 0;
01492   // With no shape_table_ maintain the previous MAX_MATCHES as the maximum
01493   // number of returned results, but with a shape_table_ we want to have room
01494   // for at least the biggest shape (which might contain hundreds of Indic
01495   // grapheme fragments) and more, so use double the size of the biggest shape
01496   // if that is more than the default.
01497   int max_matches = MAX_MATCHES;
01498   if (shape_table_ != NULL) {
01499     max_matches = shape_table_->MaxNumUnichars() * 2;
01500     if (max_matches < MAX_MATCHES)
01501       max_matches = MAX_MATCHES;
01502   }
01503 
01504   float best_certainty = -MAX_FLOAT32;
01505   for (int i = 0; i < Results->match.size(); i++) {
01506     ScoredClass next = Results->match[i];
01507     int fontinfo_id = next.fontinfo_id;
01508     int fontinfo_id2 = next.fontinfo_id2;
01509     bool adapted = next.adapted;
01510     bool current_is_frag = (unicharset.get_fragment(next.unichar_id) != NULL);
01511     if (temp_it.length()+1 == max_matches &&
01512         !contains_nonfrag && current_is_frag) {
01513       continue;  // look for a non-fragmented character to fill the
01514                  // last spot in Choices if only fragments are present
01515     }
01516     // BlobLength can never be legally 0, this means recognition failed.
01517     // But we must return a classification result because some invoking
01518     // functions (chopper/permuter) do not anticipate a null blob choice.
01519     // So we need to assign a poor, but not infinitely bad score.
01520     if (Results->BlobLength == 0) {
01521       Certainty = -20;
01522       Rating = 100;    // should be -certainty * real_blob_length
01523     } else {
01524       Rating = Certainty = next.rating;
01525       Rating *= rating_scale * Results->BlobLength;
01526       Certainty *= -(getDict().certainty_scale);
01527     }
01528     // Adapted results, by their very nature, should have good certainty.
01529     // Those that don't are at best misleading, and often lead to errors,
01530     // so don't accept adapted results that are too far behind the best result,
01531     // whether adapted or static.
01532     // TODO(rays) find some way of automatically tuning these constants.
01533     if (Certainty > best_certainty) {
01534       best_certainty = MIN(Certainty, classify_adapted_pruning_threshold);
01535     } else if (adapted &&
01536                Certainty / classify_adapted_pruning_factor < best_certainty) {
01537       continue;  // Don't accept bad adapted results.
01538     }
01539 
01540     float min_xheight, max_xheight, yshift;
01541     denorm.XHeightRange(next.unichar_id, unicharset, box,
01542                         &min_xheight, &max_xheight, &yshift);
01543     temp_it.add_to_end(new BLOB_CHOICE(next.unichar_id, Rating, Certainty,
01544                                         fontinfo_id, fontinfo_id2,
01545                                         unicharset.get_script(next.unichar_id),
01546                                         min_xheight, max_xheight, yshift,
01547                                         adapted ? BCC_ADAPTED_CLASSIFIER
01548                                                 : BCC_STATIC_CLASSIFIER));
01549     contains_nonfrag |= !current_is_frag;  // update contains_nonfrag
01550     choices_length++;
01551     if (choices_length >= max_matches) break;
01552   }
01553   Results->match.truncate(choices_length);
01554 }  // ConvertMatchesToChoices
01555 
01556 
01557 /*---------------------------------------------------------------------------*/
01558 #ifndef GRAPHICS_DISABLED
01559 
01569 void Classify::DebugAdaptiveClassifier(TBLOB *blob,
01570                                        ADAPT_RESULTS *Results) {
01571   if (static_classifier_ == NULL) return;
01572   for (int i = 0; i < Results->match.size(); i++) {
01573     if (i == 0 || Results->match[i].rating < Results->best_match.rating)
01574       Results->best_match = Results->match[i];
01575   }
01576   INT_FX_RESULT_STRUCT fx_info;
01577   GenericVector<INT_FEATURE_STRUCT> bl_features;
01578   TrainingSample* sample =
01579       BlobToTrainingSample(*blob, false, &fx_info, &bl_features);
01580   if (sample == NULL) return;
01581   static_classifier_->DebugDisplay(*sample, blob->denorm().pix(),
01582                                    Results->best_match.unichar_id);
01583 }                                /* DebugAdaptiveClassifier */
01584 #endif
01585 
01586 /*---------------------------------------------------------------------------*/
01609 void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
01610   UNICHAR_ID *Ambiguities;
01611 
01612   INT_FX_RESULT_STRUCT fx_info;
01613   GenericVector<INT_FEATURE_STRUCT> bl_features;
01614   TrainingSample* sample =
01615       BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
01616                            &bl_features);
01617   if (sample == NULL) return;
01618 
01619   if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min ||
01620       tess_cn_matching) {
01621     CharNormClassifier(Blob, *sample, Results);
01622   } else {
01623     Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
01624                                      AdaptedTemplates, Results);
01625     if ((!Results->match.empty() && MarginalMatch(Results->best_match.rating) &&
01626          !tess_bn_matching) ||
01627         Results->match.empty()) {
01628       CharNormClassifier(Blob, *sample, Results);
01629     } else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) {
01630       AmbigClassifier(bl_features, fx_info, Blob,
01631                       PreTrainedTemplates,
01632                       AdaptedTemplates->Class,
01633                       Ambiguities,
01634                       Results);
01635     }
01636   }
01637 
01638   // Force the blob to be classified as noise
01639   // if the results contain only fragments.
01640   // TODO(daria): verify that this is better than
01641   // just adding a NULL classification.
01642   if (!Results->HasNonfragment || Results->match.empty())
01643     ClassifyAsNoise(Results);
01644   delete sample;
01645 }   /* DoAdaptiveMatch */
01646 
01647 /*---------------------------------------------------------------------------*/
01664 UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob,
01665                                      CLASS_ID CorrectClass) {
01666   ADAPT_RESULTS *Results = new ADAPT_RESULTS();
01667   UNICHAR_ID *Ambiguities;
01668   int i;
01669 
01670   Results->Initialize();
01671   INT_FX_RESULT_STRUCT fx_info;
01672   GenericVector<INT_FEATURE_STRUCT> bl_features;
01673   TrainingSample* sample =
01674       BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
01675                            &bl_features);
01676   if (sample == NULL) {
01677     delete Results;
01678     return NULL;
01679   }
01680 
01681   CharNormClassifier(Blob, *sample, Results);
01682   delete sample;
01683   RemoveBadMatches(Results);
01684   Results->match.sort(CompareByRating);
01685 
01686   /* copy the class id's into an string of ambiguities - don't copy if
01687      the correct class is the only class id matched */
01688   Ambiguities = new UNICHAR_ID[Results->match.size() + 1];
01689   if (Results->match.size() > 1 ||
01690       (Results->match.size() == 1 &&
01691           Results->match[0].unichar_id != CorrectClass)) {
01692     for (i = 0; i < Results->match.size(); i++)
01693       Ambiguities[i] = Results->match[i].unichar_id;
01694     Ambiguities[i] = -1;
01695   } else {
01696     Ambiguities[0] = -1;
01697   }
01698 
01699   delete Results;
01700   return Ambiguities;
01701 }                              /* GetAmbiguities */
01702 
01703 // Returns true if the given blob looks too dissimilar to any character
01704 // present in the classifier templates.
01705 bool Classify::LooksLikeGarbage(TBLOB *blob) {
01706   BLOB_CHOICE_LIST *ratings = new BLOB_CHOICE_LIST();
01707   AdaptiveClassifier(blob, ratings);
01708   BLOB_CHOICE_IT ratings_it(ratings);
01709   const UNICHARSET &unicharset = getDict().getUnicharset();
01710   if (classify_debug_character_fragments) {
01711     print_ratings_list("======================\nLooksLikeGarbage() got ",
01712                        ratings, unicharset);
01713   }
01714   for (ratings_it.mark_cycle_pt(); !ratings_it.cycled_list();
01715        ratings_it.forward()) {
01716     if (unicharset.get_fragment(ratings_it.data()->unichar_id()) != NULL) {
01717       continue;
01718     }
01719     float certainty = ratings_it.data()->certainty();
01720     delete ratings;
01721     return certainty <
01722             classify_character_fragments_garbage_certainty_threshold;
01723   }
01724   delete ratings;
01725   return true;  // no whole characters in ratings
01726 }
01727 
01728 /*---------------------------------------------------------------------------*/
01754 int Classify::GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info,
01755                                  INT_TEMPLATES templates,
01756                                  uinT8* pruner_norm_array,
01757                                  uinT8* char_norm_array) {
01758   FEATURE norm_feature = NewFeature(&CharNormDesc);
01759   float baseline = kBlnBaselineOffset;
01760   float scale = MF_SCALE_FACTOR;
01761   norm_feature->Params[CharNormY] = (fx_info.Ymean - baseline) * scale;
01762   norm_feature->Params[CharNormLength] =
01763       fx_info.Length * scale / LENGTH_COMPRESSION;
01764   norm_feature->Params[CharNormRx] = fx_info.Rx * scale;
01765   norm_feature->Params[CharNormRy] = fx_info.Ry * scale;
01766   // Deletes norm_feature.
01767   ComputeCharNormArrays(norm_feature, templates, char_norm_array,
01768                         pruner_norm_array);
01769   return IntCastRounded(fx_info.Length / kStandardFeatureLength);
01770 }                              /* GetCharNormFeature */
01771 
01772 // Computes the char_norm_array for the unicharset and, if not NULL, the
01773 // pruner_array as appropriate according to the existence of the shape_table.
01774 void Classify::ComputeCharNormArrays(FEATURE_STRUCT* norm_feature,
01775                                      INT_TEMPLATES_STRUCT* templates,
01776                                      uinT8* char_norm_array,
01777                                      uinT8* pruner_array) {
01778   ComputeIntCharNormArray(*norm_feature, char_norm_array);
01779   if (pruner_array != NULL) {
01780     if (shape_table_ == NULL) {
01781       ComputeIntCharNormArray(*norm_feature, pruner_array);
01782     } else {
01783       memset(pruner_array, MAX_UINT8,
01784              templates->NumClasses * sizeof(pruner_array[0]));
01785       // Each entry in the pruner norm array is the MIN of all the entries of
01786       // the corresponding unichars in the CharNormArray.
01787       for (int id = 0; id < templates->NumClasses; ++id) {
01788         int font_set_id = templates->Class[id]->font_set_id;
01789         const FontSet &fs = fontset_table_.get(font_set_id);
01790         for (int config = 0; config < fs.size; ++config) {
01791           const Shape& shape = shape_table_->GetShape(fs.configs[config]);
01792           for (int c = 0; c < shape.size(); ++c) {
01793             if (char_norm_array[shape[c].unichar_id] < pruner_array[id])
01794               pruner_array[id] = char_norm_array[shape[c].unichar_id];
01795           }
01796         }
01797       }
01798     }
01799   }
01800   FreeFeature(norm_feature);
01801 }
01802 
01803 /*---------------------------------------------------------------------------*/
01818 int Classify::MakeNewTemporaryConfig(ADAPT_TEMPLATES Templates,
01819                            CLASS_ID ClassId,
01820                            int FontinfoId,
01821                            int NumFeatures,
01822                            INT_FEATURE_ARRAY Features,
01823                            FEATURE_SET FloatFeatures) {
01824   INT_CLASS IClass;
01825   ADAPT_CLASS Class;
01826   PROTO_ID OldProtos[MAX_NUM_PROTOS];
01827   FEATURE_ID BadFeatures[MAX_NUM_INT_FEATURES];
01828   int NumOldProtos;
01829   int NumBadFeatures;
01830   int MaxProtoId, OldMaxProtoId;
01831   int BlobLength = 0;
01832   int MaskSize;
01833   int ConfigId;
01834   TEMP_CONFIG Config;
01835   int i;
01836   int debug_level = NO_DEBUG;
01837 
01838   if (classify_learning_debug_level >= 3)
01839     debug_level =
01840         PRINT_MATCH_SUMMARY | PRINT_FEATURE_MATCHES | PRINT_PROTO_MATCHES;
01841 
01842   IClass = ClassForClassId(Templates->Templates, ClassId);
01843   Class = Templates->Class[ClassId];
01844 
01845   if (IClass->NumConfigs >= MAX_NUM_CONFIGS) {
01846     ++NumAdaptationsFailed;
01847     if (classify_learning_debug_level >= 1)
01848       cprintf("Cannot make new temporary config: maximum number exceeded.\n");
01849     return -1;
01850   }
01851 
01852   OldMaxProtoId = IClass->NumProtos - 1;
01853 
01854   NumOldProtos = im_.FindGoodProtos(IClass, AllProtosOn, AllConfigsOff,
01855                                     BlobLength, NumFeatures, Features,
01856                                     OldProtos, classify_adapt_proto_threshold,
01857                                     debug_level);
01858 
01859   MaskSize = WordsInVectorOfSize(MAX_NUM_PROTOS);
01860   zero_all_bits(TempProtoMask, MaskSize);
01861   for (i = 0; i < NumOldProtos; i++)
01862     SET_BIT(TempProtoMask, OldProtos[i]);
01863 
01864   NumBadFeatures = im_.FindBadFeatures(IClass, TempProtoMask, AllConfigsOn,
01865                                        BlobLength, NumFeatures, Features,
01866                                        BadFeatures,
01867                                        classify_adapt_feature_threshold,
01868                                        debug_level);
01869 
01870   MaxProtoId = MakeNewTempProtos(FloatFeatures, NumBadFeatures, BadFeatures,
01871                                  IClass, Class, TempProtoMask);
01872   if (MaxProtoId == NO_PROTO) {
01873     ++NumAdaptationsFailed;
01874     if (classify_learning_debug_level >= 1)
01875       cprintf("Cannot make new temp protos: maximum number exceeded.\n");
01876     return -1;
01877   }
01878 
01879   ConfigId = AddIntConfig(IClass);
01880   ConvertConfig(TempProtoMask, ConfigId, IClass);
01881   Config = NewTempConfig(MaxProtoId, FontinfoId);
01882   TempConfigFor(Class, ConfigId) = Config;
01883   copy_all_bits(TempProtoMask, Config->Protos, Config->ProtoVectorSize);
01884 
01885   if (classify_learning_debug_level >= 1)
01886     cprintf("Making new temp config %d fontinfo id %d"
01887             " using %d old and %d new protos.\n",
01888             ConfigId, Config->FontinfoId,
01889             NumOldProtos, MaxProtoId - OldMaxProtoId);
01890 
01891   return ConfigId;
01892 }                              /* MakeNewTemporaryConfig */
01893 
01894 /*---------------------------------------------------------------------------*/
01915 PROTO_ID Classify::MakeNewTempProtos(FEATURE_SET Features,
01916                                      int NumBadFeat,
01917                                      FEATURE_ID BadFeat[],
01918                                      INT_CLASS IClass,
01919                                      ADAPT_CLASS Class,
01920                                      BIT_VECTOR TempProtoMask) {
01921   FEATURE_ID *ProtoStart;
01922   FEATURE_ID *ProtoEnd;
01923   FEATURE_ID *LastBad;
01924   TEMP_PROTO TempProto;
01925   PROTO Proto;
01926   FEATURE F1, F2;
01927   FLOAT32 X1, X2, Y1, Y2;
01928   FLOAT32 A1, A2, AngleDelta;
01929   FLOAT32 SegmentLength;
01930   PROTO_ID Pid;
01931 
01932   for (ProtoStart = BadFeat, LastBad = ProtoStart + NumBadFeat;
01933        ProtoStart < LastBad; ProtoStart = ProtoEnd) {
01934     F1 = Features->Features[*ProtoStart];
01935     X1 = F1->Params[PicoFeatX];
01936     Y1 = F1->Params[PicoFeatY];
01937     A1 = F1->Params[PicoFeatDir];
01938 
01939     for (ProtoEnd = ProtoStart + 1,
01940          SegmentLength = GetPicoFeatureLength();
01941          ProtoEnd < LastBad;
01942          ProtoEnd++, SegmentLength += GetPicoFeatureLength()) {
01943       F2 = Features->Features[*ProtoEnd];
01944       X2 = F2->Params[PicoFeatX];
01945       Y2 = F2->Params[PicoFeatY];
01946       A2 = F2->Params[PicoFeatDir];
01947 
01948       AngleDelta = fabs(A1 - A2);
01949       if (AngleDelta > 0.5)
01950         AngleDelta = 1.0 - AngleDelta;
01951 
01952       if (AngleDelta > matcher_clustering_max_angle_delta ||
01953           fabs(X1 - X2) > SegmentLength ||
01954           fabs(Y1 - Y2) > SegmentLength)
01955         break;
01956     }
01957 
01958     F2 = Features->Features[*(ProtoEnd - 1)];
01959     X2 = F2->Params[PicoFeatX];
01960     Y2 = F2->Params[PicoFeatY];
01961     A2 = F2->Params[PicoFeatDir];
01962 
01963     Pid = AddIntProto(IClass);
01964     if (Pid == NO_PROTO)
01965       return (NO_PROTO);
01966 
01967     TempProto = NewTempProto();
01968     Proto = &(TempProto->Proto);
01969 
01970     /* compute proto params - NOTE that Y_DIM_OFFSET must be used because
01971        ConvertProto assumes that the Y dimension varies from -0.5 to 0.5
01972        instead of the -0.25 to 0.75 used in baseline normalization */
01973     Proto->Length = SegmentLength;
01974     Proto->Angle = A1;
01975     Proto->X = (X1 + X2) / 2.0;
01976     Proto->Y = (Y1 + Y2) / 2.0 - Y_DIM_OFFSET;
01977     FillABC(Proto);
01978 
01979     TempProto->ProtoId = Pid;
01980     SET_BIT(TempProtoMask, Pid);
01981 
01982     ConvertProto(Proto, Pid, IClass);
01983     AddProtoToProtoPruner(Proto, Pid, IClass,
01984                           classify_learning_debug_level >= 2);
01985 
01986     Class->TempProtos = push(Class->TempProtos, TempProto);
01987   }
01988   return IClass->NumProtos - 1;
01989 }                              /* MakeNewTempProtos */
01990 
01991 /*---------------------------------------------------------------------------*/
02004 void Classify::MakePermanent(ADAPT_TEMPLATES Templates,
02005                              CLASS_ID ClassId,
02006                              int ConfigId,
02007                              TBLOB *Blob) {
02008   UNICHAR_ID *Ambigs;
02009   TEMP_CONFIG Config;
02010   ADAPT_CLASS Class;
02011   PROTO_KEY ProtoKey;
02012 
02013   Class = Templates->Class[ClassId];
02014   Config = TempConfigFor(Class, ConfigId);
02015 
02016   MakeConfigPermanent(Class, ConfigId);
02017   if (Class->NumPermConfigs == 0)
02018     Templates->NumPermClasses++;
02019   Class->NumPermConfigs++;
02020 
02021   // Initialize permanent config.
02022   Ambigs = GetAmbiguities(Blob, ClassId);
02023   PERM_CONFIG Perm = (PERM_CONFIG) alloc_struct(sizeof(PERM_CONFIG_STRUCT),
02024                                                 "PERM_CONFIG_STRUCT");
02025   Perm->Ambigs = Ambigs;
02026   Perm->FontinfoId = Config->FontinfoId;
02027 
02028   // Free memory associated with temporary config (since ADAPTED_CONFIG
02029   // is a union we need to clean up before we record permanent config).
02030   ProtoKey.Templates = Templates;
02031   ProtoKey.ClassId = ClassId;
02032   ProtoKey.ConfigId = ConfigId;
02033   Class->TempProtos = delete_d(Class->TempProtos, &ProtoKey, MakeTempProtoPerm);
02034   FreeTempConfig(Config);
02035 
02036   // Record permanent config.
02037   PermConfigFor(Class, ConfigId) = Perm;
02038 
02039   if (classify_learning_debug_level >= 1) {
02040     tprintf("Making config %d for %s (ClassId %d) permanent:"
02041             " fontinfo id %d, ambiguities '",
02042             ConfigId, getDict().getUnicharset().debug_str(ClassId).string(),
02043             ClassId, PermConfigFor(Class, ConfigId)->FontinfoId);
02044     for (UNICHAR_ID *AmbigsPointer = Ambigs;
02045         *AmbigsPointer >= 0; ++AmbigsPointer)
02046       tprintf("%s", unicharset.id_to_unichar(*AmbigsPointer));
02047     tprintf("'.\n");
02048   }
02049 }                              /* MakePermanent */
02050 }  // namespace tesseract
02051 
02052 /*---------------------------------------------------------------------------*/
02067 int MakeTempProtoPerm(void *item1, void *item2) {
02068   ADAPT_CLASS Class;
02069   TEMP_CONFIG Config;
02070   TEMP_PROTO TempProto;
02071   PROTO_KEY *ProtoKey;
02072 
02073   TempProto = (TEMP_PROTO) item1;
02074   ProtoKey = (PROTO_KEY *) item2;
02075 
02076   Class = ProtoKey->Templates->Class[ProtoKey->ClassId];
02077   Config = TempConfigFor(Class, ProtoKey->ConfigId);
02078 
02079   if (TempProto->ProtoId > Config->MaxProtoId ||
02080       !test_bit (Config->Protos, TempProto->ProtoId))
02081     return FALSE;
02082 
02083   MakeProtoPermanent(Class, TempProto->ProtoId);
02084   AddProtoToClassPruner(&(TempProto->Proto), ProtoKey->ClassId,
02085                          ProtoKey->Templates->Templates);
02086   FreeTempProto(TempProto);
02087 
02088   return TRUE;
02089 }                              /* MakeTempProtoPerm */
02090 
02091 /*---------------------------------------------------------------------------*/
02092 namespace tesseract {
02104 void Classify::PrintAdaptiveMatchResults(FILE *File, ADAPT_RESULTS *Results) {
02105   for (int i = 0; i < Results->match.size(); ++i) {
02106     tprintf("%s(%d), shape %d, %.2f  ",
02107             unicharset.debug_str(Results->match[i].unichar_id).string(),
02108             Results->match[i].unichar_id, Results->match[i].shape_id,
02109             Results->match[i].rating * 100.0);
02110   }
02111   tprintf("\n");
02112 }                              /* PrintAdaptiveMatchResults */
02113 
02114 /*---------------------------------------------------------------------------*/
02130 void Classify::RemoveBadMatches(ADAPT_RESULTS *Results) {
02131   int Next, NextGood;
02132   FLOAT32 BadMatchThreshold;
02133   static const char* romans = "i v x I V X";
02134   BadMatchThreshold = Results->best_match.rating + matcher_bad_match_pad;
02135 
02136   if (classify_bln_numeric_mode) {
02137     UNICHAR_ID unichar_id_one = unicharset.contains_unichar("1") ?
02138         unicharset.unichar_to_id("1") : -1;
02139     UNICHAR_ID unichar_id_zero = unicharset.contains_unichar("0") ?
02140         unicharset.unichar_to_id("0") : -1;
02141     ScoredClass scored_one = ScoredUnichar(Results, unichar_id_one);
02142     ScoredClass scored_zero = ScoredUnichar(Results, unichar_id_zero);
02143 
02144     for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
02145       if (Results->match[Next].rating <= BadMatchThreshold) {
02146         ScoredClass match = Results->match[Next];
02147         if (!unicharset.get_isalpha(match.unichar_id) ||
02148             strstr(romans,
02149                    unicharset.id_to_unichar(match.unichar_id)) != NULL) {
02150           Results->match[NextGood++] = Results->match[Next];
02151         } else if (unicharset.eq(match.unichar_id, "l") &&
02152                    scored_one.rating >= BadMatchThreshold) {
02153           Results->match[NextGood] = scored_one;
02154           Results->match[NextGood].rating = match.rating;
02155           NextGood++;
02156         } else if (unicharset.eq(match.unichar_id, "O") &&
02157                    scored_zero.rating >= BadMatchThreshold) {
02158           Results->match[NextGood] = scored_zero;
02159           Results->match[NextGood].rating = match.rating;
02160           NextGood++;
02161         }
02162       }
02163     }
02164   } else {
02165     for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
02166       if (Results->match[Next].rating <= BadMatchThreshold)
02167         Results->match[NextGood++] = Results->match[Next];
02168     }
02169   }
02170   Results->match.truncate(NextGood);
02171 }                              /* RemoveBadMatches */
02172 
02173 /*----------------------------------------------------------------------------*/
02183 void Classify::RemoveExtraPuncs(ADAPT_RESULTS *Results) {
02184   int Next, NextGood;
02185   int punc_count;              /*no of garbage characters */
02186   int digit_count;
02187   /*garbage characters */
02188   static char punc_chars[] = ". , ; : / ` ~ ' - = \\ | \" ! _ ^";
02189   static char digit_chars[] = "0 1 2 3 4 5 6 7 8 9";
02190 
02191   punc_count = 0;
02192   digit_count = 0;
02193   for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
02194     ScoredClass match = Results->match[Next];
02195     if (strstr(punc_chars,
02196                unicharset.id_to_unichar(match.unichar_id)) != NULL) {
02197       if (punc_count < 2)
02198         Results->match[NextGood++] = match;
02199       punc_count++;
02200     } else {
02201       if (strstr(digit_chars,
02202                  unicharset.id_to_unichar(match.unichar_id)) != NULL) {
02203         if (digit_count < 1)
02204           Results->match[NextGood++] = match;
02205         digit_count++;
02206       } else {
02207         Results->match[NextGood++] = match;
02208       }
02209     }
02210   }
02211   Results->match.truncate(NextGood);
02212 }                              /* RemoveExtraPuncs */
02213 
02214 /*---------------------------------------------------------------------------*/
02228 void Classify::SetAdaptiveThreshold(FLOAT32 Threshold) {
02229   Threshold = (Threshold == matcher_good_threshold) ? 0.9: (1.0 - Threshold);
02230   classify_adapt_proto_threshold.set_value(
02231       ClipToRange<int>(255 * Threshold, 0, 255));
02232   classify_adapt_feature_threshold.set_value(
02233       ClipToRange<int>(255 * Threshold, 0, 255));
02234 }                              /* SetAdaptiveThreshold */
02235 
02236 /*---------------------------------------------------------------------------*/
02249 void Classify::ShowBestMatchFor(int shape_id,
02250                                 const INT_FEATURE_STRUCT* features,
02251                                 int num_features) {
02252 #ifndef GRAPHICS_DISABLED
02253   uinT32 config_mask;
02254   if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) {
02255     tprintf("No built-in templates for class/shape %d\n", shape_id);
02256     return;
02257   }
02258   if (num_features <= 0) {
02259     tprintf("Illegal blob (char norm features)!\n");
02260     return;
02261   }
02262   INT_RESULT_STRUCT cn_result;
02263   classify_norm_method.set_value(character);
02264   im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
02265             AllProtosOn, AllConfigsOn,
02266             num_features, features, &cn_result,
02267             classify_adapt_feature_threshold, NO_DEBUG,
02268             matcher_debug_separate_windows);
02269   tprintf("\n");
02270   config_mask = 1 << cn_result.Config;
02271 
02272   tprintf("Static Shape ID: %d\n", shape_id);
02273   ShowMatchDisplay();
02274   im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
02275             AllProtosOn, reinterpret_cast<BIT_VECTOR>(&config_mask),
02276             num_features, features, &cn_result,
02277             classify_adapt_feature_threshold,
02278             matcher_debug_flags,
02279             matcher_debug_separate_windows);
02280   UpdateMatchDisplay();
02281 #endif  // GRAPHICS_DISABLED
02282 }                              /* ShowBestMatchFor */
02283 
02284 // Returns a string for the classifier class_id: either the corresponding
02285 // unicharset debug_str or the shape_table_ debug str.
02286 STRING Classify::ClassIDToDebugStr(const INT_TEMPLATES_STRUCT* templates,
02287                                    int class_id, int config_id) const {
02288   STRING class_string;
02289   if (templates == PreTrainedTemplates && shape_table_ != NULL) {
02290     int shape_id = ClassAndConfigIDToFontOrShapeID(class_id, config_id);
02291     class_string = shape_table_->DebugStr(shape_id);
02292   } else {
02293     class_string = unicharset.debug_str(class_id);
02294   }
02295   return class_string;
02296 }
02297 
02298 // Converts a classifier class_id index to a shape_table_ index
02299 int Classify::ClassAndConfigIDToFontOrShapeID(int class_id,
02300                                               int int_result_config) const {
02301   int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id;
02302   // Older inttemps have no font_ids.
02303   if (font_set_id < 0)
02304     return kBlankFontinfoId;
02305   const FontSet &fs = fontset_table_.get(font_set_id);
02306   ASSERT_HOST(int_result_config >= 0 && int_result_config < fs.size);
02307   return fs.configs[int_result_config];
02308 }
02309 
02310 // Converts a shape_table_ index to a classifier class_id index (not a
02311 // unichar-id!). Uses a search, so not fast.
02312 int Classify::ShapeIDToClassID(int shape_id) const {
02313   for (int id = 0; id < PreTrainedTemplates->NumClasses; ++id) {
02314     int font_set_id = PreTrainedTemplates->Class[id]->font_set_id;
02315     ASSERT_HOST(font_set_id >= 0);
02316     const FontSet &fs = fontset_table_.get(font_set_id);
02317     for (int config = 0; config < fs.size; ++config) {
02318       if (fs.configs[config] == shape_id)
02319         return id;
02320     }
02321   }
02322   tprintf("Shape %d not found\n", shape_id);
02323   return -1;
02324 }
02325 
02326 // Returns true if the given TEMP_CONFIG is good enough to make it
02327 // a permanent config.
02328 bool Classify::TempConfigReliable(CLASS_ID class_id,
02329                                   const TEMP_CONFIG &config) {
02330   if (classify_learning_debug_level >= 1) {
02331     tprintf("NumTimesSeen for config of %s is %d\n",
02332             getDict().getUnicharset().debug_str(class_id).string(),
02333             config->NumTimesSeen);
02334   }
02335   if (config->NumTimesSeen >= matcher_sufficient_examples_for_prototyping) {
02336     return true;
02337   } else if (config->NumTimesSeen < matcher_min_examples_for_prototyping) {
02338     return false;
02339   } else if (use_ambigs_for_adaption) {
02340     // Go through the ambigs vector and see whether we have already seen
02341     // enough times all the characters represented by the ambigs vector.
02342     const UnicharIdVector *ambigs =
02343       getDict().getUnicharAmbigs().AmbigsForAdaption(class_id);
02344     int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
02345     for (int ambig = 0; ambig < ambigs_size; ++ambig) {
02346       ADAPT_CLASS ambig_class = AdaptedTemplates->Class[(*ambigs)[ambig]];
02347       assert(ambig_class != NULL);
02348       if (ambig_class->NumPermConfigs == 0 &&
02349           ambig_class->MaxNumTimesSeen <
02350           matcher_min_examples_for_prototyping) {
02351         if (classify_learning_debug_level >= 1) {
02352           tprintf("Ambig %s has not been seen enough times,"
02353                   " not making config for %s permanent\n",
02354                   getDict().getUnicharset().debug_str(
02355                       (*ambigs)[ambig]).string(),
02356                   getDict().getUnicharset().debug_str(class_id).string());
02357         }
02358         return false;
02359       }
02360     }
02361   }
02362   return true;
02363 }
02364 
02365 void Classify::UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob) {
02366   const UnicharIdVector *ambigs =
02367     getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id);
02368   int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
02369   if (classify_learning_debug_level >= 1) {
02370     tprintf("Running UpdateAmbigsGroup for %s class_id=%d\n",
02371             getDict().getUnicharset().debug_str(class_id).string(), class_id);
02372   }
02373   for (int ambig = 0; ambig < ambigs_size; ++ambig) {
02374     CLASS_ID ambig_class_id = (*ambigs)[ambig];
02375     const ADAPT_CLASS ambigs_class = AdaptedTemplates->Class[ambig_class_id];
02376     for (int cfg = 0; cfg < MAX_NUM_CONFIGS; ++cfg) {
02377       if (ConfigIsPermanent(ambigs_class, cfg)) continue;
02378       const TEMP_CONFIG config =
02379         TempConfigFor(AdaptedTemplates->Class[ambig_class_id], cfg);
02380       if (config != NULL && TempConfigReliable(ambig_class_id, config)) {
02381         if (classify_learning_debug_level >= 1) {
02382           tprintf("Making config %d of %s permanent\n", cfg,
02383                   getDict().getUnicharset().debug_str(
02384                       ambig_class_id).string());
02385         }
02386         MakePermanent(AdaptedTemplates, ambig_class_id, cfg, Blob);
02387       }
02388     }
02389   }
02390 }
02391 
02392 }  // namespace tesseract
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines