41 #include "config_auto.h"
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();
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();
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();
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))
511 delete blob->
cblob();
512 delete b_it.extract();
534 BLOBNBOX_IT blob_it = &block->
blobs;
535 TO_ROW_IT row_it = block->
get_rows ();
537 #ifndef GRAPHICS_DISABLED
554 expand_rows(page_tr, block, gradient, rotation, block_edge, testing_on);
555 blob_it.set_to_list (&block->
blobs);
556 row_it.set_to_list (block->
get_rows ());
557 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ())
558 blob_it.add_list_after (row_it.data ()->blob_list ());
562 blob_it.set_to_list (&block->
blobs);
566 blob_it.set_to_list (&block->
blobs);
596 TO_ROW_IT row_it = block->
get_rows ();
597 BLOBNBOX_IT blob_it = &block->
blobs;
599 if (row_it.length () == 0)
604 min_y = block_box.
bottom () - 1;
605 max_y = block_box.
top () + 1;
606 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
607 line_index = (
inT32) floor (row_it.data ()->intercept ());
608 if (line_index <= min_y)
609 min_y = line_index - 1;
610 if (line_index >= max_y)
611 max_y = line_index + 1;
613 line_count = max_y - min_y + 1;
618 if (deltas ==
NULL || occupation ==
NULL)
629 max_y - min_y + 1, occupation, deltas);
630 #ifndef GRAPHICS_DISABLED
636 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
637 row = row_it.data ();
639 distance = deltas[line_index - min_y];
641 line_index, &row_it, testing_on)) {
642 #ifndef GRAPHICS_DISABLED
647 blob_it.add_list_after (row_it.data ()->blob_list ());
648 delete row_it.extract ();
651 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
652 blob_it.add_list_after (row_it.data ()->blob_list ());
681 tprintf (
"Row at %g(%g), dropout dist=%d,",
685 abs_dist = -distance;
691 if (abs_dist > dist_limit) {
693 tprintf (
" too far - deleting\n");
697 if ((distance < 0 && !row_it->at_last ())
698 || (distance >= 0 && !row_it->at_first ())) {
699 row_offset = row_inc;
701 next_row = row_it->data_relative (row_offset);
704 && next_index < line_index
705 && next_index > line_index + distance + distance)
707 && next_index > line_index
708 && next_index < line_index + distance + distance)) {
710 tprintf (
" nearer neighbour (%d) at %g\n",
711 line_index + distance - next_index,
716 else if (next_index == line_index
717 || next_index == line_index + distance + distance) {
720 tprintf (
" equal but more believable at %g (%g/%g)\n",
728 row_offset += row_inc;
730 while ((next_index == line_index
731 || next_index == line_index + distance + distance)
732 && row_offset < row_it->length ());
754 TO_ROW_IT row_it = block->
get_rows ();
759 length = sqrt (gradient * gradient + 1);
760 rotation =
FCOORD (1 / length, -gradient / length);
761 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
762 row = row_it.data ();
764 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
765 blob_it.forward ()) {
766 blob = blob_it.data ();
768 blob_box.
rotate (rotation);
796 TO_ROW_IT row_it = block->
get_rows ();
803 line_count = max_y - min_y + 1;
804 length = sqrt (gradient * gradient + 1);
805 rotation =
FCOORD (1 / length, -gradient / length);
806 for (line_index = 0; line_index < line_count; line_index++)
807 deltas[line_index] = 0;
808 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
809 row = row_it.data ();
811 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
812 blob_it.forward ()) {
813 blob = blob_it.data ();
815 blob_box.
rotate (rotation);
816 top = blob_box.
top ();
817 bottom = blob_box.
bottom ();
820 if ((
inT32) floor (bottom) < min_y
821 || (
inT32) floor (bottom) - min_y >= line_count)
826 index = (
inT32) floor (bottom) - min_y;
827 deltas[index] += width;
828 if ((
inT32) floor (top) < min_y
829 || (
inT32) floor (top) - min_y >= line_count)
831 "Bad y coord of top, " INT32FORMAT
"(" INT32FORMAT
","
832 INT32FORMAT
")\n", (
inT32) floor (top), min_y, max_y);
833 index = (
inT32) floor (top) - min_y;
834 deltas[index] -= width;
837 occupation[0] = deltas[0];
838 for (line_index = 1; line_index < line_count; line_index++)
839 occupation[line_index] = occupation[line_index - 1] + deltas[line_index];
866 if (low_window + high_window < line_count) {
867 for (sum = 0, high_index = 0; high_index < low_window; high_index++)
868 sum += occupation[high_index];
869 for (low_index = 0; low_index < high_window; low_index++, high_index++)
870 sum += occupation[high_index];
871 min_occ = occupation[0];
873 for (test_index = 1; test_index < high_index; test_index++) {
874 if (occupation[test_index] <= min_occ) {
875 min_occ = occupation[test_index];
876 min_index = test_index;
879 for (line_index = 0; line_index < low_window; line_index++)
880 thresholds[line_index] = (sum - min_occ) / divisor + min_occ;
882 for (low_index = 0; high_index < line_count; low_index++, high_index++) {
883 sum -= occupation[low_index];
884 sum += occupation[high_index];
885 if (occupation[high_index] <= min_occ) {
887 min_occ = occupation[high_index];
888 min_index = high_index;
891 if (min_index <= low_index) {
892 min_occ = occupation[low_index + 1];
893 min_index = low_index + 1;
894 for (test_index = low_index + 2; test_index <= high_index;
896 if (occupation[test_index] <= min_occ) {
897 min_occ = occupation[test_index];
899 min_index = test_index;
903 thresholds[line_index++] = (sum - min_occ) / divisor + min_occ;
907 min_occ = occupation[0];
909 for (sum = 0, low_index = 0; low_index < line_count; low_index++) {
910 if (occupation[low_index] < min_occ) {
911 min_occ = occupation[low_index];
912 min_index = low_index;
914 sum += occupation[low_index];
918 for (; line_index < line_count; line_index++)
919 thresholds[line_index] = (sum - min_occ) / divisor + min_occ;
938 inT32 prev_threshold;
940 distance = -line_count;
945 prev_threshold = thresholds[line_index];
947 thresholds[line_index] = distance;
950 while (line_index < line_count
951 && (occupation[line_index] < thresholds[line_index]
952 || occupation[line_index - 1] >= prev_threshold));
953 if (line_index < line_count) {
954 back_index = line_index - 1;
956 while (next_dist < -distance && back_index >= 0) {
957 thresholds[back_index] = next_dist;
965 while (line_index < line_count);
986 float y_bottom, y_top;
990 BLOBNBOX_IT blob_it = &block->
blobs;
991 TO_ROW_IT row_it = block->
get_rows ();
993 #ifndef GRAPHICS_DISABLED
1002 if (block->
get_rows ()->length () == 0)
1008 if (block->
get_rows ()->length () == 0)
1017 row_it.move_to_last ();
1019 row = row_it.data ();
1020 y_max = row->
max_y ();
1021 y_min = row->
min_y ();
1027 if (y_min > y_bottom) {
1029 tprintf(
"Expanding bottom of row at %f from %f to %f\n",
1032 swallowed_row =
TRUE;
1033 while (swallowed_row && !row_it.at_last ()) {
1034 swallowed_row =
FALSE;
1036 test_row = row_it.data_relative (1);
1038 if (test_row->
max_y () > y_bottom) {
1039 if (test_row->
min_y () > y_bottom) {
1043 #ifndef GRAPHICS_DISABLED
1051 blob_it.set_to_list (row->
blob_list ());
1052 blob_it.add_list_after (test_row->
blob_list ());
1054 delete row_it.extract ();
1056 swallowed_row =
TRUE;
1058 else if (test_row->
max_y () < y_min) {
1060 y_bottom = test_row->
max_y ();
1062 tprintf(
"Truncating limit to %f due to touching row at %f\n",
1068 tprintf(
"Not expanding limit beyond %f due to touching row at %f\n",
1075 if (y_max < y_top) {
1077 tprintf(
"Expanding top of row at %f from %f to %f\n",
1079 swallowed_row =
TRUE;
1080 while (swallowed_row && !row_it.at_first ()) {
1081 swallowed_row =
FALSE;
1083 test_row = row_it.data_relative (-1);
1084 if (test_row->
min_y () < y_top) {
1085 if (test_row->
max_y () < y_top) {
1089 blob_it.set_to_list (row->
blob_list ());
1090 #ifndef GRAPHICS_DISABLED
1098 blob_it.add_list_after (test_row->
blob_list ());
1100 delete row_it.extract ();
1102 swallowed_row =
TRUE;
1104 else if (test_row->
min_y () < y_max) {
1106 y_top = test_row->
min_y ();
1108 tprintf(
"Truncating limit to %f due to touching row at %f\n",
1114 tprintf(
"Not expanding limit beyond %f due to touching row at %f\n",
1125 while (!row_it.at_last ());
1141 TO_ROW_IT row_it = block->
get_rows ();
1144 tprintf(
"Adjusting row limits for block(%d,%d)\n",
1147 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1148 row = row_it.data ();
1151 tprintf(
"Row at %f has min %f, max %f, size %f\n",
1178 TO_ROW_IT row_it = block->
get_rows ();
1180 inT16 rowcount = row_it.length ();
1188 row_it.move_to_last ();
1190 row = row_it.data ();
1191 if (prev_row !=
NULL) {
1192 rows[rowcount++] = prev_row;
1195 tprintf (
"Row at %g yields spacing of %g\n",
1201 while (!row_it.at_last ());
1206 tprintf (
"Blob based spacing=(%g,%g), offset=%g",
1211 iqr = rows[row_index]->
spacing;
1214 iqr -= rows[row_index]->
spacing;
1217 block->
key_row = rows[row_index];
1219 tprintf (
" row based=%g(%g)", rows[row_index]->spacing, iqr);
1223 if (rows[row_index]->spacing < block->line_spacing
1224 && rows[row_index]->spacing > block->
line_size)
1228 else if (rows[row_index]->spacing > block->
line_spacing)
1233 if (rows[row_index]->spacing < block->line_spacing)
1249 tprintf (
"\nEstimate line size=%g, spacing=%g, offset=%g\n",
1291 inT32 min_height, max_height;
1292 TO_ROW_IT row_it = block->
get_rows();
1293 if (row_it.empty())
return;
1298 STATS row_asc_xheights(min_height, max_height + 1);
1299 STATS row_asc_ascrise(static_cast<int>(min_height * asc_frac_xheight),
1300 static_cast<int>(max_height * asc_frac_xheight) + 1);
1301 int min_desc_height =
static_cast<int>(min_height * desc_frac_xheight);
1302 int max_desc_height =
static_cast<int>(max_height * desc_frac_xheight);
1303 STATS row_asc_descdrop(min_desc_height, max_desc_height + 1);
1304 STATS row_desc_xheights(min_height, max_height + 1);
1305 STATS row_desc_descdrop(min_desc_height, max_desc_height + 1);
1306 STATS row_cap_xheights(min_height, max_height + 1);
1307 STATS row_cap_floating_xheights(min_height, max_height + 1);
1308 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1309 row = row_it.data();
1317 row_asc_xheights.
add(static_cast<inT32>(row->
xheight),
1319 row_asc_ascrise.
add(static_cast<inT32>(row->
ascrise),
1321 row_asc_descdrop.
add(static_cast<inT32>(-row->
descdrop),
1324 row_desc_xheights.
add(static_cast<inT32>(row->
xheight),
1326 row_desc_descdrop.
add(static_cast<inT32>(-row->
descdrop),
1330 &row_cap_xheights, &row_cap_floating_xheights);
1334 float xheight = 0.0;
1335 float ascrise = 0.0;
1336 float descdrop = 0.0;
1340 xheight = row_asc_xheights.
median();
1341 ascrise = row_asc_ascrise.
median();
1342 descdrop = -row_asc_descdrop.
median();
1343 }
else if (row_desc_xheights.
get_total() > 0) {
1345 xheight = row_desc_xheights.
median();
1346 descdrop = -row_desc_descdrop.
median();
1347 }
else if (row_cap_xheights.
get_total() > 0) {
1358 min_height, max_height, &(xheight), &(ascrise));
1366 bool corrected_xheight =
false;
1369 corrected_xheight =
true;
1371 if (corrected_xheight || ascrise <= 0.0) {
1372 ascrise = xheight * asc_frac_xheight;
1374 if (corrected_xheight || descdrop >= 0.0) {
1375 descdrop = -(xheight * desc_frac_xheight);
1380 tprintf(
"Block average xheight=%.4f, ascrise=%.4f, descdrop=%.4f\n",
1381 xheight, ascrise, descdrop);
1384 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1400 int block_line_size) {
1408 int min_height, max_height;
1410 STATS heights(min_height, max_height + 1);
1411 STATS floating_heights(min_height, max_height + 1);
1413 &heights, &floating_heights);
1419 rotation.
y() == 0.0,
1420 min_height, max_height,
1424 row->
descdrop =
static_cast<float>(
1438 int max_height,
STATS *heights,
STATS *floating_heights) {
1445 if (blob_it.empty())
return;
1446 bool has_rep_chars =
1449 blob = blob_it.data();
1458 top -= gradient * xcentre + row->
parallel_c();
1459 if (top >= min_height && top <= max_height) {
1460 heights->
add(static_cast<inT32>(floor(top + 0.5)), 1);
1462 floating_heights->
add(static_cast<inT32>(floor(top + 0.5)), 1);
1470 while (!blob_it.at_first() &&
1471 blob_it.data()->repeated_set() == repeated_set) {
1474 tprintf(
"Skipping repeated char when computing xheight\n");
1479 }
while (!blob_it.at_first());
1499 STATS *heights,
STATS *floating_heights,
bool cap_only,
int min_height,
1500 int max_height,
float *xheight,
float *ascrise) {
1501 int blob_index = heights->
mode();
1502 int blob_count = heights->
pile_count(blob_index);
1504 tprintf(
"min_height=%d, max_height=%d, mode=%d, count=%d, total=%d\n",
1505 min_height, max_height, blob_index, blob_count,
1508 floating_heights->
print();
1510 if (blob_count == 0)
return 0;
1512 bool in_best_pile =
FALSE;
1517 if (cap_only && mode_count > 1)
1521 tprintf(
"found %d modes: ", mode_count);
1522 for (x = 0; x < mode_count; x++)
tprintf(
"%d ", modes[x]);
1526 for (x = 0; x < mode_count - 1; x++) {
1527 if (modes[x] != prev_size + 1)
1528 in_best_pile =
FALSE;
1529 int modes_x_count = heights->
pile_count(modes[x]) -
1532 (in_best_pile || modes_x_count > best_count)) {
1533 for (
int asc = x + 1; asc < mode_count; asc++) {
1535 static_cast<float>(modes[asc]) / static_cast<float>(modes[x]);
1540 if (modes_x_count > best_count) {
1541 in_best_pile =
true;
1542 best_count = modes_x_count;
1545 tprintf(
"X=%d, asc=%d, count=%d, ratio=%g\n",
1546 modes[x], modes[asc]-modes[x], modes_x_count, ratio);
1548 prev_size = modes[x];
1549 *xheight =
static_cast<float>(modes[x]);
1550 *ascrise =
static_cast<float>(modes[asc] - modes[x]);
1555 if (*xheight == 0) {
1562 if (floating_heights->
get_total() > 0) {
1563 for (x = min_height; x < max_height; ++x) {
1566 blob_index = heights->
mode();
1567 for (x = min_height; x < max_height; ++x) {
1571 *xheight =
static_cast<float>(blob_index);
1573 best_count = heights->
pile_count(blob_index);
1575 tprintf(
"Single mode xheight set to %g\n", *xheight);
1577 tprintf(
"Multi-mode xheight set to %g, asc=%g\n", *xheight, *ascrise);
1595 int xheight_blob_count,
STATS *asc_heights) {
1599 i_min =
static_cast<int>(
1606 int num_potential_asc = 0;
1607 for (
int i = i_min; i <= i_max; ++i) {
1608 num_potential_asc += asc_heights->
pile_count(i);
1618 STATS heights (min_height, max_height + 1);
1619 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1620 blob = blob_it.data();
1624 height = (gradient * xcentre + row->
parallel_c() -
1626 if (height >= min_height && height <= max_height)
1627 heights.
add(static_cast<int>(floor(height + 0.5)), 1);
1630 int blob_index = heights.
mode();
1631 int blob_count = heights.
pile_count(blob_index);
1632 float total_fraction =
1634 if (static_cast<float>(blob_count + num_potential_asc) <
1635 xheight_blob_count * total_fraction) {
1638 int descdrop = blob_count > 0 ? -blob_index : 0;
1640 tprintf(
"Descdrop: %d (potential ascenders %d, descenders %d)\n",
1641 descdrop, num_potential_asc, blob_count);
1666 src_count = max_height + 1 - min_height;
1670 for (src_index = 0; src_index < src_count; src_index++) {
1671 pile_count = heights->
pile_count(min_height + src_index);
1672 if (pile_count > 0) {
1673 if (dest_count < maxmodes) {
1674 if (pile_count < least_count) {
1676 least_count = pile_count;
1677 least_index = dest_count;
1679 modes[dest_count++] = min_height + src_index;
1680 }
else if (pile_count >= least_count) {
1681 while (least_index < maxmodes - 1) {
1682 modes[least_index] = modes[least_index + 1];
1687 modes[maxmodes - 1] = min_height + src_index;
1688 if (pile_count == least_count) {
1690 least_index = maxmodes - 1;
1694 for (dest_count = 1; dest_count < maxmodes; dest_count++) {
1695 pile_count = heights->
pile_count(modes[dest_count]);
1696 if (pile_count < least_count) {
1698 least_count = pile_count;
1699 least_index = dest_count;
1717 float ascrise,
float descdrop) {
1720 tprintf(
"correcting row xheight: row->xheight %.4f"
1721 ", row->acrise %.4f row->descdrop %.4f\n",
1724 bool normal_xheight =
1741 (normal_xheight || cap_xheight)) ||
1742 (row_category ==
ROW_UNKNOWN && normal_xheight)) {
1766 if (row->
xheight < xheight + ascrise && row->
xheight > xheight) {
1769 tprintf(
"all caps with irregular xheight\n");
1778 tprintf(
"corrected row->xheight = %.4f, row->acrise = %.4f, row->descdrop"
1783 static int CountOverlaps(
const TBOX& box,
int min_height,
1784 BLOBNBOX_LIST* blobs) {
1786 BLOBNBOX_IT blob_it(blobs);
1787 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
1814 BLOBNBOX_IT blob_it;
1818 TO_ROW_IT row_it = block->
get_rows();
1823 length = sqrt(1 + gradient * gradient);
1824 g_vec =
FCOORD(1 / length, -gradient / length);
1825 blob_rotation =
FCOORD(rotation.
x(), -rotation.
y());
1826 blob_rotation.
rotate(g_vec);
1827 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
1828 row = row_it.data();
1831 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list();
1832 blob_it.forward()) {
1833 blob = blob_it.data();
1841 rotated_blob, static_cast<inT16>(row->
intercept()),
1846 under_it.add_after_then_move(blob_it.extract());
1848 tprintf(
"Underlined blob at:");
1849 rotated_blob->bounding_box().print();
1853 }
else if (CountOverlaps(blob->
bounding_box(), min_blob_height,
1856 large_it.add_after_then_move(blob_it.extract());
1858 tprintf(
"Large blob overlaps %d blobs at:",
1859 CountOverlaps(blob_box, min_blob_height,
1864 delete rotated_blob;
1882 #ifndef GRAPHICS_DISABLED
1889 BLOBNBOX_IT blob_it;
1890 BLOBNBOX_IT start_it;
1891 TO_ROW_IT row_it = block->
get_rows ();
1893 #ifndef GRAPHICS_DISABLED
1897 blob_rotation =
FCOORD (rotation.
x (), -rotation.
y ());
1898 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1900 blob_it.set_to_list (row_it.data ()->blob_list ());
1901 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
1902 blob_it.forward ()) {
1903 blob = blob_it.data ();
1916 if (!blob_it.at_last ()) {
1917 nextblob = blob_it.data_relative(1);
1920 blob->
merge(nextblob);
1927 blob->
chop (&start_it, &blob_it,
1933 #ifndef GRAPHICS_DISABLED
1938 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list ();
1939 blob_it.forward ()) {
1940 blob = blob_it.data ();
1942 blob_box.
rotate (rotation);
1945 blob_box.
right (), blob_box.
top ());
1969 #ifndef GRAPHICS_DISABLED
1972 TO_ROW_IT row_it = block->
get_rows ();
1974 row_it.move_to_first ();
1975 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1976 if (row_it.data ()->blob_list ()->empty ())
1977 delete row_it.extract ();
1981 #ifndef GRAPHICS_DISABLED
1984 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
1986 block_edge, colour, rotation);
2011 for (blob_it.mark_cycle_pt(); !blob_it.cycled_list(); blob_it.forward()) {
2012 if (!blob_it.data()->joined_to_prev()) {
2013 const TBOX& box = blob_it.data()->bounding_box();
2021 error = lms.
Fit(&gradient, &c);
2037 #ifndef GRAPHICS_DISABLED
2040 TO_ROW_IT row_it = block->
get_rows ();
2042 row_it.move_to_first ();
2043 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
2044 if (row_it.data ()->blob_list ()->empty ())
2045 delete row_it.extract ();
2050 #ifndef GRAPHICS_DISABLED
2053 for (row_it.mark_cycle_pt (); !row_it.cycled_list ();
2054 row_it.forward ()) {
2055 row_it.data ()->baseline.plot (
to_win, colour);
2062 make_old_baselines(block, testing_on, gradient);
2064 #ifndef GRAPHICS_DISABLED
2067 for (row_it.mark_cycle_pt (); !row_it.cycled_list (); row_it.forward ()) {
2068 row_it.data ()->baseline.plot (
to_win, colour);
2099 xstarts[1] = xstarts[segments];
2101 coeffs = (
double *)
alloc_mem (3 *
sizeof (
double));
2103 coeffs[1] = row->
line_m ();
2104 coeffs[2] = row->
line_c ();
2136 BLOBNBOX_IT blob_it = row->
blob_list ();
2137 BLOBNBOX_IT new_it = blob_it;
2140 needs_curve =
FALSE;
2142 xstarts[0] = box.
left ();
2144 blobcount = row->
blob_list ()->length ();
2146 tprintf (
"Segmenting baseline of %d blobs at (%d,%d)\n",
2150 blob_it.move_to_last ();
2151 box = blob_it.data ()->bounding_box ();
2152 xstarts[1] = box.
right ();
2156 new_it.mark_cycle_pt ();
2159 middle = (new_box.
left () + new_box.
right ()) / 2.0;
2162 yshifts.
add (yshift, blobindex);
2163 if (new_it.cycled_list ()) {
2164 xstarts[1] = new_box.
right ();
2168 for (blobcount = 0; blobcount < textord_spline_medianwin / 2; blobcount++)
2173 yshift = yshifts[textord_spline_medianwin / 2];
2185 xstarts[segments++] = box.
left ();
2189 yshifts.
remove (blobindex - textord_spline_medianwin);
2191 middle = (new_box.
left () + new_box.
right ()) / 2.0;
2193 yshifts.
add (yshift, blobindex);
2197 while (!new_it.cycled_list ());
2199 xstarts[segments] = new_box.
right ();
2202 xstarts[--segments] = new_box.
right ();
2205 tprintf (
"Made %d segments on row at (%d,%d)\n",
2228 int blobs_per_segment;
2232 BLOBNBOX_IT blob_it = row->
blob_list ();
2233 BLOBNBOX_IT new_it = blob_it;
2240 xstarts[0] = box.
left ();
2242 while (!blob_it.at_first ()) {
2249 blobs_per_segment = blobcount / segments;
2250 coeffs = (
double *)
alloc_mem (segments * 3 *
sizeof (
double));
2253 (
"Linear splining baseline of %d blobs at (%d,%d), into %d segments of %d blobs\n",
2254 blobcount, box.
left (), box.
bottom (), segments, blobs_per_segment);
2256 for (index2 = 0; index2 < blobs_per_segment / 2; index2++)
2261 blobindex += blobs_per_segment;
2263 while (index1 < blobindex || (segment == segments && index1 < blobcount)) {
2265 int middle = (box.
left() + box.
right()) / 2;
2268 if (index1 == blobindex - blobs_per_segment / 2
2269 || index1 == blobcount - 1) {
2270 xstarts[segment] = box.
left ();
2274 coeffs[segment * 3 - 3] = 0;
2275 coeffs[segment * 3 - 2] = b;
2276 coeffs[segment * 3 - 1] = c;
2278 if (segment > segments)
2281 blobindex += blobs_per_segment;
2283 while (index2 < blobindex || (segment == segments && index2 < blobcount)) {
2285 int middle = (new_box.
left() + new_box.
right()) / 2;
2288 if (index2 == blobindex - blobs_per_segment / 2
2289 || index2 == blobcount - 1) {
2290 xstarts[segment] = new_box.
left ();
2294 coeffs[segment * 3 - 3] = 0;
2295 coeffs[segment * 3 - 2] = b;
2296 coeffs[segment * 3 - 1] = c;
2299 while (segment <= segments);
2314 BOOL8 reject_misses,
2315 BOOL8 make_new_rows,
2321 float g_length = 1.0f;
2326 float smooth_factor;
2333 BLOBNBOX_IT blob_it = &block->
blobs;
2334 TO_ROW_IT row_it = block->
get_rows ();
2339 if (gradient !=
NULL)
2340 g_length = sqrt (1 + *gradient * *gradient);
2341 #ifndef GRAPHICS_DISABLED
2347 smooth_factor = 1.0;
2349 row_count = row_it.length ();
2350 if (!blob_it.empty ()) {
2351 left_x = blob_it.data ()->bounding_box ().left ();
2357 for (blob_it.mark_cycle_pt (); !blob_it.cycled_list (); blob_it.forward ()) {
2358 blob = blob_it.data ();
2359 if (gradient !=
NULL) {
2364 && last_x - left_x > block->
line_size * 2
2368 / (last_x - left_x);
2374 #ifndef GRAPHICS_DISABLED
2378 if (!row_it.empty ()) {
2379 for (row_it.move_to_first ();
2380 !row_it.at_last () && row_it.data ()->min_y () > top;
2382 row = row_it.data ();
2383 if (row->
min_y () <= top && row->
max_y () >= bottom) {
2391 if (overlap_result ==
NEW_ROW && !reject_misses)
2396 if (!make_new_rows) {
2397 near_dist = row_it.data_relative (-1)->min_y () - top;
2399 if (bottom < row->min_y ()) {
2400 if (row->
min_y () - bottom <=
2408 else if (near_dist > 0
2409 && near_dist < bottom - row->max_y ()) {
2411 dest_row = row_it.data ();
2412 if (dest_row->
min_y () - bottom <=
2420 if (top - row->
max_y () <=
2431 if (overlap_result ==
ASSIGN)
2432 dest_row->
add_blob (blob_it.extract (), top, bottom,
2434 if (overlap_result ==
NEW_ROW) {
2435 if (make_new_rows && top - bottom < block->max_blob_size) {
2437 new TO_ROW (blob_it.extract (), top, bottom,
2440 if (bottom > row_it.data ()->min_y ())
2441 row_it.add_before_then_move (dest_row);
2444 row_it.add_after_then_move (dest_row);
2453 else if (make_new_rows && top - bottom < block->max_blob_size) {
2458 row_it.add_after_then_move(dest_row);
2465 if (overlap_result !=
REJECT) {
2466 tprintf(
"Test blob assigned to row at (%g,%g) on pass %d\n",
2470 tprintf(
"Test blob assigned to no row on pass %d\n", pass);
2473 if (overlap_result !=
REJECT) {
2474 while (!row_it.at_first() &&
2475 row_it.data()->min_y() > row_it.data_relative(-1)->min_y()) {
2476 row = row_it.extract();
2478 row_it.add_before_then_move(row);
2480 while (!row_it.at_last() &&
2481 row_it.data ()->min_y() < row_it.data_relative (1)->min_y()) {
2482 row = row_it.extract();
2485 row_it.add_after_then_move(row);
2487 BLOBNBOX_IT added_blob_it(dest_row->
blob_list());
2488 added_blob_it.move_to_last();
2489 TBOX prev_box = added_blob_it.data_relative(-1)->bounding_box();
2490 if (dest_row->
blob_list()->singleton() ||
2492 block_skew = (1 - smooth_factor) * block_skew
2498 for (row_it.mark_cycle_pt(); !row_it.cycled_list(); row_it.forward()) {
2499 if (row_it.data()->blob_list()->empty())
2500 delete row_it.extract();
2521 float merge_top, merge_bottom;
2525 BLOBNBOX_IT blob_it;
2528 row = row_it->data ();
2529 bestover = top - bottom;
2530 if (top > row->
max_y ())
2531 bestover -= top - row->
max_y ();
2532 if (bottom < row->min_y ())
2534 bestover -= row->
min_y () - bottom;
2536 tprintf(
"Test blob y=(%g,%g), row=(%f,%f), size=%g, overlap=%f\n",
2537 bottom, top, row->
min_y(), row->
max_y(), rowsize, bestover);
2541 if (!row_it->at_last ()) {
2543 test_row = row_it->data ();
2544 if (test_row->
min_y () <= top && test_row->
max_y () >= bottom) {
2546 test_row->
max_y () >
2549 test_row->
min_y () <
2551 if (merge_top - merge_bottom <= rowsize) {
2553 tprintf (
"Merging rows at (%g,%g), (%g,%g)\n",
2557 test_row->
set_limits (merge_bottom, merge_top);
2558 blob_it.set_to_list (test_row->
blob_list ());
2559 blob_it.add_list_after (row->
blob_list ());
2561 row_it->backward ();
2562 delete row_it->extract ();
2566 overlap = top - bottom;
2567 if (top > test_row->
max_y ())
2568 overlap -= top - test_row->
max_y ();
2569 if (bottom < test_row->min_y ())
2570 overlap -= test_row->
min_y () - bottom;
2571 if (bestover >= rowsize - 1 && overlap >= rowsize - 1) {
2574 if (overlap > bestover) {
2579 tprintf(
"Test blob y=(%g,%g), row=(%f,%f), size=%g, overlap=%f->%f\n",
2580 bottom, top, test_row->
min_y(), test_row->
max_y(),
2581 rowsize, overlap, bestover);
2586 while (!row_it->at_last ()
2587 && test_row->
min_y () <= top && test_row->
max_y () >= bottom);
2588 while (row_it->data () != row)
2589 row_it->backward ();
2607 const void *item2) {
2629 const void *item2) {
2651 const void *item2) {
2673 int num_repeated_sets = 0;
2674 if (!box_it.empty()) {
2677 int repeat_length = 1;
2680 BLOBNBOX_IT test_it(box_it);
2681 for (test_it.forward(); !test_it.at_first();) {
2682 bblob = test_it.data();
2686 bblob = test_it.data();
2695 num_repeated_sets++;
2696 for (; repeat_length > 0; box_it.forward(), --repeat_length) {
2697 bblob = box_it.data();
2704 }
while (!box_it.at_first());
double textord_descx_ratio_max
void plot_to_row(TO_ROW *row, ScrollView::Color colour, FCOORD rotation)
void assign_blobs_to_rows(TO_BLOCK *block, float *gradient, int pass, BOOL8 reject_misses, BOOL8 make_new_rows, BOOL8 drawing_skew)
bool textord_old_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)
ScrollView * create_to_win(ICOORD page_tr)
bool textord_show_final_blobs
bool within_error_margin(float test, float num, float margin)
static const double kXHeightCapRatio
int textord_spline_medianwin
void compute_dropout_distances(inT32 *occupation, inT32 *thresholds, inT32 line_count)
#define double_VAR(name, val, comment)
void free_mem(void *oldchunk)
double textord_spline_outlier_fraction
bool textord_new_initial_xheight
const int kMinLeaderCount
void CheckInverseFlagAndDirection()
bool textord_show_initial_rows
void set_parallel_line(float gradient, float new_c, float new_error)
bool joined_to_prev() const
void compute_block_xheight(TO_BLOCK *block, float gradient)
void DrawTo(int x, int y)
void get_min_max_xheight(int block_linesize, int *min_height, int *max_height)
double textord_linespace_iqrlimit
void separate_underlines(TO_BLOCK *block, float gradient, FCOORD rotation, BOOL8 testing_on)
C_BLOB * crotate_cblob(C_BLOB *blob, FCOORD rotation)
int blob_x_order(const void *item1, const void *item2)
void rotate(const FCOORD vec)
const ICOORD & start_pos() const
static const double kDescenderFraction
int num_repeated_sets() const
bool textord_fix_makerow_bug
void add(inT32 value, inT32 count)
int row_y_order(const void *item1, const void *item2)
EXTERN bool textord_oldbl_debug
inT32 compute_row_descdrop(TO_ROW *row, float gradient, int xheight_blob_count, STATS *asc_heights)
#define BOOL_VAR(name, val, comment)
int compute_xheight_from_modes(STATS *heights, STATS *floating_heights, bool cap_only, int min_height, int max_height, float *xheight, float *ascrise)
int textord_min_blobs_in_row
void plot_parallel_row(TO_ROW *row, float gradient, inT32 left, ScrollView::Color colour, FCOORD rotation)
double textord_ascx_ratio_min
int textord_max_blob_overlaps
double textord_chop_width
void make_initial_textrows(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, BOOL8 testing_on)
bool textord_parallel_baselines
bool textord_interpolating_skew
BLOBNBOX_LIST * blob_list()
void fit_lms_line(TO_ROW *row)
float make_single_row(ICOORD page_tr, bool allow_sub_blobs, TO_BLOCK *block, TO_BLOCK_LIST *blocks)
void expand_rows(ICOORD page_tr, TO_BLOCK *block, float gradient, FCOORD rotation, inT32 block_edge, BOOL8 testing_on)
double textord_spline_shift_fraction
double textord_xheight_error_margin
void delete_non_dropout_rows(TO_BLOCK *block, float gradient, FCOORD rotation, inT32 block_edge, BOOL8 testing_on)
BLOBNBOX_LIST small_blobs
C_OUTLINE_LIST * out_list()
bool textord_debug_xheights
TBOX deskew_block_coords(TO_BLOCK *block, float gradient)
inT32 choose_nth_item(inT32 index, float *array, inT32 count)
void set_num_repeated_sets(int num_sets)
bool textord_show_expanded_rows
void set_line(float new_m, float new_c, float new_error)
void add_blob(BLOBNBOX *blob, float top, float bottom, float row_size)
void pre_associate_blobs(ICOORD page_tr, TO_BLOCK *block, FCOORD rotation, BOOL8 testing_on)
FCOORD classify_rotation() const
static const double kAscenderFraction
void fit_parallel_lms(float gradient, TO_ROW *row)
EXTERN ScrollView * to_win
bool textord_fix_xheight_bug
double textord_ascx_ratio_max
bool textord_biased_skewcalc
bool textord_show_parallel_rows
double textord_expansion_factor
double textord_descx_ratio_min
inT16 y() const
access_function
void set_repeated_set(int set_id)
double textord_excess_blobsize
void SetCursor(int x, int y)
bool textord_test_landscape
int textord_skewsmooth_offset2
void chop(BLOBNBOX_IT *start_it, BLOBNBOX_IT *blob_it, FCOORD rotation, float xheight)
int row_spacing_order(const void *item1, const void *item2)
double textord_width_limit
void bounding_box(ICOORD &bottom_left, ICOORD &top_right) const
get box
float believability() const
BLOBNBOX_LIST noise_blobs
float MakeRowFromSubBlobs(TO_BLOCK *block, C_BLOB *blob, TO_ROW_IT *row_it)
void compute_page_skew(TO_BLOCK_LIST *blocks, float &page_m, float &page_err)
float make_rows(ICOORD page_tr, TO_BLOCK_LIST *port_blocks)
bool rep_chars_marked() const
double textord_xheight_mode_fraction
double textord_underline_width
int textord_lms_line_trials
void error(const char *caller, TessErrorLogCode action, const char *format,...) const
#define INT_VAR(name, val, comment)
void add(float value, inT32 key)
int textord_skewsmooth_offset
bool major_x_overlap(const TBOX &box) const
void compute_row_xheight(TO_ROW *row, const FCOORD &rotation, float gradient, int block_line_size)
BOOL8 segment_baseline(TO_ROW *row, TO_BLOCK *block, inT32 &segments, inT32 xstarts[])
bool major_overlap(const TBOX &box) const
bool textord_show_final_rows
void make_baseline_spline(TO_ROW *row, TO_BLOCK *block)
void correct_row_xheight(TO_ROW *row, float xheight, float ascrise, float descdrop)
void Add(const ICOORD &pt)
TBOX box_next_pre_chopped(BLOBNBOX_IT *it)
void fit_parallel_rows(TO_BLOCK *block, float gradient, FCOORD rotation, inT32 block_edge, BOOL8 testing_on)
void compute_row_stats(TO_BLOCK *block, BOOL8 testing_on)
double textord_ascheight_mode_fraction
void vigorous_noise_removal(TO_BLOCK *block)
int textord_spline_minblobs
void draw_occupation(inT32 xleft, inT32 ybottom, inT32 min_y, inT32 max_y, inT32 occupation[], inT32 thresholds[])
static C_BLOB * FakeBlob(const TBOX &box)
inT16 x() const
access function
double Fit(ICOORD *pt1, ICOORD *pt2)
double textord_occupancy_threshold
void mark_repeated_chars(TO_ROW *row)
bool textord_straight_baselines
void Rectangle(int x1, int y1, int x2, int y2)
bool contains(const FCOORD pt) const
static const double kXHeightFraction
inT32 pile_count(inT32 value) const
ICOORD step(int index) const
void * alloc_mem(inT32 count)
double textord_min_linesize
void fill_heights(TO_ROW *row, float gradient, int min_height, int max_height, STATS *heights, STATS *floating_heights)
const TBOX & bounding_box() const
void cleanup_rows_making(ICOORD page_tr, TO_BLOCK *block, float gradient, FCOORD rotation, inT32 block_edge, BOOL8 testing_on)
ROW_CATEGORY get_row_category(const TO_ROW *row)
inT32 compute_height_modes(STATS *heights, inT32 min_height, inT32 max_height, inT32 *modes, inT32 maxmodes)
OVERLAP_STATE most_overlapping_row(TO_ROW_IT *row_it, TO_ROW *&best_row, float top, float bottom, float rowsize, BOOL8 testing_blob)
double textord_descheight_mode_fraction
double ConstrainedFit(const FCOORD &direction, double min_dist, double max_dist, bool debug, ICOORD *line_pt)
BlobTextFlowType flow() const
BLOBNBOX_LIST large_blobs
void adjust_row_limits(TO_BLOCK *block)
void compute_occupation_threshold(inT32 low_window, inT32 high_window, inT32 line_count, inT32 *occupation, inT32 *thresholds)
void make_spline_rows(TO_BLOCK *block, float gradient, BOOL8 testing_on)
void merge(BLOBNBOX *nextblob)
double * linear_spline_baseline(TO_ROW *row, TO_BLOCK *block, inT32 &segments, inT32 xstarts[])
bool textord_single_height_mode
void set_limits(float new_min, float new_max)
BOOL8 test_underline(BOOL8 testing_on, C_BLOB *blob, inT16 baseline, inT16 xheight)
double textord_min_blob_height_fraction
void compute_line_occupation(TO_BLOCK *block, float gradient, inT32 min_y, inT32 max_y, inT32 *occupation, inT32 *deltas)
void rotate(const FCOORD &vec)
float initial_min_y() const
static C_OUTLINE * deep_copy(const C_OUTLINE *src)