19 #define _USE_MATH_DEFINES // for M_PI
21 #include "config_auto.h"
28 #include "allheaders.h"
29 #include "arrayaccess.h"
58 #define MAX_NEAREST_DIST 600 //for block skew stats
71 int pix_height = pixGetHeight(pix);
72 const TBOX& box = blob->bounding_box();
73 int width = box.
width();
75 Box* blob_pix_box = boxCreate(box.
left(), pix_height - box.
top(),
77 Pix* pix_blob = pixClipRectangle(pix, blob_pix_box,
nullptr);
78 boxDestroy(&blob_pix_box);
79 Pix* dist_pix = pixDistanceFunction(pix_blob, 4, 8, L_BOUNDARY_BG);
80 pixDestroy(&pix_blob);
82 uint32_t* data = pixGetData(dist_pix);
83 int wpl = pixGetWpl(dist_pix);
85 STATS h_stats(0, width + 1);
86 for (
int y = 0; y < height; ++y) {
87 uint32_t* pixels = data + y*wpl;
89 int pixel = GET_DATA_BYTE(pixels, 0);
90 for (
int x = 1; x < width; ++x) {
91 int next_pixel = GET_DATA_BYTE(pixels, x);
94 if (prev_pixel < pixel &&
95 (y == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) &&
96 (y == height - 1 || pixel == GET_DATA_BYTE(pixels + wpl, x - 1))) {
97 if (pixel > next_pixel) {
99 h_stats.
add(pixel * 2 - 1, 1);
100 }
else if (pixel == next_pixel && x + 1 < width &&
101 pixel > GET_DATA_BYTE(pixels, x + 1)) {
103 h_stats.
add(pixel * 2, 1);
111 STATS v_stats(0, height + 1);
112 for (
int x = 0; x < width; ++x) {
114 int pixel = GET_DATA_BYTE(data, x);
115 for (
int y = 1; y < height; ++y) {
116 uint32_t* pixels = data + y*wpl;
117 int next_pixel = GET_DATA_BYTE(pixels, x);
120 if (prev_pixel < pixel &&
121 (x == 0 || pixel == GET_DATA_BYTE(pixels - wpl, x - 1)) &&
122 (x == width - 1 || pixel == GET_DATA_BYTE(pixels - wpl, x + 1))) {
123 if (pixel > next_pixel) {
125 v_stats.
add(pixel * 2 - 1, 1);
126 }
else if (pixel == next_pixel && y + 1 < height &&
127 pixel > GET_DATA_BYTE(pixels + wpl, x)) {
129 v_stats.
add(pixel * 2, 1);
136 pixDestroy(&dist_pix);
143 if (h_stats.
get_total() >= (width + height) / 4) {
144 blob->set_horz_stroke_width(h_stats.
ile(0.5f));
145 if (v_stats.
get_total() >= (width + height) / 4)
146 blob->set_vert_stroke_width(v_stats.
ile(0.5f));
148 blob->set_vert_stroke_width(0.0f);
150 if (v_stats.
get_total() >= (width + height) / 4 ||
152 blob->set_horz_stroke_width(0.0f);
153 blob->set_vert_stroke_width(v_stats.
ile(0.5f));
155 blob->set_horz_stroke_width(h_stats.
get_total() > 2 ? h_stats.
ile(0.5f)
157 blob->set_vert_stroke_width(0.0f);
170 TO_BLOCK_LIST *port_blocks) {
174 BLOCK_IT block_it = blocks;
176 BLOBNBOX_IT port_box_it;
178 TO_BLOCK_IT port_block_it = port_blocks;
181 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
182 block = block_it.data();
186 port_box_it.set_to_list(&port_block->
blobs);
188 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
189 blob = blob_it.extract();
192 port_box_it.add_after_then_move(newblob);
200 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
201 blob = blob_it.extract();
204 port_box_it.add_after_then_move(newblob);
207 port_block_it.add_after_then_move(port_block);
220 TO_BLOCK_LIST *to_blocks) {
221 int width = pixGetWidth(pix);
222 int height = pixGetHeight(pix);
223 if (width > INT16_MAX || height > INT16_MAX) {
224 tprintf(
"Input image too large! (%d, %d)\n", width, height);
230 BLOCK_IT block_it(blocks);
231 for (block_it.mark_cycle_pt(); !block_it.cycled_list();
232 block_it.forward()) {
233 BLOCK* block = block_it.data();
240 ICOORD page_tr(width, height);
251 TO_BLOCK_LIST* blocks,
253 TO_BLOCK_IT block_it = blocks;
256 #ifndef GRAPHICS_DISABLED
259 #endif // GRAPHICS_DISABLED
261 for (block_it.mark_cycle_pt(); !block_it.cycled_list();
262 block_it.forward()) {
263 block = block_it.data();
277 #ifndef GRAPHICS_DISABLED
291 #endif // GRAPHICS_DISABLED
301 float Textord::filter_noise_blobs(
302 BLOBNBOX_LIST *src_list,
303 BLOBNBOX_LIST *noise_list,
304 BLOBNBOX_LIST *small_list,
305 BLOBNBOX_LIST *large_list) {
310 BLOBNBOX_IT src_it = src_list;
311 BLOBNBOX_IT noise_it = noise_list;
312 BLOBNBOX_IT small_it = small_list;
313 BLOBNBOX_IT large_it = large_list;
321 for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
322 blob = src_it.data();
324 noise_it.add_after_then_move(src_it.extract());
327 small_it.add_after_then_move(src_it.extract());
329 for (src_it.mark_cycle_pt(); !src_it.cycled_list(); src_it.forward()) {
330 size_stats.add(src_it.data()->bounding_box().height(), 1);
333 max_y = ceil(initial_x *
338 min_y = floor (initial_x / 2);
340 small_it.move_to_first ();
341 for (small_it.mark_cycle_pt (); !small_it.cycled_list ();
342 small_it.forward ()) {
343 height = small_it.data()->bounding_box().height();
345 large_it.add_after_then_move(small_it.extract ());
346 else if (height >= min_y)
347 src_it.add_after_then_move(small_it.extract ());
350 for (src_it.mark_cycle_pt (); !src_it.cycled_list (); src_it.forward ()) {
351 height = src_it.data ()->bounding_box ().height ();
352 width = src_it.data ()->bounding_box ().width ();
354 small_it.add_after_then_move (src_it.extract ());
355 else if (height > max_y || width > max_x)
356 large_it.add_after_then_move (src_it.extract ());
358 size_stats.add (height, 1);
364 if (max_height > initial_x)
365 initial_x = max_height;
374 void Textord::cleanup_nontext_block(
BLOCK* block) {
377 if (row_it.empty()) {
379 float height = box.
height();
380 int32_t xstarts[2] = {box.
left(), box.
right()};
381 double coeffs[3] = {0.0, 0.0, static_cast<double>(box.
bottom())};
382 ROW* row =
new ROW(1, xstarts, coeffs, height / 2.0f, height / 4.0f,
383 height / 4.0f, 0, 1);
384 row_it.add_after_then_move(row);
387 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
388 ROW* row = row_it.data();
396 C_BLOB_IT blob_it(&blobs);
397 blob_it.add_after_then_move(blob);
398 WERD* word =
new WERD(&blobs, 0,
nullptr);
399 w_it.add_after_then_move(word);
402 for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
403 WERD* word = w_it.data();
418 void Textord::cleanup_blocks(
bool clean_noise, BLOCK_LIST* blocks) {
419 BLOCK_IT block_it = blocks;
423 int num_rows_all = 0;
425 int num_blocks_all = 0;
426 for (block_it.mark_cycle_pt(); !block_it.cycled_list();
427 block_it.forward()) {
428 BLOCK* block = block_it.data();
430 cleanup_nontext_block(block);
436 row_it.set_to_list(block->
row_list());
437 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
438 ROW* row = row_it.data();
440 clean_small_noise_from_words(row);
442 clean_noise_from_row(row)) ||
444 delete row_it.extract();
447 clean_noise_from_words(row_it.data());
456 delete block_it.extract();
462 tprintf(
"cleanup_blocks: # rows = %d / %d\n", num_rows, num_rows_all);
465 tprintf(
"cleanup_blocks: # blocks = %d / %d\n", num_blocks, num_blocks_all);
475 bool Textord::clean_noise_from_row(
484 int32_t trans_count = 0;
485 int32_t trans_threshold;
488 int32_t super_norm_count;
499 super_norm_count = 0;
500 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
501 word = word_it.data ();
504 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
505 blob_it.forward ()) {
506 blob = blob_it.data ();
509 out_it.set_to_list (blob->
out_list ());
510 for (out_it.mark_cycle_pt (); !out_it.cycled_list ();
512 outline = out_it.data ();
518 if (blob_size < textord_noise_sizelimit * row->x_height ())
520 if (!outline->
child ()->empty ()
525 && blob_box.
width () <
527 && blob_box.
width () >
539 && blob_size < row->
x_height () * 2) {
546 && (!word_it.at_first () || !blob_it.at_first ()))
550 (
"Blob at (%d,%d) -> (%d,%d), ols=%d, tc=%d, bldiff=%g\n",
552 blob_box.
top (), blob->
out_list ()->length (), trans_count,
558 tprintf (
"Row ending at (%d,%g):",
560 tprintf (
" R=%g, dc=%d, nc=%d, %s\n",
561 norm_count > 0 ? static_cast<float>(dot_count) / norm_count : 9999,
562 dot_count, norm_count,
564 && dot_count > 2 ?
"REJECTED" :
"ACCEPTED");
576 void Textord::clean_noise_from_words(
585 int32_t trans_threshold;
596 ok_words = word_it.length ();
600 std::vector<int8_t> word_dud(ok_words);
604 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
605 word = word_it.data ();
610 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
611 blob_it.forward ()) {
612 blob = blob_it.data ();
615 out_it.set_to_list (blob->
out_list ());
616 for (out_it.mark_cycle_pt (); !out_it.cycled_list ();
618 outline = out_it.data ();
624 if (blob_size < textord_noise_sizelimit * row->x_height ())
626 if (!outline->
child ()->empty ()
631 && blob_box.
width () <
633 && blob_box.
width () >
645 && blob_size < row->
x_height () * 2) {
652 && (!word_it.at_first () || !blob_it.at_first ()))
657 word_dud[word_index] = 2;
659 word_dud[word_index] = 1;
661 word_dud[word_index] = 0;
663 word_dud[word_index] = 0;
665 if (word_dud[word_index] == 2)
673 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
674 if (word_dud[word_index] == 2
675 || (word_dud[word_index] == 1 && dud_words > ok_words)) {
676 word = word_it.data();
688 void Textord::clean_small_noise_from_words(
ROW *row) {
690 for (word_it.mark_cycle_pt(); !word_it.cycled_list(); word_it.forward()) {
691 WERD* word = word_it.data();
692 int min_size = static_cast<int>(
695 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
696 C_BLOB* blob = blob_it.data();
697 C_OUTLINE_IT out_it(blob->
out_list());
698 for (out_it.mark_cycle_pt(); !out_it.cycled_list(); out_it.forward()) {
703 delete blob_it.extract();
707 if (!word_it.at_last()) {
710 WERD* next_word = word_it.data_relative(1);
715 delete word_it.extract();
745 void Textord::TransferDiacriticsToBlockGroups(BLOBNBOX_LIST* diacritic_blobs,
746 BLOCK_LIST* blocks) {
749 const double kMaxAngleDiff = 0.01;
751 BLOCK_IT bk_it(blocks);
752 for (bk_it.mark_cycle_pt(); !bk_it.cycled_list(); bk_it.forward()) {
753 BLOCK* block = bk_it.data();
760 float best_angle_diff = FLT_MAX;
761 for (
int g = 0; g < groups.
size(); ++g) {
762 double angle_diff = fabs(block_angle - groups[g]->angle);
763 if (angle_diff > M_PI) angle_diff = fabs(angle_diff - 2.0 * M_PI);
764 if (angle_diff < best_angle_diff) {
765 best_angle_diff = angle_diff;
769 if (best_angle_diff > kMaxAngleDiff) {
775 if (x_height < groups[best_g]->min_xheight)
776 groups[best_g]->min_xheight = x_height;
780 PointerVector<WordWithBox> word_ptrs;
781 for (
int g = 0; g < groups.
size(); ++g) {
782 const BlockGroup* group = groups[g];
783 if (group->bounding_box.null_box())
continue;
784 WordGrid word_grid(group->min_xheight, group->bounding_box.botleft(),
785 group->bounding_box.topright());
786 for (
int b = 0; b < group->blocks.size(); ++b) {
787 ROW_IT row_it(group->blocks[b]->row_list());
788 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
789 ROW* row = row_it.data();
792 for (w_it.mark_cycle_pt(); !w_it.cycled_list(); w_it.forward()) {
793 WERD* word = w_it.data();
794 auto* box_word =
new WordWithBox(word);
795 word_grid.InsertBBox(
true,
true, box_word);
797 word_ptrs.push_back(box_word);
801 FCOORD rotation = group->rotation;
803 rotation.
set_y(-rotation.
y());
804 TransferDiacriticsToWords(diacritic_blobs, rotation, &word_grid);
811 void Textord::TransferDiacriticsToWords(BLOBNBOX_LIST* diacritic_blobs,
815 BLOBNBOX_IT b_it(diacritic_blobs);
819 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
822 blob_box.
rotate(rotation);
823 ws.StartRectSearch(blob_box);
828 WordWithBox* best_above_word =
nullptr;
829 WordWithBox* best_below_word =
nullptr;
830 int best_above_distance = 0;
831 int best_below_distance = 0;
832 for (WordWithBox* word = ws.NextRectSearch(); word !=
nullptr;
833 word = ws.NextRectSearch()) {
836 int x_distance = blob_box.
x_gap(word_box);
837 int y_distance = blob_box.
y_gap(word_box);
838 if (x_distance > 0) {
848 y_distance += x_distance;
851 (best_above_word ==
nullptr || y_distance < best_above_distance)) {
852 best_above_word = word;
853 best_above_distance = y_distance;
856 (best_below_word ==
nullptr || y_distance < best_below_distance)) {
857 best_below_word = word;
858 best_below_distance = y_distance;
862 best_above_word !=
nullptr &&
863 (best_below_word ==
nullptr ||
864 best_above_distance < best_below_distance + blob_box.
height());
866 best_below_word !=
nullptr && best_below_word != best_above_word &&
867 (best_above_word ==
nullptr ||
868 best_below_distance < best_above_distance + blob_box.
height());
871 copied_blob->
rotate(rotation);
873 C_BLOB_IT blob_it(best_below_word->RejBlobs());
874 blob_it.add_to_end(copied_blob);
878 copied_blob->
rotate(rotation);
880 C_BLOB_IT blob_it(best_above_word->RejBlobs());
881 blob_it.add_to_end(copied_blob);
896 double blshift_maxshift,
897 double blshift_xfraction) {
911 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
912 word = word_it.data ();
919 std::vector<int32_t> xstarts(blob_count + row->baseline.segments + 1);
921 std::vector<double> coeffs((blob_count + row->baseline.segments) * 3);
925 xstarts[0] = row->baseline.xcoords[0];
926 for (word_it.mark_cycle_pt (); !word_it.cycled_list (); word_it.forward ()) {
927 word = word_it.data ();
930 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
931 blob_it.forward ()) {
932 blob = blob_it.data ();
934 x_centre = (blob_box.
left () + blob_box.
right ()) / 2.0;
940 if (ydiff < blshift_maxshift
942 if (xstarts[dest_index] >= x_centre)
943 xstarts[dest_index] = blob_box.
left ();
944 coeffs[dest_index * 3] = 0;
945 coeffs[dest_index * 3 + 1] = 0;
946 coeffs[dest_index * 3 + 2] = blob_box.
bottom ();
949 xstarts[dest_index] = blob_box.
right () + 1;
952 if (xstarts[dest_index] <= x_centre) {
953 while (row->baseline.xcoords[src_index + 1] <= x_centre
954 && src_index < row->
baseline.segments - 1) {
955 if (row->baseline.xcoords[src_index + 1] >
956 xstarts[dest_index]) {
957 coeffs[dest_index * 3] =
958 row->baseline.quadratics[src_index].
a;
959 coeffs[dest_index * 3 + 1] =
960 row->baseline.quadratics[src_index].
b;
961 coeffs[dest_index * 3 + 2] =
962 row->baseline.quadratics[src_index].
c;
964 xstarts[dest_index] =
965 row->baseline.xcoords[src_index + 1];
969 coeffs[dest_index * 3] =
970 row->baseline.quadratics[src_index].
a;
971 coeffs[dest_index * 3 + 1] =
972 row->baseline.quadratics[src_index].
b;
973 coeffs[dest_index * 3 + 2] =
974 row->baseline.quadratics[src_index].
c;
976 xstarts[dest_index] = row->baseline.xcoords[src_index + 1];
981 while (src_index < row->
baseline.segments
982 && row->baseline.xcoords[src_index + 1] <= xstarts[dest_index])
984 while (src_index < row->
baseline.segments) {
985 coeffs[dest_index * 3] = row->baseline.quadratics[src_index].
a;
986 coeffs[dest_index * 3 + 1] = row->baseline.quadratics[src_index].
b;
987 coeffs[dest_index * 3 + 2] = row->baseline.quadratics[src_index].
c;
990 xstarts[dest_index] = row->baseline.xcoords[src_index];
993 row->baseline =
QSPLINE(dest_index, &xstarts[0], &coeffs[0]);