68 "Max number of blobs a big blob can overlap");
71 "Fraction of line spacing for quad");
73 "Fraction of line spacing for outlier");
80 "Factor to expand rows by in expand_rows");
85 "New row made if blob makes row this big");
89 "Min blob height/top to include blob top into xheight stats");
91 "Min pile height to make xheight");
93 "Min pile height to make ascheight");
95 "Min pile height to make descheight");
105 #define MAX_HEIGHT_MODES 12
111 static float MakeRowFromBlobs(
float line_size,
112 BLOBNBOX_IT* blob_it, TO_ROW_IT* row_it) {
114 blob_it->move_to_first();
116 float total_size = 0.0f;
119 for (; !blob_it->empty(); blob_it->forward()) {
120 BLOBNBOX* blob = blob_it->extract();
124 row =
new TO_ROW(blob, top, bottom, line_size);
125 row_it->add_before_then_move(row);
127 row->
add_blob(blob, top, bottom, line_size);
129 total_size += top - bottom;
132 return blob_count > 0 ? total_size / blob_count : total_size;
140 C_OUTLINE_IT ol_it(blob->
out_list());
142 ol_it.set_to_list(ol_it.data()->child());
145 for (ol_it.mark_cycle_pt(); !ol_it.cycled_list(); ol_it.forward()) {
151 bb_it.add_after_then_move(bbox);
154 return MakeRowFromBlobs(block->
line_size, &bb_it, row_it);
165 TO_BLOCK* block, TO_BLOCK_LIST* blocks) {
166 BLOBNBOX_IT blob_it = &block->
blobs;
167 TO_ROW_IT row_it = block->
get_rows();
173 if (block->
blobs.singleton() && allow_sub_blobs) {
174 blob_it.move_to_first();
178 }
else if (block->
blobs.empty()) {
183 blob_it.add_after_then_move(bblob);
185 MakeRowFromBlobs(block->
line_size, &blob_it, &row_it);
187 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward())
204 TO_BLOCK_IT block_it;
206 block_it.set_to_list(port_blocks);
207 for (block_it.mark_cycle_pt(); !block_it.cycled_list();
213 block_it.set_to_list(port_blocks);
214 for (block_it.mark_cycle_pt(); !block_it.cycled_list(); block_it.forward()) {
216 block_it.data()->block->bounding_box().left(),
233 TO_ROW_IT row_it = block->
get_rows ();
235 #ifndef GRAPHICS_DISABLED
245 row_it.move_to_first ();
246 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ())
248 #ifndef GRAPHICS_DISABLED
251 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
272 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
273 const TBOX& box = blob_it.data()->bounding_box();
276 double error = lms.
Fit(&m, &c);
288 TO_BLOCK_LIST *blocks,
299 TO_BLOCK_IT block_it = blocks;
304 for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
305 block_it.forward ()) {
306 POLY_BLOCK* pb = block_it.data()->block->poly_block();
307 if (pb != NULL && !pb->
IsText())
309 row_count += block_it.data ()->get_rows ()->length ();
311 row_it.set_to_list (block_it.data ()->get_rows ());
312 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ())
313 blob_count += row_it.data ()->blob_list ()->length ();
315 if (row_count == 0) {
320 gradients = (
float *)
alloc_mem (blob_count *
sizeof (
float));
322 errors = (
float *)
alloc_mem (blob_count *
sizeof (
float));
323 if (gradients == NULL || errors == NULL)
327 for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
328 block_it.forward ()) {
329 POLY_BLOCK* pb = block_it.data()->block->poly_block();
330 if (pb != NULL && !pb->
IsText())
332 row_it.set_to_list (block_it.data ()->get_rows ());
333 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
334 row = row_it.data ();
335 blob_count = row->
blob_list ()->length ();
340 blob_count /= row_err;
341 for (blob_count /= row_err; blob_count > 0; blob_count--) {
342 gradients[row_index] = row->
line_m ();
349 gradients[row_index] = row->
line_m ();
355 if (row_index == 0) {
357 for (block_it.mark_cycle_pt (); !block_it.cycled_list ();
358 block_it.forward ()) {
359 POLY_BLOCK* pb = block_it.data()->block->poly_block();
360 if (pb != NULL && !pb->
IsText())
362 row_it.set_to_list (block_it.data ()->get_rows ());
363 for (row_it.mark_cycle_pt (); !row_it.cycled_list ();
365 row = row_it.data ();
366 gradients[row_index] = row->
line_m ();
372 row_count = row_index;
374 gradients, row_count);
375 page_m = gradients[row_index];
378 page_err = errors[row_index];
398 (overlap * 2 < ibox.
width() && overlap < dotbox.
width()))
408 const double kHeightFraction = 0.6;
409 double target_height =
MIN(dotbox.
bottom(), ibox.
top());
411 target_height *= kHeightFraction;
412 int left_min = dotbox.
left() - dotbox.
width();
413 int middle = (dotbox.
left() + dotbox.
right())/2;
414 int right_max = dotbox.
right() + dotbox.
width();
419 bool found_left =
false;
420 bool found_right =
false;
421 bool in_left =
false;
422 bool in_right =
false;
424 C_OUTLINE_IT o_it = blob->
out_list();
425 for (o_it.mark_cycle_pt(); !o_it.cycled_list(); o_it.forward()) {
429 for (
int step = 0; step < length; pos += outline->
step(step++)) {
432 if (x >= left_min && x < middle && !found_left) {
435 if (y > left_maxy) left_maxy = y;
436 if (y < left_miny) left_miny = y;
438 left_maxy = left_miny = y;
441 }
else if (in_left) {
443 if (left_maxy - left_miny > target_height) {
450 if (x <= right_max && x > middle && !found_right) {
453 if (y > right_maxy) right_maxy = y;
454 if (y < right_miny) right_miny = y;
456 right_maxy = right_miny = y;
459 }
else if (in_right) {
461 if (right_maxy - right_miny > target_height) {
474 TO_ROW_IT row_it = block->
get_rows ();
475 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
476 TO_ROW* row = row_it.data();
480 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
485 STATS hstats(0, max_height + 1);
486 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
492 float xheight = hstats.
median();
495 for (b_it.mark_cycle_pt(); !b_it.cycled_list(); b_it.forward()) {
501 if (dot_of_i(blob, prev, row))
504 if (!b_it.at_last()) {
505 BLOBNBOX* next = b_it.data_relative(1);
506 if (dot_of_i(blob, next, row))
510 delete blob->
cblob();
511 delete b_it.extract();
533 BLOBNBOX_IT blob_it = &block->
blobs;
534 TO_ROW_IT row_it = block->
get_rows ();
536 #ifndef GRAPHICS_DISABLED
553 expand_rows(page_tr, block, gradient, rotation, block_edge, testing_on);
554 blob_it.set_to_list (&block->
blobs);
555 row_it.set_to_list (block->
get_rows ());
556 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ())
557 blob_it.add_list_after (row_it.data ()->blob_list ());
561 blob_it.set_to_list (&block->
blobs);
565 blob_it.set_to_list (&block->
blobs);
595 TO_ROW_IT row_it = block->
get_rows ();
596 BLOBNBOX_IT blob_it = &block->
blobs;
598 if (row_it.length () == 0)
603 min_y = block_box.
bottom () - 1;
604 max_y = block_box.
top () + 1;
605 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
606 line_index = (
inT32) floor (row_it.data ()->intercept ());
607 if (line_index <= min_y)
608 min_y = line_index - 1;
609 if (line_index >= max_y)
610 max_y = line_index + 1;
612 line_count = max_y - min_y + 1;
617 if (deltas == NULL || occupation == NULL)
628 max_y - min_y + 1, occupation, deltas);
629 #ifndef GRAPHICS_DISABLED
635 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
636 row = row_it.data ();
638 distance = deltas[line_index - min_y];
640 line_index, &row_it, testing_on)) {
641 #ifndef GRAPHICS_DISABLED
646 blob_it.add_list_after (row_it.data ()->blob_list ());
647 delete row_it.extract ();
650 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
651 blob_it.add_list_after (row_it.data ()->blob_list ());
680 tprintf (
"Row at %g(%g), dropout dist=%d,",
684 abs_dist = -distance;
690 if (abs_dist > dist_limit) {
692 tprintf (
" too far - deleting\n");
696 if ((distance < 0 && !row_it->at_last ())
697 || (distance >= 0 && !row_it->at_first ())) {
698 row_offset = row_inc;
700 next_row = row_it->data_relative (row_offset);
703 && next_index < line_index
704 && next_index > line_index + distance + distance)
706 && next_index > line_index
707 && next_index < line_index + distance + distance)) {
709 tprintf (
" nearer neighbour (%d) at %g\n",
710 line_index + distance - next_index,
715 else if (next_index == line_index
716 || next_index == line_index + distance + distance) {
719 tprintf (
" equal but more believable at %g (%g/%g)\n",
727 row_offset += row_inc;
729 while ((next_index == line_index
730 || next_index == line_index + distance + distance)
731 && row_offset < row_it->length ());
753 TO_ROW_IT row_it = block->
get_rows ();
758 length = sqrt (gradient * gradient + 1);
759 rotation =
FCOORD (1 / length, -gradient / length);
760 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
761 row = row_it.data ();
763 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
764 blob_it.forward ()) {
765 blob = blob_it.data ();
767 blob_box.
rotate (rotation);
795 TO_ROW_IT row_it = block->
get_rows ();
802 line_count = max_y - min_y + 1;
803 length = sqrt (gradient * gradient + 1);
804 rotation =
FCOORD (1 / length, -gradient / length);
805 for (line_index = 0; line_index < line_count; line_index++)
806 deltas[line_index] = 0;
807 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
808 row = row_it.data ();
810 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
811 blob_it.forward ()) {
812 blob = blob_it.data ();
814 blob_box.
rotate (rotation);
815 top = blob_box.
top ();
816 bottom = blob_box.
bottom ();
819 if ((
inT32) floor (bottom) < min_y
820 || (
inT32) floor (bottom) - min_y >= line_count)
825 index = (
inT32) floor (bottom) - min_y;
826 deltas[index] += width;
827 if ((
inT32) floor (top) < min_y
828 || (
inT32) floor (top) - min_y >= line_count)
830 "Bad y coord of top, " INT32FORMAT
"(" INT32FORMAT
","
831 INT32FORMAT
")\n", (
inT32) floor (top), min_y, max_y);
832 index = (
inT32) floor (top) - min_y;
833 deltas[index] -= width;
836 occupation[0] = deltas[0];
837 for (line_index = 1; line_index < line_count; line_index++)
838 occupation[line_index] = occupation[line_index - 1] + deltas[line_index];
865 if (low_window + high_window < line_count) {
866 for (sum = 0, high_index = 0; high_index < low_window; high_index++)
867 sum += occupation[high_index];
868 for (low_index = 0; low_index < high_window; low_index++, high_index++)
869 sum += occupation[high_index];
870 min_occ = occupation[0];
872 for (test_index = 1; test_index < high_index; test_index++) {
873 if (occupation[test_index] <= min_occ) {
874 min_occ = occupation[test_index];
875 min_index = test_index;
878 for (line_index = 0; line_index < low_window; line_index++)
879 thresholds[line_index] = (sum - min_occ) / divisor + min_occ;
881 for (low_index = 0; high_index < line_count; low_index++, high_index++) {
882 sum -= occupation[low_index];
883 sum += occupation[high_index];
884 if (occupation[high_index] <= min_occ) {
886 min_occ = occupation[high_index];
887 min_index = high_index;
890 if (min_index <= low_index) {
891 min_occ = occupation[low_index + 1];
892 min_index = low_index + 1;
893 for (test_index = low_index + 2; test_index <= high_index;
895 if (occupation[test_index] <= min_occ) {
896 min_occ = occupation[test_index];
898 min_index = test_index;
902 thresholds[line_index++] = (sum - min_occ) / divisor + min_occ;
906 min_occ = occupation[0];
908 for (sum = 0, low_index = 0; low_index < line_count; low_index++) {
909 if (occupation[low_index] < min_occ) {
910 min_occ = occupation[low_index];
911 min_index = low_index;
913 sum += occupation[low_index];
917 for (; line_index < line_count; line_index++)
918 thresholds[line_index] = (sum - min_occ) / divisor + min_occ;
937 inT32 prev_threshold;
939 distance = -line_count;
944 prev_threshold = thresholds[line_index];
946 thresholds[line_index] = distance;
949 while (line_index < line_count
950 && (occupation[line_index] < thresholds[line_index]
951 || occupation[line_index - 1] >= prev_threshold));
952 if (line_index < line_count) {
953 back_index = line_index - 1;
955 while (next_dist < -distance && back_index >= 0) {
956 thresholds[back_index] = next_dist;
964 while (line_index < line_count);
985 float y_bottom, y_top;
989 BLOBNBOX_IT blob_it = &block->
blobs;
990 TO_ROW_IT row_it = block->
get_rows ();
992 #ifndef GRAPHICS_DISABLED
1001 if (block->
get_rows ()->length () == 0)
1007 if (block->
get_rows ()->length () == 0)
1016 row_it.move_to_last ();
1018 row = row_it.data ();
1019 y_max = row->
max_y ();
1020 y_min = row->
min_y ();
1026 if (y_min > y_bottom) {
1028 tprintf(
"Expanding bottom of row at %f from %f to %f\n",
1031 swallowed_row =
TRUE;
1032 while (swallowed_row && !row_it.at_last ()) {
1033 swallowed_row =
FALSE;
1035 test_row = row_it.data_relative (1);
1037 if (test_row->
max_y () > y_bottom) {
1038 if (test_row->
min_y () > y_bottom) {
1042 #ifndef GRAPHICS_DISABLED
1050 blob_it.set_to_list (row->
blob_list ());
1051 blob_it.add_list_after (test_row->
blob_list ());
1053 delete row_it.extract ();
1055 swallowed_row =
TRUE;
1057 else if (test_row->
max_y () < y_min) {
1059 y_bottom = test_row->
max_y ();
1061 tprintf(
"Truncating limit to %f due to touching row at %f\n",
1067 tprintf(
"Not expanding limit beyond %f due to touching row at %f\n",
1074 if (y_max < y_top) {
1076 tprintf(
"Expanding top of row at %f from %f to %f\n",
1078 swallowed_row =
TRUE;
1079 while (swallowed_row && !row_it.at_first ()) {
1080 swallowed_row =
FALSE;
1082 test_row = row_it.data_relative (-1);
1083 if (test_row->
min_y () < y_top) {
1084 if (test_row->
max_y () < y_top) {
1088 blob_it.set_to_list (row->
blob_list ());
1089 #ifndef GRAPHICS_DISABLED
1097 blob_it.add_list_after (test_row->
blob_list ());
1099 delete row_it.extract ();
1101 swallowed_row =
TRUE;
1103 else if (test_row->
min_y () < y_max) {
1105 y_top = test_row->
min_y ();
1107 tprintf(
"Truncating limit to %f due to touching row at %f\n",
1113 tprintf(
"Not expanding limit beyond %f due to touching row at %f\n",
1124 while (!row_it.at_last ());
1140 TO_ROW_IT row_it = block->
get_rows ();
1143 tprintf(
"Adjusting row limits for block(%d,%d)\n",
1146 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1147 row = row_it.data ();
1150 tprintf(
"Row at %f has min %f, max %f, size %f\n",
1177 TO_ROW_IT row_it = block->
get_rows ();
1179 inT16 rowcount = row_it.length ();
1187 row_it.move_to_last ();
1189 row = row_it.data ();
1190 if (prev_row != NULL) {
1191 rows[rowcount++] = prev_row;
1194 tprintf (
"Row at %g yields spacing of %g\n",
1200 while (!row_it.at_last ());
1205 tprintf (
"Blob based spacing=(%g,%g), offset=%g",
1210 iqr = rows[row_index]->
spacing;
1213 iqr -= rows[row_index]->
spacing;
1216 block->
key_row = rows[row_index];
1218 tprintf (
" row based=%g(%g)", rows[row_index]->spacing, iqr);
1222 if (rows[row_index]->spacing < block->line_spacing
1223 && rows[row_index]->spacing > block->
line_size)
1227 else if (rows[row_index]->spacing > block->
line_spacing)
1232 if (rows[row_index]->spacing < block->line_spacing)
1248 tprintf (
"\nEstimate line size=%g, spacing=%g, offset=%g\n",
1290 inT32 min_height, max_height;
1291 TO_ROW_IT row_it = block->
get_rows();
1292 if (row_it.empty())
return;
1297 STATS row_asc_xheights(min_height, max_height + 1);
1298 STATS row_asc_ascrise(static_cast<int>(min_height * asc_frac_xheight),
1299 static_cast<int>(max_height * asc_frac_xheight) + 1);
1300 int min_desc_height =
static_cast<int>(min_height * desc_frac_xheight);
1301 int max_desc_height =
static_cast<int>(max_height * desc_frac_xheight);
1302 STATS row_asc_descdrop(min_desc_height, max_desc_height + 1);
1303 STATS row_desc_xheights(min_height, max_height + 1);
1304 STATS row_desc_descdrop(min_desc_height, max_desc_height + 1);
1305 STATS row_cap_xheights(min_height, max_height + 1);
1306 STATS row_cap_floating_xheights(min_height, max_height + 1);
1307 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1308 row = row_it.data();
1316 row_asc_xheights.
add(static_cast<inT32>(row->
xheight),
1318 row_asc_ascrise.
add(static_cast<inT32>(row->
ascrise),
1320 row_asc_descdrop.
add(static_cast<inT32>(-row->
descdrop),
1323 row_desc_xheights.
add(static_cast<inT32>(row->
xheight),
1325 row_desc_descdrop.
add(static_cast<inT32>(-row->
descdrop),
1329 &row_cap_xheights, &row_cap_floating_xheights);
1333 float xheight = 0.0;
1334 float ascrise = 0.0;
1335 float descdrop = 0.0;
1339 xheight = row_asc_xheights.
median();
1340 ascrise = row_asc_ascrise.
median();
1341 descdrop = -row_asc_descdrop.
median();
1342 }
else if (row_desc_xheights.
get_total() > 0) {
1344 xheight = row_desc_xheights.
median();
1345 descdrop = -row_desc_descdrop.
median();
1346 }
else if (row_cap_xheights.
get_total() > 0) {
1357 min_height, max_height, &(xheight), &(ascrise));
1365 bool corrected_xheight =
false;
1368 corrected_xheight =
true;
1370 if (corrected_xheight || ascrise <= 0.0) {
1371 ascrise = xheight * asc_frac_xheight;
1373 if (corrected_xheight || descdrop >= 0.0) {
1374 descdrop = -(xheight * desc_frac_xheight);
1379 tprintf(
"Block average xheight=%.4f, ascrise=%.4f, descdrop=%.4f\n",
1380 xheight, ascrise, descdrop);
1383 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1399 int block_line_size) {
1407 int min_height, max_height;
1409 STATS heights(min_height, max_height + 1);
1410 STATS floating_heights(min_height, max_height + 1);
1412 &heights, &floating_heights);
1418 rotation.
y() == 0.0,
1419 min_height, max_height,
1423 row->
descdrop =
static_cast<float>(
1437 int max_height,
STATS *heights,
STATS *floating_heights) {
1444 if (blob_it.empty())
return;
1445 bool has_rep_chars =
1448 blob = blob_it.data();
1457 top -= gradient * xcentre + row->
parallel_c();
1458 if (top >= min_height && top <= max_height) {
1459 heights->
add(static_cast<inT32>(floor(top + 0.5)), 1);
1461 floating_heights->
add(static_cast<inT32>(floor(top + 0.5)), 1);
1469 while (!blob_it.at_first() &&
1470 blob_it.data()->repeated_set() == repeated_set) {
1473 tprintf(
"Skipping repeated char when computing xheight\n");
1478 }
while (!blob_it.at_first());
1498 STATS *heights,
STATS *floating_heights,
bool cap_only,
int min_height,
1499 int max_height,
float *xheight,
float *ascrise) {
1500 int blob_index = heights->
mode();
1501 int blob_count = heights->
pile_count(blob_index);
1503 tprintf(
"min_height=%d, max_height=%d, mode=%d, count=%d, total=%d\n",
1504 min_height, max_height, blob_index, blob_count,
1507 floating_heights->
print();
1509 if (blob_count == 0)
return 0;
1511 bool in_best_pile =
FALSE;
1516 if (cap_only && mode_count > 1)
1520 tprintf(
"found %d modes: ", mode_count);
1521 for (x = 0; x < mode_count; x++)
tprintf(
"%d ", modes[x]);
1525 for (x = 0; x < mode_count - 1; x++) {
1526 if (modes[x] != prev_size + 1)
1527 in_best_pile =
FALSE;
1528 int modes_x_count = heights->
pile_count(modes[x]) -
1531 (in_best_pile || modes_x_count > best_count)) {
1532 for (
int asc = x + 1; asc < mode_count; asc++) {
1534 static_cast<float>(modes[asc]) / static_cast<float>(modes[x]);
1539 if (modes_x_count > best_count) {
1540 in_best_pile =
true;
1541 best_count = modes_x_count;
1544 tprintf(
"X=%d, asc=%d, count=%d, ratio=%g\n",
1545 modes[x], modes[asc]-modes[x], modes_x_count, ratio);
1547 prev_size = modes[x];
1548 *xheight =
static_cast<float>(modes[x]);
1549 *ascrise =
static_cast<float>(modes[asc] - modes[x]);
1554 if (*xheight == 0) {
1561 if (floating_heights->
get_total() > 0) {
1562 for (x = min_height; x < max_height; ++x) {
1565 blob_index = heights->
mode();
1566 for (x = min_height; x < max_height; ++x) {
1570 *xheight =
static_cast<float>(blob_index);
1572 best_count = heights->
pile_count(blob_index);
1574 tprintf(
"Single mode xheight set to %g\n", *xheight);
1576 tprintf(
"Multi-mode xheight set to %g, asc=%g\n", *xheight, *ascrise);
1594 int xheight_blob_count,
STATS *asc_heights) {
1598 i_min =
static_cast<int>(
1605 int num_potential_asc = 0;
1606 for (
int i = i_min; i <= i_max; ++i) {
1607 num_potential_asc += asc_heights->
pile_count(i);
1617 STATS heights (min_height, max_height + 1);
1618 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1619 blob = blob_it.data();
1623 height = (gradient * xcentre + row->
parallel_c() -
1625 if (height >= min_height && height <= max_height)
1626 heights.
add(static_cast<int>(floor(height + 0.5)), 1);
1629 int blob_index = heights.
mode();
1630 int blob_count = heights.
pile_count(blob_index);
1631 float total_fraction =
1633 if (static_cast<float>(blob_count + num_potential_asc) <
1634 xheight_blob_count * total_fraction) {
1637 int descdrop = blob_count > 0 ? -blob_index : 0;
1639 tprintf(
"Descdrop: %d (potential ascenders %d, descenders %d)\n",
1640 descdrop, num_potential_asc, blob_count);
1665 src_count = max_height + 1 - min_height;
1669 for (src_index = 0; src_index < src_count; src_index++) {
1670 pile_count = heights->
pile_count(min_height + src_index);
1671 if (pile_count > 0) {
1672 if (dest_count < maxmodes) {
1673 if (pile_count < least_count) {
1675 least_count = pile_count;
1676 least_index = dest_count;
1678 modes[dest_count++] = min_height + src_index;
1679 }
else if (pile_count >= least_count) {
1680 while (least_index < maxmodes - 1) {
1681 modes[least_index] = modes[least_index + 1];
1686 modes[maxmodes - 1] = min_height + src_index;
1687 if (pile_count == least_count) {
1689 least_index = maxmodes - 1;
1693 for (dest_count = 1; dest_count < maxmodes; dest_count++) {
1694 pile_count = heights->
pile_count(modes[dest_count]);
1695 if (pile_count < least_count) {
1697 least_count = pile_count;
1698 least_index = dest_count;
1716 float ascrise,
float descdrop) {
1719 tprintf(
"correcting row xheight: row->xheight %.4f"
1720 ", row->acrise %.4f row->descdrop %.4f\n",
1723 bool normal_xheight =
1740 (normal_xheight || cap_xheight)) ||
1741 (row_category ==
ROW_UNKNOWN && normal_xheight)) {
1765 if (row->
xheight < xheight + ascrise && row->
xheight > xheight) {
1768 tprintf(
"all caps with irregular xheight\n");
1777 tprintf(
"corrected row->xheight = %.4f, row->acrise = %.4f, row->descdrop"
1782 static int CountOverlaps(
const TBOX& box,
int min_height,
1783 BLOBNBOX_LIST* blobs) {
1785 BLOBNBOX_IT blob_it(blobs);
1786 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1813 BLOBNBOX_IT blob_it;
1817 TO_ROW_IT row_it = block->
get_rows();
1822 length = sqrt(1 + gradient * gradient);
1823 g_vec =
FCOORD(1 / length, -gradient / length);
1824 blob_rotation =
FCOORD(rotation.
x(), -rotation.
y());
1825 blob_rotation.
rotate(g_vec);
1826 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1827 row = row_it.data();
1830 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list();
1831 blob_it.forward()) {
1832 blob = blob_it.data();
1840 rotated_blob, static_cast<inT16>(row->
intercept()),
1845 under_it.add_after_then_move(blob_it.extract());
1847 tprintf(
"Underlined blob at:");
1848 rotated_blob->bounding_box().print();
1852 }
else if (CountOverlaps(blob->
bounding_box(), min_blob_height,
1855 large_it.add_after_then_move(blob_it.extract());
1857 tprintf(
"Large blob overlaps %d blobs at:",
1858 CountOverlaps(blob_box, min_blob_height,
1863 delete rotated_blob;
1881 #ifndef GRAPHICS_DISABLED
1888 BLOBNBOX_IT blob_it;
1889 BLOBNBOX_IT start_it;
1890 TO_ROW_IT row_it = block->
get_rows ();
1892 #ifndef GRAPHICS_DISABLED
1896 blob_rotation =
FCOORD (rotation.
x (), -rotation.
y ());
1897 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1899 blob_it.set_to_list (row_it.data ()->blob_list ());
1900 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
1901 blob_it.forward ()) {
1902 blob = blob_it.data ();
1915 if (!blob_it.at_last ()) {
1916 nextblob = blob_it.data_relative(1);
1919 blob->
merge(nextblob);
1926 blob->
chop (&start_it, &blob_it,
1932 #ifndef GRAPHICS_DISABLED
1937 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
1938 blob_it.forward ()) {
1939 blob = blob_it.data ();
1941 blob_box.
rotate (rotation);
1944 blob_box.
right (), blob_box.
top ());
1968 #ifndef GRAPHICS_DISABLED
1971 TO_ROW_IT row_it = block->
get_rows ();
1973 row_it.move_to_first ();
1974 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1975 if (row_it.data ()->blob_list ()->empty ())
1976 delete row_it.extract ();
1980 #ifndef GRAPHICS_DISABLED
1983 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1985 block_edge, colour, rotation);
2010 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
2011 if (!blob_it.data()->joined_to_prev()) {
2012 const TBOX& box = blob_it.data()->bounding_box();
2020 error = lms.
Fit(&gradient, &c);
2036 #ifndef GRAPHICS_DISABLED
2039 TO_ROW_IT row_it = block->
get_rows ();
2041 row_it.move_to_first ();
2042 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
2043 if (row_it.data ()->blob_list ()->empty ())
2044 delete row_it.extract ();
2049 #ifndef GRAPHICS_DISABLED
2052 for (row_it.mark_cycle_pt (); !row_it.cycled_list ();
2053 row_it.forward ()) {
2054 row_it.data ()->baseline.plot (
to_win, colour);
2061 make_old_baselines(block, testing_on, gradient);
2063 #ifndef GRAPHICS_DISABLED
2066 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
2067 row_it.data ()->baseline.plot (
to_win, colour);
2098 xstarts[1] = xstarts[segments];
2100 coeffs = (
double *)
alloc_mem (3 *
sizeof (
double));
2102 coeffs[1] = row->
line_m ();
2103 coeffs[2] = row->
line_c ();
2135 BLOBNBOX_IT blob_it = row->
blob_list ();
2136 BLOBNBOX_IT new_it = blob_it;
2139 needs_curve =
FALSE;
2141 xstarts[0] = box.
left ();
2143 blobcount = row->
blob_list ()->length ();
2145 tprintf (
"Segmenting baseline of %d blobs at (%d,%d)\n",
2149 blob_it.move_to_last ();
2150 box = blob_it.data ()->bounding_box ();
2151 xstarts[1] = box.
right ();
2155 new_it.mark_cycle_pt ();
2158 middle = (new_box.
left () + new_box.
right ()) / 2.0;
2161 yshifts.
add (yshift, blobindex);
2162 if (new_it.cycled_list ()) {
2163 xstarts[1] = new_box.
right ();
2167 for (blobcount = 0; blobcount < textord_spline_medianwin / 2; blobcount++)
2172 yshift = yshifts[textord_spline_medianwin / 2];
2184 xstarts[segments++] = box.
left ();
2188 yshifts.
remove (blobindex - textord_spline_medianwin);
2190 middle = (new_box.
left () + new_box.
right ()) / 2.0;
2192 yshifts.
add (yshift, blobindex);
2196 while (!new_it.cycled_list ());
2198 xstarts[segments] = new_box.
right ();
2201 xstarts[--segments] = new_box.
right ();
2204 tprintf (
"Made %d segments on row at (%d,%d)\n",
2227 int blobs_per_segment;
2231 BLOBNBOX_IT blob_it = row->
blob_list ();
2232 BLOBNBOX_IT new_it = blob_it;
2239 xstarts[0] = box.
left ();
2241 while (!blob_it.at_first ()) {
2248 blobs_per_segment = blobcount / segments;
2249 coeffs = (
double *)
alloc_mem (segments * 3 *
sizeof (
double));
2252 (
"Linear splining baseline of %d blobs at (%d,%d), into %d segments of %d blobs\n",
2253 blobcount, box.
left (), box.
bottom (), segments, blobs_per_segment);
2255 for (index2 = 0; index2 < blobs_per_segment / 2; index2++)
2260 blobindex += blobs_per_segment;
2262 while (index1 < blobindex || (segment == segments && index1 < blobcount)) {
2264 int middle = (box.
left() + box.
right()) / 2;
2267 if (index1 == blobindex - blobs_per_segment / 2
2268 || index1 == blobcount - 1) {
2269 xstarts[segment] = box.
left ();
2273 coeffs[segment * 3 - 3] = 0;
2274 coeffs[segment * 3 - 2] = b;
2275 coeffs[segment * 3 - 1] = c;
2277 if (segment > segments)
2280 blobindex += blobs_per_segment;
2282 while (index2 < blobindex || (segment == segments && index2 < blobcount)) {
2284 int middle = (new_box.
left() + new_box.
right()) / 2;
2287 if (index2 == blobindex - blobs_per_segment / 2
2288 || index2 == blobcount - 1) {
2289 xstarts[segment] = new_box.
left ();
2293 coeffs[segment * 3 - 3] = 0;
2294 coeffs[segment * 3 - 2] = b;
2295 coeffs[segment * 3 - 1] = c;
2298 while (segment <= segments);
2313 BOOL8 reject_misses,
2314 BOOL8 make_new_rows,
2320 float g_length = 1.0f;
2325 float smooth_factor;
2332 BLOBNBOX_IT blob_it = &block->
blobs;
2333 TO_ROW_IT row_it = block->
get_rows ();
2338 if (gradient != NULL)
2339 g_length = sqrt (1 + *gradient * *gradient);
2340 #ifndef GRAPHICS_DISABLED
2346 smooth_factor = 1.0;
2348 row_count = row_it.length ();
2349 if (!blob_it.empty ()) {
2350 left_x = blob_it.data ()->bounding_box ().left ();
2356 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
2357 blob = blob_it.data ();
2358 if (gradient != NULL) {
2363 && last_x - left_x > block->
line_size * 2
2367 / (last_x - left_x);
2373 #ifndef GRAPHICS_DISABLED
2377 if (!row_it.empty ()) {
2378 for (row_it.move_to_first ();
2379 !row_it.at_last () && row_it.data ()->min_y () > top;
2381 row = row_it.data ();
2382 if (row->
min_y () <= top && row->
max_y () >= bottom) {
2390 if (overlap_result ==
NEW_ROW && !reject_misses)
2395 if (!make_new_rows) {
2396 near_dist = row_it.data_relative (-1)->min_y () - top;
2398 if (bottom < row->min_y ()) {
2399 if (row->
min_y () - bottom <=
2407 else if (near_dist > 0
2408 && near_dist < bottom - row->max_y ()) {
2410 dest_row = row_it.data ();
2411 if (dest_row->
min_y () - bottom <=
2419 if (top - row->
max_y () <=
2430 if (overlap_result ==
ASSIGN)
2431 dest_row->
add_blob (blob_it.extract (), top, bottom,
2433 if (overlap_result ==
NEW_ROW) {
2434 if (make_new_rows && top - bottom < block->max_blob_size) {
2436 new TO_ROW (blob_it.extract (), top, bottom,
2439 if (bottom > row_it.data ()->min_y ())
2440 row_it.add_before_then_move (dest_row);
2443 row_it.add_after_then_move (dest_row);
2452 else if (make_new_rows && top - bottom < block->max_blob_size) {
2457 row_it.add_after_then_move(dest_row);
2464 if (overlap_result !=
REJECT) {
2465 tprintf(
"Test blob assigned to row at (%g,%g) on pass %d\n",
2469 tprintf(
"Test blob assigned to no row on pass %d\n", pass);
2472 if (overlap_result !=
REJECT) {
2473 while (!row_it.at_first() &&
2474 row_it.data()->min_y() > row_it.data_relative(-1)->min_y()) {
2475 row = row_it.extract();
2477 row_it.add_before_then_move(row);
2479 while (!row_it.at_last() &&
2480 row_it.data ()->min_y() < row_it.data_relative (1)->min_y()) {
2481 row = row_it.extract();
2484 row_it.add_after_then_move(row);
2486 BLOBNBOX_IT added_blob_it(dest_row->
blob_list());
2487 added_blob_it.move_to_last();
2488 TBOX prev_box = added_blob_it.data_relative(-1)->bounding_box();
2489 if (dest_row->
blob_list()->singleton() ||
2491 block_skew = (1 - smooth_factor) * block_skew
2497 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
2498 if (row_it.data()->blob_list()->empty())
2499 delete row_it.extract();
2520 float merge_top, merge_bottom;
2524 BLOBNBOX_IT blob_it;
2527 row = row_it->data ();
2528 bestover = top - bottom;
2529 if (top > row->
max_y ())
2530 bestover -= top - row->
max_y ();
2531 if (bottom < row->min_y ())
2533 bestover -= row->
min_y () - bottom;
2535 tprintf(
"Test blob y=(%g,%g), row=(%f,%f), size=%g, overlap=%f\n",
2536 bottom, top, row->
min_y(), row->
max_y(), rowsize, bestover);
2540 if (!row_it->at_last ()) {
2542 test_row = row_it->data ();
2543 if (test_row->
min_y () <= top && test_row->
max_y () >= bottom) {
2545 test_row->
max_y () >
2548 test_row->
min_y () <
2550 if (merge_top - merge_bottom <= rowsize) {
2552 tprintf (
"Merging rows at (%g,%g), (%g,%g)\n",
2556 test_row->
set_limits (merge_bottom, merge_top);
2557 blob_it.set_to_list (test_row->
blob_list ());
2558 blob_it.add_list_after (row->
blob_list ());
2560 row_it->backward ();
2561 delete row_it->extract ();
2565 overlap = top - bottom;
2566 if (top > test_row->
max_y ())
2567 overlap -= top - test_row->
max_y ();
2568 if (bottom < test_row->min_y ())
2569 overlap -= test_row->
min_y () - bottom;
2570 if (bestover >= rowsize - 1 && overlap >= rowsize - 1) {
2573 if (overlap > bestover) {
2578 tprintf(
"Test blob y=(%g,%g), row=(%f,%f), size=%g, overlap=%f->%f\n",
2579 bottom, top, test_row->
min_y(), test_row->
max_y(),
2580 rowsize, overlap, bestover);
2585 while (!row_it->at_last ()
2586 && test_row->
min_y () <= top && test_row->
max_y () >= bottom);
2587 while (row_it->data () != row)
2588 row_it->backward ();
2606 const void *item2) {
2628 const void *item2) {
2650 const void *item2) {
2672 int num_repeated_sets = 0;
2673 if (!box_it.empty()) {
2676 int repeat_length = 1;
2679 BLOBNBOX_IT test_it(box_it);
2680 for (test_it.forward(); !test_it.at_first();) {
2681 bblob = test_it.data();
2685 bblob = test_it.data();
2694 num_repeated_sets++;
2695 for (; repeat_length > 0; box_it.forward(), --repeat_length) {
2696 bblob = box_it.data();
2703 }
while (!box_it.at_first());
bool textord_show_expanded_rows
void set_repeated_set(int set_id)
void merge(BLOBNBOX *nextblob)
bool joined_to_prev() const
TBOX deskew_block_coords(TO_BLOCK *block, float gradient)
void expand_rows(ICOORD page_tr, TO_BLOCK *block, float gradient, FCOORD rotation, inT32 block_edge, BOOL8 testing_on)
double textord_descheight_mode_fraction
bool textord_test_landscape
BLOBNBOX_LIST * blob_list()
void rotate(const FCOORD &vec)
double textord_occupancy_threshold
void mark_repeated_chars(TO_ROW *row)
int textord_spline_medianwin
void compute_dropout_distances(inT32 *occupation, inT32 *thresholds, inT32 line_count)
bool textord_old_baselines
bool textord_fix_xheight_bug
void set_num_repeated_sets(int num_sets)
void set_line(float new_m, float new_c, float new_error)
bool textord_show_parallel_rows
const int kMinLeaderCount
void make_initial_textrows(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, BOOL8 testing_on)
int compute_xheight_from_modes(STATS *heights, STATS *floating_heights, bool cap_only, int min_height, int max_height, float *xheight, float *ascrise)
void adjust_row_limits(TO_BLOCK *block)
bool textord_fix_makerow_bug
inT32 choose_nth_item(inT32 index, float *array, inT32 count)
bool textord_show_final_blobs
#define double_VAR(name, val, comment)
void compute_line_occupation(TO_BLOCK *block, float gradient, inT32 min_y, inT32 max_y, inT32 *occupation, inT32 *deltas)
int blob_x_order(const void *item1, const void *item2)
void set_limits(float new_min, float new_max)
int row_spacing_order(const void *item1, const void *item2)
double textord_xheight_mode_fraction
BOOL8 segment_baseline(TO_ROW *row, TO_BLOCK *block, inT32 &segments, inT32 xstarts[])
const ICOORD & start_pos() const
void SetCursor(int x, int y)
void CheckInverseFlagAndDirection()
void compute_page_skew(TO_BLOCK_LIST *blocks, float &page_m, float &page_err)
float believability() const
BLOBNBOX_LIST large_blobs
BOOL8 test_underline(BOOL8 testing_on, C_BLOB *blob, inT16 baseline, inT16 xheight)
static C_OUTLINE * deep_copy(const C_OUTLINE *src)
double Fit(ICOORD *pt1, ICOORD *pt2)
void get_min_max_xheight(int block_linesize, int *min_height, int *max_height)
void separate_underlines(TO_BLOCK *block, float gradient, FCOORD rotation, BOOL8 testing_on)
ICOORD step(int index) const
double textord_min_linesize
bool textord_debug_xheights
int textord_min_blobs_in_row
bool textord_interpolating_skew
static const double kAscenderFraction
int textord_max_blob_overlaps
double textord_descx_ratio_min
void fit_lms_line(TO_ROW *row)
ROW_CATEGORY get_row_category(const TO_ROW *row)
void rotate(const FCOORD vec)
float make_single_row(ICOORD page_tr, bool allow_sub_blobs, TO_BLOCK *block, TO_BLOCK_LIST *blocks)
double textord_spline_outlier_fraction
bool textord_new_initial_xheight
#define INT_VAR(name, val, comment)
TBOX box_next_pre_chopped(BLOBNBOX_IT *it)
const TBOX & bounding_box() const
static const double kDescenderFraction
double textord_underline_width
double * linear_spline_baseline(TO_ROW *row, TO_BLOCK *block, inT32 &segments, inT32 xstarts[])
double textord_excess_blobsize
double textord_expansion_factor
double textord_ascx_ratio_min
bool textord_straight_baselines
OVERLAP_STATE most_overlapping_row(TO_ROW_IT *row_it, TO_ROW *&best_row, float top, float bottom, float rowsize, BOOL8 testing_blob)
void draw_occupation(inT32 xleft, inT32 ybottom, inT32 min_y, inT32 max_y, inT32 occupation[], inT32 thresholds[])
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
void Rectangle(int x1, int y1, int x2, int y2)
int row_y_order(const void *item1, const void *item2)
inT32 pile_count(inT32 value) const
int textord_skewsmooth_offset2
void * alloc_mem(inT32 count)
double ConstrainedFit(const FCOORD &direction, double min_dist, double max_dist, bool debug, ICOORD *line_pt)
BLOBNBOX_LIST noise_blobs
void plot_to_row(TO_ROW *row, ScrollView::Color colour, FCOORD rotation)
inT16 x() const
access function
double textord_xheight_error_margin
int textord_lms_line_trials
void compute_occupation_threshold(inT32 low_window, inT32 high_window, inT32 line_count, inT32 *occupation, inT32 *thresholds)
int textord_spline_minblobs
static C_BLOB * FakeBlob(const TBOX &box)
C_OUTLINE_LIST * out_list()
void compute_row_xheight(TO_ROW *row, const FCOORD &rotation, float gradient, int block_line_size)
inT32 compute_height_modes(STATS *heights, inT32 min_height, inT32 max_height, inT32 *modes, inT32 maxmodes)
int textord_skewsmooth_offset
double textord_ascheight_mode_fraction
int num_repeated_sets() const
void assign_blobs_to_rows(TO_BLOCK *block, float *gradient, int pass, BOOL8 reject_misses, BOOL8 make_new_rows, BOOL8 drawing_skew)
bool textord_parallel_baselines
BOOL8 find_best_dropout_row(TO_ROW *row, inT32 distance, float dist_limit, inT32 line_index, TO_ROW_IT *row_it, BOOL8 testing_on)
bool major_overlap(const TBOX &box) const
bool textord_biased_skewcalc
bool textord_single_height_mode
BLOBNBOX_LIST small_blobs
bool major_x_overlap(const TBOX &box) const
BlobTextFlowType flow() const
double textord_linespace_iqrlimit
void make_baseline_spline(TO_ROW *row, TO_BLOCK *block)
float initial_min_y() const
void Add(const ICOORD &pt)
inT16 y() const
access_function
void fit_parallel_rows(TO_BLOCK *block, float gradient, FCOORD rotation, inT32 block_edge, BOOL8 testing_on)
void make_spline_rows(TO_BLOCK *block, float gradient, BOOL8 testing_on)
FCOORD classify_rotation() const
bool textord_show_final_rows
bool within_error_margin(float test, float num, float margin)
void vigorous_noise_removal(TO_BLOCK *block)
static const double kXHeightFraction
void correct_row_xheight(TO_ROW *row, float xheight, float ascrise, float descdrop)
void plot_parallel_row(TO_ROW *row, float gradient, inT32 left, ScrollView::Color colour, FCOORD rotation)
bool contains(const FCOORD pt) const
void delete_non_dropout_rows(TO_BLOCK *block, float gradient, FCOORD rotation, inT32 block_edge, BOOL8 testing_on)
double textord_chop_width
void pre_associate_blobs(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, BOOL8 testing_on)
C_BLOB * crotate_cblob(C_BLOB *blob, FCOORD rotation)
void add(float value, inT32 key)
EXTERN ScrollView * to_win
void compute_row_stats(TO_BLOCK *block, BOOL8 testing_on)
bool textord_show_initial_rows
float make_rows(ICOORD page_tr, TO_BLOCK_LIST *port_blocks)
double textord_min_blob_height_fraction
inT32 compute_row_descdrop(TO_ROW *row, float gradient, int xheight_blob_count, STATS *asc_heights)
double textord_width_limit
double textord_ascx_ratio_max
bool rep_chars_marked() const
void chop(BLOBNBOX_IT *start_it, BLOBNBOX_IT *blob_it, FCOORD rotation, float xheight)
void fill_heights(TO_ROW *row, float gradient, int min_height, int max_height, STATS *heights, STATS *floating_heights)
#define BOOL_VAR(name, val, comment)
double textord_spline_shift_fraction
float MakeRowFromSubBlobs(TO_BLOCK *block, C_BLOB *blob, TO_ROW_IT *row_it)
void set_parallel_line(float gradient, float new_c, float new_error)
void error(const char *caller, TessErrorLogCode action, const char *format,...) const
ScrollView * create_to_win(ICOORD page_tr)
void DrawTo(int x, int y)
void free_mem(void *oldchunk)
void cleanup_rows_making(ICOORD page_tr, TO_BLOCK *block, float gradient, FCOORD rotation, inT32 block_edge, BOOL8 testing_on)
void add(inT32 value, inT32 count)
void add_blob(BLOBNBOX *blob, float top, float bottom, float row_size)
EXTERN bool textord_oldbl_debug
static const double kXHeightCapRatio
double textord_descx_ratio_max
void fit_parallel_lms(float gradient, TO_ROW *row)
void compute_block_xheight(TO_BLOCK *block, float gradient)