/** * @name uniformly_spaced() * Return true if one of the following are true: * - All inter-char gaps are the same width * - The largest gap is no larger than twice the mean/median of the others * - The largest gap is < normalised_max_nonspace * **** REMEMBER - WE'RE NOW WORKING WITH A BLN WERD !!! */ BOOL8 Tesseract::uniformly_spaced(WERD_RES *word) { TBOX box; inT16 prev_right = -MAX_INT16; inT16 gap; inT16 max_gap = -MAX_INT16; inT16 max_gap_count = 0; STATS gap_stats(0, MAXSPACING); BOOL8 result; const ROW *row = word->denorm.row(); float max_non_space; float normalised_max_nonspace; inT16 i = 0; inT16 offset = 0; STRING punct_chars = "\"`',.:;"; for (TBLOB* blob = word->rebuild_word->blobs; blob != NULL; blob = blob->next) { box = blob->bounding_box(); if ((prev_right > -MAX_INT16) && (!punct_chars.contains( word->best_choice->unichar_string() [offset - word->best_choice->unichar_lengths()[i - 1]]) && !punct_chars.contains( word->best_choice->unichar_string()[offset]))) { gap = box.left() - prev_right; if (gap < max_gap) { gap_stats.add(gap, 1); } else if (gap == max_gap) { max_gap_count++; } else { if (max_gap_count > 0) gap_stats.add(max_gap, max_gap_count); max_gap = gap; max_gap_count = 1; } } prev_right = box.right(); offset += word->best_choice->unichar_lengths()[i++]; } max_non_space = (row->space() + 3 * row->kern()) / 4; normalised_max_nonspace = max_non_space * kBlnXHeight / row->x_height(); result = ( gap_stats.get_total() == 0 || max_gap <= normalised_max_nonspace || (gap_stats.get_total() > 2 && max_gap <= 2 * gap_stats.median()) || (gap_stats.get_total() <= 2 && max_gap <= 2 * gap_stats.mean())); #ifndef SECURE_NAMES if ((debug_fix_space_level > 1)) { if (result) { tprintf( "ACCEPT SPACING FOR: \"%s\" norm_maxnon = %f max=%d maxcount=%d " "total=%d mean=%f median=%f\n", word->best_choice->unichar_string().string(), normalised_max_nonspace, max_gap, max_gap_count, gap_stats.get_total(), gap_stats.mean(), gap_stats.median()); } else { tprintf( "REJECT SPACING FOR: \"%s\" norm_maxnon = %f max=%d maxcount=%d " "total=%d mean=%f median=%f\n", word->best_choice->unichar_string().string(), normalised_max_nonspace, max_gap, max_gap_count, gap_stats.get_total(), gap_stats.mean(), gap_stats.median()); } } #endif return result; }
int32_t row_words2( //compute space size TO_BLOCK* block, //block it came from TO_ROW* row, //row to operate on int32_t maxwidth, //max expected space size FCOORD rotation, //for drawing bool testing_on //for debug ) { bool prev_valid; //if decent size bool this_valid; //current blob big enough int32_t prev_x; //end of prev blob int32_t min_width; //min interesting width int32_t valid_count; //good gaps int32_t total_count; //total gaps int32_t cluster_count; //no of clusters int32_t prev_count; //previous cluster_count int32_t gap_index; //which cluster int32_t smooth_factor; //for smoothing stats BLOBNBOX *blob; //current blob float lower, upper; //clustering parameters ICOORD testpt; TBOX blob_box; //bounding box //iterator BLOBNBOX_IT blob_it = row->blob_list (); STATS gap_stats (0, maxwidth); //gap sizes float gaps[BLOCK_STATS_CLUSTERS]; STATS cluster_stats[BLOCK_STATS_CLUSTERS + 1]; //clusters testpt = ICOORD (textord_test_x, textord_test_y); smooth_factor = static_cast<int32_t>(block->xheight * textord_wordstats_smooth_factor + 1.5); // if (testing_on) // tprintf("Row smooth factor=%d\n",smooth_factor); prev_valid = false; prev_x = -INT16_MAX; const bool testing_row = false; //min blob size min_width = static_cast<int32_t>(block->pr_space); total_count = 0; for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) { blob = blob_it.data (); if (!blob->joined_to_prev ()) { blob_box = blob->bounding_box (); this_valid = blob_box.width () >= min_width; if (this_valid && prev_valid && blob_box.left () - prev_x < maxwidth) { gap_stats.add (blob_box.left () - prev_x, 1); } total_count++; //count possibles prev_x = blob_box.right (); prev_valid = this_valid; } } valid_count = gap_stats.get_total (); if (valid_count < total_count * textord_words_minlarge) { gap_stats.clear (); prev_x = -INT16_MAX; for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) { blob = blob_it.data (); if (!blob->joined_to_prev ()) { blob_box = blob->bounding_box (); if (blob_box.left () - prev_x < maxwidth) { gap_stats.add (blob_box.left () - prev_x, 1); } prev_x = blob_box.right (); } } } if (gap_stats.get_total () == 0) { row->min_space = 0; //no evidence row->max_nonspace = 0; return 0; } cluster_count = 0; lower = block->xheight * words_initial_lower; upper = block->xheight * words_initial_upper; gap_stats.smooth (smooth_factor); do { prev_count = cluster_count; cluster_count = gap_stats.cluster (lower, upper, textord_spacesize_ratioprop, BLOCK_STATS_CLUSTERS, cluster_stats); } while (cluster_count > prev_count && cluster_count < BLOCK_STATS_CLUSTERS); if (cluster_count < 1) { row->min_space = 0; row->max_nonspace = 0; return 0; } for (gap_index = 0; gap_index < cluster_count; gap_index++) gaps[gap_index] = cluster_stats[gap_index + 1].ile (0.5); //get medians if (testing_on) { tprintf ("cluster_count=%d:", cluster_count); for (gap_index = 0; gap_index < cluster_count; gap_index++) tprintf (" %g(%d)", gaps[gap_index], cluster_stats[gap_index + 1].get_total ()); tprintf ("\n"); } //Try to find proportional non-space and space for row. for (gap_index = 0; gap_index < cluster_count && gaps[gap_index] > block->max_nonspace; gap_index++); if (gap_index < cluster_count) lower = gaps[gap_index]; //most frequent below else { if (testing_on) tprintf ("No cluster below block threshold!, using default=%g\n", block->pr_nonsp); lower = block->pr_nonsp; } for (gap_index = 0; gap_index < cluster_count && gaps[gap_index] <= block->max_nonspace; gap_index++); if (gap_index < cluster_count) upper = gaps[gap_index]; //most frequent above else { if (testing_on) tprintf ("No cluster above block threshold!, using default=%g\n", block->pr_space); upper = block->pr_space; } row->min_space = static_cast<int32_t>(ceil (upper - (upper - lower) * textord_words_definite_spread)); row->max_nonspace = static_cast<int32_t>(floor (lower + (upper - lower) * textord_words_definite_spread)); row->space_threshold = (row->max_nonspace + row->min_space) / 2; row->space_size = upper; row->kern_size = lower; if (testing_on) { if (testing_row) { tprintf ("GAP STATS\n"); gap_stats.print(); tprintf ("SPACE stats\n"); cluster_stats[2].print_summary(); tprintf ("NONSPACE stats\n"); cluster_stats[1].print_summary(); } tprintf ("Row at %g has minspace=%d(%g), max_non=%d(%g)\n", row->intercept (), row->min_space, upper, row->max_nonspace, lower); } return 1; }
/************************************************************************* * uniformly_spaced() * Return true if one of the following are true: * - All inter-char gaps are the same width * - The largest gap is no larger than twice the mean/median of the others * - The largest gap is < 64/5 = 13 and all others are <= 0 * **** REMEMBER - WE'RE NOW WORKING WITH A BLN WERD !!! *************************************************************************/ BOOL8 uniformly_spaced( //sensible word WERD_RES *word) { PBLOB_IT blob_it; TBOX box; inT16 prev_right = -MAX_INT16; inT16 gap; inT16 max_gap = -MAX_INT16; inT16 max_gap_count = 0; STATS gap_stats (0, MAXSPACING); BOOL8 result; const ROW *row = word->denorm.row (); float max_non_space; float normalised_max_nonspace; inT16 i = 0; inT16 offset = 0; STRING punct_chars = "\"`',.:;"; blob_it.set_to_list (word->outword->blob_list ()); for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) { box = blob_it.data ()->bounding_box (); if ((prev_right > -MAX_INT16) && (!fixsp_ignore_punct || (!punct_chars.contains (word->best_choice->string () [offset - word->best_choice->lengths()[i - 1]]) && !punct_chars.contains (word->best_choice->string ()[offset])))) { gap = box.left () - prev_right; if (gap < max_gap) gap_stats.add (gap, 1); else if (gap == max_gap) max_gap_count++; else { if (max_gap_count > 0) gap_stats.add (max_gap, max_gap_count); max_gap = gap; max_gap_count = 1; } } prev_right = box.right (); offset += word->best_choice->lengths()[i++]; } max_non_space = (row->space () + 3 * row->kern ()) / 4; normalised_max_nonspace = max_non_space * bln_x_height / row->x_height (); result = ((gap_stats.get_total () == 0) || (max_gap <= normalised_max_nonspace) || ((gap_stats.get_total () > 2) && (max_gap <= 2 * gap_stats.median ())) || ((gap_stats.get_total () <= 2) && (max_gap <= 2 * gap_stats.mean ()))); #ifndef SECURE_NAMES if ((debug_fix_space_level > 1)) { if (result) tprintf ("ACCEPT SPACING FOR: \"%s\" norm_maxnon = %f max=%d maxcount=%d total=%d mean=%f median=%f\n", word->best_choice->string ().string (), normalised_max_nonspace, max_gap, max_gap_count, gap_stats.get_total (), gap_stats.mean (), gap_stats.median ()); else tprintf ("REJECT SPACING FOR: \"%s\" norm_maxnon = %f max=%d maxcount=%d total=%d mean=%f median=%f\n", word->best_choice->string ().string (), normalised_max_nonspace, max_gap, max_gap_count, gap_stats.get_total (), gap_stats.mean (), gap_stats.median ()); } #endif return result; }
int32_t row_words( //compute space size TO_BLOCK* block, //block it came from TO_ROW* row, //row to operate on int32_t maxwidth, //max expected space size FCOORD rotation, //for drawing bool testing_on //for debug ) { bool testing_row; //contains testpt bool prev_valid; //if decent size int32_t prev_x; //end of prev blob int32_t cluster_count; //no of clusters int32_t gap_index; //which cluster int32_t smooth_factor; //for smoothing stats BLOBNBOX *blob; //current blob float lower, upper; //clustering parameters float gaps[3]; //gap clusers ICOORD testpt; TBOX blob_box; //bounding box //iterator BLOBNBOX_IT blob_it = row->blob_list (); STATS gap_stats (0, maxwidth); STATS cluster_stats[4]; //clusters testpt = ICOORD (textord_test_x, textord_test_y); smooth_factor = static_cast<int32_t>(block->xheight * textord_wordstats_smooth_factor + 1.5); // if (testing_on) // tprintf("Row smooth factor=%d\n",smooth_factor); prev_valid = false; prev_x = -INT32_MAX; testing_row = false; for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) { blob = blob_it.data (); blob_box = blob->bounding_box (); if (blob_box.contains (testpt)) testing_row = true; gap_stats.add (blob_box.width (), 1); } gap_stats.clear (); for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) { blob = blob_it.data (); if (!blob->joined_to_prev ()) { blob_box = blob->bounding_box (); if (prev_valid && blob_box.left () - prev_x < maxwidth) { gap_stats.add (blob_box.left () - prev_x, 1); } prev_valid = true; prev_x = blob_box.right (); } } if (gap_stats.get_total () == 0) { row->min_space = 0; //no evidence row->max_nonspace = 0; return 0; } gap_stats.smooth (smooth_factor); lower = row->xheight * textord_words_initial_lower; upper = row->xheight * textord_words_initial_upper; cluster_count = gap_stats.cluster (lower, upper, textord_spacesize_ratioprop, 3, cluster_stats); while (cluster_count < 2 && ceil (lower) < floor (upper)) { //shrink gap upper = (upper * 3 + lower) / 4; lower = (lower * 3 + upper) / 4; cluster_count = gap_stats.cluster (lower, upper, textord_spacesize_ratioprop, 3, cluster_stats); } if (cluster_count < 2) { row->min_space = 0; //no evidence row->max_nonspace = 0; return 0; } for (gap_index = 0; gap_index < cluster_count; gap_index++) gaps[gap_index] = cluster_stats[gap_index + 1].ile (0.5); //get medians if (cluster_count > 2) { if (testing_on && textord_show_initial_words) { tprintf ("Row at %g has 3 sizes of gap:%g,%g,%g\n", row->intercept (), cluster_stats[1].ile (0.5), cluster_stats[2].ile (0.5), cluster_stats[3].ile (0.5)); } lower = gaps[0]; if (gaps[1] > lower) { upper = gaps[1]; //prefer most frequent if (upper < block->xheight * textord_words_min_minspace && gaps[2] > gaps[1]) { upper = gaps[2]; } } else if (gaps[2] > lower && gaps[2] >= block->xheight * textord_words_min_minspace) upper = gaps[2]; else if (lower >= block->xheight * textord_words_min_minspace) { upper = lower; //not nice lower = gaps[1]; if (testing_on && textord_show_initial_words) { tprintf ("Had to switch most common from lower to upper!!\n"); gap_stats.print(); } } else { row->min_space = 0; //no evidence row->max_nonspace = 0; return 0; } } else { if (gaps[1] < gaps[0]) { if (testing_on && textord_show_initial_words) { tprintf ("Had to switch most common from lower to upper!!\n"); gap_stats.print(); } lower = gaps[1]; upper = gaps[0]; } else { upper = gaps[1]; lower = gaps[0]; } } if (upper < block->xheight * textord_words_min_minspace) { row->min_space = 0; //no evidence row->max_nonspace = 0; return 0; } if (upper * 3 < block->min_space * 2 + block->max_nonspace || lower * 3 > block->min_space * 2 + block->max_nonspace) { if (testing_on && textord_show_initial_words) { tprintf ("Disagreement between block and row at %g!!\n", row->intercept ()); tprintf ("Lower=%g, upper=%g, Stats:\n", lower, upper); gap_stats.print(); } } row->min_space = static_cast<int32_t>(ceil (upper - (upper - lower) * textord_words_definite_spread)); row->max_nonspace = static_cast<int32_t>(floor (lower + (upper - lower) * textord_words_definite_spread)); row->space_threshold = (row->max_nonspace + row->min_space) / 2; row->space_size = upper; row->kern_size = lower; if (testing_on && textord_show_initial_words) { if (testing_row) { tprintf ("GAP STATS\n"); gap_stats.print(); tprintf ("SPACE stats\n"); cluster_stats[2].print_summary(); tprintf ("NONSPACE stats\n"); cluster_stats[1].print_summary(); } tprintf ("Row at %g has minspace=%d(%g), max_non=%d(%g)\n", row->intercept (), row->min_space, upper, row->max_nonspace, lower); } return cluster_stats[2].get_total (); }