21 #include "config_auto.h"
39 #include "allheaders.h"
47 #define MAX_NEAREST_DIST 600 //for block skew stats
60 int pix_height = pixGetHeight(pix);
61 const TBOX& box = blob->bounding_box();
62 int width = box.
width();
64 Box* blob_pix_box = boxCreate(box.
left(), pix_height - box.
top(),
66 Pix* pix_blob = pixClipRectangle(pix, blob_pix_box,
NULL);
67 boxDestroy(&blob_pix_box);
68 Pix* dist_pix = pixDistanceFunction(pix_blob, 4, 8, L_BOUNDARY_BG);
69 pixDestroy(&pix_blob);
71 uinT32* data = pixGetData(dist_pix);
72 int wpl = pixGetWpl(dist_pix);
74 STATS h_stats(0, width + 1);
75 for (
int y = 0; y < height; ++y) {
76 uinT32* pixels = data + y*wpl;
78 int pixel = GET_DATA_BYTE(pixels, 0);
79 for (
int x = 1; x < width; ++x) {
80 int next_pixel = GET_DATA_BYTE(pixels, x);
83 if (prev_pixel < pixel &&
84 (y == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) &&
85 (y == height - 1 || pixel == GET_DATA_BYTE(pixels + wpl, x - 1))) {
86 if (pixel > next_pixel) {
88 h_stats.
add(pixel * 2 - 1, 1);
89 }
else if (pixel == next_pixel && x + 1 < width &&
90 pixel > GET_DATA_BYTE(pixels, x + 1)) {
92 h_stats.
add(pixel * 2, 1);
100 STATS v_stats(0, height + 1);
101 for (
int x = 0; x < width; ++x) {
103 int pixel = GET_DATA_BYTE(data, x);
104 for (
int y = 1; y < height; ++y) {
105 uinT32* pixels = data + y*wpl;
106 int next_pixel = GET_DATA_BYTE(pixels, x);
109 if (prev_pixel < pixel &&
110 (x == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) &&
111 (x == width - 1 || pixel == GET_DATA_BYTE(pixels - wpl, x + 1))) {
112 if (pixel > next_pixel) {
114 v_stats.
add(pixel * 2 - 1, 1);
115 }
else if (pixel == next_pixel && y + 1 < height &&
116 pixel > GET_DATA_BYTE(pixels + wpl, x)) {
118 v_stats.
add(pixel * 2, 1);
125 pixDestroy(&dist_pix);
132 if (h_stats.
get_total() >= (width + height) / 4) {
133 blob->set_horz_stroke_width(h_stats.
ile(0.5f));
134 if (v_stats.
get_total() >= (width + height) / 4)
135 blob->set_vert_stroke_width(v_stats.
ile(0.5f));
137 blob->set_vert_stroke_width(0.0f);
139 if (v_stats.
get_total() >= (width + height) / 4 ||
141 blob->set_horz_stroke_width(0.0f);
142 blob->set_vert_stroke_width(v_stats.
ile(0.5f));
144 blob->set_horz_stroke_width(h_stats.
get_total() > 2 ? h_stats.
ile(0.5f)
146 blob->set_vert_stroke_width(0.0f);
159 TO_BLOCK_LIST *port_blocks) {
163 BLOCK_IT block_it = blocks;
165 BLOBNBOX_IT port_box_it;
167 TO_BLOCK_IT port_block_it = port_blocks;
170 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
171 block = block_it.data();
175 port_box_it.set_to_list(&port_block->
blobs);
177 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
178 blob = blob_it.extract();
181 port_box_it.add_after_then_move(newblob);
189 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
190 blob = blob_it.extract();
193 port_box_it.add_after_then_move(newblob);
196 port_block_it.add_after_then_move(port_block);
209 TO_BLOCK_LIST *to_blocks) {
210 int width = pixGetWidth(pix);
211 int height = pixGetHeight(pix);
213 tprintf(
"Input image too large! (%d, %d)\n", width, height);
219 BLOCK_IT block_it(blocks);
220 for (block_it.mark_cycle_pt(); !block_it.cycled_list();
221 block_it.forward()) {
222 BLOCK* block = block_it.data();
229 ICOORD page_tr(width, height);
240 TO_BLOCK_LIST *blocks,
242 TO_BLOCK_IT block_it = blocks;
245 #ifndef GRAPHICS_DISABLED
248 #endif // GRAPHICS_DISABLED
250 for (block_it.mark_cycle_pt(); !block_it.cycled_list();
251 block_it.forward()) {
252 block = block_it.data();
265 #ifndef GRAPHICS_DISABLED
279 #endif // GRAPHICS_DISABLED
289 float Textord::filter_noise_blobs(
290 BLOBNBOX_LIST *src_list,
291 BLOBNBOX_LIST *noise_list,
292 BLOBNBOX_LIST *small_list,
293 BLOBNBOX_LIST *large_list) {
298 BLOBNBOX_IT src_it = src_list;
299 BLOBNBOX_IT noise_it = noise_list;
300 BLOBNBOX_IT small_it = small_list;
301 BLOBNBOX_IT large_it = large_list;
309 for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
310 blob = src_it.data();
312 noise_it.add_after_then_move(src_it.extract());
315 small_it.add_after_then_move(src_it.extract());
317 for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
318 size_stats.add(src_it.data()->bounding_box().height(), 1);
321 max_y = ceil(initial_x *
326 min_y = floor (initial_x / 2);
328 small_it.move_to_first ();
329 for (small_it.mark_cycle_pt (); !small_it.cycled_list ();
330 small_it.forward ()) {
331 height = small_it.data()->bounding_box().height();
333 large_it.add_after_then_move(small_it.extract ());
334 else if (height >= min_y)
335 src_it.add_after_then_move(small_it.extract ());
338 for (src_it.mark_cycle_pt (); !src_it.cycled_list (); src_it.forward ()) {
339 height = src_it.data ()->bounding_box ().height ();
340 width = src_it.data ()->bounding_box ().width ();
342 small_it.add_after_then_move (src_it.extract ());
343 else if (height > max_y || width > max_x)
344 large_it.add_after_then_move (src_it.extract ());
346 size_stats.add (height, 1);
352 if (max_height > initial_x)
353 initial_x = max_height;
362 void Textord::cleanup_nontext_block(
BLOCK* block) {
365 if (row_it.empty()) {
367 float height = box.
height();
369 double coeffs[3] = {0.0, 0.0,
static_cast<double>(box.
bottom())};
370 ROW* row =
new ROW(1, xstarts, coeffs, height / 2.0f, height / 4.0f,
371 height / 4.0f, 0, 1);
372 row_it.add_after_then_move(row);
375 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
376 ROW* row = row_it.data();
384 C_BLOB_IT blob_it(&blobs);
385 blob_it.add_after_then_move(blob);
387 w_it.add_after_then_move(word);
390 for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
391 WERD* word = w_it.data();
406 void Textord::cleanup_blocks(
bool clean_noise, BLOCK_LIST* blocks) {
407 BLOCK_IT block_it = blocks;
411 int num_rows_all = 0;
413 int num_blocks_all = 0;
414 for (block_it.mark_cycle_pt(); !block_it.cycled_list();
415 block_it.forward()) {
416 BLOCK* block = block_it.data();
418 cleanup_nontext_block(block);
424 row_it.set_to_list(block->
row_list());
425 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
426 ROW* row = row_it.data();
428 clean_small_noise_from_words(row);
430 clean_noise_from_row(row)) ||
432 delete row_it.extract();
435 clean_noise_from_words(row_it.data());
444 delete block_it.extract();
450 tprintf(
"cleanup_blocks: # rows = %d / %d\n", num_rows, num_rows_all);
453 tprintf(
"cleanup_blocks: # blocks = %d / %d\n", num_blocks, num_blocks_all);
463 BOOL8 Textord::clean_noise_from_row(
472 inT32 trans_count = 0;
473 inT32 trans_threshold;
476 inT32 super_norm_count;
490 super_norm_count = 0;
491 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
492 word = word_it.data ();
495 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
496 blob_it.forward ()) {
497 blob = blob_it.data ();
500 out_it.set_to_list (blob->
out_list ());
501 for (out_it.mark_cycle_pt (); !out_it.cycled_list ();
503 outline = out_it.data ();
509 if (blob_size < textord_noise_sizelimit * row->x_height ())
511 if (!outline->
child ()->empty ()
516 && blob_box.
width () <
518 && blob_box.
width () >
530 && blob_size < row->
x_height () * 2) {
537 && (!word_it.at_first () || !blob_it.at_first ()))
541 (
"Blob at (%d,%d) -> (%d,%d), ols=%d, tc=%d, bldiff=%g\n",
543 blob_box.
top (), blob->
out_list ()->length (), trans_count,
549 tprintf (
"Row ending at (%d,%g):",
551 tprintf (
" R=%g, dc=%d, nc=%d, %s\n",
552 norm_count > 0 ? (
float) dot_count / norm_count : 9999,
553 dot_count, norm_count,
555 && dot_count > 2 ?
"REJECTED" :
"ACCEPTED");
567 void Textord::clean_noise_from_words(
577 inT32 trans_threshold;
588 ok_words = word_it.length ();
595 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
596 word = word_it.data ();
601 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
602 blob_it.forward ()) {
603 blob = blob_it.data ();
606 out_it.set_to_list (blob->
out_list ());
607 for (out_it.mark_cycle_pt (); !out_it.cycled_list ();
609 outline = out_it.data ();
615 if (blob_size < textord_noise_sizelimit * row->x_height ())
617 if (!outline->
child ()->empty ()
622 && blob_box.
width () <
624 && blob_box.
width () >
636 && blob_size < row->
x_height () * 2) {
643 && (!word_it.at_first () || !blob_it.at_first ()))
648 word_dud[word_index] = 2;
649 else if (dot_count > norm_count * textord_noise_normratio)
650 word_dud[word_index] = 1;
652 word_dud[word_index] = 0;
654 word_dud[word_index] = 0;
656 if (word_dud[word_index] == 2)
664 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
665 if (word_dud[word_index] == 2
666 || (word_dud[word_index] == 1 && dud_words > ok_words)) {
667 word = word_it.data();
680 void Textord::clean_small_noise_from_words(
ROW *row) {
682 for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
683 WERD* word = word_it.data();
684 int min_size =
static_cast<int>(
687 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
688 C_BLOB* blob = blob_it.data();
689 C_OUTLINE_IT out_it(blob->
out_list());
690 for (out_it.mark_cycle_pt(); !out_it.cycled_list(); out_it.forward()) {
695 delete blob_it.extract();
699 if (!word_it.at_last()) {
702 WERD* next_word = word_it.data_relative(1);
707 delete word_it.extract();
714 BlockGroup() : rotation(1.0f, 0.0f), angle(0.0f), min_xheight(1.0f) {}
716 : bounding_box(block->bounding_box()),
717 rotation(block->re_rotation()),
718 angle(block->re_rotation().angle()),
719 min_xheight(block->x_height()) {
720 blocks.push_back(block);
737 void Textord::TransferDiacriticsToBlockGroups(BLOBNBOX_LIST* diacritic_blobs,
738 BLOCK_LIST* blocks) {
741 const double kMaxAngleDiff = 0.01;
743 BLOCK_IT bk_it(blocks);
744 for (bk_it.mark_cycle_pt(); !bk_it.cycled_list(); bk_it.forward()) {
745 BLOCK* block = bk_it.data();
753 for (
int g = 0; g < groups.
size(); ++g) {
754 double angle_diff = fabs(block_angle - groups[g]->angle);
755 if (angle_diff > M_PI) angle_diff = fabs(angle_diff - 2.0 * M_PI);
756 if (angle_diff < best_angle_diff) {
757 best_angle_diff = angle_diff;
761 if (best_angle_diff > kMaxAngleDiff) {
767 if (x_height < groups[best_g]->min_xheight)
768 groups[best_g]->min_xheight = x_height;
772 PointerVector<WordWithBox> word_ptrs;
773 for (
int g = 0; g < groups.
size(); ++g) {
774 const BlockGroup* group = groups[g];
775 WordGrid word_grid(group->min_xheight, group->bounding_box.botleft(),
776 group->bounding_box.topright());
777 for (
int b = 0; b < group->blocks.size(); ++b) {
778 ROW_IT row_it(group->blocks[b]->row_list());
779 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
780 ROW* row = row_it.data();
783 for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
784 WERD* word = w_it.data();
785 WordWithBox* box_word =
new WordWithBox(word);
786 word_grid.InsertBBox(
true,
true, box_word);
788 word_ptrs.push_back(box_word);
792 FCOORD rotation = group->rotation;
794 rotation.
set_y(-rotation.
y());
795 TransferDiacriticsToWords(diacritic_blobs, rotation, &word_grid);
802 void Textord::TransferDiacriticsToWords(BLOBNBOX_LIST* diacritic_blobs,
806 BLOBNBOX_IT b_it(diacritic_blobs);
810 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
813 blob_box.
rotate(rotation);
814 ws.StartRectSearch(blob_box);
819 WordWithBox* best_above_word =
NULL;
820 WordWithBox* best_below_word =
NULL;
821 int best_above_distance = 0;
822 int best_below_distance = 0;
823 for (WordWithBox* word = ws.NextRectSearch(); word !=
NULL;
824 word = ws.NextRectSearch()) {
827 int x_distance = blob_box.
x_gap(word_box);
828 int y_distance = blob_box.
y_gap(word_box);
829 if (x_distance > 0) {
839 y_distance += x_distance;
842 (best_above_word ==
NULL || y_distance < best_above_distance)) {
843 best_above_word = word;
844 best_above_distance = y_distance;
847 (best_below_word ==
NULL || y_distance < best_below_distance)) {
848 best_below_word = word;
849 best_below_distance = y_distance;
853 best_above_word !=
NULL &&
854 (best_below_word ==
NULL ||
855 best_above_distance < best_below_distance + blob_box.
height());
857 best_below_word !=
NULL && best_below_word != best_above_word &&
858 (best_above_word ==
NULL ||
859 best_below_distance < best_above_distance + blob_box.
height());
862 copied_blob->
rotate(rotation);
864 C_BLOB_IT blob_it(best_below_word->RejBlobs());
865 blob_it.add_to_end(copied_blob);
869 copied_blob->
rotate(rotation);
871 C_BLOB_IT blob_it(best_above_word->RejBlobs());
872 blob_it.add_to_end(copied_blob);
887 double blshift_maxshift,
888 double blshift_xfraction) {
904 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
905 word = word_it.data ();
915 (
double *)
alloc_mem ((blob_count + row->baseline.segments) * 3 *
920 xstarts[0] = row->baseline.xcoords[0];
921 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
922 word = word_it.data ();
925 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
926 blob_it.forward ()) {
927 blob = blob_it.data ();
929 x_centre = (blob_box.
left () + blob_box.
right ()) / 2.0;
935 if (ydiff < blshift_maxshift
937 if (xstarts[dest_index] >= x_centre)
938 xstarts[dest_index] = blob_box.
left ();
939 coeffs[dest_index * 3] = 0;
940 coeffs[dest_index * 3 + 1] = 0;
941 coeffs[dest_index * 3 + 2] = blob_box.
bottom ();
944 xstarts[dest_index] = blob_box.
right () + 1;
947 if (xstarts[dest_index] <= x_centre) {
948 while (row->baseline.xcoords[src_index + 1] <= x_centre
949 && src_index < row->
baseline.segments - 1) {
950 if (row->baseline.xcoords[src_index + 1] >
951 xstarts[dest_index]) {
952 coeffs[dest_index * 3] =
953 row->baseline.quadratics[src_index].
a;
954 coeffs[dest_index * 3 + 1] =
955 row->baseline.quadratics[src_index].
b;
956 coeffs[dest_index * 3 + 2] =
957 row->baseline.quadratics[src_index].
c;
959 xstarts[dest_index] =
960 row->baseline.xcoords[src_index + 1];
964 coeffs[dest_index * 3] =
965 row->baseline.quadratics[src_index].
a;
966 coeffs[dest_index * 3 + 1] =
967 row->baseline.quadratics[src_index].
b;
968 coeffs[dest_index * 3 + 2] =
969 row->baseline.quadratics[src_index].
c;
971 xstarts[dest_index] = row->baseline.xcoords[src_index + 1];
976 while (src_index < row->
baseline.segments
977 && row->baseline.xcoords[src_index + 1] <= xstarts[dest_index])
979 while (src_index < row->
baseline.segments) {
980 coeffs[dest_index * 3] = row->baseline.quadratics[src_index].
a;
981 coeffs[dest_index * 3 + 1] = row->baseline.quadratics[src_index].
b;
982 coeffs[dest_index * 3 + 2] = row->baseline.quadratics[src_index].
c;
985 xstarts[dest_index] = row->baseline.xcoords[src_index];
988 row->baseline =
QSPLINE (dest_index, xstarts, coeffs);
C_BLOB_LIST * blob_list()
get blobs
void set_global_loc_code(int loc_code)
void extract_edges(Pix *pix, BLOCK *block)
int textord_max_noise_size
static C_BLOB * deep_copy(const C_BLOB *src)
bool textord_noise_rejrows
ScrollView * create_to_win(ICOORD page_tr)
void tweak_row_baseline(ROW *row, double blshift_maxshift, double blshift_xfraction)
void rotate(const FCOORD &rotation)
inT32 count_transitions(inT32 threshold)
static const double kXHeightCapRatio
void free_mem(void *oldchunk)
double textord_noise_syfract
BBGrid< WordWithBox, WordWithBox_CLIST, WordWithBox_C_IT > WordGrid
GenericVector< BLOCK * > blocks
static const double kDescenderFraction
void add(inT32 value, inT32 count)
TBOX bounding_box() const
double textord_noise_hfract
double textord_blshift_maxshift
int textord_noise_sncount
double textord_noise_normratio
void RemoveSmallRecursive(int min_size, C_OUTLINE_IT *it)
float base_line(float xpos) const
BLOBNBOX_LIST small_blobs
float angle() const
find angle
C_OUTLINE_LIST * out_list()
bool textord_noise_rejwords
double textord_initialasc_ile
double textord_noise_rowratio
static const double kAscenderFraction
EXTERN ScrollView * to_win
double ile(double frac) const
#define CLISTIZE(CLASSNAME)
double textord_initialx_ile
inT32 x_height() const
return xheight
FCOORD re_rotation() const
double textord_excess_blobsize
int y_gap(const TBOX &box) const
bool textord_test_landscape
GridSearch< WordWithBox, WordWithBox_CLIST, WordWithBox_C_IT > WordSearch
double textord_width_limit
void set_y(float yin)
rewrite function
double textord_blshift_xfraction
TBOX bounding_box() const
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
BLOBNBOX_LIST noise_blobs
void plot_box_list(ScrollView *win, BLOBNBOX_LIST *list, ScrollView::Color body_colour)
double textord_noise_sxfract
const TBOX & bounding_box() const
void filter_blobs(ICOORD page_tr, TO_BLOCK_LIST *blocks, BOOL8 testing_on)
void CleanNoise(float size_threshold)
inT32 enclosed_area() const
int textord_noise_sizefraction
void plot_graded_blobs(ScrollView *to_win)
double textord_noise_sizelimit
int x_gap(const TBOX &box) const
bool major_y_overlap(const TBOX &box) const
static C_BLOB * FakeBlob(const TBOX &box)
TBOX true_bounding_box() const
BOOL8 flag(WERD_FLAGS mask) const
static const double kXHeightFraction
TBOX bounding_box() const
void find_components(Pix *pix, BLOCK_LIST *blocks, TO_BLOCK_LIST *to_blocks)
void * alloc_mem(inT32 count)
double textord_min_linesize
const TBOX & bounding_box() const
ROW_LIST * row_list()
get rows
POLY_BLOCK * poly_block() const
void set_flag(WERD_FLAGS mask, BOOL8 value)
BLOBNBOX_LIST large_blobs
double textord_noise_area_ratio
void assign_blobs_to_blocks2(Pix *pix, BLOCK_LIST *blocks, TO_BLOCK_LIST *port_blocks)
C_BLOB_LIST * cblob_list()
void recalc_bounding_box()
C_BLOB_LIST * reject_blobs()
void SetBlobStrokeWidth(Pix *pix, BLOBNBOX *blob)
int textord_noise_translimit
void rotate(const FCOORD &vec)