Tesseract 3.02

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

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