22 #include "config_auto.h" 75 #define ADAPT_TEMPLATE_SUFFIX ".a" 77 #define MAX_MATCHES 10 78 #define UNLIKELY_NUM_FEAT 200 80 #define MAX_ADAPTABLE_WERD_SIZE 40 82 #define ADAPTABLE_WERD_ADJUSTMENT (0.05) 84 #define Y_DIM_OFFSET (Y_SHIFT - BASELINE_Y_SHIFT) 86 #define WORST_POSSIBLE_RATING (0.0f) 103 BlobLength = INT32_MAX;
104 HasNonfragment =
false;
109 best_unichar_id = INVALID_UNICHAR_ID;
110 best_match_index = -1;
112 for (
int i = 0; i < match.
size(); ++i) {
113 if (match[i].rating > best_rating) {
114 best_rating = match[i].rating;
115 best_unichar_id = match[i].unichar_id;
116 best_match_index = i;
131 inline bool MarginalMatch(
float confidence,
float matcher_great_threshold) {
132 return (1.0f - confidence) > matcher_great_threshold;
141 for (
int i = 0; i < results.
match.
size(); i++) {
142 if (results.
match[i].unichar_id ==
id)
151 int index = FindScoredUnichar(
id, results);
153 return results.
match[index].rating;
191 void Classify::AdaptiveClassifier(
TBLOB *Blob, BLOB_CHOICE_LIST *Choices) {
192 assert(Choices !=
nullptr);
198 DoAdaptiveMatch(Blob, Results);
200 RemoveBadMatches(Results);
201 Results->match.sort(&UnicharRating::SortDescendingRating);
202 RemoveExtraPuncs(Results);
203 Results->ComputeBest();
208 if (LargeSpeckle(*Blob) || Choices->length() == 0)
209 AddLargeSpeckleTo(Results->BlobLength, Choices);
211 if (matcher_debug_level >= 1) {
213 PrintAdaptiveMatchResults(*Results);
216 #ifndef GRAPHICS_DISABLED 217 if (classify_enable_adaptive_debugger)
218 DebugAdaptiveClassifier(Blob, Results);
226 void Classify::RefreshDebugWindow(
ScrollView **win,
const char *msg,
227 int y_offset,
const TBOX &wbox) {
228 #ifndef GRAPHICS_DISABLED 229 const int kSampleSpaceWidth = 500;
230 if (*win ==
nullptr) {
231 *win =
new ScrollView(msg, 100, y_offset, kSampleSpaceWidth * 2, 200,
232 kSampleSpaceWidth * 2, 200,
true);
235 (*win)->Pen(64, 64, 64);
240 (*win)->ZoomToRectangle(wbox.
left(), wbox.
top(),
242 #endif // GRAPHICS_DISABLED 250 void Classify::LearnWord(
const char* fontname,
WERD_RES* word) {
252 if (word_len == 0)
return;
254 float* thresholds =
nullptr;
255 if (fontname ==
nullptr) {
257 if (!EnableLearning || word->
best_choice ==
nullptr)
260 if (classify_learning_debug_level >= 1)
261 tprintf(
"\n\nAdapting to word = %s\n",
263 thresholds =
new float[word_len];
265 matcher_perfect_threshold,
266 matcher_good_threshold,
267 matcher_rating_margin, thresholds);
271 #ifndef GRAPHICS_DISABLED 272 if (classify_debug_character_fragments) {
273 if (learn_fragmented_word_debug_win_ !=
nullptr) {
276 RefreshDebugWindow(&learn_fragments_debug_win_,
"LearnPieces", 400,
278 RefreshDebugWindow(&learn_fragmented_word_debug_win_,
"LearnWord", 200,
283 #endif // GRAPHICS_DISABLED 285 for (
int ch = 0; ch < word_len; ++ch) {
286 if (classify_debug_character_fragments) {
290 float threshold = thresholds !=
nullptr ? thresholds[ch] : 0.0f;
292 LearnPieces(fontname, start_blob, word->
best_state[ch], threshold,
299 bool garbage =
false;
301 for (frag = 0; frag < word->
best_state[ch]; ++frag) {
303 if (classify_character_fragments_garbage_certainty_threshold < 0) {
304 garbage |= LooksLikeGarbage(frag_blob);
311 if (pieces_all_natural || !prioritize_division) {
312 for (frag = 0; frag < word->
best_state[ch]; ++frag) {
317 tokens[0].
string(), frag, word->
best_state[ch],
321 for (
int i = 0; i < tokens.
size(); i++) {
322 full_string += tokens[i];
323 if (i != tokens.
size() - 1)
326 LearnPieces(fontname, start_blob + frag, 1, threshold,
362 delete [] thresholds;
374 void Classify::LearnPieces(
const char* fontname,
int start,
int length,
376 const char* correct_text,
WERD_RES* word) {
390 if (rotated_blob ==
nullptr)
393 #ifndef GRAPHICS_DISABLED 395 if (strcmp(classify_learn_debug_str.string(), correct_text) == 0) {
396 RefreshDebugWindow(&learn_debug_win_,
"LearnPieces", 600,
399 learn_debug_win_->Update();
402 if (classify_debug_character_fragments && segmentation ==
CST_FRAGMENT) {
403 ASSERT_HOST(learn_fragments_debug_win_ !=
nullptr);
404 blob->
plot(learn_fragments_debug_win_,
406 learn_fragments_debug_win_->Update();
408 #endif // GRAPHICS_DISABLED 410 if (fontname !=
nullptr) {
411 classify_norm_method.set_value(
character);
412 tess_bn_matching.set_value(
false);
413 tess_cn_matching.set_value(
false);
414 DENORM bl_denorm, cn_denorm;
416 SetupBLCNDenorms(*rotated_blob, classify_nonlinear_norm,
417 &bl_denorm, &cn_denorm, &fx_info);
418 LearnBlob(fontname, rotated_blob, cn_denorm, fx_info, correct_text);
419 }
else if (unicharset.contains_unichar(correct_text)) {
420 UNICHAR_ID class_id = unicharset.unichar_to_id(correct_text);
421 int font_id = word->
fontinfo !=
nullptr 422 ? fontinfo_table_.get_id(*word->
fontinfo)
424 if (classify_learning_debug_level >= 1)
425 tprintf(
"Adapting to char = %s, thr= %g font_id= %d\n",
426 unicharset.id_to_unichar(class_id), threshold, font_id);
429 AdaptToChar(rotated_blob, class_id, font_id, threshold, AdaptedTemplates);
430 if (BackupAdaptedTemplates !=
nullptr) {
433 AdaptToChar(rotated_blob, class_id, font_id, threshold,
434 BackupAdaptedTemplates);
436 }
else if (classify_debug_level >= 1) {
437 tprintf(
"Can't adapt to %s not in unicharset\n", correct_text);
439 if (rotated_blob != blob) {
459 void Classify::EndAdaptiveClassifier() {
463 if (AdaptedTemplates !=
nullptr &&
464 classify_enable_adaptive_matcher && classify_save_adapted_templates) {
466 File = fopen (Filename.
string(),
"wb");
468 cprintf (
"Unable to save adapted templates to %s!\n", Filename.
string());
470 cprintf (
"\nSaving adapted templates to %s ...", Filename.
string());
472 WriteAdaptedTemplates(File, AdaptedTemplates);
478 if (AdaptedTemplates !=
nullptr) {
480 AdaptedTemplates =
nullptr;
482 if (BackupAdaptedTemplates !=
nullptr) {
484 BackupAdaptedTemplates =
nullptr;
487 if (PreTrainedTemplates !=
nullptr) {
489 PreTrainedTemplates =
nullptr;
491 getDict().EndDangerousAmbigs();
493 if (AllProtosOn !=
nullptr) {
498 AllProtosOn =
nullptr;
499 AllConfigsOn =
nullptr;
500 AllConfigsOff =
nullptr;
501 TempProtoMask =
nullptr;
504 shape_table_ =
nullptr;
505 delete static_classifier_;
506 static_classifier_ =
nullptr;
528 if (!classify_enable_adaptive_matcher)
530 if (AllProtosOn !=
nullptr)
531 EndAdaptiveClassifier();
535 if (language_data_path_prefix.length() > 0 && mgr !=
nullptr) {
538 PreTrainedTemplates = ReadIntTemplates(&fp);
542 if (!shape_table_->DeSerialize(&fp)) {
543 tprintf(
"Error loading shape table!\n");
545 shape_table_ =
nullptr;
550 ReadNewCutoffs(&fp, CharNormCutoffs);
553 NormProtos = ReadNormProtos(&fp);
567 for (uint16_t& BaselineCutoff : BaselineCutoffs) {
571 if (classify_use_pre_adapted_templates) {
575 Filename = imagefile;
578 AdaptedTemplates = NewAdaptedTemplates(
true);
580 cprintf(
"\nReading pre-adapted templates from %s ...\n",
583 AdaptedTemplates = ReadAdaptedTemplates(&fp);
585 PrintAdaptedTemplates(stdout, AdaptedTemplates);
587 for (
int i = 0; i < AdaptedTemplates->Templates->NumClasses; i++) {
588 BaselineCutoffs[i] = CharNormCutoffs[i];
592 if (AdaptedTemplates !=
nullptr)
594 AdaptedTemplates = NewAdaptedTemplates(
true);
598 void Classify::ResetAdaptiveClassifierInternal() {
599 if (classify_learning_debug_level > 0) {
600 tprintf(
"Resetting adaptive classifier (NumAdaptationsFailed=%d)\n",
601 NumAdaptationsFailed);
604 AdaptedTemplates = NewAdaptedTemplates(
true);
605 if (BackupAdaptedTemplates !=
nullptr)
607 BackupAdaptedTemplates =
nullptr;
608 NumAdaptationsFailed = 0;
613 void Classify::SwitchAdaptiveClassifier() {
614 if (BackupAdaptedTemplates ==
nullptr) {
615 ResetAdaptiveClassifierInternal();
618 if (classify_learning_debug_level > 0) {
619 tprintf(
"Switch to backup adaptive classifier (NumAdaptationsFailed=%d)\n",
620 NumAdaptationsFailed);
623 AdaptedTemplates = BackupAdaptedTemplates;
624 BackupAdaptedTemplates =
nullptr;
625 NumAdaptationsFailed = 0;
629 void Classify::StartBackupAdaptiveClassifier() {
630 if (BackupAdaptedTemplates !=
nullptr)
632 BackupAdaptedTemplates = NewAdaptedTemplates(
true);
652 void Classify::SettupPass1() {
653 EnableLearning = classify_enable_learning;
655 getDict().SettupStopperPass1();
669 void Classify::SettupPass2() {
670 EnableLearning =
false;
671 getDict().SettupStopperPass2();
693 void Classify::InitAdaptedClass(
TBLOB *Blob,
707 classify_norm_method.set_value(
baseline);
708 Features = ExtractOutlineFeatures(Blob);
719 if (Templates == AdaptedTemplates)
720 BaselineCutoffs[ClassId] = CharNormCutoffs[ClassId];
724 for (Fid = 0; Fid < Features->
NumFeatures; Fid++) {
730 Proto = &(TempProto->
Proto);
744 ConvertProto(Proto, Pid, IClass);
746 classify_learning_debug_level >= 2);
755 if (classify_learning_debug_level >= 1) {
756 tprintf(
"Added new class '%s' with class id %d and %d protos.\n",
757 unicharset.id_to_unichar(ClassId), ClassId, NumFeatures);
758 if (classify_learning_debug_level > 1)
759 DisplayAdaptedChar(Blob, IClass);
786 int Classify::GetAdaptiveFeatures(
TBLOB *Blob,
792 classify_norm_method.set_value(
baseline);
793 Features = ExtractPicoFeatures(Blob);
801 ComputeIntFeatures(Features, IntFeatures);
802 *FloatFeatures = Features;
824 float adaptable_score =
827 BestChoiceLength > 0 &&
869 Class = adaptive_templates->
Class[ClassId];
870 assert(Class !=
nullptr);
872 InitAdaptedClass(Blob, ClassId, FontinfoId, Class, adaptive_templates);
876 NumFeatures = GetAdaptiveFeatures(Blob, IntFeatures, &FloatFeatures);
877 if (NumFeatures <= 0) {
883 for (
int cfg = 0; cfg < IClass->
NumConfigs; ++cfg) {
884 if (GetFontinfoId(Class, cfg) == FontinfoId) {
885 SET_BIT(MatchingFontConfigs, cfg);
890 im_.Match(IClass, AllProtosOn, MatchingFontConfigs,
891 NumFeatures, IntFeatures,
892 &int_result, classify_adapt_feature_threshold,
893 NO_DEBUG, matcher_debug_separate_windows);
898 if (1.0f - int_result.
rating <= Threshold) {
900 if (classify_learning_debug_level >= 1)
901 tprintf(
"Found good match to perm config %d = %4.1f%%.\n",
912 if (classify_learning_debug_level >= 1)
913 tprintf(
"Increasing reliability of temp config %d to %d.\n",
916 if (TempConfigReliable(ClassId, TempConfig)) {
917 MakePermanent(adaptive_templates, ClassId, int_result.
config, Blob);
918 UpdateAmbigsGroup(ClassId, Blob);
921 if (classify_learning_debug_level >= 1) {
922 tprintf(
"Found poor match to temp config %d = %4.1f%%.\n",
924 if (classify_learning_debug_level > 2)
925 DisplayAdaptedChar(Blob, IClass);
928 MakeNewTemporaryConfig(adaptive_templates, ClassId, FontinfoId,
929 NumFeatures, IntFeatures, FloatFeatures);
930 if (NewTempConfigId >= 0 &&
931 TempConfigReliable(ClassId,
TempConfigFor(Class, NewTempConfigId))) {
932 MakePermanent(adaptive_templates, ClassId, NewTempConfigId, Blob);
933 UpdateAmbigsGroup(ClassId, Blob);
936 #ifndef GRAPHICS_DISABLED 937 if (classify_learning_debug_level > 1) {
938 DisplayAdaptedChar(Blob, IClass);
947 #ifndef GRAPHICS_DISABLED 953 if (sample ==
nullptr)
return;
956 im_.Match(int_class, AllProtosOn, AllConfigsOn,
957 bl_features.
size(), &bl_features[0],
958 &int_result, classify_adapt_feature_threshold,
959 NO_DEBUG, matcher_debug_separate_windows);
960 tprintf(
"Best match to temp config %d = %4.1f%%.\n",
961 int_result.config, int_result.rating * 100.0);
962 if (classify_learning_debug_level >= 2) {
964 ConfigMask = 1 << int_result.config;
966 im_.Match(int_class, AllProtosOn, static_cast<BIT_VECTOR>(&ConfigMask),
967 bl_features.
size(), &bl_features[0],
968 &int_result, classify_adapt_feature_threshold,
969 6 | 0x19, matcher_debug_separate_windows);
996 int old_match = FindScoredUnichar(new_result.
unichar_id, *results);
1000 new_result.
rating <= results->
match[old_match].rating))
1003 if (!unicharset.get_fragment(new_result.
unichar_id))
1006 if (old_match < results->
match.
size()) {
1007 results->
match[old_match].rating = new_result.
rating;
1018 !unicharset.get_fragment(new_result.
unichar_id)) {
1045 void Classify::AmbigClassifier(
1053 if (int_features.
empty())
return;
1054 auto* CharNormArray =
new uint8_t[unicharset.size()];
1057 results->
BlobLength = GetCharNormFeature(fx_info, templates,
nullptr,
1059 bool debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1065 while (*ambiguities >= 0) {
1070 AllProtosOn, AllConfigsOn,
1071 int_features.
size(), &int_features[0],
1073 classify_adapt_feature_threshold,
NO_DEBUG,
1074 matcher_debug_separate_windows);
1076 ExpandShapesAndApplyCorrections(
nullptr, debug, class_id, bottom, top, 0,
1079 CharNormArray, &int_result, results);
1082 delete [] CharNormArray;
1089 int16_t num_features,
1091 const uint8_t* norm_factors,
1094 int matcher_multiplier,
1095 const TBOX& blob_box,
1098 int top = blob_box.
top();
1099 int bottom = blob_box.
bottom();
1101 for (
int c = 0; c < results.
size(); c++) {
1102 CLASS_ID class_id = results[c].Class;
1111 num_features, features,
1112 &int_result, classify_adapt_feature_threshold, debug,
1113 matcher_debug_separate_windows);
1114 bool is_debug = matcher_debug_level >= 2 || classify_debug_level > 1;
1115 ExpandShapesAndApplyCorrections(classes, is_debug, class_id, bottom, top,
1118 matcher_multiplier, norm_factors,
1119 &int_result, final_results);
1128 void Classify::ExpandShapesAndApplyCorrections(
1129 ADAPT_CLASS* classes,
bool debug,
int class_id,
int bottom,
int top,
1130 float cp_rating,
int blob_length,
int matcher_multiplier,
1131 const uint8_t* cn_factors,
1133 if (classes !=
nullptr) {
1136 for (
int f = 0; f < int_result->
fonts.size(); ++f) {
1137 int_result->
fonts[f].fontinfo_id =
1138 GetFontinfoId(classes[class_id], int_result->
fonts[f].fontinfo_id);
1143 for (
int f = 0; f < int_result->
fonts.size(); ++f) {
1144 int_result->
fonts[f].fontinfo_id =
1145 ClassAndConfigIDToFontOrShapeID(class_id,
1146 int_result->
fonts[f].fontinfo_id);
1148 if (shape_table_ !=
nullptr) {
1157 for (
int f = 0; f < int_result->
fonts.size(); ++f) {
1158 int shape_id = int_result->
fonts[f].fontinfo_id;
1159 const Shape& shape = shape_table_->GetShape(shape_id);
1160 for (
int c = 0; c < shape.
size(); ++c) {
1161 int unichar_id = shape[c].unichar_id;
1162 if (!unicharset.get_enabled(unichar_id))
continue;
1165 for (r = 0; r < mapped_results.
size() &&
1166 mapped_results[r].unichar_id != unichar_id; ++r) {}
1167 if (r == mapped_results.
size()) {
1169 mapped_results[r].unichar_id = unichar_id;
1170 mapped_results[r].fonts.
truncate(0);
1172 for (
int i = 0; i < shape[c].font_ids.
size(); ++i) {
1178 for (
int m = 0; m < mapped_results.
size(); ++m) {
1179 mapped_results[m].rating =
1180 ComputeCorrectedRating(debug, mapped_results[m].unichar_id,
1181 cp_rating, int_result->
rating,
1183 blob_length, matcher_multiplier, cn_factors);
1184 AddNewResult(mapped_results[m], final_results);
1189 if (unicharset.get_enabled(class_id)) {
1190 int_result->
rating = ComputeCorrectedRating(debug, class_id, cp_rating,
1193 bottom, top, blob_length,
1194 matcher_multiplier, cn_factors);
1195 AddNewResult(*int_result, final_results);
1202 double Classify::ComputeCorrectedRating(
bool debug,
int unichar_id,
1203 double cp_rating,
double im_rating,
1205 int bottom,
int top,
1206 int blob_length,
int matcher_multiplier,
1207 const uint8_t* cn_factors) {
1209 double cn_corrected = im_.ApplyCNCorrection(1.0 - im_rating, blob_length,
1210 cn_factors[unichar_id],
1211 matcher_multiplier);
1212 double miss_penalty = tessedit_class_miss_scale * feature_misses;
1213 double vertical_penalty = 0.0;
1215 if (!unicharset.get_isalpha(unichar_id) &&
1216 !unicharset.get_isdigit(unichar_id) &&
1217 cn_factors[unichar_id] != 0 && classify_misfit_junk_penalty > 0.0) {
1218 int min_bottom, max_bottom, min_top, max_top;
1219 unicharset.get_top_bottom(unichar_id, &min_bottom, &max_bottom,
1220 &min_top, &max_top);
1222 tprintf(
"top=%d, vs [%d, %d], bottom=%d, vs [%d, %d]\n",
1223 top, min_top, max_top, bottom, min_bottom, max_bottom);
1225 if (top < min_top || top > max_top ||
1226 bottom < min_bottom || bottom > max_bottom) {
1227 vertical_penalty = classify_misfit_junk_penalty;
1230 double result = 1.0 - (cn_corrected + miss_penalty + vertical_penalty);
1234 tprintf(
"%s: %2.1f%%(CP%2.1f, IM%2.1f + CN%.2f(%d) + MP%2.1f + VP%2.1f)\n",
1235 unicharset.id_to_unichar(unichar_id),
1238 (1.0 - im_rating) * 100.0,
1239 (cn_corrected - (1.0 - im_rating)) * 100.0,
1240 cn_factors[unichar_id],
1241 miss_penalty * 100.0,
1242 vertical_penalty * 100.0);
1269 if (int_features.
empty())
return nullptr;
1270 auto* CharNormArray =
new uint8_t[unicharset.size()];
1271 ClearCharNormArray(CharNormArray);
1274 PruneClasses(Templates->
Templates, int_features.
size(), -1, &int_features[0],
1275 CharNormArray, BaselineCutoffs, &Results->
CPResults);
1277 if (matcher_debug_level >= 2 || classify_debug_level > 1)
1280 MasterMatcher(Templates->
Templates, int_features.
size(), &int_features[0],
1282 Templates->
Class, matcher_debug_flags, 0,
1285 delete [] CharNormArray;
1290 return Templates->
Class[ClassId]->
1311 int Classify::CharNormClassifier(
TBLOB *blob,
1318 static_classifier_->UnicharClassifySample(sample, blob->
denorm().
pix(), 0,
1319 -1, &unichar_results);
1321 for (
int r = 0; r < unichar_results.size(); ++r) {
1322 AddNewResult(unichar_results[r], adapt_results);
1329 int Classify::CharNormTrainingSample(
bool pruner_only,
1335 adapt_results->Initialize();
1344 auto* char_norm_array =
new uint8_t[unicharset.size()];
1345 int num_pruner_classes = std::max(unicharset.size(),
1346 PreTrainedTemplates->NumClasses);
1347 auto* pruner_norm_array =
new uint8_t[num_pruner_classes];
1348 adapt_results->BlobLength =
1350 ComputeCharNormArrays(norm_feature, PreTrainedTemplates, char_norm_array,
1353 PruneClasses(PreTrainedTemplates, num_features, keep_this, sample.
features(),
1355 shape_table_ !=
nullptr ? &shapetable_cutoffs_[0] : CharNormCutoffs,
1356 &adapt_results->CPResults);
1357 delete [] pruner_norm_array;
1358 if (keep_this >= 0) {
1359 adapt_results->CPResults[0].Class = keep_this;
1360 adapt_results->CPResults.truncate(1);
1364 for (
int i = 0; i < adapt_results->CPResults.size(); ++i) {
1365 int class_id = adapt_results->CPResults[i].Class;
1367 UnicharRating(class_id, 1.0f - adapt_results->CPResults[i].Rating));
1370 MasterMatcher(PreTrainedTemplates, num_features, sample.
features(),
1372 nullptr, matcher_debug_flags,
1374 blob_box, adapt_results->CPResults, adapt_results);
1376 for (
int i = 0; i < adapt_results->match.size(); i++) {
1377 results->
push_back(adapt_results->match[i]);
1379 results->
sort(&UnicharRating::SortDescendingRating);
1381 delete [] char_norm_array;
1382 delete adapt_results;
1383 return num_features;
1400 float rating = results->
BlobLength / matcher_avg_noise_size;
1402 rating /= 1.0 + rating;
1413 void Classify::ConvertMatchesToChoices(
const DENORM& denorm,
const TBOX& box,
1415 BLOB_CHOICE_LIST *Choices) {
1416 assert(Choices !=
nullptr);
1419 BLOB_CHOICE_IT temp_it;
1420 bool contains_nonfrag =
false;
1421 temp_it.set_to_list(Choices);
1422 int choices_length = 0;
1429 if (shape_table_ !=
nullptr) {
1430 max_matches = shape_table_->MaxNumUnichars() * 2;
1435 float best_certainty = -FLT_MAX;
1436 for (
int i = 0; i < Results->
match.
size(); i++) {
1438 bool adapted = result.
adapted;
1439 bool current_is_frag = (unicharset.get_fragment(result.
unichar_id) !=
nullptr);
1440 if (temp_it.length()+1 == max_matches &&
1441 !contains_nonfrag && current_is_frag) {
1453 Rating = Certainty = (1.0f - result.
rating);
1454 Rating *= rating_scale * Results->
BlobLength;
1455 Certainty *= -(getDict().certainty_scale);
1462 if (Certainty > best_certainty) {
1463 best_certainty = std::min(Certainty, static_cast<float>(classify_adapted_pruning_threshold));
1464 }
else if (adapted &&
1465 Certainty / classify_adapted_pruning_factor < best_certainty) {
1469 float min_xheight, max_xheight, yshift;
1471 &min_xheight, &max_xheight, &yshift);
1475 min_xheight, max_xheight, yshift,
1479 temp_it.add_to_end(choice);
1480 contains_nonfrag |= !current_is_frag;
1482 if (choices_length >= max_matches)
break;
1489 #ifndef GRAPHICS_DISABLED 1497 void Classify::DebugAdaptiveClassifier(
TBLOB *blob,
1499 if (static_classifier_ ==
nullptr)
return;
1504 if (sample ==
nullptr)
return;
1505 static_classifier_->DebugDisplay(*sample, blob->
denorm().
pix(),
1538 if (sample ==
nullptr)
return;
1542 if (static_classifier_ ==
nullptr) {
1547 if (AdaptedTemplates->NumPermClasses < matcher_permanent_classes_min ||
1549 CharNormClassifier(Blob, *sample, Results);
1551 Ambiguities = BaselineClassifier(Blob, bl_features, fx_info,
1552 AdaptedTemplates, Results);
1555 matcher_reliable_adaptive_result) &&
1556 !tess_bn_matching) ||
1558 CharNormClassifier(Blob, *sample, Results);
1559 }
else if (Ambiguities && *Ambiguities >= 0 && !tess_bn_matching) {
1560 AmbigClassifier(bl_features, fx_info, Blob,
1561 PreTrainedTemplates,
1562 AdaptedTemplates->Class,
1573 ClassifyAsNoise(Results);
1598 Results->Initialize();
1604 if (sample ==
nullptr) {
1609 CharNormClassifier(Blob, *sample, Results);
1611 RemoveBadMatches(Results);
1612 Results->match.sort(&UnicharRating::SortDescendingRating);
1616 Ambiguities =
new UNICHAR_ID[Results->match.size() + 1];
1617 if (Results->match.size() > 1 ||
1618 (Results->match.size() == 1 &&
1619 Results->match[0].unichar_id != CorrectClass)) {
1620 for (i = 0; i < Results->match.size(); i++)
1621 Ambiguities[i] = Results->match[i].unichar_id;
1622 Ambiguities[i] = -1;
1624 Ambiguities[0] = -1;
1633 bool Classify::LooksLikeGarbage(
TBLOB *blob) {
1634 auto *ratings =
new BLOB_CHOICE_LIST();
1635 AdaptiveClassifier(blob, ratings);
1636 BLOB_CHOICE_IT ratings_it(ratings);
1637 const UNICHARSET &unicharset = getDict().getUnicharset();
1638 if (classify_debug_character_fragments) {
1640 ratings, unicharset);
1642 for (ratings_it.mark_cycle_pt(); !ratings_it.cycled_list();
1643 ratings_it.forward()) {
1644 if (unicharset.
get_fragment(ratings_it.data()->unichar_id()) !=
nullptr) {
1647 float certainty = ratings_it.data()->certainty();
1650 classify_character_fragments_garbage_certainty_threshold;
1680 uint8_t* pruner_norm_array,
1681 uint8_t* char_norm_array) {
1691 ComputeCharNormArrays(norm_feature, templates, char_norm_array,
1700 uint8_t* char_norm_array,
1701 uint8_t* pruner_array) {
1702 ComputeIntCharNormArray(*norm_feature, char_norm_array);
1703 if (pruner_array !=
nullptr) {
1704 if (shape_table_ ==
nullptr) {
1705 ComputeIntCharNormArray(*norm_feature, pruner_array);
1707 memset(pruner_array, UINT8_MAX,
1708 templates->
NumClasses *
sizeof(pruner_array[0]));
1711 for (
int id = 0;
id < templates->
NumClasses; ++id) {
1713 const FontSet &fs = fontset_table_.get(font_set_id);
1714 for (
int config = 0; config < fs.
size; ++config) {
1715 const Shape& shape = shape_table_->GetShape(fs.
configs[config]);
1716 for (
int c = 0; c < shape.
size(); ++c) {
1717 if (char_norm_array[shape[c].unichar_id] < pruner_array[
id])
1718 pruner_array[id] = char_norm_array[shape[c].unichar_id];
1752 int MaxProtoId, OldMaxProtoId;
1759 if (classify_learning_debug_level >= 3)
1764 Class = Templates->
Class[ClassId];
1767 ++NumAdaptationsFailed;
1768 if (classify_learning_debug_level >= 1)
1769 cprintf(
"Cannot make new temporary config: maximum number exceeded.\n");
1775 NumOldProtos = im_.FindGoodProtos(IClass, AllProtosOn, AllConfigsOff,
1776 NumFeatures, Features,
1777 OldProtos, classify_adapt_proto_threshold,
1782 for (i = 0; i < NumOldProtos; i++)
1783 SET_BIT(TempProtoMask, OldProtos[i]);
1785 NumBadFeatures = im_.FindBadFeatures(IClass, TempProtoMask, AllConfigsOn,
1786 NumFeatures, Features,
1788 classify_adapt_feature_threshold,
1791 MaxProtoId = MakeNewTempProtos(FloatFeatures, NumBadFeatures, BadFeatures,
1792 IClass, Class, TempProtoMask);
1794 ++NumAdaptationsFailed;
1795 if (classify_learning_debug_level >= 1)
1796 cprintf(
"Cannot make new temp protos: maximum number exceeded.\n");
1806 if (classify_learning_debug_level >= 1)
1807 cprintf(
"Making new temp config %d fontinfo id %d" 1808 " using %d old and %d new protos.\n",
1810 NumOldProtos, MaxProtoId - OldMaxProtoId);
1846 float X1, X2, Y1, Y2;
1847 float A1, A2, AngleDelta;
1848 float SegmentLength;
1851 for (ProtoStart = BadFeat, LastBad = ProtoStart + NumBadFeat;
1852 ProtoStart < LastBad; ProtoStart = ProtoEnd) {
1853 F1 = Features->
Features[*ProtoStart];
1858 for (ProtoEnd = ProtoStart + 1,
1862 F2 = Features->
Features[*ProtoEnd];
1867 AngleDelta = fabs(A1 - A2);
1868 if (AngleDelta > 0.5)
1869 AngleDelta = 1.0 - AngleDelta;
1871 if (AngleDelta > matcher_clustering_max_angle_delta ||
1872 fabs(X1 - X2) > SegmentLength ||
1873 fabs(Y1 - Y2) > SegmentLength)
1877 F2 = Features->
Features[*(ProtoEnd - 1)];
1887 Proto = &(TempProto->
Proto);
1892 Proto->
Length = SegmentLength;
1894 Proto->
X = (X1 + X2) / 2.0;
1901 ConvertProto(Proto, Pid, IClass);
1903 classify_learning_debug_level >= 2);
1929 Class = Templates->
Class[ClassId];
1938 Ambigs = GetAmbiguities(Blob, ClassId);
1954 if (classify_learning_debug_level >= 1) {
1955 tprintf(
"Making config %d for %s (ClassId %d) permanent:" 1956 " fontinfo id %d, ambiguities '",
1957 ConfigId, getDict().getUnicharset().debug_str(ClassId).
string(),
1960 *AmbigsPointer >= 0; ++AmbigsPointer)
1961 tprintf(
"%s", unicharset.id_to_unichar(*AmbigsPointer));
1987 ProtoKey =
static_cast<PROTO_KEY *
>(item2);
2014 for (
int i = 0; i < results.
match.
size(); ++i) {
2015 tprintf(
"%s ", unicharset.debug_str(results.
match[i].unichar_id).string());
2016 results.
match[i].Print();
2035 float BadMatchThreshold;
2036 static const char* romans =
"i v x I V X";
2037 BadMatchThreshold = Results->
best_rating - matcher_bad_match_pad;
2039 if (classify_bln_numeric_mode) {
2040 UNICHAR_ID unichar_id_one = unicharset.contains_unichar(
"1") ?
2041 unicharset.unichar_to_id(
"1") : -1;
2042 UNICHAR_ID unichar_id_zero = unicharset.contains_unichar(
"0") ?
2043 unicharset.unichar_to_id(
"0") : -1;
2044 float scored_one = ScoredUnichar(unichar_id_one, *Results);
2045 float scored_zero = ScoredUnichar(unichar_id_zero, *Results);
2047 for (Next = NextGood = 0; Next < Results->
match.
size(); Next++) {
2049 if (match.
rating >= BadMatchThreshold) {
2050 if (!unicharset.get_isalpha(match.
unichar_id) ||
2052 unicharset.id_to_unichar(match.
unichar_id)) !=
nullptr) {
2053 }
else if (unicharset.eq(match.
unichar_id,
"l") &&
2054 scored_one < BadMatchThreshold) {
2055 Results->
match[Next].unichar_id = unichar_id_one;
2056 }
else if (unicharset.eq(match.
unichar_id,
"O") &&
2057 scored_zero < BadMatchThreshold) {
2058 Results->
match[Next].unichar_id = unichar_id_zero;
2060 Results->
match[Next].unichar_id = INVALID_UNICHAR_ID;
2062 if (Results->
match[Next].unichar_id != INVALID_UNICHAR_ID) {
2063 if (NextGood == Next) {
2066 Results->
match[NextGood++] = Results->
match[Next];
2072 for (Next = NextGood = 0; Next < Results->
match.
size(); Next++) {
2073 if (Results->
match[Next].rating >= BadMatchThreshold) {
2074 if (NextGood == Next) {
2077 Results->
match[NextGood++] = Results->
match[Next];
2098 static char punc_chars[] =
". , ; : / ` ~ ' - = \\ | \" ! _ ^";
2099 static char digit_chars[] =
"0 1 2 3 4 5 6 7 8 9";
2103 for (Next = NextGood = 0; Next < Results->
match.
size(); Next++) {
2106 if (strstr(punc_chars,
2107 unicharset.id_to_unichar(match.
unichar_id)) !=
nullptr) {
2108 if (punc_count >= 2)
2112 if (strstr(digit_chars,
2113 unicharset.id_to_unichar(match.
unichar_id)) !=
nullptr) {
2114 if (digit_count >= 1)
2120 if (NextGood == Next) {
2142 Threshold = (Threshold == matcher_good_threshold) ? 0.9: (1.0 - Threshold);
2143 classify_adapt_proto_threshold.set_value(
2144 ClipToRange<int>(255 * Threshold, 0, 255));
2145 classify_adapt_feature_threshold.set_value(
2146 ClipToRange<int>(255 * Threshold, 0, 255));
2159 void Classify::ShowBestMatchFor(
int shape_id,
2162 #ifndef GRAPHICS_DISABLED 2163 uint32_t config_mask;
2165 tprintf(
"No built-in templates for class/shape %d\n", shape_id);
2168 if (num_features <= 0) {
2169 tprintf(
"Illegal blob (char norm features)!\n");
2173 classify_norm_method.set_value(
character);
2175 AllProtosOn, AllConfigsOn,
2176 num_features, features, &cn_result,
2177 classify_adapt_feature_threshold,
NO_DEBUG,
2178 matcher_debug_separate_windows);
2180 config_mask = 1 << cn_result.
config;
2182 tprintf(
"Static Shape ID: %d\n", shape_id);
2184 im_.Match(
ClassForClassId(PreTrainedTemplates, shape_id), AllProtosOn,
2185 &config_mask, num_features, features, &cn_result,
2186 classify_adapt_feature_threshold, matcher_debug_flags,
2187 matcher_debug_separate_windows);
2189 #endif // GRAPHICS_DISABLED 2195 int class_id,
int config_id)
const {
2197 if (templates == PreTrainedTemplates && shape_table_ !=
nullptr) {
2198 int shape_id = ClassAndConfigIDToFontOrShapeID(class_id, config_id);
2199 class_string = shape_table_->DebugStr(shape_id);
2201 class_string = unicharset.debug_str(class_id);
2203 return class_string;
2207 int Classify::ClassAndConfigIDToFontOrShapeID(
int class_id,
2208 int int_result_config)
const {
2209 int font_set_id = PreTrainedTemplates->Class[class_id]->font_set_id;
2211 if (font_set_id < 0)
2212 return kBlankFontinfoId;
2213 const FontSet &fs = fontset_table_.get(font_set_id);
2215 return fs.
configs[int_result_config];
2220 int Classify::ShapeIDToClassID(
int shape_id)
const {
2221 for (
int id = 0;
id < PreTrainedTemplates->NumClasses; ++id) {
2222 int font_set_id = PreTrainedTemplates->Class[id]->font_set_id;
2224 const FontSet &fs = fontset_table_.get(font_set_id);
2225 for (
int config = 0; config < fs.
size; ++config) {
2226 if (fs.
configs[config] == shape_id)
2230 tprintf(
"Shape %d not found\n", shape_id);
2238 if (classify_learning_debug_level >= 1) {
2239 tprintf(
"NumTimesSeen for config of %s is %d\n",
2240 getDict().getUnicharset().debug_str(class_id).
string(),
2243 if (config->
NumTimesSeen >= matcher_sufficient_examples_for_prototyping) {
2245 }
else if (config->
NumTimesSeen < matcher_min_examples_for_prototyping) {
2247 }
else if (use_ambigs_for_adaption) {
2251 getDict().getUnicharAmbigs().AmbigsForAdaption(class_id);
2252 int ambigs_size = (ambigs ==
nullptr) ? 0 : ambigs->
size();
2253 for (
int ambig = 0; ambig < ambigs_size; ++ambig) {
2254 ADAPT_CLASS ambig_class = AdaptedTemplates->Class[(*ambigs)[ambig]];
2255 assert(ambig_class !=
nullptr);
2258 matcher_min_examples_for_prototyping) {
2259 if (classify_learning_debug_level >= 1) {
2260 tprintf(
"Ambig %s has not been seen enough times," 2261 " not making config for %s permanent\n",
2262 getDict().getUnicharset().debug_str(
2263 (*ambigs)[ambig]).
string(),
2264 getDict().getUnicharset().debug_str(class_id).
string());
2275 getDict().getUnicharAmbigs().ReverseAmbigsForAdaption(class_id);
2276 int ambigs_size = (ambigs ==
nullptr) ? 0 : ambigs->
size();
2277 if (classify_learning_debug_level >= 1) {
2278 tprintf(
"Running UpdateAmbigsGroup for %s class_id=%d\n",
2279 getDict().getUnicharset().debug_str(class_id).
string(), class_id);
2281 for (
int ambig = 0; ambig < ambigs_size; ++ambig) {
2282 CLASS_ID ambig_class_id = (*ambigs)[ambig];
2283 const ADAPT_CLASS ambigs_class = AdaptedTemplates->Class[ambig_class_id];
2287 TempConfigFor(AdaptedTemplates->Class[ambig_class_id], cfg);
2288 if (config !=
nullptr && TempConfigReliable(ambig_class_id, config)) {
2289 if (classify_learning_debug_level >= 1) {
2290 tprintf(
"Making config %d of %s permanent\n", cfg,
2291 getDict().getUnicharset().debug_str(
2292 ambig_class_id).
string());
2294 MakePermanent(AdaptedTemplates, ambig_class_id, cfg, Blob);
#define PRINT_PROTO_MATCHES
#define ClassForClassId(T, c)
int IntCastRounded(double x)
#define WordsInVectorOfSize(NumBits)
void FreeBitVector(BIT_VECTOR BitVector)
float adjust_factor() const
TBLOB * ClassifyNormalizeIfNeeded() const
void UpdateMatchDisplay()
#define reset_bit(array, bit)
#define PermConfigFor(Class, ConfigId)
FEATURE_STRUCT * GetCNFeature() const
void FreeFeature(FEATURE Feature)
#define MakeProtoPermanent(Class, ProtoId)
#define ADAPTABLE_WERD_ADJUSTMENT
#define zero_all_bits(array, length)
#define IsEmptyAdaptedClass(Class)
TEMP_PROTO NewTempProto()
bool disable_character_fragments
const INT_FEATURE_STRUCT * features() const
void FreeTempProto(void *arg)
GenericVector< CP_RESULT_STRUCT > CPResults
GenericVector< TBLOB * > blobs
#define MakeConfigPermanent(Class, ConfigId)
int geo_feature(int index) const
void ConvertConfig(BIT_VECTOR Config, int ConfigId, INT_CLASS Class)
static void JoinPieces(const GenericVector< SEAM * > &seams, const GenericVector< TBLOB * > &blobs, int first, int last)
GenericVector< STRING > correct_text
FEATURE NewFeature(const FEATURE_DESC_STRUCT *FeatureDesc)
#define copy_all_bits(source, dest, length)
TBOX bounding_box() const
void InitMatcherRatings(float *Rating)
bool PiecesAllNatural(int start, int count) const
GenericVector< SEAM * > seam_array
void XHeightRange(int unichar_id, const UNICHARSET &unicharset, const TBOX &bbox, float *min_xht, float *max_xht, float *yshift) const
static void BreakPieces(const GenericVector< SEAM * > &seams, const GenericVector< TBLOB * > &blobs, int first, int last)
void SetAdaptiveThreshold(float Threshold)
#define ConfigIsPermanent(Class, ConfigId)
GenericVector< UnicharRating > match
bool GetComponent(TessdataType type, TFile *fp)
#define ADAPT_TEMPLATE_SUFFIX
INT_FEATURE_STRUCT INT_FEATURE_ARRAY[MAX_NUM_INT_FEATURES]
LIST push(LIST list, void *element)
ADAPT_CLASS Class[MAX_NUM_CLASSES]
void plot(ScrollView *window)
#define WORST_POSSIBLE_RATING
ADAPT_TEMPLATES Templates
BIT_VECTOR NewBitVector(int NumBits)
#define PRINT_FEATURE_MATCHES
#define MAX_ADAPTABLE_WERD_SIZE
INT_CLASS Class[MAX_NUM_CLASSES]
GenericVector< ScoredFont > fonts
#define MAX_NUM_INT_FEATURES
#define test_bit(array, bit)
bool MarginalMatch(float confidence, float matcher_great_threshold)
bool Open(const STRING &filename, FileReader reader)
#define SET_BIT(array, bit)
const int kBlnBaselineOffset
void print_ratings_list(const char *msg, BLOB_CHOICE_LIST *ratings, const UNICHARSET ¤t_unicharset)
void plot(ScrollView *window, ScrollView::Color color, ScrollView::Color child_color)
const FEATURE_DESC_STRUCT CharNormDesc
#define PRINT_MATCH_SUMMARY
void cprintf(const char *format,...)
char window_wait(ScrollView *win)
bool AlternativeChoiceAdjustmentsWorseThan(float threshold) const
void FillABC(PROTO Proto)
void free_int_templates(INT_TEMPLATES templates)
const FontInfo * fontinfo
const char * string() const
void FreeFeatureSet(FEATURE_SET FeatureSet)
DLLSYM void tprintf(const char *format,...)
uint32_t num_features() const
TBOX bounding_box() const
#define GetPicoFeatureLength()
int classify_integer_matcher_multiplier
#define set_all_bits(array, length)
const CHAR_FRAGMENT * get_fragment(UNICHAR_ID unichar_id) const
void free_adapted_templates(ADAPT_TEMPLATES templates)
int MakeTempProtoPerm(void *item1, void *item2)
void AddProtoToProtoPruner(PROTO Proto, int ProtoId, INT_CLASS Class, bool debug)
TEMP_CONFIG NewTempConfig(int MaxProtoId, int FontinfoId)
void set_fonts(const GenericVector< tesseract::ScoredFont > &fonts)
void ComputeAdaptionThresholds(float certainty_scale, float min_rating, float max_rating, float rating_margin, float *thresholds)
const DENORM & denorm() const
WERD_CHOICE * best_choice
#define TempConfigFor(Class, ConfigId)
TrainingSample * BlobToTrainingSample(const TBLOB &blob, bool nonlinear_norm, INT_FX_RESULT_STRUCT *fx_info, GenericVector< INT_FEATURE_STRUCT > *bl_features)
const double kStandardFeatureLength
float ActualOutlineLength(FEATURE Feature)
#define UnusedClassIdIn(T, c)
GenericVector< int > best_state
int AddIntConfig(INT_CLASS Class)
const STRING debug_string() const
LIST delete_d(LIST list, void *key, int_compare is_equal)
#define LENGTH_COMPRESSION
int AddIntProto(INT_CLASS Class)
#define UNLIKELY_NUM_FEAT
int outline_length() const
void FreeTempConfig(TEMP_CONFIG Config)
#define IncreaseConfidence(TempConfig)
void AddProtoToClassPruner(PROTO Proto, CLASS_ID ClassId, INT_TEMPLATES Templates)
UNICHAR_ID best_unichar_id