~ubuntu-branches/ubuntu/vivid/tesseract/vivid

« back to all changes in this revision

Viewing changes to classify/adaptmatch.cpp

  • Committer: Package Import Robot
  • Author(s): Jeff Breidenbach
  • Date: 2014-02-03 11:10:20 UTC
  • mfrom: (1.3.1) (19.1.1 experimental)
  • Revision ID: package-import@ubuntu.com-20140203111020-igquodd7pjlp3uri
Tags: 3.03.01-1
* New upstream release, includes critical fix to PDF rendering
* Complete leptonlib transition (see bug #735509)
* Promote from experimental to unstable

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
/*-----------------------------------------------------------------------------
20
20
          Include Files and Type Defines
21
21
-----------------------------------------------------------------------------*/
 
22
#ifdef HAVE_CONFIG_H
 
23
#include "config_auto.h"
 
24
#endif
 
25
 
22
26
#include <ctype.h>
23
27
#include "ambigs.h"
24
28
#include "blobclass.h"
31
35
#include "outfeat.h"
32
36
#include "emalloc.h"
33
37
#include "intfx.h"
34
 
#include "speckle.h"
35
38
#include "efio.h"
36
39
#include "normmatch.h"
37
 
#include "permute.h"
38
40
#include "ndminx.h"
39
41
#include "intproto.h"
40
42
#include "const.h"
60
62
#include <assert.h>
61
63
#endif
62
64
 
63
 
// Include automatically generated configuration file if running autoconf.
64
 
#ifdef HAVE_CONFIG_H
65
 
#include "config_auto.h"
66
 
#endif
67
 
 
68
65
#define ADAPT_TEMPLATE_SUFFIX ".a"
69
66
 
70
67
#define MAX_MATCHES         10
90
87
 
91
88
struct ADAPT_RESULTS {
92
89
  inT32 BlobLength;
93
 
  int NumMatches;
94
90
  bool HasNonfragment;
95
 
  ScoredClass match[MAX_NUM_CLASSES];
 
91
  GenericVector<ScoredClass> match;
96
92
  ScoredClass best_match;
97
 
  CLASS_PRUNER_RESULTS CPResults;
 
93
  GenericVector<CP_RESULT_STRUCT> CPResults;
98
94
 
99
95
  /// Initializes data members to the default values. Sets the initial
100
96
  /// rating of each class to be the worst possible rating (1.0).
101
97
  inline void Initialize() {
102
98
     BlobLength = MAX_INT32;
103
 
     NumMatches = 0;
104
99
     HasNonfragment = false;
105
100
     best_match.unichar_id = NO_CLASS;
106
101
     best_match.shape_id = -1;
124
119
#define MarginalMatch(Rating)       \
125
120
((Rating) > matcher_great_threshold)
126
121
 
127
 
#define InitIntFX() (FeaturesHaveBeenExtracted = FALSE)
128
 
 
129
122
/*-----------------------------------------------------------------------------
130
123
          Private Function Prototypes
131
124
-----------------------------------------------------------------------------*/
167
160
 * @note History: Mon Mar 11 10:00:58 1991, DSJ, Created.
168
161
 *
169
162
 * @param Blob    blob to be classified
170
 
 * @param denorm normalization/denormalization parameters
171
163
 * @param[out] Choices    List of choices found by adaptive matcher.
172
 
 * @param[out] CPResults  Array of CPResultStruct of size MAX_NUM_CLASSES is
173
164
 * filled on return with the choices found by the
174
165
 * class pruner and the ratings therefrom. Also
175
166
 * contains the detailed results of the integer matcher.
176
167
 *
177
168
 */
178
 
void Classify::AdaptiveClassifier(TBLOB *Blob,
179
 
                                  const DENORM& denorm,
180
 
                                  BLOB_CHOICE_LIST *Choices,
181
 
                                  CLASS_PRUNER_RESULTS CPResults) {
 
169
void Classify::AdaptiveClassifier(TBLOB *Blob, BLOB_CHOICE_LIST *Choices) {
182
170
  assert(Choices != NULL);
183
 
  ADAPT_RESULTS *Results = new ADAPT_RESULTS();
 
171
  ADAPT_RESULTS *Results = new ADAPT_RESULTS;
184
172
  Results->Initialize();
185
173
 
186
 
  if (AdaptedTemplates == NULL)
187
 
    AdaptedTemplates = NewAdaptedTemplates (true);
188
 
  DoAdaptiveMatch(Blob, denorm, Results);
189
 
  if (CPResults != NULL)
190
 
    memcpy(CPResults, Results->CPResults,
191
 
           sizeof(CPResults[0]) * Results->NumMatches);
 
174
  ASSERT_HOST(AdaptedTemplates != NULL);
 
175
 
 
176
  DoAdaptiveMatch(Blob, Results);
192
177
 
193
178
  RemoveBadMatches(Results);
194
 
  qsort((void *)Results->match, Results->NumMatches,
195
 
        sizeof(ScoredClass), CompareByRating);
 
179
  Results->match.sort(CompareByRating);
196
180
  RemoveExtraPuncs(Results);
197
 
  ConvertMatchesToChoices(denorm, Blob->bounding_box(), Results, Choices);
 
181
  ConvertMatchesToChoices(Blob->denorm(), Blob->bounding_box(), Results,
 
182
                          Choices);
198
183
 
199
184
  if (matcher_debug_level >= 1) {
200
185
    cprintf ("AD Matches =  ");
201
186
    PrintAdaptiveMatchResults(stdout, Results);
202
187
  }
203
188
 
204
 
  if (LargeSpeckle(Blob))
205
 
    AddLargeSpeckleTo(Choices);
 
189
  if (LargeSpeckle(*Blob) || Choices->length() == 0)
 
190
    AddLargeSpeckleTo(Results->BlobLength, Choices);
206
191
 
207
192
#ifndef GRAPHICS_DISABLED
208
193
  if (classify_enable_adaptive_debugger)
209
 
    DebugAdaptiveClassifier(Blob, denorm, Results);
 
194
    DebugAdaptiveClassifier(Blob, Results);
210
195
#endif
211
196
 
212
 
  NumClassesOutput += Choices->length();
213
 
  if (Choices->length() == 0) {
214
 
    if (!classify_bln_numeric_mode)
215
 
      tprintf ("Empty classification!\n");  // Should never normally happen.
216
 
    Choices = new BLOB_CHOICE_LIST();
217
 
    BLOB_CHOICE_IT temp_it;
218
 
    temp_it.set_to_list(Choices);
219
 
    temp_it.add_to_end(
220
 
        new BLOB_CHOICE(0, 50.0f, -20.0f, -1, -1, NULL, 0, 0, false));
221
 
  }
222
 
 
223
197
  delete Results;
224
198
}                                /* AdaptiveClassifier */
225
199
 
251
225
// Otherwise AdaptToBlob is called for adaption within a document.
252
226
// If rejmap is not NULL, then only chars with a rejmap entry of '1' will
253
227
// be learned, otherwise all chars with good correct_text are learned.
254
 
void Classify::LearnWord(const char* filename, const char *rejmap,
255
 
                         WERD_RES *word) {
 
228
void Classify::LearnWord(const char* filename, WERD_RES *word) {
256
229
  int word_len = word->correct_text.size();
257
230
  if (word_len == 0) return;
258
231
 
259
232
  float* thresholds = NULL;
260
233
  if (filename == NULL) {
261
234
    // Adaption mode.
262
 
    if (!EnableLearning || word->best_choice == NULL ||
263
 
        // If word->best_choice is not recorded at the top of accumulator's
264
 
        // best choices (which could happen for choices that are
265
 
        // altered with ReplaceAmbig()) we skip the adaption.
266
 
        !getDict().CurrentBestChoiceIs(*(word->best_choice)))
 
235
    if (!EnableLearning || word->best_choice == NULL)
267
236
      return;  // Can't or won't adapt.
268
237
 
269
 
    NumWordsAdaptedTo++;
270
238
    if (classify_learning_debug_level >= 1)
271
239
      tprintf("\n\nAdapting to word = %s\n",
272
240
              word->best_choice->debug_string().string());
273
241
    thresholds = new float[word_len];
274
 
    GetAdaptThresholds(word->rebuild_word, word->denorm, *word->best_choice,
275
 
                       *word->raw_choice, thresholds);
 
242
    word->ComputeAdaptionThresholds(certainty_scale,
 
243
                                    matcher_perfect_threshold,
 
244
                                    matcher_good_threshold,
 
245
                                    matcher_rating_margin, thresholds);
276
246
  }
277
247
  int start_blob = 0;
278
 
  char prev_map_char = '0';
279
248
 
280
249
  #ifndef GRAPHICS_DISABLED
281
250
  if (classify_debug_character_fragments) {
295
264
    if (classify_debug_character_fragments) {
296
265
      tprintf("\nLearning %s\n",  word->correct_text[ch].string());
297
266
    }
298
 
    char rej_map_char = rejmap != NULL ? *rejmap++ : '1';
299
 
 
300
 
    if (word->correct_text[ch].length() > 0 && rej_map_char == '1') {
 
267
    if (word->correct_text[ch].length() > 0) {
301
268
      float threshold = thresholds != NULL ? thresholds[ch] : 0.0f;
302
269
 
303
270
      LearnPieces(filename, start_blob, word->best_state[ch],
308
275
        // that each match a whole character with at least
309
276
        // classify_character_fragments_garbage_certainty_threshold
310
277
        bool garbage = false;
311
 
        TBLOB* frag_blob = word->chopped_word->blobs;
312
 
        for (int i = 0; i < start_blob; ++i) frag_blob = frag_blob->next;
313
278
        int frag;
314
279
        for (frag = 0; frag < word->best_state[ch]; ++frag) {
 
280
          TBLOB* frag_blob = word->chopped_word->blobs[start_blob + frag];
315
281
          if (classify_character_fragments_garbage_certainty_threshold < 0) {
316
 
            garbage |= LooksLikeGarbage(word->denorm, frag_blob);
 
282
            garbage |= LooksLikeGarbage(frag_blob);
317
283
          }
318
 
          frag_blob = frag_blob->next;
319
284
        }
320
285
        // Learn the fragments.
321
286
        if (!garbage) {
346
311
      // TODO(rays): re-enable this part of the code when we switch to the
347
312
      // new classifier that needs to see examples of garbage.
348
313
      /*
349
 
      char next_map_char = ch + 1 < word_len
350
 
                           ? (rejmap != NULL ? *rejmap : '1')
351
 
                           : '0';
352
314
      if (word->best_state[ch] > 1) {
353
315
        // If the next blob is good, make junk with the rightmost fragment.
354
 
        if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0 &&
355
 
            next_map_char == '1') {
 
316
        if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
356
317
          LearnPieces(filename, start_blob + word->best_state[ch] - 1,
357
318
                      word->best_state[ch + 1] + 1,
358
319
                      threshold, CST_IMPROPER, INVALID_UNICHAR, word);
359
320
        }
360
321
        // If the previous blob is good, make junk with the leftmost fragment.
361
 
        if (ch > 0 && word->correct_text[ch - 1].length() > 0 &&
362
 
            prev_map_char == '1') {
 
322
        if (ch > 0 && word->correct_text[ch - 1].length() > 0) {
363
323
          LearnPieces(filename, start_blob - word->best_state[ch - 1],
364
324
                      word->best_state[ch - 1] + 1,
365
325
                      threshold, CST_IMPROPER, INVALID_UNICHAR, word);
366
326
        }
367
327
      }
368
328
      // If the next blob is good, make a join with it.
369
 
      if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0 &&
370
 
          next_map_char == '1') {
 
329
      if (ch + 1 < word_len && word->correct_text[ch + 1].length() > 0) {
371
330
        STRING joined_text = word->correct_text[ch];
372
331
        joined_text += word->correct_text[ch + 1];
373
332
        LearnPieces(filename, start_blob,
377
336
      */
378
337
    }
379
338
    start_blob += word->best_state[ch];
380
 
    prev_map_char = rej_map_char;
381
339
  }
382
340
  delete [] thresholds;
383
341
}  // LearnWord.
388
346
// is called and the data will be written to a file for static training.
389
347
// Otherwise AdaptToBlob is called for adaption within a document.
390
348
// threshold is a magic number required by AdaptToChar and generated by
391
 
// GetAdaptThresholds.
 
349
// ComputeAdaptionThresholds.
392
350
// Although it can be partly inferred from the string, segmentation is
393
351
// provided to explicitly clarify the character segmentation.
394
352
void Classify::LearnPieces(const char* filename, int start, int length,
401
359
    return;
402
360
 
403
361
  if (length > 1) {
404
 
    join_pieces(word->chopped_word->blobs, word->seam_array,
405
 
                start, start + length - 1);
 
362
    join_pieces(word->seam_array, start, start + length - 1,
 
363
                word->chopped_word);
406
364
  }
407
 
  TBLOB* blob = word->chopped_word->blobs;
408
 
  for (int i = 0; i < start; ++i)
409
 
    blob = blob->next;
 
365
  TBLOB* blob = word->chopped_word->blobs[start];
410
366
  // Rotate the blob if needed for classification.
411
 
  const DENORM* denorm = &word->denorm;
412
 
  TBLOB* rotated_blob = blob->ClassifyNormalizeIfNeeded(&denorm);
 
367
  TBLOB* rotated_blob = blob->ClassifyNormalizeIfNeeded();
413
368
  if (rotated_blob == NULL)
414
369
    rotated_blob = blob;
415
370
 
434
389
    classify_norm_method.set_value(character);  // force char norm spc 30/11/93
435
390
    tess_bn_matching.set_value(false);    // turn it off
436
391
    tess_cn_matching.set_value(false);
437
 
    LearnBlob(feature_defs_, filename, rotated_blob, *denorm,
438
 
              correct_text);
 
392
    DENORM bl_denorm, cn_denorm;
 
393
    INT_FX_RESULT_STRUCT fx_info;
 
394
    SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm,
 
395
                     &bl_denorm, &cn_denorm, &fx_info);
 
396
    LearnBlob(feature_defs_, filename, rotated_blob, bl_denorm, cn_denorm,
 
397
              fx_info, correct_text);
439
398
  } else if (unicharset.contains_unichar(correct_text)) {
440
399
    UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text);
441
400
    int font_id = word->fontinfo != NULL
446
405
              unicharset.id_to_unichar(class_id), threshold, font_id);
447
406
    // If filename is not NULL we are doing recognition
448
407
    // (as opposed to training), so we must have already set word fonts.
449
 
    AdaptToChar(rotated_blob, *denorm, class_id, font_id, threshold);
 
408
    AdaptToChar(rotated_blob, class_id, font_id, threshold);
450
409
  } else if (classify_debug_level >= 1) {
451
410
    tprintf("Can't adapt to %s not in unicharset\n", correct_text);
452
411
  }
453
412
  if (rotated_blob != blob) {
454
413
    delete rotated_blob;
455
 
    delete denorm;
456
414
  }
457
415
 
458
 
  break_pieces(blob, word->seam_array, start, start + length - 1);
 
416
  break_pieces(word->seam_array, start, start + length - 1, word->chopped_word);
459
417
}  // LearnPieces.
460
418
 
461
419
/*---------------------------------------------------------------------------*/
507
465
  FreeNormProtos();
508
466
  if (AllProtosOn != NULL) {
509
467
    FreeBitVector(AllProtosOn);
510
 
    FreeBitVector(PrunedProtos);
511
468
    FreeBitVector(AllConfigsOn);
512
 
    FreeBitVector(AllProtosOff);
513
469
    FreeBitVector(AllConfigsOff);
514
470
    FreeBitVector(TempProtoMask);
515
471
    AllProtosOn = NULL;
516
 
    PrunedProtos = NULL;
517
472
    AllConfigsOn = NULL;
518
 
    AllProtosOff = NULL;
519
473
    AllConfigsOff = NULL;
520
474
    TempProtoMask = NULL;
521
475
  }
522
476
  delete shape_table_;
523
477
  shape_table_ = NULL;
 
478
  if (static_classifier_ != NULL) {
 
479
    delete static_classifier_;
 
480
    static_classifier_ = NULL;
 
481
  }
524
482
}                                /* EndAdaptiveClassifier */
525
483
 
526
484
 
581
539
      ReadNormProtos(tessdata_manager.GetDataFilePtr(),
582
540
                     tessdata_manager.GetEndOffset(TESSDATA_NORMPROTO));
583
541
    if (tessdata_manager.DebugLevel() > 0) tprintf("Loaded normproto\n");
 
542
    static_classifier_ = new TessClassifier(false, this);
584
543
  }
585
544
 
586
 
  im_.Init(&classify_debug_level, classify_integer_matcher_multiplier);
 
545
  im_.Init(&classify_debug_level);
587
546
  InitIntegerFX();
588
547
 
589
548
  AllProtosOn = NewBitVector(MAX_NUM_PROTOS);
590
 
  PrunedProtos = NewBitVector(MAX_NUM_PROTOS);
591
549
  AllConfigsOn = NewBitVector(MAX_NUM_CONFIGS);
592
 
  AllProtosOff = NewBitVector(MAX_NUM_PROTOS);
593
550
  AllConfigsOff = NewBitVector(MAX_NUM_CONFIGS);
594
551
  TempProtoMask = NewBitVector(MAX_NUM_PROTOS);
595
552
  set_all_bits(AllProtosOn, WordsInVectorOfSize(MAX_NUM_PROTOS));
596
 
  set_all_bits(PrunedProtos, WordsInVectorOfSize(MAX_NUM_PROTOS));
597
553
  set_all_bits(AllConfigsOn, WordsInVectorOfSize(MAX_NUM_CONFIGS));
598
 
  zero_all_bits(AllProtosOff, WordsInVectorOfSize(MAX_NUM_PROTOS));
599
554
  zero_all_bits(AllConfigsOff, WordsInVectorOfSize(MAX_NUM_CONFIGS));
600
555
 
601
556
  for (int i = 0; i < MAX_NUM_CLASSES; i++) {
639
594
            NumAdaptationsFailed);
640
595
  }
641
596
  free_adapted_templates(AdaptedTemplates);
642
 
  AdaptedTemplates = NULL;
 
597
  AdaptedTemplates = NewAdaptedTemplates(true);
643
598
  NumAdaptationsFailed = 0;
644
599
}
645
600
 
646
601
 
647
 
/*---------------------------------------------------------------------------*/
648
 
/**
649
 
 * Print to File the statistics which have
650
 
 * been gathered for the adaptive matcher.
651
 
 *
652
 
 * @param File open text file to print adaptive statistics to
653
 
 *
654
 
 * Globals: none
655
 
 *
656
 
 * @note Exceptions: none
657
 
 * @note History: Thu Apr 18 14:37:37 1991, DSJ, Created.
658
 
 */
659
 
void Classify::PrintAdaptiveStatistics(FILE *File) {
660
 
  #ifndef SECURE_NAMES
661
 
 
662
 
  fprintf (File, "\nADAPTIVE MATCHER STATISTICS:\n");
663
 
  fprintf (File, "\tNum blobs classified = %d\n", AdaptiveMatcherCalls);
664
 
  fprintf (File, "\tNum classes output   = %d (Avg = %4.2f)\n",
665
 
    NumClassesOutput,
666
 
    ((AdaptiveMatcherCalls == 0) ? (0.0) :
667
 
  ((float) NumClassesOutput / AdaptiveMatcherCalls)));
668
 
  fprintf (File, "\t\tBaseline Classifier: %4d calls (%4.2f classes/call)\n",
669
 
    BaselineClassifierCalls,
670
 
    ((BaselineClassifierCalls == 0) ? (0.0) :
671
 
  ((float) NumBaselineClassesTried / BaselineClassifierCalls)));
672
 
  fprintf (File, "\t\tCharNorm Classifier: %4d calls (%4.2f classes/call)\n",
673
 
    CharNormClassifierCalls,
674
 
    ((CharNormClassifierCalls == 0) ? (0.0) :
675
 
  ((float) NumCharNormClassesTried / CharNormClassifierCalls)));
676
 
  fprintf (File, "\t\tAmbig    Classifier: %4d calls (%4.2f classes/call)\n",
677
 
    AmbigClassifierCalls,
678
 
    ((AmbigClassifierCalls == 0) ? (0.0) :
679
 
  ((float) NumAmbigClassesTried / AmbigClassifierCalls)));
680
 
 
681
 
  fprintf (File, "\nADAPTIVE LEARNER STATISTICS:\n");
682
 
  fprintf (File, "\tNumber of words adapted to: %d\n", NumWordsAdaptedTo);
683
 
  fprintf (File, "\tNumber of chars adapted to: %d\n", NumCharsAdaptedTo);
684
 
 
685
 
  PrintAdaptedTemplates(File, AdaptedTemplates);
686
 
  #endif
687
 
}                                /* PrintAdaptiveStatistics */
688
 
 
689
602
 
690
603
/*---------------------------------------------------------------------------*/
691
604
/**
741
654
 * config in that class.
742
655
 *
743
656
 * @param Blob blob to model new class after
744
 
 * @param denorm normalization/denormalization parameters
745
657
 * @param ClassId id of the class to be initialized
746
658
 * @param FontinfoId font information inferred from pre-trained templates
747
659
 * @param Class adapted class to be initialized
756
668
 * @note History: Thu Mar 14 12:49:39 1991, DSJ, Created.
757
669
 */
758
670
void Classify::InitAdaptedClass(TBLOB *Blob,
759
 
                                const DENORM& denorm,
760
671
                                CLASS_ID ClassId,
761
672
                                int FontinfoId,
762
673
                                ADAPT_CLASS Class,
822
733
    cprintf ("Added new class '%s' with class id %d and %d protos.\n",
823
734
             unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
824
735
    if (classify_learning_debug_level > 1)
825
 
      DisplayAdaptedChar(Blob, denorm, IClass);
 
736
      DisplayAdaptedChar(Blob, IClass);
826
737
  }
827
738
 
828
739
  if (IsEmptyAdaptedClass(Class))
885
796
 *
886
797
 * @param Word current word
887
798
 * @param BestChoiceWord best overall choice for word with context
888
 
 * @param RawChoiceWord best choice for word without context
889
799
 *
890
800
 * @return TRUE or FALSE
891
801
 * @note Exceptions: none
892
802
 * @note History: Thu May 30 14:25:06 1991, DSJ, Created.
893
803
 */
894
 
int Classify::AdaptableWord(TWERD *Word,
895
 
                            const WERD_CHOICE &BestChoiceWord,
896
 
                            const WERD_CHOICE &RawChoiceWord) {
897
 
  int BestChoiceLength = BestChoiceWord.length();
 
804
bool Classify::AdaptableWord(WERD_RES* word) {
 
805
  if (word->best_choice == NULL) return false;
 
806
  int BestChoiceLength = word->best_choice->length();
898
807
  float adaptable_score =
899
808
    getDict().segment_penalty_dict_case_ok + ADAPTABLE_WERD_ADJUSTMENT;
900
809
  return   // rules that apply in general - simplest to compute first
901
810
      BestChoiceLength > 0 &&
902
 
      BestChoiceLength == Word->NumBlobs() &&
 
811
      BestChoiceLength == word->rebuild_word->NumBlobs() &&
903
812
      BestChoiceLength <= MAX_ADAPTABLE_WERD_SIZE &&
904
813
      // This basically ensures that the word is at least a dictionary match
905
814
      // (freq word, user word, system dawg word, etc).
907
816
      // than higher than adaptable_score=1.1+0.05=1.15
908
817
      // Since these are other flags that ensure that the word is dict word,
909
818
      // this check could be at times redundant.
910
 
      getDict().CurrentBestChoiceAdjustFactor() <= adaptable_score &&
 
819
      word->best_choice->adjust_factor() <= adaptable_score &&
911
820
      // Make sure that alternative choices are not dictionary words.
912
 
      getDict().AlternativeChoicesWorseThan(adaptable_score) &&
913
 
      getDict().CurrentBestChoiceIs(BestChoiceWord);
 
821
      word->AlternativeChoiceAdjustmentsWorseThan(adaptable_score);
914
822
}
915
823
 
916
824
/*---------------------------------------------------------------------------*/
917
825
/**
918
826
 * @param Blob blob to add to templates for ClassId
919
 
 * @param denorm normalization/denormalization parameters
920
827
 * @param ClassId class to add blob to
921
828
 * @param FontinfoId font information from pre-trained templates
922
829
 * @param Threshold minimum match rating to existing template
931
838
 * @note History: Thu Mar 14 09:36:03 1991, DSJ, Created.
932
839
 */
933
840
void Classify::AdaptToChar(TBLOB *Blob,
934
 
                           const DENORM& denorm,
935
841
                           CLASS_ID ClassId,
936
842
                           int FontinfoId,
937
843
                           FLOAT32 Threshold) {
944
850
  FEATURE_SET FloatFeatures;
945
851
  int NewTempConfigId;
946
852
 
947
 
  ResetFeaturesHaveBeenExtracted();
948
 
  NumCharsAdaptedTo++;
949
853
  if (!LegalClassId (ClassId))
950
854
    return;
951
855
 
952
856
  Class = AdaptedTemplates->Class[ClassId];
953
857
  assert(Class != NULL);
954
858
  if (IsEmptyAdaptedClass(Class)) {
955
 
    InitAdaptedClass(Blob, denorm, ClassId, FontinfoId, Class,
956
 
                     AdaptedTemplates);
 
859
    InitAdaptedClass(Blob, ClassId, FontinfoId, Class, AdaptedTemplates);
957
860
  }
958
861
  else {
959
862
    IClass = ClassForClassId (AdaptedTemplates->Templates, ClassId);
962
865
    if (NumFeatures <= 0)
963
866
      return;
964
867
 
965
 
    im_.SetBaseLineMatch();
966
868
    // Only match configs with the matching font.
967
869
    BIT_VECTOR MatchingFontConfigs = NewBitVector(MAX_NUM_PROTOS);
968
870
    for (int cfg = 0; cfg < IClass->NumConfigs; ++cfg) {
999
901
          IntResult.Config, TempConfig->NumTimesSeen);
1000
902
 
1001
903
      if (TempConfigReliable(ClassId, TempConfig)) {
1002
 
        MakePermanent(AdaptedTemplates, ClassId, IntResult.Config, denorm,
1003
 
                      Blob);
1004
 
        UpdateAmbigsGroup(ClassId, denorm, Blob);
 
904
        MakePermanent(AdaptedTemplates, ClassId, IntResult.Config, Blob);
 
905
        UpdateAmbigsGroup(ClassId, Blob);
1005
906
      }
1006
907
    }
1007
908
    else {
1009
910
        cprintf ("Found poor match to temp config %d = %4.1f%%.\n",
1010
911
          IntResult.Config, (1.0 - IntResult.Rating) * 100.0);
1011
912
        if (classify_learning_debug_level > 2)
1012
 
          DisplayAdaptedChar(Blob, denorm, IClass);
 
913
          DisplayAdaptedChar(Blob, IClass);
1013
914
      }
1014
915
      NewTempConfigId = MakeNewTemporaryConfig(AdaptedTemplates,
1015
916
                                               ClassId,
1019
920
                                               FloatFeatures);
1020
921
      if (NewTempConfigId >= 0 &&
1021
922
          TempConfigReliable(ClassId, TempConfigFor(Class, NewTempConfigId))) {
1022
 
        MakePermanent(AdaptedTemplates, ClassId, NewTempConfigId, denorm, Blob);
1023
 
        UpdateAmbigsGroup(ClassId, denorm, Blob);
 
923
        MakePermanent(AdaptedTemplates, ClassId, NewTempConfigId, Blob);
 
924
        UpdateAmbigsGroup(ClassId, Blob);
1024
925
      }
1025
926
 
1026
927
#ifndef GRAPHICS_DISABLED
1027
928
      if (classify_learning_debug_level > 1) {
1028
 
        DisplayAdaptedChar(Blob, denorm, IClass);
 
929
        DisplayAdaptedChar(Blob, IClass);
1029
930
      }
1030
931
#endif
1031
932
    }
1033
934
  }
1034
935
}                                /* AdaptToChar */
1035
936
 
1036
 
void Classify::DisplayAdaptedChar(TBLOB* blob, const DENORM& denorm,
1037
 
                                  INT_CLASS_STRUCT* int_class) {
 
937
void Classify::DisplayAdaptedChar(TBLOB* blob, INT_CLASS_STRUCT* int_class) {
1038
938
#ifndef GRAPHICS_DISABLED
1039
 
  int bloblength = 0;
1040
 
  INT_FEATURE_ARRAY features;
1041
 
  uinT8* norm_array = new uinT8[unicharset.size()];
1042
 
  int num_features = GetBaselineFeatures(blob, denorm, PreTrainedTemplates,
1043
 
                                         features,
1044
 
                                         norm_array, &bloblength);
1045
 
  delete [] norm_array;
 
939
  INT_FX_RESULT_STRUCT fx_info;
 
940
  GenericVector<INT_FEATURE_STRUCT> bl_features;
 
941
  TrainingSample* sample =
 
942
      BlobToTrainingSample(*blob, classify_nonlinear_norm, &fx_info,
 
943
                           &bl_features);
 
944
  if (sample == NULL) return;
 
945
 
1046
946
  INT_RESULT_STRUCT IntResult;
1047
 
 
1048
947
  im_.Match(int_class, AllProtosOn, AllConfigsOn,
1049
 
            num_features, features,
 
948
            bl_features.size(), &bl_features[0],
1050
949
            &IntResult, classify_adapt_feature_threshold,
1051
950
            NO_DEBUG, matcher_debug_separate_windows);
1052
951
  cprintf ("Best match to temp config %d = %4.1f%%.\n",
1056
955
    ConfigMask = 1 << IntResult.Config;
1057
956
    ShowMatchDisplay();
1058
957
    im_.Match(int_class, AllProtosOn, (BIT_VECTOR)&ConfigMask,
1059
 
              num_features, features,
 
958
              bl_features.size(), &bl_features[0],
1060
959
              &IntResult, classify_adapt_feature_threshold,
1061
960
              6 | 0x19, matcher_debug_separate_windows);
1062
961
    UpdateMatchDisplay();
1065
964
}
1066
965
 
1067
966
 
1068
 
/*---------------------------------------------------------------------------*/
1069
 
/**
1070
 
 * @param Blob blob to add to templates for ClassId
1071
 
 * @param denorm normalization/denormalization parameters
1072
 
 * @param ClassId class to add blob to
1073
 
 * @param FontinfoId font information from pre-trained teamples
1074
 
 * @param Threshold minimum match rating to existing template
1075
 
 *
1076
 
 * Globals:
1077
 
 * - PreTrainedTemplates current set of built-in templates
1078
 
 *
1079
 
 * @note Exceptions: none
1080
 
 * @note History: Thu Mar 14 09:36:03 1991, DSJ, Created.
1081
 
 */
1082
 
void Classify::AdaptToPunc(TBLOB *Blob,
1083
 
                           const DENORM& denorm,
1084
 
                           CLASS_ID ClassId,
1085
 
                           int FontinfoId,
1086
 
                           FLOAT32 Threshold) {
1087
 
  ADAPT_RESULTS *Results = new ADAPT_RESULTS();
1088
 
  int i;
1089
 
 
1090
 
  Results->Initialize();
1091
 
  CharNormClassifier(Blob, denorm, PreTrainedTemplates, Results);
1092
 
  RemoveBadMatches(Results);
1093
 
 
1094
 
  if (Results->NumMatches != 1) {
1095
 
    if (classify_learning_debug_level >= 1) {
1096
 
      cprintf ("Rejecting punc = %s (Alternatives = ",
1097
 
               unicharset.id_to_unichar(ClassId));
1098
 
 
1099
 
      for (i = 0; i < Results->NumMatches; i++)
1100
 
        tprintf("%s", unicharset.id_to_unichar(Results->match[i].unichar_id));
1101
 
      tprintf(")\n");
1102
 
    }
1103
 
  } else {
1104
 
    #ifndef SECURE_NAMES
1105
 
    if (classify_learning_debug_level >= 1)
1106
 
      cprintf ("Adapting to punc = %s, thr= %g\n",
1107
 
               unicharset.id_to_unichar(ClassId), Threshold);
1108
 
    #endif
1109
 
    AdaptToChar(Blob, denorm, ClassId, FontinfoId, Threshold);
1110
 
  }
1111
 
  delete Results;
1112
 
}                                /* AdaptToPunc */
1113
 
 
1114
967
 
1115
968
/*---------------------------------------------------------------------------*/
1116
969
/**
1167
1020
  if (old_match)
1168
1021
    old_match->rating = rating;
1169
1022
  else
1170
 
    results->match[results->NumMatches++] = match;
 
1023
    results->match.push_back(match);
1171
1024
 
1172
1025
  if (rating < results->best_match.rating &&
1173
1026
      // Ensure that fragments do not affect best rating, class and config.
1193
1046
 * - #AllConfigsOn mask that enables all configs
1194
1047
 *
1195
1048
 * @param Blob blob to be classified
1196
 
 * @param denorm normalization/denormalization parameters
1197
1049
 * @param Templates built-in templates to classify against
1198
1050
 * @param Classes adapted class templates
1199
1051
 * @param Ambiguities array of class id's to match against
1202
1054
 * @note Exceptions: none
1203
1055
 * @note History: Tue Mar 12 19:40:36 1991, DSJ, Created.
1204
1056
 */
1205
 
void Classify::AmbigClassifier(TBLOB *Blob,
1206
 
                               const DENORM& denorm,
1207
 
                               INT_TEMPLATES Templates,
1208
 
                               ADAPT_CLASS *Classes,
1209
 
                               UNICHAR_ID *Ambiguities,
1210
 
                               ADAPT_RESULTS *Results) {
1211
 
  int NumFeatures;
1212
 
  INT_FEATURE_ARRAY IntFeatures;
 
1057
void Classify::AmbigClassifier(
 
1058
    const GenericVector<INT_FEATURE_STRUCT>& int_features,
 
1059
    const INT_FX_RESULT_STRUCT& fx_info,
 
1060
    const TBLOB *blob,
 
1061
    INT_TEMPLATES templates,
 
1062
    ADAPT_CLASS *classes,
 
1063
    UNICHAR_ID *ambiguities,
 
1064
    ADAPT_RESULTS *results) {
 
1065
  if (int_features.empty()) return;
1213
1066
  uinT8* CharNormArray = new uinT8[unicharset.size()];
1214
1067
  INT_RESULT_STRUCT IntResult;
1215
 
  CLASS_ID ClassId;
1216
 
 
1217
 
  AmbigClassifierCalls++;
1218
 
 
1219
 
  NumFeatures = GetCharNormFeatures(Blob, denorm, Templates, IntFeatures,
1220
 
                                    NULL, CharNormArray,
1221
 
                                    &(Results->BlobLength), NULL);
1222
 
  if (NumFeatures <= 0) {
1223
 
    delete [] CharNormArray;
1224
 
    return;
1225
 
  }
1226
 
 
 
1068
 
 
1069
  results->BlobLength = GetCharNormFeature(fx_info, templates, NULL,
 
1070
                                           CharNormArray);
1227
1071
  bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1228
1072
  if (debug)
1229
1073
    tprintf("AM Matches =  ");
1230
1074
 
1231
 
  int top = Blob->bounding_box().top();
1232
 
  int bottom = Blob->bounding_box().bottom();
1233
 
  while (*Ambiguities >= 0) {
1234
 
    ClassId = *Ambiguities;
 
1075
  int top = blob->bounding_box().top();
 
1076
  int bottom = blob->bounding_box().bottom();
 
1077
  while (*ambiguities >= 0) {
 
1078
    CLASS_ID class_id = *ambiguities;
1235
1079
 
1236
 
    im_.SetCharNormMatch(classify_integer_matcher_multiplier);
1237
 
    im_.Match(ClassForClassId(Templates, ClassId),
 
1080
    im_.Match(ClassForClassId(templates, class_id),
1238
1081
              AllProtosOn, AllConfigsOn,
1239
 
              NumFeatures, IntFeatures,
 
1082
              int_features.size(), &int_features[0],
1240
1083
              &IntResult,
1241
1084
              classify_adapt_feature_threshold, NO_DEBUG,
1242
1085
              matcher_debug_separate_windows);
1243
1086
 
1244
 
    ExpandShapesAndApplyCorrections(NULL, debug, ClassId, bottom, top, 0,
1245
 
                                    Results->BlobLength, CharNormArray,
1246
 
                                    IntResult, Results);
1247
 
    Ambiguities++;
1248
 
 
1249
 
    NumAmbigClassesTried++;
 
1087
    ExpandShapesAndApplyCorrections(NULL, debug, class_id, bottom, top, 0,
 
1088
                                    results->BlobLength,
 
1089
                                    classify_integer_matcher_multiplier,
 
1090
                                    CharNormArray, IntResult, results);
 
1091
    ambiguities++;
1250
1092
  }
1251
1093
  delete [] CharNormArray;
1252
1094
}                                /* AmbigClassifier */
1260
1102
                             const uinT8* norm_factors,
1261
1103
                             ADAPT_CLASS* classes,
1262
1104
                             int debug,
1263
 
                             int num_classes,
 
1105
                             int matcher_multiplier,
1264
1106
                             const TBOX& blob_box,
1265
 
                             CLASS_PRUNER_RESULTS results,
 
1107
                             const GenericVector<CP_RESULT_STRUCT>& results,
1266
1108
                             ADAPT_RESULTS* final_results) {
1267
1109
  int top = blob_box.top();
1268
1110
  int bottom = blob_box.bottom();
1269
 
  for (int c = 0; c < num_classes; c++) {
 
1111
  for (int c = 0; c < results.size(); c++) {
1270
1112
    CLASS_ID class_id = results[c].Class;
1271
1113
    INT_RESULT_STRUCT& int_result = results[c].IMResult;
1272
1114
    BIT_VECTOR protos = classes != NULL ? classes[class_id]->PermProtos
1282
1124
    bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1283
1125
    ExpandShapesAndApplyCorrections(classes, debug, class_id, bottom, top,
1284
1126
                                    results[c].Rating,
1285
 
                                    final_results->BlobLength, norm_factors,
 
1127
                                    final_results->BlobLength,
 
1128
                                    matcher_multiplier, norm_factors,
1286
1129
                                    int_result, final_results);
1287
1130
  }
1288
1131
}
1294
1137
// The results are added to the final_results output.
1295
1138
void Classify::ExpandShapesAndApplyCorrections(
1296
1139
    ADAPT_CLASS* classes, bool debug, int class_id, int bottom, int top,
1297
 
    float cp_rating, int blob_length, const uinT8* cn_factors,
 
1140
    float cp_rating, int blob_length, int matcher_multiplier,
 
1141
    const uinT8* cn_factors,
1298
1142
    INT_RESULT_STRUCT& int_result, ADAPT_RESULTS* final_results) {
1299
1143
  // Compute the fontinfo_ids.
1300
1144
  int fontinfo_id = kBlankFontinfoId;
1328
1172
                                               int_result.Rating,
1329
1173
                                               int_result.FeatureMisses,
1330
1174
                                               bottom, top, blob_length,
1331
 
                                               cn_factors);
 
1175
                                               matcher_multiplier, cn_factors);
1332
1176
        if (c == 0 || rating < min_rating)
1333
1177
          min_rating = rating;
1334
1178
        if (unicharset.get_enabled(unichar_id)) {
1345
1189
                                         int_result.Rating,
1346
1190
                                         int_result.FeatureMisses,
1347
1191
                                         bottom, top, blob_length,
1348
 
                                         cn_factors);
 
1192
                                         matcher_multiplier, cn_factors);
1349
1193
  if (unicharset.get_enabled(class_id)) {
1350
1194
    AddNewResult(final_results, class_id, -1, rating,
1351
1195
                 classes != NULL, int_result.Config,
1361
1205
                                        double cp_rating, double im_rating,
1362
1206
                                        int feature_misses,
1363
1207
                                        int bottom, int top,
1364
 
                                        int blob_length,
 
1208
                                        int blob_length, int matcher_multiplier,
1365
1209
                                        const uinT8* cn_factors) {
1366
1210
  // Compute class feature corrections.
1367
1211
  double cn_corrected = im_.ApplyCNCorrection(im_rating, blob_length,
1368
 
                                              cn_factors[unichar_id]);
 
1212
                                              cn_factors[unichar_id],
 
1213
                                              matcher_multiplier);
1369
1214
  double miss_penalty = tessedit_class_miss_scale * feature_misses;
1370
1215
  double vertical_penalty = 0.0;
1371
1216
  // Penalize non-alnums for being vertical misfits.
1412
1257
 * - BaselineCutoffs expected num features for each class
1413
1258
 *
1414
1259
 * @param Blob blob to be classified
1415
 
 * @param denorm normalization/denormalization parameters
1416
1260
 * @param Templates current set of adapted templates
1417
1261
 * @param Results place to put match results
1418
1262
 *
1420
1264
 * @note Exceptions: none
1421
1265
 * @note History: Tue Mar 12 19:38:03 1991, DSJ, Created.
1422
1266
 */
1423
 
UNICHAR_ID *Classify::BaselineClassifier(TBLOB *Blob,
1424
 
                                         const DENORM& denorm,
1425
 
                                         ADAPT_TEMPLATES Templates,
1426
 
                                         ADAPT_RESULTS *Results) {
1427
 
  int NumFeatures;
1428
 
  int NumClasses;
1429
 
  INT_FEATURE_ARRAY IntFeatures;
 
1267
UNICHAR_ID *Classify::BaselineClassifier(
 
1268
    TBLOB *Blob, const GenericVector<INT_FEATURE_STRUCT>& int_features,
 
1269
    const INT_FX_RESULT_STRUCT& fx_info,
 
1270
    ADAPT_TEMPLATES Templates, ADAPT_RESULTS *Results) {
 
1271
  if (int_features.empty()) return NULL;
1430
1272
  uinT8* CharNormArray = new uinT8[unicharset.size()];
1431
 
  CLASS_ID ClassId;
1432
 
 
1433
 
  BaselineClassifierCalls++;
1434
 
 
1435
 
  NumFeatures = GetBaselineFeatures(
1436
 
      Blob, denorm, Templates->Templates, IntFeatures, CharNormArray,
1437
 
      &(Results->BlobLength));
1438
 
  if (NumFeatures <= 0) {
1439
 
    delete [] CharNormArray;
1440
 
    return NULL;
1441
 
  }
1442
 
 
1443
 
  NumClasses = PruneClasses(Templates->Templates, NumFeatures, IntFeatures,
1444
 
                            CharNormArray, BaselineCutoffs, Results->CPResults);
1445
 
 
1446
 
  NumBaselineClassesTried += NumClasses;
 
1273
  ClearCharNormArray(CharNormArray);
 
1274
 
 
1275
  Results->BlobLength = IntCastRounded(fx_info.Length / kStandardFeatureLength);
 
1276
  PruneClasses(Templates->Templates, int_features.size(), &int_features[0],
 
1277
               CharNormArray, BaselineCutoffs, &Results->CPResults);
1447
1278
 
1448
1279
  if (matcher_debug_level >= 2 || classify_debug_level > 1)
1449
1280
    cprintf ("BL Matches =  ");
1450
1281
 
1451
 
  im_.SetBaseLineMatch();
1452
 
  MasterMatcher(Templates->Templates, NumFeatures, IntFeatures, CharNormArray,
1453
 
                Templates->Class, matcher_debug_flags, NumClasses,
 
1282
  MasterMatcher(Templates->Templates, int_features.size(), &int_features[0],
 
1283
                CharNormArray,
 
1284
                Templates->Class, matcher_debug_flags, 0,
1454
1285
                Blob->bounding_box(), Results->CPResults, Results);
1455
1286
 
1456
1287
  delete [] CharNormArray;
1457
 
  ClassId = Results->best_match.unichar_id;
 
1288
  CLASS_ID ClassId = Results->best_match.unichar_id;
1458
1289
  if (ClassId == NO_CLASS)
1459
1290
    return (NULL);
1460
1291
  /* this is a bug - maybe should return "" */
1472
1303
 * are added to Results.
1473
1304
 *
1474
1305
 * @param Blob blob to be classified
1475
 
 * @param denorm normalization/denormalization parameters
1476
1306
 * @param Templates templates to classify unknown against
1477
1307
 * @param Results place to put match results
1478
1308
 *
1484
1314
 * @note Exceptions: none
1485
1315
 * @note History: Tue Mar 12 16:02:52 1991, DSJ, Created.
1486
1316
 */
1487
 
int Classify::CharNormClassifier(TBLOB *Blob,
1488
 
                                 const DENORM& denorm,
1489
 
                                 INT_TEMPLATES Templates,
1490
 
                                 ADAPT_RESULTS *Results) {
1491
 
  int NumFeatures;
1492
 
  int NumClasses;
1493
 
  INT_FEATURE_ARRAY IntFeatures;
1494
 
 
1495
 
  CharNormClassifierCalls++;
1496
 
 
1497
 
  uinT8* CharNormArray = new uinT8[unicharset.size()];
1498
 
  int num_pruner_classes = MAX(unicharset.size(),
1499
 
                               PreTrainedTemplates->NumClasses);
1500
 
  uinT8* PrunerNormArray = new uinT8[num_pruner_classes];
1501
 
  NumFeatures = GetCharNormFeatures(Blob, denorm, Templates, IntFeatures,
1502
 
                                    PrunerNormArray, CharNormArray,
1503
 
                                    &(Results->BlobLength), NULL);
1504
 
  if (NumFeatures <= 0) {
1505
 
    delete [] CharNormArray;
1506
 
    delete [] PrunerNormArray;
1507
 
    return 0;
 
1317
int Classify::CharNormClassifier(TBLOB *blob,
 
1318
                                 const TrainingSample& sample,
 
1319
                                 ADAPT_RESULTS *adapt_results) {
 
1320
  // This is the length that is used for scaling ratings vs certainty.
 
1321
  adapt_results->BlobLength =
 
1322
      IntCastRounded(sample.outline_length() / kStandardFeatureLength);
 
1323
  GenericVector<UnicharRating> unichar_results;
 
1324
  static_classifier_->UnicharClassifySample(sample, blob->denorm().pix(), 0,
 
1325
                                            -1, &unichar_results);
 
1326
  // Convert results to the format used internally by AdaptiveClassifier.
 
1327
  for (int r = 0; r < unichar_results.size(); ++r) {
 
1328
    int unichar_id = unichar_results[r].unichar_id;
 
1329
    // Fonts are listed in order of preference.
 
1330
    int font1 = unichar_results[r].fonts.size() >= 1
 
1331
              ? unichar_results[r].fonts[0] : kBlankFontinfoId;
 
1332
    int font2 = unichar_results[r].fonts.size() >= 2
 
1333
              ? unichar_results[r].fonts[1] : kBlankFontinfoId;
 
1334
    float rating = 1.0f - unichar_results[r].rating;
 
1335
    AddNewResult(adapt_results, unichar_id, -1, rating, false, 0, font1, font2);
1508
1336
  }
1509
 
 
1510
 
  NumClasses = PruneClasses(Templates, NumFeatures, IntFeatures,
1511
 
                            PrunerNormArray,
1512
 
                            shape_table_ != NULL ? &shapetable_cutoffs_[0]
1513
 
                                                 : CharNormCutoffs,
1514
 
                            Results->CPResults);
1515
 
 
1516
 
  if (tessedit_single_match && NumClasses > 1)
1517
 
    NumClasses = 1;
1518
 
  NumCharNormClassesTried += NumClasses;
1519
 
 
1520
 
  im_.SetCharNormMatch(classify_integer_matcher_multiplier);
1521
 
  MasterMatcher(Templates, NumFeatures, IntFeatures, CharNormArray,
1522
 
                NULL, matcher_debug_flags, NumClasses,
1523
 
                Blob->bounding_box(), Results->CPResults, Results);
1524
 
  delete [] CharNormArray;
1525
 
  delete [] PrunerNormArray;
1526
 
  return NumFeatures;
 
1337
  return sample.num_features();
1527
1338
}                                /* CharNormClassifier */
1528
1339
 
1529
1340
// As CharNormClassifier, but operates on a TrainingSample and outputs to
1530
1341
// a GenericVector of ShapeRating without conversion to classes.
1531
1342
int Classify::CharNormTrainingSample(bool pruner_only,
 
1343
                                     int keep_this,
1532
1344
                                     const TrainingSample& sample,
1533
 
                                     GenericVector<ShapeRating>* results) {
 
1345
                                     GenericVector<UnicharRating>* results) {
1534
1346
  results->clear();
1535
1347
  ADAPT_RESULTS* adapt_results = new ADAPT_RESULTS();
1536
1348
  adapt_results->Initialize();
1537
1349
  // Compute the bounding box of the features.
1538
1350
  int num_features = sample.num_features();
1539
 
  TBOX blob_box;
1540
 
  for (int f = 0; f < num_features; ++f) {
1541
 
    const INT_FEATURE_STRUCT feature = sample.features()[f];
1542
 
    TBOX fbox(feature.X, feature.Y, feature.X, feature.Y);
1543
 
    blob_box += fbox;
1544
 
  }
 
1351
  // Only the top and bottom of the blob_box are used by MasterMatcher, so
 
1352
  // fabricate right and left using top and bottom.
 
1353
  TBOX blob_box(sample.geo_feature(GeoBottom), sample.geo_feature(GeoBottom),
 
1354
                sample.geo_feature(GeoTop), sample.geo_feature(GeoTop));
1545
1355
  // Compute the char_norm_array from the saved cn_feature.
1546
 
  FEATURE norm_feature = NewFeature(&CharNormDesc);
1547
 
  norm_feature->Params[CharNormY] = sample.cn_feature(CharNormY);
1548
 
  norm_feature->Params[CharNormLength] = sample.cn_feature(CharNormLength);
1549
 
  norm_feature->Params[CharNormRx] = sample.cn_feature(CharNormRx);
1550
 
  norm_feature->Params[CharNormRy] = sample.cn_feature(CharNormRy);
 
1356
  FEATURE norm_feature = sample.GetCNFeature();
1551
1357
  uinT8* char_norm_array = new uinT8[unicharset.size()];
1552
1358
  int num_pruner_classes = MAX(unicharset.size(),
1553
1359
                               PreTrainedTemplates->NumClasses);
1557
1363
  ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
1558
1364
                        pruner_norm_array);
1559
1365
 
1560
 
  int num_classes = PruneClasses(PreTrainedTemplates, num_features,
1561
 
                                 sample.features(),
1562
 
                                 pruner_norm_array,
1563
 
                                 shape_table_ != NULL ? &shapetable_cutoffs_[0]
1564
 
                                                      : CharNormCutoffs,
1565
 
                                 adapt_results->CPResults);
 
1366
  PruneClasses(PreTrainedTemplates, num_features, sample.features(),
 
1367
               pruner_norm_array,
 
1368
               shape_table_ != NULL ? &shapetable_cutoffs_[0] : CharNormCutoffs,
 
1369
               &adapt_results->CPResults);
1566
1370
  delete [] pruner_norm_array;
 
1371
  if (keep_this >= 0) {
 
1372
    adapt_results->CPResults[0].Class = keep_this;
 
1373
    adapt_results->CPResults.truncate(1);
 
1374
  }
1567
1375
  if (pruner_only) {
1568
1376
    // Convert pruner results to output format.
1569
 
    for (int i = 0; i < num_classes; ++i) {
 
1377
    for (int i = 0; i < adapt_results->CPResults.size(); ++i) {
1570
1378
      int class_id = adapt_results->CPResults[i].Class;
1571
 
      int shape_id = class_id;
1572
 
      if (shape_table_ != NULL) {
1573
 
        // All shapes in a class have the same combination of unichars, so
1574
 
        // it doesn't really matter which config we give it, as we aren't
1575
 
        // trying to get the font here.
1576
 
        shape_id = ClassAndConfigIDToFontOrShapeID(class_id, 0);
1577
 
      }
1578
1379
      results->push_back(
1579
 
          ShapeRating(shape_id, 1.0f - adapt_results->CPResults[i].Rating));
 
1380
          UnicharRating(class_id, 1.0f - adapt_results->CPResults[i].Rating));
1580
1381
    }
1581
1382
  } else {
1582
 
    im_.SetCharNormMatch(classify_integer_matcher_multiplier);
1583
1383
    MasterMatcher(PreTrainedTemplates, num_features, sample.features(),
1584
1384
                  char_norm_array,
1585
 
                  NULL, matcher_debug_flags, num_classes,
 
1385
                  NULL, matcher_debug_flags,
 
1386
                  classify_integer_matcher_multiplier,
1586
1387
                  blob_box, adapt_results->CPResults, adapt_results);
1587
1388
    // Convert master matcher results to output format.
1588
 
    for (int i = 0; i < adapt_results->NumMatches; i++) {
 
1389
    for (int i = 0; i < adapt_results->match.size(); i++) {
1589
1390
      ScoredClass next = adapt_results->match[i];
1590
 
      results->push_back(ShapeRating(next.shape_id, 1.0f - next.rating));
 
1391
      UnicharRating rating(next.unichar_id, 1.0f - next.rating);
 
1392
      if (next.fontinfo_id >= 0) {
 
1393
        rating.fonts.push_back(next.fontinfo_id);
 
1394
        if (next.fontinfo_id2 >= 0)
 
1395
          rating.fonts.push_back(next.fontinfo_id2);
 
1396
      }
 
1397
      results->push_back(rating);
1591
1398
    }
1592
 
    results->sort(&ShapeRating::SortDescendingRating);
 
1399
    results->sort(&UnicharRating::SortDescendingRating);
1593
1400
  }
1594
1401
  delete [] char_norm_array;
1595
1402
  delete adapt_results;
1628
1435
/*---------------------------------------------------------------------------*/
1629
1436
// Return a pointer to the scored unichar in results, or NULL if not present.
1630
1437
ScoredClass *FindScoredUnichar(ADAPT_RESULTS *results, UNICHAR_ID id) {
1631
 
  for (int i = 0; i < results->NumMatches; i++) {
 
1438
  for (int i = 0; i < results->match.size(); i++) {
1632
1439
    if (results->match[i].unichar_id == id)
1633
1440
      return &results->match[i];
1634
1441
  }
1694
1501
      max_matches = MAX_MATCHES;
1695
1502
  }
1696
1503
 
1697
 
  for (int i = 0; i < Results->NumMatches; i++) {
 
1504
  float best_certainty = -MAX_FLOAT32;
 
1505
  for (int i = 0; i < Results->match.size(); i++) {
1698
1506
    ScoredClass next = Results->match[i];
1699
1507
    int fontinfo_id = next.fontinfo_id;
1700
1508
    int fontinfo_id2 = next.fontinfo_id2;
1717
1525
      Rating *= rating_scale * Results->BlobLength;
1718
1526
      Certainty *= -(getDict().certainty_scale);
1719
1527
    }
1720
 
    inT16 min_xheight, max_xheight;
 
1528
    // Adapted results, by their very nature, should have good certainty.
 
1529
    // Those that don't are at best misleading, and often lead to errors,
 
1530
    // so don't accept adapted results that are too far behind the best result,
 
1531
    // whether adapted or static.
 
1532
    // TODO(rays) find some way of automatically tuning these constants.
 
1533
    if (Certainty > best_certainty) {
 
1534
      best_certainty = MIN(Certainty, classify_adapted_pruning_threshold);
 
1535
    } else if (adapted &&
 
1536
               Certainty / classify_adapted_pruning_factor < best_certainty) {
 
1537
      continue;  // Don't accept bad adapted results.
 
1538
    }
 
1539
 
 
1540
    float min_xheight, max_xheight, yshift;
1721
1541
    denorm.XHeightRange(next.unichar_id, unicharset, box,
1722
 
                        &min_xheight, &max_xheight);
 
1542
                        &min_xheight, &max_xheight, &yshift);
1723
1543
    temp_it.add_to_end(new BLOB_CHOICE(next.unichar_id, Rating, Certainty,
1724
1544
                                        fontinfo_id, fontinfo_id2,
1725
1545
                                        unicharset.get_script(next.unichar_id),
1726
 
                                        min_xheight, max_xheight, adapted));
 
1546
                                        min_xheight, max_xheight, yshift,
 
1547
                                        adapted ? BCC_ADAPTED_CLASSIFIER
 
1548
                                                : BCC_STATIC_CLASSIFIER));
1727
1549
    contains_nonfrag |= !current_is_frag;  // update contains_nonfrag
1728
1550
    choices_length++;
1729
1551
    if (choices_length >= max_matches) break;
1730
1552
  }
1731
 
  Results->NumMatches = choices_length;
 
1553
  Results->match.truncate(choices_length);
1732
1554
}  // ConvertMatchesToChoices
1733
1555
 
1734
1556
 
1737
1559
/**
1738
1560
 *
1739
1561
 * @param Blob blob whose classification is being debugged
1740
 
 * @param denorm normalization/denormalization parameters
1741
1562
 * @param Results results of match being debugged
1742
1563
 *
1743
1564
 * Globals: none
1745
1566
 * @note Exceptions: none
1746
1567
 * @note History: Wed Mar 13 16:44:41 1991, DSJ, Created.
1747
1568
 */
1748
 
void Classify::DebugAdaptiveClassifier(TBLOB *Blob,
1749
 
                                       const DENORM& denorm,
 
1569
void Classify::DebugAdaptiveClassifier(TBLOB *blob,
1750
1570
                                       ADAPT_RESULTS *Results) {
1751
 
  for (int i = 0; i < Results->NumMatches; i++) {
1752
 
    if (Results->match[i].rating < Results->best_match.rating)
 
1571
  if (static_classifier_ == NULL) return;
 
1572
  for (int i = 0; i < Results->match.size(); i++) {
 
1573
    if (i == 0 || Results->match[i].rating < Results->best_match.rating)
1753
1574
      Results->best_match = Results->match[i];
1754
1575
  }
1755
 
  const char *Prompt =
1756
 
    "Left-click in IntegerMatch Window to continue or right click to debug...";
1757
 
  CLASS_ID unichar_id = Results->best_match.unichar_id;
1758
 
  int shape_id = Results->best_match.shape_id;
1759
 
  bool adaptive_on = true;
1760
 
  bool pretrained_on = true;
1761
 
 
1762
 
  const char* debug_mode;
1763
 
  do {
1764
 
    if (!pretrained_on)
1765
 
      debug_mode = "Adaptive Templates Only";
1766
 
    else if (!adaptive_on)
1767
 
      debug_mode = "PreTrained Templates Only";
1768
 
    else
1769
 
      debug_mode = "All Templates";
1770
 
    ShowMatchDisplay();
1771
 
    tprintf("Debugging class %d = %s in mode %s ...",
1772
 
            unichar_id, unicharset.id_to_unichar(unichar_id), debug_mode);
1773
 
    if (shape_id >= 0 && shape_table_ != NULL) {
1774
 
      tprintf(" from shape %s\n", shape_table_->DebugStr(shape_id).string());
1775
 
    }
1776
 
    ShowBestMatchFor(Blob, denorm, unichar_id, shape_id, adaptive_on,
1777
 
                     pretrained_on, Results);
1778
 
    UpdateMatchDisplay();
1779
 
  } while ((unichar_id = GetClassToDebug(Prompt, &adaptive_on,
1780
 
                                         &pretrained_on, &shape_id)) != 0);
 
1576
  INT_FX_RESULT_STRUCT fx_info;
 
1577
  GenericVector<INT_FEATURE_STRUCT> bl_features;
 
1578
  TrainingSample* sample =
 
1579
      BlobToTrainingSample(*blob, false, &fx_info, &bl_features);
 
1580
  if (sample == NULL) return;
 
1581
  static_classifier_->DebugDisplay(*sample, blob->denorm().pix(),
 
1582
                                   Results->best_match.unichar_id);
1781
1583
}                                /* DebugAdaptiveClassifier */
1782
1584
#endif
1783
1585
 
1794
1596
 * of these classifications are merged together into Results.
1795
1597
 *
1796
1598
 * @param Blob blob to be classified
1797
 
 * @param denorm normalization/denormalization parameters
1798
1599
 * @param Results place to put match results
1799
1600
 *
1800
1601
 * Globals:
1805
1606
 * @note Exceptions: none
1806
1607
 * @note History: Tue Mar 12 08:50:11 1991, DSJ, Created.
1807
1608
 */
1808
 
void Classify::DoAdaptiveMatch(TBLOB *Blob,
1809
 
                               const DENORM& denorm,
1810
 
                               ADAPT_RESULTS *Results) {
 
1609
void Classify::DoAdaptiveMatch(TBLOB *Blob, ADAPT_RESULTS *Results) {
1811
1610
  UNICHAR_ID *Ambiguities;
1812
1611
 
1813
 
  AdaptiveMatcherCalls++;
1814
 
  InitIntFX();
 
1612
  INT_FX_RESULT_STRUCT fx_info;
 
1613
  GenericVector<INT_FEATURE_STRUCT> bl_features;
 
1614
  TrainingSample* sample =
 
1615
      BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
 
1616
                           &bl_features);
 
1617
  if (sample == NULL) return;
1815
1618
 
1816
1619
  if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min ||
1817
1620
      tess_cn_matching) {
1818
 
    CharNormClassifier(Blob, denorm, PreTrainedTemplates, Results);
 
1621
    CharNormClassifier(Blob, *sample, Results);
1819
1622
  } else {
1820
 
    Ambiguities = BaselineClassifier(Blob, denorm, AdaptedTemplates, Results);
1821
 
    if ((Results->NumMatches > 0 &&
1822
 
         MarginalMatch (Results->best_match.rating) &&
 
1623
    Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
 
1624
                                     AdaptedTemplates, Results);
 
1625
    if ((!Results->match.empty() && MarginalMatch(Results->best_match.rating) &&
1823
1626
         !tess_bn_matching) ||
1824
 
        Results->NumMatches == 0) {
1825
 
      CharNormClassifier(Blob, denorm, PreTrainedTemplates, Results);
 
1627
        Results->match.empty()) {
 
1628
      CharNormClassifier(Blob, *sample, Results);
1826
1629
    } else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) {
1827
 
      AmbigClassifier(Blob, denorm,
 
1630
      AmbigClassifier(bl_features, fx_info, Blob,
1828
1631
                      PreTrainedTemplates,
1829
1632
                      AdaptedTemplates->Class,
1830
1633
                      Ambiguities,
1836
1639
  // if the results contain only fragments.
1837
1640
  // TODO(daria): verify that this is better than
1838
1641
  // just adding a NULL classification.
1839
 
  if (!Results->HasNonfragment || Results->NumMatches == 0)
 
1642
  if (!Results->HasNonfragment || Results->match.empty())
1840
1643
    ClassifyAsNoise(Results);
 
1644
  delete sample;
1841
1645
}   /* DoAdaptiveMatch */
1842
1646
 
1843
1647
/*---------------------------------------------------------------------------*/
1844
1648
/**
1845
 
 * This routine tries to estimate how tight the adaptation
1846
 
 * threshold should be set for each character in the current
1847
 
 * word.  In general, the routine tries to set tighter
1848
 
 * thresholds for a character when the current set of templates
1849
 
 * would have made an error on that character.  It tries
1850
 
 * to set a threshold tight enough to eliminate the error.
1851
 
 * Two different sets of rules can be used to determine the
1852
 
 * desired thresholds.
1853
 
 *
1854
 
 * @param Word current word
1855
 
 * @param denorm normalization/denormalization parameters
1856
 
 * @param BestChoice best choice for current word with context
1857
 
 * @param BestRawChoice best choice for current word without context
1858
 
 * @param[out] Thresholds array of thresholds to be filled in
1859
 
 *
1860
 
 * Globals:
1861
 
 * - matcher_good_threshold
1862
 
 * - matcher_perfect_threshold
1863
 
 * - matcher_rating_margin
1864
 
 *
1865
 
 * @return none (results are returned in Thresholds)
1866
 
 * @note Exceptions: none
1867
 
 * @note History: Fri May 31 09:22:08 1991, DSJ, Created.
1868
 
 */
1869
 
void Classify::GetAdaptThresholds(TWERD * Word,
1870
 
                                  const DENORM& denorm,
1871
 
                                  const WERD_CHOICE& BestChoice,
1872
 
                                  const WERD_CHOICE& BestRawChoice,
1873
 
                                  FLOAT32 Thresholds[]) {
1874
 
  getDict().FindClassifierErrors(matcher_perfect_threshold,
1875
 
                                 matcher_good_threshold,
1876
 
                                 matcher_rating_margin,
1877
 
                                 Thresholds);
1878
 
}                              /* GetAdaptThresholds */
1879
 
 
1880
 
/*---------------------------------------------------------------------------*/
1881
 
/**
1882
1649
 * This routine matches blob to the built-in templates
1883
1650
 * to find out if there are any classes other than the correct
1884
1651
 * class which are potential ambiguities.
1885
1652
 *
1886
1653
 * @param Blob blob to get classification ambiguities for
1887
 
 * @param denorm normalization/denormalization parameters
1888
1654
 * @param CorrectClass correct class for Blob
1889
1655
 *
1890
1656
 * Globals:
1896
1662
 * @note History: Fri Mar 15 08:08:22 1991, DSJ, Created.
1897
1663
 */
1898
1664
UNICHAR_ID *Classify::GetAmbiguities(TBLOB *Blob,
1899
 
                                     const DENORM& denorm,
1900
1665
                                     CLASS_ID CorrectClass) {
1901
1666
  ADAPT_RESULTS *Results = new ADAPT_RESULTS();
1902
1667
  UNICHAR_ID *Ambiguities;
1903
1668
  int i;
1904
1669
 
1905
1670
  Results->Initialize();
 
1671
  INT_FX_RESULT_STRUCT fx_info;
 
1672
  GenericVector<INT_FEATURE_STRUCT> bl_features;
 
1673
  TrainingSample* sample =
 
1674
      BlobToTrainingSample(*Blob, classify_nonlinear_norm, &fx_info,
 
1675
                           &bl_features);
 
1676
  if (sample == NULL) {
 
1677
    delete Results;
 
1678
    return NULL;
 
1679
  }
1906
1680
 
1907
 
  CharNormClassifier(Blob, denorm, PreTrainedTemplates, Results);
 
1681
  CharNormClassifier(Blob, *sample, Results);
 
1682
  delete sample;
1908
1683
  RemoveBadMatches(Results);
1909
 
  qsort((void *)Results->match, Results->NumMatches,
1910
 
        sizeof(ScoredClass), CompareByRating);
 
1684
  Results->match.sort(CompareByRating);
1911
1685
 
1912
1686
  /* copy the class id's into an string of ambiguities - don't copy if
1913
1687
     the correct class is the only class id matched */
1914
 
  Ambiguities = (UNICHAR_ID *) Emalloc (sizeof (UNICHAR_ID) *
1915
 
                                        (Results->NumMatches + 1));
1916
 
  if (Results->NumMatches > 1 ||
1917
 
      (Results->NumMatches == 1 &&
 
1688
  Ambiguities = new UNICHAR_ID[Results->match.size() + 1];
 
1689
  if (Results->match.size() > 1 ||
 
1690
      (Results->match.size() == 1 &&
1918
1691
          Results->match[0].unichar_id != CorrectClass)) {
1919
 
    for (i = 0; i < Results->NumMatches; i++)
 
1692
    for (i = 0; i < Results->match.size(); i++)
1920
1693
      Ambiguities[i] = Results->match[i].unichar_id;
1921
1694
    Ambiguities[i] = -1;
1922
1695
  } else {
1927
1700
  return Ambiguities;
1928
1701
}                              /* GetAmbiguities */
1929
1702
 
1930
 
/*---------------------------------------------------------------------------*/
1931
 
/**
1932
 
 * This routine calls the integer (Hardware) feature
1933
 
 * extractor if it has not been called before for this blob.
1934
 
 * The results from the feature extractor are placed into
1935
 
 * globals so that they can be used in other routines without
1936
 
 * re-extracting the features.
1937
 
 * It then copies the baseline features into the IntFeatures
1938
 
 * array provided by the caller.
1939
 
 *
1940
 
 * @param Blob blob to extract features from
1941
 
 * @param denorm normalization/denormalization parameters
1942
 
 * @param Templates used to compute char norm adjustments
1943
 
 * @param IntFeatures array to fill with integer features
1944
 
 * @param CharNormArray array to fill with dummy char norm adjustments
1945
 
 * @param BlobLength length of blob in baseline-normalized units
1946
 
 *
1947
 
 * Globals:
1948
 
 * - FeaturesHaveBeenExtracted TRUE if fx has been done
1949
 
 * - BaselineFeatures holds extracted baseline feat
1950
 
 * - CharNormFeatures holds extracted char norm feat
1951
 
 * - FXInfo holds misc. FX info
1952
 
 *
1953
 
 * @return Number of features extracted or 0 if an error occured.
1954
 
 * @note Exceptions: none
1955
 
 * @note History: Tue May 28 10:40:52 1991, DSJ, Created.
1956
 
 */
1957
 
int Classify::GetBaselineFeatures(TBLOB *Blob,
1958
 
                                  const DENORM& denorm,
1959
 
                                  INT_TEMPLATES Templates,
1960
 
                                  INT_FEATURE_ARRAY IntFeatures,
1961
 
                                  uinT8* CharNormArray,
1962
 
                                  inT32 *BlobLength) {
1963
 
  register INT_FEATURE Src, Dest, End;
1964
 
 
1965
 
  if (!FeaturesHaveBeenExtracted) {
1966
 
    FeaturesOK = ExtractIntFeat(Blob, denorm, BaselineFeatures,
1967
 
                                CharNormFeatures, &FXInfo, NULL);
1968
 
    FeaturesHaveBeenExtracted = TRUE;
1969
 
  }
1970
 
 
1971
 
  if (!FeaturesOK) {
1972
 
    *BlobLength = FXInfo.NumBL;
1973
 
    return 0;
1974
 
  }
1975
 
 
1976
 
  for (Src = BaselineFeatures, End = Src + FXInfo.NumBL, Dest = IntFeatures;
1977
 
       Src < End;
1978
 
       *Dest++ = *Src++);
1979
 
 
1980
 
  ClearCharNormArray(CharNormArray);
1981
 
  *BlobLength = FXInfo.NumBL;
1982
 
  return FXInfo.NumBL;
1983
 
}                              /* GetBaselineFeatures */
1984
 
 
1985
 
void Classify::ResetFeaturesHaveBeenExtracted() {
1986
 
  FeaturesHaveBeenExtracted = FALSE;
1987
 
}
1988
 
 
1989
1703
// Returns true if the given blob looks too dissimilar to any character
1990
1704
// present in the classifier templates.
1991
 
bool Classify::LooksLikeGarbage(const DENORM& denorm, TBLOB *blob) {
 
1705
bool Classify::LooksLikeGarbage(TBLOB *blob) {
1992
1706
  BLOB_CHOICE_LIST *ratings = new BLOB_CHOICE_LIST();
1993
 
  AdaptiveClassifier(blob, denorm, ratings, NULL);
 
1707
  AdaptiveClassifier(blob, ratings);
1994
1708
  BLOB_CHOICE_IT ratings_it(ratings);
1995
1709
  const UNICHARSET &unicharset = getDict().getUnicharset();
1996
1710
  if (classify_debug_character_fragments) {
2002
1716
    if (unicharset.get_fragment(ratings_it.data()->unichar_id()) != NULL) {
2003
1717
      continue;
2004
1718
    }
 
1719
    float certainty = ratings_it.data()->certainty();
2005
1720
    delete ratings;
2006
 
    return (ratings_it.data()->certainty() <
2007
 
            classify_character_fragments_garbage_certainty_threshold);
 
1721
    return certainty <
 
1722
            classify_character_fragments_garbage_certainty_threshold;
2008
1723
  }
2009
1724
  delete ratings;
2010
1725
  return true;  // no whole characters in ratings
2023
1738
 * array provided by the caller.
2024
1739
 *
2025
1740
 * @param Blob blob to extract features from
2026
 
 * @param denorm normalization/denormalization parameters
2027
1741
 * @param Templates used to compute char norm adjustments
2028
1742
 * @param IntFeatures array to fill with integer features
2029
1743
 * @param PrunerNormArray Array of factors from blob normalization
2030
1744
 *        process
2031
1745
 * @param CharNormArray array to fill with dummy char norm adjustments
2032
1746
 * @param BlobLength length of blob in baseline-normalized units
2033
 
 * @param FeatureOutlineArray
2034
1747
 *
2035
1748
 * Globals:
2036
 
 * - FeaturesHaveBeenExtracted TRUE if fx has been done
2037
 
 * - BaselineFeatures holds extracted baseline feat
2038
 
 * - CharNormFeatures holds extracted char norm feat
2039
 
 * - FXInfo holds misc. FX info
2040
1749
 *
2041
1750
 * @return Number of features extracted or 0 if an error occured.
2042
1751
 * @note Exceptions: none
2043
1752
 * @note History: Tue May 28 10:40:52 1991, DSJ, Created.
2044
1753
 */
2045
 
int Classify::GetCharNormFeatures(TBLOB *Blob,
2046
 
                                  const DENORM& denorm,
2047
 
                                  INT_TEMPLATES Templates,
2048
 
                                  INT_FEATURE_ARRAY IntFeatures,
2049
 
                                  uinT8* PrunerNormArray,
2050
 
                                  uinT8* CharNormArray,
2051
 
                                  inT32 *BlobLength,
2052
 
                                  inT32 *FeatureOutlineArray) {
2053
 
  register INT_FEATURE Src, Dest, End;
2054
 
  FEATURE NormFeature;
2055
 
  FLOAT32 Baseline, Scale;
2056
 
  inT32 FeatureOutlineIndex[MAX_NUM_INT_FEATURES];
2057
 
 
2058
 
  if (!FeaturesHaveBeenExtracted) {
2059
 
    FeaturesOK = ExtractIntFeat(Blob, denorm, BaselineFeatures,
2060
 
                                CharNormFeatures, &FXInfo,
2061
 
                                FeatureOutlineIndex);
2062
 
    FeaturesHaveBeenExtracted = TRUE;
2063
 
  }
2064
 
 
2065
 
  if (!FeaturesOK) {
2066
 
    *BlobLength = FXInfo.NumBL;
2067
 
    return (0);
2068
 
  }
2069
 
 
2070
 
  for (Src = CharNormFeatures, End = Src + FXInfo.NumCN, Dest = IntFeatures;
2071
 
       Src < End;
2072
 
       *Dest++ = *Src++);
2073
 
  for (int i = 0;  FeatureOutlineArray && i < FXInfo.NumCN; ++i) {
2074
 
    FeatureOutlineArray[i] = FeatureOutlineIndex[i];
2075
 
  }
2076
 
 
2077
 
  NormFeature = NewFeature(&CharNormDesc);
2078
 
  Baseline = BASELINE_OFFSET;
2079
 
  Scale = MF_SCALE_FACTOR;
2080
 
  NormFeature->Params[CharNormY] = (FXInfo.Ymean - Baseline) * Scale;
2081
 
  NormFeature->Params[CharNormLength] =
2082
 
    FXInfo.Length * Scale / LENGTH_COMPRESSION;
2083
 
  NormFeature->Params[CharNormRx] = FXInfo.Rx * Scale;
2084
 
  NormFeature->Params[CharNormRy] = FXInfo.Ry * Scale;
2085
 
  ComputeCharNormArrays(NormFeature, Templates, CharNormArray, PrunerNormArray);
2086
 
  *BlobLength = FXInfo.NumBL;
2087
 
  return (FXInfo.NumCN);
2088
 
}                              /* GetCharNormFeatures */
 
1754
int Classify::GetCharNormFeature(const INT_FX_RESULT_STRUCT& fx_info,
 
1755
                                 INT_TEMPLATES templates,
 
1756
                                 uinT8* pruner_norm_array,
 
1757
                                 uinT8* char_norm_array) {
 
1758
  FEATURE norm_feature = NewFeature(&CharNormDesc);
 
1759
  float baseline = kBlnBaselineOffset;
 
1760
  float scale = MF_SCALE_FACTOR;
 
1761
  norm_feature->Params[CharNormY] = (fx_info.Ymean - baseline) * scale;
 
1762
  norm_feature->Params[CharNormLength] =
 
1763
      fx_info.Length * scale / LENGTH_COMPRESSION;
 
1764
  norm_feature->Params[CharNormRx] = fx_info.Rx * scale;
 
1765
  norm_feature->Params[CharNormRy] = fx_info.Ry * scale;
 
1766
  // Deletes norm_feature.
 
1767
  ComputeCharNormArrays(norm_feature, templates, char_norm_array,
 
1768
                        pruner_norm_array);
 
1769
  return IntCastRounded(fx_info.Length / kStandardFeatureLength);
 
1770
}                              /* GetCharNormFeature */
2089
1771
 
2090
1772
// Computes the char_norm_array for the unicharset and, if not NULL, the
2091
1773
// pruner_array as appropriate according to the existence of the shape_table.
2312
1994
 * @param Templates current set of adaptive templates
2313
1995
 * @param ClassId class containing config to be made permanent
2314
1996
 * @param ConfigId config to be made permanent
2315
 
 * @param denorm normalization/denormalization parameters
2316
1997
 * @param Blob current blob being adapted to
2317
1998
 *
2318
1999
 * Globals: none
2323
2004
void Classify::MakePermanent(ADAPT_TEMPLATES Templates,
2324
2005
                             CLASS_ID ClassId,
2325
2006
                             int ConfigId,
2326
 
                             const DENORM& denorm,
2327
2007
                             TBLOB *Blob) {
2328
2008
  UNICHAR_ID *Ambigs;
2329
2009
  TEMP_CONFIG Config;
2339
2019
  Class->NumPermConfigs++;
2340
2020
 
2341
2021
  // Initialize permanent config.
2342
 
  Ambigs = GetAmbiguities(Blob, denorm, ClassId);
 
2022
  Ambigs = GetAmbiguities(Blob, ClassId);
2343
2023
  PERM_CONFIG Perm = (PERM_CONFIG) alloc_struct(sizeof(PERM_CONFIG_STRUCT),
2344
2024
                                                "PERM_CONFIG_STRUCT");
2345
2025
  Perm->Ambigs = Ambigs;
2422
2102
 * @note History: Mon Mar 18 09:24:53 1991, DSJ, Created.
2423
2103
 */
2424
2104
void Classify::PrintAdaptiveMatchResults(FILE *File, ADAPT_RESULTS *Results) {
2425
 
  for (int i = 0; i < Results->NumMatches; ++i) {
 
2105
  for (int i = 0; i < Results->match.size(); ++i) {
2426
2106
    tprintf("%s(%d), shape %d, %.2f  ",
2427
2107
            unicharset.debug_str(Results->match[i].unichar_id).string(),
2428
2108
            Results->match[i].unichar_id, Results->match[i].shape_id,
2461
2141
    ScoredClass scored_one = ScoredUnichar(Results, unichar_id_one);
2462
2142
    ScoredClass scored_zero = ScoredUnichar(Results, unichar_id_zero);
2463
2143
 
2464
 
    for (Next = NextGood = 0; Next < Results->NumMatches; Next++) {
 
2144
    for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2465
2145
      if (Results->match[Next].rating <= BadMatchThreshold) {
2466
2146
        ScoredClass match = Results->match[Next];
2467
2147
        if (!unicharset.get_isalpha(match.unichar_id) ||
2482
2162
      }
2483
2163
    }
2484
2164
  } else {
2485
 
    for (Next = NextGood = 0; Next < Results->NumMatches; Next++) {
 
2165
    for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2486
2166
      if (Results->match[Next].rating <= BadMatchThreshold)
2487
2167
        Results->match[NextGood++] = Results->match[Next];
2488
2168
    }
2489
2169
  }
2490
 
  Results->NumMatches = NextGood;
 
2170
  Results->match.truncate(NextGood);
2491
2171
}                              /* RemoveBadMatches */
2492
2172
 
2493
2173
/*----------------------------------------------------------------------------*/
2510
2190
 
2511
2191
  punc_count = 0;
2512
2192
  digit_count = 0;
2513
 
  for (Next = NextGood = 0; Next < Results->NumMatches; Next++) {
 
2193
  for (Next = NextGood = 0; Next < Results->match.size(); Next++) {
2514
2194
    ScoredClass match = Results->match[Next];
2515
2195
    if (strstr(punc_chars,
2516
2196
               unicharset.id_to_unichar(match.unichar_id)) != NULL) {
2528
2208
      }
2529
2209
    }
2530
2210
  }
2531
 
  Results->NumMatches = NextGood;
 
2211
  Results->match.truncate(NextGood);
2532
2212
}                              /* RemoveExtraPuncs */
2533
2213
 
2534
2214
/*---------------------------------------------------------------------------*/
2555
2235
 
2556
2236
/*---------------------------------------------------------------------------*/
2557
2237
/**
2558
 
 * This routine compares Blob to both sets of templates
2559
 
 * (adaptive and pre-trained) and then displays debug
2560
 
 * information for the config which matched best.
2561
 
 *
2562
 
 * @param Blob blob to show best matching config for
2563
 
 * @param denorm normalization/denormalization parameters
2564
 
 * @param ClassId class whose configs are to be searched
2565
 
 * @param shape_id shape index
2566
 
 * @param AdaptiveOn TRUE if adaptive configs are enabled
2567
 
 * @param PreTrainedOn TRUE if pretrained configs are enabled
2568
 
 * @param Results results of match being debugged
2569
 
 *
2570
 
 * Globals:
2571
 
 * - PreTrainedTemplates built-in training
2572
 
 * - AdaptedTemplates adaptive templates
2573
 
 * - AllProtosOn dummy proto mask
2574
 
 * - AllConfigsOn dummy config mask
 
2238
 * This routine displays debug information for the best config
 
2239
 * of the given shape_id for the given set of features.
 
2240
 *
 
2241
 * @param shape_id classifier id to work with
 
2242
 * @param features features of the unknown character
 
2243
 * @param num_features Number of features in the features array.
2575
2244
 *
2576
2245
 * @note Exceptions: none
2577
2246
 * @note History: Fri Mar 22 08:43:52 1991, DSJ, Created.
2578
2247
 */
2579
 
void Classify::ShowBestMatchFor(TBLOB *Blob,
2580
 
                                const DENORM& denorm,
2581
 
                                CLASS_ID ClassId,
2582
 
                                int shape_id,
2583
 
                                BOOL8 AdaptiveOn,
2584
 
                                BOOL8 PreTrainedOn,
2585
 
                                ADAPT_RESULTS *Results) {
2586
 
  int NumCNFeatures = 0, NumBLFeatures = 0;
2587
 
  INT_FEATURE_ARRAY CNFeatures, BLFeatures;
2588
 
  INT_RESULT_STRUCT CNResult, BLResult;
2589
 
  inT32 BlobLength;
2590
 
  uinT32 ConfigMask;
2591
 
  static int next_config = -1;
2592
 
 
2593
 
  if (PreTrainedOn) next_config = -1;
2594
 
 
2595
 
  CNResult.Rating = BLResult.Rating = 2.0;
2596
 
 
2597
 
  if (!LegalClassId (ClassId)) {
2598
 
    cprintf ("%d is not a legal class id!!\n", ClassId);
2599
 
    return;
2600
 
  }
2601
 
 
2602
 
  uinT8 *CNAdjust = new uinT8[MAX_NUM_CLASSES];
2603
 
  uinT8 *BLAdjust = new uinT8[MAX_NUM_CLASSES];
2604
 
 
2605
 
  if (shape_table_ == NULL)
2606
 
    shape_id = ClassId;
2607
 
  else
2608
 
    shape_id = ShapeIDToClassID(shape_id);
2609
 
  if (PreTrainedOn && shape_id >= 0) {
2610
 
    if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) {
2611
 
      tprintf("No built-in templates for class/shape %d\n", shape_id);
2612
 
    } else {
2613
 
      NumCNFeatures = GetCharNormFeatures(Blob, denorm, PreTrainedTemplates,
2614
 
                                          CNFeatures, NULL, CNAdjust,
2615
 
                                          &BlobLength, NULL);
2616
 
      if (NumCNFeatures <= 0) {
2617
 
        tprintf("Illegal blob (char norm features)!\n");
2618
 
      } else {
2619
 
        im_.SetCharNormMatch(classify_integer_matcher_multiplier);
2620
 
        im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
2621
 
                  AllProtosOn, AllConfigsOn,
2622
 
                  NumCNFeatures, CNFeatures,
2623
 
                  &CNResult,
2624
 
                  classify_adapt_feature_threshold, NO_DEBUG,
2625
 
                  matcher_debug_separate_windows);
2626
 
        ExpandShapesAndApplyCorrections(NULL, false, shape_id,
2627
 
                                        Blob->bounding_box().bottom(),
2628
 
                                        Blob->bounding_box().top(),
2629
 
                                        0, BlobLength, CNAdjust,
2630
 
                                        CNResult, Results);
2631
 
      }
2632
 
    }
2633
 
  }
2634
 
 
2635
 
  if (AdaptiveOn) {
2636
 
    if (ClassId < 0 || ClassId >= AdaptedTemplates->Templates->NumClasses) {
2637
 
      tprintf("Invalid adapted class id: %d\n", ClassId);
2638
 
    } else if (UnusedClassIdIn(AdaptedTemplates->Templates, ClassId) ||
2639
 
               AdaptedTemplates->Class[ClassId] == NULL ||
2640
 
               IsEmptyAdaptedClass(AdaptedTemplates->Class[ClassId])) {
2641
 
      tprintf("No AD templates for class %d = %s\n",
2642
 
              ClassId, unicharset.id_to_unichar(ClassId));
2643
 
    } else {
2644
 
      NumBLFeatures = GetBaselineFeatures(Blob,
2645
 
                                          denorm,
2646
 
                                          AdaptedTemplates->Templates,
2647
 
                                          BLFeatures, BLAdjust,
2648
 
                                          &BlobLength);
2649
 
      if (NumBLFeatures <= 0)
2650
 
        tprintf("Illegal blob (baseline features)!\n");
2651
 
      else {
2652
 
        im_.SetBaseLineMatch();
2653
 
        im_.Match(ClassForClassId(AdaptedTemplates->Templates, ClassId),
2654
 
                  AllProtosOn, AllConfigsOn,
2655
 
                  NumBLFeatures, BLFeatures,
2656
 
                  &BLResult,
2657
 
                  classify_adapt_feature_threshold, NO_DEBUG,
2658
 
                  matcher_debug_separate_windows);
2659
 
        ExpandShapesAndApplyCorrections(
2660
 
            AdaptedTemplates->Class, false,
2661
 
            ClassId, Blob->bounding_box().bottom(),
2662
 
            Blob->bounding_box().top(), 0, BlobLength, CNAdjust,
2663
 
            BLResult, Results);
2664
 
      }
2665
 
    }
2666
 
  }
2667
 
 
 
2248
 
 
2249
void Classify::ShowBestMatchFor(int shape_id,
 
2250
                                const INT_FEATURE_STRUCT* features,
 
2251
                                int num_features) {
 
2252
#ifndef GRAPHICS_DISABLED
 
2253
  uinT32 config_mask;
 
2254
  if (UnusedClassIdIn(PreTrainedTemplates, shape_id)) {
 
2255
    tprintf("No built-in templates for class/shape %d\n", shape_id);
 
2256
    return;
 
2257
  }
 
2258
  if (num_features <= 0) {
 
2259
    tprintf("Illegal blob (char norm features)!\n");
 
2260
    return;
 
2261
  }
 
2262
  INT_RESULT_STRUCT cn_result;
 
2263
  classify_norm_method.set_value(character);
 
2264
  im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
 
2265
            AllProtosOn, AllConfigsOn,
 
2266
            num_features, features, &cn_result,
 
2267
            classify_adapt_feature_threshold, NO_DEBUG,
 
2268
            matcher_debug_separate_windows);
2668
2269
  tprintf("\n");
2669
 
  if (BLResult.Rating < CNResult.Rating) {
2670
 
    if (next_config < 0) {
2671
 
      ConfigMask = 1 << BLResult.Config;
2672
 
      next_config = 0;
2673
 
    } else {
2674
 
      ConfigMask = 1 << next_config;
2675
 
      ++next_config;
2676
 
    }
2677
 
    classify_norm_method.set_value(baseline);
2678
 
 
2679
 
    im_.SetBaseLineMatch();
2680
 
    tprintf("Adaptive Class ID: %d\n", ClassId);
2681
 
    im_.Match(ClassForClassId(AdaptedTemplates->Templates, ClassId),
2682
 
              AllProtosOn, (BIT_VECTOR) &ConfigMask,
2683
 
              NumBLFeatures, BLFeatures,
2684
 
              &BLResult,
2685
 
              classify_adapt_feature_threshold,
2686
 
              matcher_debug_flags,
2687
 
              matcher_debug_separate_windows);
2688
 
    ExpandShapesAndApplyCorrections(
2689
 
        AdaptedTemplates->Class, true,
2690
 
        ClassId, Blob->bounding_box().bottom(),
2691
 
        Blob->bounding_box().top(), 0, BlobLength, CNAdjust,
2692
 
        BLResult, Results);
2693
 
  } else if (shape_id >= 0) {
2694
 
    ConfigMask = 1 << CNResult.Config;
2695
 
    classify_norm_method.set_value(character);
2696
 
 
2697
 
    tprintf("Static Shape ID: %d\n", shape_id);
2698
 
    im_.SetCharNormMatch(classify_integer_matcher_multiplier);
2699
 
    im_.Match(ClassForClassId (PreTrainedTemplates, shape_id),
2700
 
              AllProtosOn, (BIT_VECTOR) & ConfigMask,
2701
 
              NumCNFeatures, CNFeatures,
2702
 
              &CNResult,
2703
 
              classify_adapt_feature_threshold,
2704
 
              matcher_debug_flags,
2705
 
              matcher_debug_separate_windows);
2706
 
    ExpandShapesAndApplyCorrections(NULL, true, shape_id,
2707
 
                                    Blob->bounding_box().bottom(),
2708
 
                                    Blob->bounding_box().top(),
2709
 
                                    0, BlobLength, CNAdjust,
2710
 
                                    CNResult, Results);
2711
 
  }
2712
 
 
2713
 
  // Clean up.
2714
 
  delete[] CNAdjust;
2715
 
  delete[] BLAdjust;
 
2270
  config_mask = 1 << cn_result.Config;
 
2271
 
 
2272
  tprintf("Static Shape ID: %d\n", shape_id);
 
2273
  ShowMatchDisplay();
 
2274
  im_.Match(ClassForClassId(PreTrainedTemplates, shape_id),
 
2275
            AllProtosOn, reinterpret_cast<BIT_VECTOR>(&config_mask),
 
2276
            num_features, features, &cn_result,
 
2277
            classify_adapt_feature_threshold,
 
2278
            matcher_debug_flags,
 
2279
            matcher_debug_separate_windows);
 
2280
  UpdateMatchDisplay();
 
2281
#endif  // GRAPHICS_DISABLED
2716
2282
}                              /* ShowBestMatchFor */
2717
2283
 
2718
2284
// Returns a string for the classifier class_id: either the corresponding
2796
2362
  return true;
2797
2363
}
2798
2364
 
2799
 
void Classify::UpdateAmbigsGroup(CLASS_ID class_id, const DENORM& denorm,
2800
 
                                 TBLOB *Blob) {
 
2365
void Classify::UpdateAmbigsGroup(CLASS_ID class_id, TBLOB *Blob) {
2801
2366
  const UnicharIdVector *ambigs =
2802
2367
    getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id);
2803
2368
  int ambigs_size = (ambigs == NULL) ? 0 : ambigs->size();
2818
2383
                  getDict().getUnicharset().debug_str(
2819
2384
                      ambig_class_id).string());
2820
2385
        }
2821
 
        MakePermanent(AdaptedTemplates, ambig_class_id, cfg, denorm, Blob);
 
2386
        MakePermanent(AdaptedTemplates, ambig_class_id, cfg, Blob);
2822
2387
      }
2823
2388
    }
2824
2389
  }